ultrawidify/src/ext/lib/EventBus.ts

181 lines
5.1 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';
2021-10-26 22:19:41 +02:00
export interface EventBusCommand {
isGlobal?: boolean,
2023-03-29 21:51:06 +02:00
function: (commandData: any, context?: any) => void | Promise<void>
}
export interface EventBusContext {
stopPropagation?: boolean,
// Context stuff added by Comms
origin?: CommsOrigin,
comms?: {
sender?: any,
port?: any,
frame?: any,
sourceFrame?: IframeData
forwardTo?: 'all' | 'active' | 'contentScript' | 'server' | 'sameOrigin' | 'popup' | 'all-frames',
}
2021-10-26 22:19:41 +02:00
}
export default class EventBus {
private commands: { [x: string]: EventBusCommand[]} = {};
2022-05-06 00:22:35 +02:00
private downstreamBuses: EventBus[] = [];
2021-10-26 22:19:41 +02:00
private upstreamBus?: EventBus;
private comms?: CommsClient | CommsServer;
private disableTunnel: boolean = false;
private popupContext: any = {};
// private uiUri = window.location.href;
2023-07-10 22:00:53 +02:00
constructor(options?: {isUWServer?: boolean}) {
if (!options?.isUWServer) {
this.setupIframeTunnelling();
}
}
setupPopupTunnelWorkaround(context: EventBusContext): void {
this.disableTunnel = true;
this.popupContext = context;
}
//#region lifecycle
destroy() {
this.commands = null;
for (const bus of this.downstreamBuses) {
bus.destroy();
}
this.destroyIframeTunnelling();
}
//#endregion
setComms(comms: CommsClient | CommsServer) {
this.comms = comms;
}
2021-10-26 22:19:41 +02:00
//#region bus hierarchy management (single page)
2022-05-06 00:22:35 +02:00
setUpstreamBus(eventBus: EventBus, stopRecursing: boolean = false) {
this.upstreamBus = eventBus;
if (!stopRecursing) {
this.upstreamBus.addDownstreamBus(this, true);
}
}
unsetUpstreamBus(stopRecursing: boolean = false) {
if (!stopRecursing) {
this.upstreamBus.removeDownstreamBus(this, false);
}
this.upstreamBus = undefined;
}
2022-05-06 00:22:35 +02:00
addDownstreamBus(eventBus: EventBus, stopRecursing: boolean = false) {
if (!this.downstreamBuses.includes(eventBus)) {
this.downstreamBuses.push(eventBus);
if (!stopRecursing) {
eventBus.setUpstreamBus(this, true);
}
}
}
2021-10-26 22:19:41 +02:00
removeDownstreamBus(eventBus: EventBus, stopRecursing: boolean = false) {
this.downstreamBuses = this.downstreamBuses.filter(x => x !== eventBus);
if (!stopRecursing) {
eventBus.unsetUpstreamBus(true);
}
}
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);
}
}
send(command: string, commandData: any, context?: EventBusContext) {
2022-07-31 01:12:54 +02:00
// execute commands we have subscriptions for
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.
if (this.comms) {
this.comms.sendMessage({command, config: commandData, context}, context);
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
}
2022-07-31 01:12:54 +02:00
// propagate commands across the bus
this.sendUpstream(command, commandData, context);
this.sendDownstream(command, commandData, context);
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) {
this.comms.sendMessage({command, config, context: this.popupContext}, this.popupContext);
}
}
}
private sendDownstream(command: string, config: any, context?: EventBusContext, sourceEventBus?: EventBus) {
2022-05-06 00:22:35 +02:00
for (const eventBus of this.downstreamBuses) {
if (eventBus !== sourceEventBus) {
2022-07-31 01:12:54 +02:00
// prevent eventBus.send from auto-propagating the command
eventBus.send(command, config, {...context, stopPropagation: true});
2022-05-06 00:22:35 +02:00
eventBus.sendDownstream(command, config);
}
}
2021-10-26 22:19:41 +02:00
}
private sendUpstream(command: string, config: any, context?: EventBusContext) {
2022-05-06 00:22:35 +02:00
if (this.upstreamBus) {
2022-07-31 01:12:54 +02:00
// prevent eventBus.send from auto-propagating the command
this.upstreamBus.send(command, config, {...context, stopPropagation: true});
this.upstreamBus.sendUpstream(command, config, context);
this.upstreamBus.sendDownstream(command, config, context, this);
}
2021-10-26 22:19:41 +02:00
}
//#region iframe tunnelling
private setupIframeTunnelling() {
window.addEventListener('message', this.handleIframeMessage);
}
private destroyIframeTunnelling() {
window.removeEventListener('message', this.handleIframeMessage);
}
private handleIframeMessage(event: any) {
2023-03-30 00:43:30 +02:00
// console.log('GOT IFRAME MESSAGE!', event)
}
//#endregion
2021-10-26 22:19:41 +02:00
}