EventBus: Allow forwarding between iframes of a page

This commit is contained in:
Tamius Han 2023-03-02 00:37:23 +01:00
parent 57261b5094
commit 26b78f1225
2 changed files with 92 additions and 14 deletions

View File

@ -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
}

View File

@ -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,