ultrawidify/src/ext/module/EventBus.ts

207 lines
5.8 KiB
TypeScript
Raw Normal View History

2023-03-30 00:43:30 +02:00
import { IframeData } from './video-data/IframeManager';
import CommsClient, { CommsOrigin } from './comms/CommsClient';
import CommsServer from './comms/CommsServer';
2026-01-07 01:42:37 +01:00
import { EventBusCommand, EventBusContext } from '@src/common/interfaces/EventBusMessage.interface';
2021-10-26 22:19:41 +02:00
2026-01-07 01:42:37 +01:00
// export interface EventBusContext {
// stopPropagation?: boolean,
// // Context stuff added by Comms
// origin?: CommsOrigin,
// comms?: {
// sender?: chrome.runtime.MessageSender,
// port?: chrome.runtime.Port,
// frame?: any,
// sourceFrame?: IframeData
// forwardTo?: 'all' | 'active' | 'contentScript' | 'server' | 'sameOrigin' | 'popup' | 'all-frames',
// };
// borderCrossings?: {
// commsServer?: boolean,
// iframe?: boolean,
// }
// }
2021-10-26 22:19:41 +02:00
export default class EventBus {
2026-01-07 01:42:37 +01:00
private name: string;
2021-10-26 22:19:41 +02:00
private commands: { [x: string]: EventBusCommand[]} = {};
private comms?: CommsClient | CommsServer;
private disableTunnel: boolean = false;
private popupContext: any = {};
private iframeForwardingList: {iframe: any, fn: (action, payload, context?) => void}[] = [];
// private uiUri = window.location.href;
2026-01-07 01:42:37 +01:00
constructor(options?: {isUWServer?: boolean, name?: string}) {
2023-07-10 22:00:53 +02:00
if (!options?.isUWServer) {
this.setupIframeTunnelling();
}
2026-01-07 01:42:37 +01:00
this.name = options?.name;
}
setupPopupTunnelWorkaround(context: EventBusContext): void {
this.disableTunnel = true;
this.popupContext = context;
}
//#region lifecycle
destroy() {
this.commands = null;
this.destroyIframeTunnelling();
}
//#endregion
setComms(comms: CommsClient | CommsServer) {
this.comms = comms;
}
2021-10-26 22:19:41 +02: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
subscribeMulti(commands: {[commandString: string]: EventBusCommand}, source?: any) {
for (const key in commands) {
this.subscribe(
key,
{
...commands[key],
source: source ?? commands[key].source
}
);
}
}
/**
* 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
}
}
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);
}
}
send(command: string, commandData: any, context?: EventBusContext) {
2026-01-07 01:42:37 +01:00
console.info('sending eventBus command:', this.name, 'command:', {command, commandData, context});
2022-07-31 01:12:54 +02:00
// execute commands we have subscriptions for
2022-07-31 01:12:54 +02:00
if (this.commands?.[command]) {
for (const eventBusCommand of this.commands[command]) {
eventBusCommand.function(commandData, context);
2022-07-31 01:12:54 +02:00
}
}
// preventing messages from flowing back to their original senders is
// CommsServer's job. EventBus does not have enough data for this decision.
// 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.
this.send('reload-required', {});
}
}
};
// call forwarding functions if they exist
if (!context?.borderCrossings?.iframe) {
for (const forwarding of this.iframeForwardingList) {
forwarding.fn(
command,
commandData,
{
...context,
borderCrossings: {
...context?.borderCrossings,
iframe: true
}
}
);
}
2026-01-07 01:42:37 +01:00
this.sendToTunnel(command, commandData);
2022-07-31 01:12:54 +02:00
}
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
}
}
//#endregion
2021-10-26 22:19:41 +02:00
/**
* Send, but intended for sending commands from iframe to content scripts
* @param command
* @param config
*/
sendToTunnel(command: string, config: any) {
if (!this.disableTunnel) {
window.parent.postMessage(
{
action: 'uw-bus-tunnel',
payload: {action: command, config}
},
'*'
);
} 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 {
this.comms.sendMessage({command, config, context: this.popupContext}, this.popupContext);
} catch (e) {
if (command !== 'reload-required') {
this.send('reload-required', {});
}
}
}
}
}
//#region iframe tunnelling
private setupIframeTunnelling() {
// forward messages coming from iframe tunnels
window.addEventListener('message', this.handleIframeMessage);
}
private destroyIframeTunnelling() {
window.removeEventListener('message', this.handleIframeMessage);
}
private handleIframeMessage(event: any) {
2026-01-07 01:42:37 +01:00
if (event.data?.action !== 'uw-bus-tunnel') {
return;
}
console.info(this.name, 'received message from iframe. command:', event.data.payload);
this.send(event.data.payload.command, event.data.payload.config);
}
//#endregion
2021-10-26 22:19:41 +02:00
}