2026-01-12 01:07:45 +01:00
|
|
|
import { EventBusCommand, EventBusContext, EventBusMessage } from '@src/common/interfaces/EventBusMessage.interface';
|
|
|
|
|
import { IframeTunnelPayload } from '@src/common/interfaces/IframeTunnelPayload.interface';
|
2026-01-07 18:04:34 +01:00
|
|
|
import Comms from '@src/ext/module/comms/Comms';
|
|
|
|
|
import CommsClient, { CommsOrigin } from '@src/ext/module/comms/CommsClient';
|
|
|
|
|
import CommsServer from '@src/ext/module/comms/CommsServer';
|
2021-10-26 22:19:41 +02:00
|
|
|
|
2022-07-31 00:15:28 +02:00
|
|
|
|
2021-10-26 22:19:41 +02:00
|
|
|
export default class EventBus {
|
|
|
|
|
|
2026-01-07 01:42:37 +01:00
|
|
|
private name: string;
|
2026-01-12 01:07:45 +01:00
|
|
|
private uuid = crypto.randomUUID();
|
2026-01-07 01:42:37 +01:00
|
|
|
|
2021-10-26 22:19:41 +02:00
|
|
|
private commands: { [x: string]: EventBusCommand[]} = {};
|
2022-08-21 22:46:06 +02:00
|
|
|
private comms?: CommsClient | CommsServer;
|
2026-01-12 01:07:45 +01:00
|
|
|
private commsOrigin: CommsOrigin;
|
|
|
|
|
|
2022-07-31 00:15:28 +02:00
|
|
|
|
2022-11-21 01:01:28 +01:00
|
|
|
private disableTunnel: boolean = false;
|
|
|
|
|
private popupContext: any = {};
|
2025-05-19 01:16:33 +02:00
|
|
|
|
|
|
|
|
private iframeForwardingList: {iframe: any, fn: (action, payload, context?) => void}[] = [];
|
|
|
|
|
|
2023-03-02 00:37:23 +01:00
|
|
|
// private uiUri = window.location.href;
|
|
|
|
|
|
2026-01-12 01:07:45 +01:00
|
|
|
constructor(options?: {isUWServer?: boolean, name?: string, commsOrigin?: CommsOrigin}) {
|
2023-07-10 22:00:53 +02:00
|
|
|
if (!options?.isUWServer) {
|
|
|
|
|
this.setupIframeTunnelling();
|
|
|
|
|
}
|
2026-01-12 01:07:45 +01:00
|
|
|
this.name = options?.name ?? '(unnamed EventBus)';
|
|
|
|
|
this.commsOrigin = options?.commsOrigin;
|
2023-03-02 00:37:23 +01:00
|
|
|
}
|
2022-11-21 01:01:28 +01:00
|
|
|
|
|
|
|
|
setupPopupTunnelWorkaround(context: EventBusContext): void {
|
|
|
|
|
this.disableTunnel = true;
|
|
|
|
|
this.popupContext = context;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-31 00:15:28 +02:00
|
|
|
//#region lifecycle
|
|
|
|
|
destroy() {
|
|
|
|
|
this.commands = null;
|
2023-03-02 00:37:23 +01:00
|
|
|
this.destroyIframeTunnelling();
|
2022-07-31 00:15:28 +02:00
|
|
|
}
|
|
|
|
|
//#endregion
|
|
|
|
|
|
2022-08-21 22:46:06 +02:00
|
|
|
setComms(comms: CommsClient | CommsServer) {
|
2022-07-31 00:15:28 +02:00
|
|
|
this.comms = comms;
|
|
|
|
|
}
|
2021-10-26 22:19:41 +02:00
|
|
|
|
2024-12-30 23:02:55 +01:00
|
|
|
subscribe(commandString: string, command: EventBusCommand) {
|
|
|
|
|
if (!this.commands[commandString]) {
|
|
|
|
|
this.commands[commandString] = [command];
|
|
|
|
|
} else {
|
|
|
|
|
this.commands[commandString].push(command);
|
2022-05-06 00:22:35 +02:00
|
|
|
}
|
|
|
|
|
}
|
2021-10-26 22:19:41 +02:00
|
|
|
|
2024-12-30 23:02:55 +01:00
|
|
|
subscribeMulti(commands: {[commandString: string]: EventBusCommand}, source?: any) {
|
|
|
|
|
for (const key in commands) {
|
|
|
|
|
this.subscribe(
|
|
|
|
|
key,
|
|
|
|
|
{
|
|
|
|
|
...commands[key],
|
|
|
|
|
source: source ?? commands[key].source
|
|
|
|
|
}
|
|
|
|
|
);
|
2022-05-06 00:28:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-30 23:02:55 +01:00
|
|
|
/**
|
|
|
|
|
* Removes all commands from a given source
|
|
|
|
|
* @param source
|
|
|
|
|
*/
|
|
|
|
|
unsubscribeAll(source: any) {
|
|
|
|
|
for (const commandString in this.commands) {
|
|
|
|
|
this.commands[commandString] = this.commands[commandString].filter(x => x.source !== source);
|
2021-10-26 22:19:41 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-19 01:16:33 +02:00
|
|
|
forwardToIframe(iframe: any, fn: (action: string, payload: any, context?: EventBusContext) => void) {
|
|
|
|
|
this.cancelIframeForwarding(iframe);
|
|
|
|
|
this.iframeForwardingList.push({iframe, fn});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cancelIframeForwarding(iframe: any) {
|
|
|
|
|
const existingForwarding = this.iframeForwardingList.findIndex((x: any) => x.iframe === iframe);
|
|
|
|
|
if (existingForwarding !== -1) {
|
|
|
|
|
this.iframeForwardingList.splice(existingForwarding, 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-12 01:07:45 +01:00
|
|
|
send(command: string, commandData: any, context: EventBusContext = {}) {
|
2026-01-14 22:38:08 +01:00
|
|
|
context.visitedBusses = [...context.visitedBusses ?? [], this.uuid];
|
2022-07-31 01:12:54 +02:00
|
|
|
// execute commands we have subscriptions for
|
2024-12-30 23:02:55 +01:00
|
|
|
|
2022-07-31 01:12:54 +02:00
|
|
|
if (this.commands?.[command]) {
|
|
|
|
|
for (const eventBusCommand of this.commands[command]) {
|
2023-03-02 00:37:23 +01:00
|
|
|
eventBusCommand.function(commandData, context);
|
2022-07-31 01:12:54 +02:00
|
|
|
}
|
2021-11-01 01:18:07 +01:00
|
|
|
}
|
|
|
|
|
|
2022-09-20 01:34:59 +02:00
|
|
|
// preventing messages from flowing back to their original senders is
|
|
|
|
|
// CommsServer's job. EventBus does not have enough data for this decision.
|
2025-05-07 00:42:25 +02:00
|
|
|
// We do, however, have enough data to prevent backflow of messages that
|
|
|
|
|
// crossed CommsServer once already.
|
2025-10-08 19:39:55 +02:00
|
|
|
if (
|
|
|
|
|
this.comms
|
|
|
|
|
&& context?.origin !== CommsOrigin.Server
|
|
|
|
|
&& !context?.borderCrossings?.commsServer
|
|
|
|
|
) {
|
|
|
|
|
try {
|
|
|
|
|
this.comms.sendMessage({command, config: commandData, context}, context);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
if (command !== 'reload-required') {
|
|
|
|
|
// We shouldn't let reload-required command to trigger new reload-required commands.
|
2026-01-14 22:38:08 +01:00
|
|
|
this.send('reload-required', {}, {visitedBusses: [this.uuid]});
|
2025-10-08 19:39:55 +02:00
|
|
|
}
|
|
|
|
|
}
|
2025-05-19 01:16:33 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// call forwarding functions if they exist
|
2026-01-14 22:38:08 +01:00
|
|
|
for (const forwarding of this.iframeForwardingList) {
|
|
|
|
|
forwarding.fn(
|
|
|
|
|
command,
|
|
|
|
|
commandData,
|
|
|
|
|
{
|
|
|
|
|
...context,
|
|
|
|
|
borderCrossings: {
|
|
|
|
|
...context?.borderCrossings,
|
|
|
|
|
// iframe: true // we actually no longer check this prop, we should instead rely on visitedBusses
|
2025-05-19 01:16:33 +02:00
|
|
|
}
|
2026-01-14 22:38:08 +01:00
|
|
|
}
|
|
|
|
|
);
|
2022-07-31 01:12:54 +02:00
|
|
|
}
|
2026-01-14 22:38:08 +01:00
|
|
|
this.sendToTunnel(command, commandData, context);
|
2021-10-26 22:19:41 +02:00
|
|
|
|
2022-07-31 01:12:54 +02:00
|
|
|
if (context?.stopPropagation) {
|
|
|
|
|
return;
|
2021-10-26 22:19:41 +02:00
|
|
|
}
|
|
|
|
|
}
|
2023-03-02 00:37:23 +01:00
|
|
|
//#endregion
|
2021-10-26 22:19:41 +02:00
|
|
|
|
2022-03-29 01:53:16 +02:00
|
|
|
/**
|
|
|
|
|
* Send, but intended for sending commands from iframe to content scripts
|
|
|
|
|
* @param command
|
|
|
|
|
* @param config
|
|
|
|
|
*/
|
2026-01-12 01:07:45 +01:00
|
|
|
sendToTunnel(command: string, config: any, context: EventBusContext = {}) {
|
2026-01-14 22:38:08 +01:00
|
|
|
if (!context.visitedBusses) {
|
|
|
|
|
console.error('Visited busses is missing from contextn. This is illegal.');
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-01-12 01:07:45 +01:00
|
|
|
context.visitedBusses = [...context.visitedBusses ?? [], this.uuid];
|
|
|
|
|
|
|
|
|
|
if (!this.disableTunnel && typeof window !== 'undefined') {
|
2022-11-21 01:01:28 +01:00
|
|
|
window.parent.postMessage(
|
|
|
|
|
{
|
|
|
|
|
action: 'uw-bus-tunnel',
|
2026-01-12 01:07:45 +01:00
|
|
|
payload: {command, config, context} as EventBusMessage
|
2022-11-21 01:01:28 +01:00
|
|
|
},
|
|
|
|
|
'*'
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
// because iframe UI components get reused in the popup, we
|
|
|
|
|
// also need to set up a detour because the tunnel is closed
|
|
|
|
|
// in the popup
|
|
|
|
|
if (this.comms) {
|
2025-10-08 19:39:55 +02:00
|
|
|
try {
|
2026-01-12 01:07:45 +01:00
|
|
|
this.comms.sendMessage(
|
|
|
|
|
{
|
|
|
|
|
command,
|
|
|
|
|
config,
|
|
|
|
|
context: {
|
|
|
|
|
...this.popupContext,
|
|
|
|
|
...context
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
this.popupContext
|
|
|
|
|
);
|
2025-10-08 19:39:55 +02:00
|
|
|
} catch (e) {
|
|
|
|
|
if (command !== 'reload-required') {
|
2026-01-12 01:07:45 +01:00
|
|
|
this.send('reload-required', {}, context);
|
2025-10-08 19:39:55 +02:00
|
|
|
}
|
|
|
|
|
}
|
2022-11-21 01:01:28 +01:00
|
|
|
}
|
|
|
|
|
}
|
2022-03-29 01:53:16 +02:00
|
|
|
}
|
|
|
|
|
|
2023-03-02 00:37:23 +01:00
|
|
|
//#region iframe tunnelling
|
|
|
|
|
private setupIframeTunnelling() {
|
2024-06-02 16:06:26 +02:00
|
|
|
// forward messages coming from iframe tunnels
|
2026-01-12 01:07:45 +01:00
|
|
|
window.addEventListener('message', this);
|
2023-03-02 00:37:23 +01:00
|
|
|
}
|
|
|
|
|
private destroyIframeTunnelling() {
|
2026-01-12 01:07:45 +01:00
|
|
|
window.removeEventListener('message', this);
|
2023-03-02 00:37:23 +01:00
|
|
|
}
|
2026-01-12 01:07:45 +01:00
|
|
|
/**
|
|
|
|
|
* Handles 'message' events (formerly handleIframeMessage)
|
|
|
|
|
* @param event
|
|
|
|
|
* @returns
|
|
|
|
|
*/
|
|
|
|
|
handleEvent(event: any) {
|
2026-01-07 01:42:37 +01:00
|
|
|
if (event.data?.action !== 'uw-bus-tunnel') {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-01-12 01:07:45 +01:00
|
|
|
|
|
|
|
|
const payload = event.data.payload as EventBusMessage;
|
|
|
|
|
|
|
|
|
|
console.info(this.name, 'received message from iframe. command:', payload);
|
2026-01-14 22:38:08 +01:00
|
|
|
if (!payload.context?.visitedBusses) {
|
2026-01-12 01:07:45 +01:00
|
|
|
console.warn('Received iframe message without context. Doing nothing in order to avoid infinite loop. Event:', event);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (payload.context?.visitedBusses?.includes(this.uuid)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.send(payload.command, payload.config, payload.context);
|
2023-03-02 00:37:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//#endregion
|
|
|
|
|
|
2021-10-26 22:19:41 +02:00
|
|
|
}
|