EventBus: Allow forwarding between iframes of a page
This commit is contained in:
parent
57261b5094
commit
26b78f1225
@ -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
|
||||
|
||||
}
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user