Rework message passing and simplify tunnels
This commit is contained in:
parent
826acf9a3f
commit
2cce60fb9f
@ -17,11 +17,14 @@ export interface EventBusContext {
|
||||
stopPropagation?: boolean;
|
||||
|
||||
origin?: CommsOrigin;
|
||||
frameUrl?: string;
|
||||
|
||||
// tab?: number;
|
||||
// frame?: number;
|
||||
// port?: string;
|
||||
|
||||
visitedBusses?: string[];
|
||||
|
||||
comms?: {
|
||||
forwardTo?: 'all' | 'active' | 'popup' | 'contentScript' | 'all-frames';
|
||||
sender?: Runtime.MessageSender;
|
||||
|
||||
8
src/common/interfaces/HostData.interface.ts
Normal file
8
src/common/interfaces/HostData.interface.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { ExtensionEnvironment } from '@src/common/interfaces/SettingsInterface';
|
||||
|
||||
export interface HostInfo {
|
||||
host: string,
|
||||
hasVideo: boolean,
|
||||
minEnvironment: ExtensionEnvironment,
|
||||
maxEnvironment: ExtensionEnvironment
|
||||
};
|
||||
6
src/common/interfaces/IframeTunnelPayload.interface.ts
Normal file
6
src/common/interfaces/IframeTunnelPayload.interface.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { EventBusMessage } from '@src/common/interfaces/EventBusMessage.interface';
|
||||
|
||||
export interface IframeTunnelPayload {
|
||||
action: string,
|
||||
payload: EventBusMessage,
|
||||
}
|
||||
@ -5,6 +5,8 @@ import { ComponentLogger } from '@src/ext/module/logging/ComponentLogger';
|
||||
import { BLANK_LOGGER_CONFIG, LogAggregator } from '@src/ext/module/logging/LogAggregator';
|
||||
import CommsServer from '@src/ext/module/comms/CommsServer';
|
||||
import BrowserDetect from '@src/ext/conf/BrowserDetect';
|
||||
import { HostInfo } from '@src/common/interfaces/HostData.interface';
|
||||
import { ExtensionEnvironment } from '@src/common/interfaces/SettingsInterface';
|
||||
|
||||
|
||||
const BASE_LOGGING_STYLES = {
|
||||
@ -264,6 +266,7 @@ export default class UWServer {
|
||||
|
||||
const tabHostname = await this.getCurrentTabHostname();
|
||||
this.logger.info('getCurrentSite', 'Returning data:', {site, tabHostname});
|
||||
console.info('get-current-site : returning data:', {site, tabHostname});
|
||||
|
||||
this.eventBus.send(
|
||||
'set-current-site',
|
||||
@ -284,6 +287,36 @@ export default class UWServer {
|
||||
}
|
||||
|
||||
|
||||
private populateFrameVideoStatus(tabId: number, hostnames: string[]) {
|
||||
const out: HostInfo[] = [];
|
||||
|
||||
for (const host of hostnames) {
|
||||
let video = {hasVideo: false, minEnvironment: ExtensionEnvironment.Normal, maxEnvironment: ExtensionEnvironment.Fullscreen};
|
||||
|
||||
for (const frameKey in this.videoTabs[tabId].frames) {
|
||||
const frame = this.videoTabs[tabId].frames[frameKey];
|
||||
|
||||
if (frame.host === host) {
|
||||
video.hasVideo = true;
|
||||
if (frame.environment > video.maxEnvironment) {
|
||||
video.maxEnvironment = frame.environment;
|
||||
}
|
||||
if (frame.environment < video.minEnvironment) {
|
||||
video.minEnvironment = frame.environment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out.push({
|
||||
host,
|
||||
...video,
|
||||
})
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private _lastVideoTabData: any | undefined;
|
||||
async getVideoTab() {
|
||||
// friendly reminder: if current tab doesn't have a video,
|
||||
// there won't be anything in this.videoTabs[this.currentTabId]
|
||||
@ -300,41 +333,52 @@ export default class UWServer {
|
||||
|
||||
const hostnames = await this.comms.listUniqueFrameHosts();
|
||||
|
||||
if (this.videoTabs[ctab.id]) {
|
||||
// if video is older than PageInfo's video rescan period (+ 4000ms of grace),
|
||||
// we clean it up from videoTabs[tabId].frames array.
|
||||
const ageLimit = Date.now() - this.settings.active.pageInfo.timeouts.rescan - 4000;
|
||||
try {
|
||||
for (const key in this.videoTabs[ctab.id].frames) {
|
||||
if (this.videoTabs[ctab.id].frames[key].registerTime < ageLimit) {
|
||||
delete this.videoTabs[ctab.id].frames[key];
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// something went wrong. There's prolly no frames.
|
||||
return {
|
||||
host: this.extractHostname(ctab.url),
|
||||
frames: [],
|
||||
selected: this.selectedSubitem
|
||||
// this probably means we're inside a problematic page
|
||||
if (!this.videoTabs[ctab.id]) {
|
||||
return this._lastVideoTabData ?? {
|
||||
host: 'INVALID SITE',
|
||||
frames: [],
|
||||
hostnames: [],
|
||||
}
|
||||
}
|
||||
|
||||
// if video is older than PageInfo's video rescan period (+ 4000ms of grace),
|
||||
// we clean it up from videoTabs[tabId].frames array.
|
||||
const ageLimit = Date.now() - this.settings.active.pageInfo.timeouts.rescan - 4000;
|
||||
try {
|
||||
for (const key in this.videoTabs[ctab.id].frames) {
|
||||
if (this.videoTabs[ctab.id].frames[key].registerTime < ageLimit) {
|
||||
delete this.videoTabs[ctab.id].frames[key];
|
||||
}
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
// something went wrong. There's prolly no frames.
|
||||
// this probably shouldn't ever run.
|
||||
return {
|
||||
...this.videoTabs[ctab.id],
|
||||
host: this.extractHostname(ctab.url),
|
||||
hostnames,
|
||||
hostnames: [],
|
||||
frames: [],
|
||||
selected: this.selectedSubitem
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// return something more or less empty if this tab doesn't have
|
||||
// a video registered for it
|
||||
return {
|
||||
// Ensure hostnames with videos come before hostnames without videos
|
||||
// Also ensure hostnames are sorted alphabetically
|
||||
const populatedHostnames = this.populateFrameVideoStatus(ctab.id, hostnames);
|
||||
populatedHostnames.sort((a: HostInfo, b: HostInfo) => {
|
||||
return a.hasVideo === b.hasVideo
|
||||
? a.host === b.host ? 0 : a.host < b.host ? -1 : 1
|
||||
: a.hasVideo < b.hasVideo ? 1 : -1;
|
||||
});
|
||||
|
||||
this._lastVideoTabData = {
|
||||
host: this.extractHostname(ctab.url),
|
||||
frames: [],
|
||||
hostnames,
|
||||
selected: this.selectedSubitem,
|
||||
}
|
||||
hostnames: populatedHostnames.map(x => x.host), // todo: try eliminating this
|
||||
populatedHostnames: populatedHostnames,
|
||||
selected: this.selectedSubitem
|
||||
};
|
||||
|
||||
return this._lastVideoTabData;
|
||||
}
|
||||
|
||||
async getCurrentTabHostname() {
|
||||
|
||||
@ -1,34 +1,19 @@
|
||||
import { EventBusCommand, EventBusContext } from '@src/common/interfaces/EventBusMessage.interface';
|
||||
import { EventBusCommand, EventBusContext, EventBusMessage } from '@src/common/interfaces/EventBusMessage.interface';
|
||||
import { IframeTunnelPayload } from '@src/common/interfaces/IframeTunnelPayload.interface';
|
||||
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';
|
||||
|
||||
|
||||
|
||||
// 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,
|
||||
// }
|
||||
// }
|
||||
|
||||
export default class EventBus {
|
||||
|
||||
private name: string;
|
||||
private uuid = crypto.randomUUID();
|
||||
|
||||
private commands: { [x: string]: EventBusCommand[]} = {};
|
||||
private comms?: CommsClient | CommsServer;
|
||||
private commsOrigin: CommsOrigin;
|
||||
|
||||
|
||||
private disableTunnel: boolean = false;
|
||||
private popupContext: any = {};
|
||||
@ -37,11 +22,12 @@ export default class EventBus {
|
||||
|
||||
// private uiUri = window.location.href;
|
||||
|
||||
constructor(options?: {isUWServer?: boolean, name?: string}) {
|
||||
constructor(options?: {isUWServer?: boolean, name?: string, commsOrigin?: CommsOrigin}) {
|
||||
if (!options?.isUWServer) {
|
||||
this.setupIframeTunnelling();
|
||||
}
|
||||
this.name = options?.name;
|
||||
this.name = options?.name ?? '(unnamed EventBus)';
|
||||
this.commsOrigin = options?.commsOrigin;
|
||||
}
|
||||
|
||||
setupPopupTunnelWorkaround(context: EventBusContext): void {
|
||||
@ -102,8 +88,7 @@ export default class EventBus {
|
||||
}
|
||||
}
|
||||
|
||||
send(command: string, commandData: any, context?: EventBusContext) {
|
||||
console.info('sending eventBus command:', this.name, 'command:', {command, commandData, context});
|
||||
send(command: string, commandData: any, context: EventBusContext = {}) {
|
||||
// execute commands we have subscriptions for
|
||||
|
||||
if (this.commands?.[command]) {
|
||||
@ -146,7 +131,9 @@ export default class EventBus {
|
||||
}
|
||||
);
|
||||
}
|
||||
this.sendToTunnel(command, commandData);
|
||||
this.sendToTunnel(command, commandData, context);
|
||||
} else {
|
||||
console.warn('message was already sent to iframe, doing nothing ...')
|
||||
}
|
||||
|
||||
if (context?.stopPropagation) {
|
||||
@ -160,12 +147,14 @@ export default class EventBus {
|
||||
* @param command
|
||||
* @param config
|
||||
*/
|
||||
sendToTunnel(command: string, config: any) {
|
||||
if (!this.disableTunnel) {
|
||||
sendToTunnel(command: string, config: any, context: EventBusContext = {}) {
|
||||
context.visitedBusses = [...context.visitedBusses ?? [], this.uuid];
|
||||
|
||||
if (!this.disableTunnel && typeof window !== 'undefined') {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
action: 'uw-bus-tunnel',
|
||||
payload: {action: command, config}
|
||||
payload: {command, config, context} as EventBusMessage
|
||||
},
|
||||
'*'
|
||||
);
|
||||
@ -175,10 +164,20 @@ export default class EventBus {
|
||||
// in the popup
|
||||
if (this.comms) {
|
||||
try {
|
||||
this.comms.sendMessage({command, config, context: this.popupContext}, this.popupContext);
|
||||
this.comms.sendMessage(
|
||||
{
|
||||
command,
|
||||
config,
|
||||
context: {
|
||||
...this.popupContext,
|
||||
...context
|
||||
}
|
||||
},
|
||||
this.popupContext
|
||||
);
|
||||
} catch (e) {
|
||||
if (command !== 'reload-required') {
|
||||
this.send('reload-required', {});
|
||||
this.send('reload-required', {}, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -188,17 +187,34 @@ export default class EventBus {
|
||||
//#region iframe tunnelling
|
||||
private setupIframeTunnelling() {
|
||||
// forward messages coming from iframe tunnels
|
||||
window.addEventListener('message', this.handleIframeMessage);
|
||||
window.addEventListener('message', this);
|
||||
}
|
||||
private destroyIframeTunnelling() {
|
||||
window.removeEventListener('message', this.handleIframeMessage);
|
||||
window.removeEventListener('message', this);
|
||||
}
|
||||
private handleIframeMessage(event: any) {
|
||||
/**
|
||||
* Handles 'message' events (formerly handleIframeMessage)
|
||||
* @param event
|
||||
* @returns
|
||||
*/
|
||||
handleEvent(event: any) {
|
||||
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);
|
||||
|
||||
const payload = event.data.payload as EventBusMessage;
|
||||
|
||||
console.info(this.name, 'received message from iframe. command:', payload);
|
||||
if (!payload.context) {
|
||||
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);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@ -65,7 +65,8 @@ if (process.env.CHANNEL !== 'stable'){
|
||||
export enum CommsOrigin {
|
||||
ContentScript = 1,
|
||||
Popup = 2,
|
||||
Server = 3
|
||||
Server = 3,
|
||||
Ui = 4, // in-player UI
|
||||
}
|
||||
|
||||
class CommsClient {
|
||||
@ -148,7 +149,7 @@ class CommsClient {
|
||||
|
||||
// send to server
|
||||
if (!context?.borderCrossings?.commsServer) {
|
||||
return chrome.runtime.sendMessage(null, message, null);
|
||||
return chrome?.runtime?.sendMessage(null, message, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { ExtensionEnvironment } from './../../../common/interfaces/SettingsInterface';
|
||||
import { ComponentLogger } from '@src/ext/module/logging/ComponentLogger';
|
||||
import Settings from '@src/ext/module/settings/Settings';
|
||||
import EventBus from '@src/ext/module/EventBus';
|
||||
import { CommsOrigin } from './CommsClient';
|
||||
import UWServer from '@src/ext/UWServer';
|
||||
import { HostInfo } from '@src/common/interfaces/HostData.interface';
|
||||
|
||||
const BASE_LOGGING_STYLES = {
|
||||
log: "background-color: #11D; color: #aad",
|
||||
@ -151,6 +153,15 @@ class CommsServer {
|
||||
return hosts;
|
||||
}
|
||||
|
||||
async getUniqueFrameHosts() {
|
||||
const aTab = await this.activeTab;
|
||||
|
||||
const tabPort = this.ports[aTab.id];
|
||||
const hosts: HostInfo[] = [];
|
||||
|
||||
|
||||
}
|
||||
|
||||
sendMessage(message, context?) {
|
||||
this.logger.debug('sendMessage', `preparing to send message ${message.command ?? ''} ...`, {message, context});
|
||||
// stop messages from returning where they came from, and prevent
|
||||
|
||||
@ -97,10 +97,13 @@ class UI {
|
||||
window.addEventListener('message', (event: MessageEvent) => {
|
||||
const data = event.data;
|
||||
|
||||
if (data?.action !== 'uw-bus-tunnel') return;
|
||||
if (data?.action !== 'uw-bus-tunnel') {
|
||||
return;
|
||||
}
|
||||
|
||||
const command = data.payload.command;
|
||||
const config = data.payload.config;
|
||||
const payload = data.payload;
|
||||
|
||||
console.log('forwarding from tunnel to event bus. payload', payload);
|
||||
|
||||
// Forward to all iframes except the source
|
||||
(UwuiWindow as any).instances?.forEach(win => {
|
||||
@ -109,7 +112,7 @@ class UI {
|
||||
iframe.contentWindow?.postMessage(
|
||||
{
|
||||
action: 'uw-bus-tunnel',
|
||||
payload: { command, config }
|
||||
payload,
|
||||
},
|
||||
'*'
|
||||
);
|
||||
@ -454,9 +457,9 @@ class UI {
|
||||
}
|
||||
});
|
||||
|
||||
this.eventBus.forwardToIframe(iframe, (cmd, payload, context) => {
|
||||
this.eventBus.forwardToIframe(iframe, (command, config, context) => {
|
||||
iframe.contentWindow?.postMessage(
|
||||
{ action: 'uw-bus-tunnel', payload: { command: cmd, config: payload } },
|
||||
{ action: 'uw-bus-tunnel', payload: { command, config, context } },
|
||||
'*'
|
||||
);
|
||||
});
|
||||
|
||||
@ -255,7 +255,13 @@ class PageInfo {
|
||||
//
|
||||
// no but honestly fuck Chrome.
|
||||
if (videosDetected || this.hasVideo()) {
|
||||
this.eventBus.send('has-video', null);
|
||||
this.eventBus.send(
|
||||
'has-video',
|
||||
{
|
||||
site: window.location.host,
|
||||
isIframe: window.self !== window.top,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.eventBus.send('noVideo', null);
|
||||
}
|
||||
|
||||
@ -105,6 +105,11 @@ class VideoData {
|
||||
function: () => {
|
||||
this.onEnvironmentChanged();
|
||||
}
|
||||
},
|
||||
'get-current-site': {
|
||||
function: () => {
|
||||
console.warn('received get current site!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -263,36 +263,12 @@ export default defineComponent({
|
||||
this.selectedTab = this.defaultTab;
|
||||
}
|
||||
|
||||
console.log('does event bus exist yet?', this.eventBus);
|
||||
if (!this.eventBus) {
|
||||
// inter-frame communication should be set up by eventBus for free
|
||||
this.eventBus = new EventBus({name: 'ui-window'});
|
||||
}
|
||||
|
||||
/**
|
||||
* SETUP CROSS-FRAME COMMUNICATION
|
||||
*
|
||||
* 1. We need to allow eventBus to send messages to the parent page
|
||||
* 2. We need to plug messages we receive from parent page into the event bus
|
||||
*/
|
||||
this.eventBus.sendToTunnel = (command: string, config: any) => {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
action: 'uw-bus-tunnel',
|
||||
payload: { command, config }
|
||||
},
|
||||
'*'
|
||||
);
|
||||
};
|
||||
|
||||
window.addEventListener('message', (event: MessageEvent) => {
|
||||
const data = event.data;
|
||||
if (data?.action === 'uw-bus-tunnel') {
|
||||
// prevent double-crossing by marking borderCrossings
|
||||
this.eventBus.send(data.payload.command, data.payload.config, {
|
||||
borderCrossings: { iframe: true }
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Subscribe to event bus commands.
|
||||
* Note that showing and hiding of the settings window is no longer handled by iframe
|
||||
@ -336,8 +312,34 @@ export default defineComponent({
|
||||
this.loadFrames();
|
||||
}
|
||||
},
|
||||
'has-video': {
|
||||
function: (config, context) => {
|
||||
|
||||
console.log('has video received.', config, context);
|
||||
console.log('———————— building site:', !config.isIFrame, this.site?.host, config.site, this.site?.host !== config.site);
|
||||
|
||||
// update site data.
|
||||
// If we're here, then the settings page is being opened from the in-player popup.
|
||||
// This means that config.isIframe can generally be ignored.
|
||||
// However, we absolutely cannot accept iframes as source for this.site when we
|
||||
// open settings window from the extension popup. This needs to be TODO:
|
||||
if (this.site?.host !== config.site) {
|
||||
this.site = {
|
||||
host: config.site,
|
||||
frames: [],
|
||||
hostnames: [],
|
||||
};
|
||||
|
||||
this.siteSettings = this.settings.getSiteSettings({site: this.site.host});
|
||||
this.loadHostnames();
|
||||
this.loadFrames();
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
console.log('—————————————————————— polling from iframe window!!!!')
|
||||
this.startSitePolling();
|
||||
// this.eventBus.subscribe(
|
||||
// 'uw-show-ui',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user