From 26b78f1225a07f1e745ed6482608e64ab3cc8ed1 Mon Sep 17 00:00:00 2001 From: Tamius Han Date: Thu, 2 Mar 2023 00:37:23 +0100 Subject: [PATCH] EventBus: Allow forwarding between iframes of a page --- src/ext/lib/EventBus.ts | 35 ++++++++++++---- src/ext/lib/comms/CommsServer.ts | 71 ++++++++++++++++++++++++++++---- 2 files changed, 92 insertions(+), 14 deletions(-) diff --git a/src/ext/lib/EventBus.ts b/src/ext/lib/EventBus.ts index efee880..c09d71c 100644 --- a/src/ext/lib/EventBus.ts +++ b/src/ext/lib/EventBus.ts @@ -15,7 +15,7 @@ export interface EventBusContext { sender?: any, port?: any, frame?: any, - forwardTo?: 'all' | 'active' | 'contentScript' | 'server' | 'sameOrigin' | 'popup', + forwardTo?: 'all' | 'active' | 'contentScript' | 'server' | 'sameOrigin' | 'popup' | 'all-frames', } } @@ -28,6 +28,11 @@ export default class EventBus { private disableTunnel: boolean = false; private popupContext: any = {}; + // private uiUri = window.location.href; + + constructor() { + this.setupIframeTunnelling(); + } setupPopupTunnelWorkaround(context: EventBusContext): void { this.disableTunnel = true; @@ -40,6 +45,7 @@ export default class EventBus { for (const bus of this.downstreamBuses) { bus.destroy(); } + this.destroyIframeTunnelling(); } //#endregion @@ -47,6 +53,7 @@ export default class EventBus { this.comms = comms; } + //#region bus hierarchy management (single page) setUpstreamBus(eventBus: EventBus, stopRecursing: boolean = false) { this.upstreamBus = eventBus; if (!stopRecursing) { @@ -86,18 +93,18 @@ export default class EventBus { } } - send(command: string, config: any, context?: EventBusContext) { + send(command: string, commandData: any, context?: EventBusContext) { // execute commands we have subscriptions for if (this.commands?.[command]) { for (const eventBusCommand of this.commands[command]) { - eventBusCommand.function(config, context); + eventBusCommand.function(commandData, context); } } // preventing messages from flowing back to their original senders is // CommsServer's job. EventBus does not have enough data for this decision. if (this.comms) { - this.comms.sendMessage({command, config, context}, context); + this.comms.sendMessage({command, config: commandData, context}, context); } if (context?.stopPropagation) { @@ -105,9 +112,10 @@ export default class EventBus { } // propagate commands across the bus - this.sendUpstream(command, config, context); - this.sendDownstream(command, config, context); + this.sendUpstream(command, commandData, context); + this.sendDownstream(command, commandData, context); } + //#endregion /** * Send, but intended for sending commands from iframe to content scripts @@ -133,7 +141,6 @@ export default class EventBus { } } - private sendDownstream(command: string, config: any, context?: EventBusContext, sourceEventBus?: EventBus) { for (const eventBus of this.downstreamBuses) { if (eventBus !== sourceEventBus) { @@ -152,4 +159,18 @@ export default class EventBus { this.upstreamBus.sendDownstream(command, config, context, this); } } + + //#region iframe tunnelling + private setupIframeTunnelling() { + window.addEventListener('message', this.handleIframeMessage); + } + private destroyIframeTunnelling() { + window.removeEventListener('message', this.handleIframeMessage); + } + private handleIframeMessage(event: any) { + console.log('GOT IFRAME MESSAGE!', event) + } + + //#endregion + } diff --git a/src/ext/lib/comms/CommsServer.ts b/src/ext/lib/comms/CommsServer.ts index 1ad43af..6648b87 100644 --- a/src/ext/lib/comms/CommsServer.ts +++ b/src/ext/lib/comms/CommsServer.ts @@ -1,3 +1,4 @@ +import { EventBusContext } from './../EventBus'; import Debug from '../../conf/Debug'; import BrowserDetect from '../../conf/BrowserDetect'; import Logger from '../Logger'; @@ -14,10 +15,39 @@ class CommsServer { settings: Settings; eventBus: EventBus; + /** + * We can send messages to various ports. + * + * Ports start with a list of browser tabs (one for every tab of the browser), and each contain + * a list of frames. Content of the tab is a frame, and so are any iframes inside the tab. Each + * frame has at least one script (that's us 👋👋👋). + * + * For a page with no iframes, the ports object should look like this: + * + * ports + * : + * +-+ [our tab] + * | +-+ [the only frame] + * : +-- [the only port] + * + * For a page with iframes, the ports object should look like this: + * + * ports + * : + * +-+ [our tab] + * | +-+ [main frame] + * | | +-- [content script] + * | | + * | +-+ [iframe] + * | | +-- [content script] + * : : + * + * And, again, we always need to call the content script. + */ ports: { - [tab: string] : { - [frame: string] : { - [port: string]: any + [tab: string] : { // tab of a browser + [frame: string] : { // iframe inside of the tab + [port: string]: any // script inside the iframe. } } } = {}; @@ -57,7 +87,7 @@ class CommsServer { this.ports[tabId][frameId] = {}; } this.ports[tabId][frameId][port.name] = port; - this.ports[tabId][frameId][port.name].onMessage.addListener( (m,p) => this.processReceivedMessage(m, p)); + this.ports[tabId][frameId][port.name].onMessage.addListener( (m,p) => this.processReceivedMessage(m, p, {tabId, frameId})); this.ports[tabId][frameId][port.name].onDisconnect.addListener( (p) => { try { @@ -97,6 +127,14 @@ class CommsServer { return this.sendToPopup(message); } } + + // okay I lied! Messages originating from content script can be forwarded to + // content scripts running in _other_ frames of the tab + if (context?.origin === CommsOrigin.ContentScript) { + if (context?.comms.forwardTo === 'all-frames') { + this.sendToOtherFrames(message, context); + } + } } /** @@ -138,6 +176,22 @@ class CommsServer { } } + /** + * Forwards messages to other content scripts within the same tab + * @param message + * @param tab + * @param frame + */ + private async sendToOtherFrames(message, context) { + const sender = context.comms.sourceFrame; + + for (const frame in this.ports[sender.tabId]) { + if (frame !== sender.frameId) { + this.sendToFrameContentScripts(message, sender.tabId, sender.frameId); + } + } + } + private async sendToFrame(message, tab, frame, port?) { this.logger.log('info', 'comms', `%c[CommsServer::sendToFrame] attempting to send message to tab ${tab}, frame ${frame}`, "background: #dda; color: #11D", message); @@ -178,7 +232,8 @@ class CommsServer { } } - private async processReceivedMessage(message, port){ + + private async processReceivedMessage(message, port, sender?: {frameId: string, tabId: string}){ // this triggers events this.eventBus.send( message.command, @@ -187,7 +242,8 @@ class CommsServer { ...message.context, comms: { ...message.context?.comms, - port + port, + sourceFrame: sender, }, // origin is required to stop cross-pollination between content scripts, while still @@ -202,7 +258,8 @@ class CommsServer { this.eventBus.send( message.command, - message.config, { + message.config, + { ...message.context, comms: { ...message.context?.comms,