Continue slowly working on unified event bus

This commit is contained in:
Tamius Han 2022-08-21 22:46:06 +02:00
parent 46cb5e1078
commit a0388689cf
7 changed files with 152 additions and 143 deletions

View File

@ -2,16 +2,16 @@
* For some reason, Chrome really doesn't like when chrome.runtime * For some reason, Chrome really doesn't like when chrome.runtime
* methods are wrapped inside a ES6 proxy object. If 'port' is a * methods are wrapped inside a ES6 proxy object. If 'port' is a
* ES6 Proxy of a Port object that `chrome.runtime.connect()` creates, * ES6 Proxy of a Port object that `chrome.runtime.connect()` creates,
* then Chrome will do bullshits like `port.sendMessage` and * then Chrome will do bullshits like `port.sendMessage` and
* `port.onMessage.addListener` crashing your Vue3 UI with bullshits * `port.onMessage.addListener` crashing your Vue3 UI with bullshits
* excuses, e.g. * excuses, e.g.
* *
* | TypeError: Illegal invocation. Function must be called on * | TypeError: Illegal invocation. Function must be called on
* | an object of type Port * | an object of type Port
* *
* which is some grade A bullshit because Firefox can handle that just * which is some grade A bullshit because Firefox can handle that just
* fine. * fine.
* *
* There's two ways how I could handle this: * There's two ways how I could handle this:
* * Find out how to get the original object from the proxy Vue3 * * Find out how to get the original object from the proxy Vue3
* creates, which would take time and ruin my xmass holiday, or * creates, which would take time and ruin my xmass holiday, or
@ -19,8 +19,8 @@
* the very real possibility that there's prolly a reason Chrome * the very real possibility that there's prolly a reason Chrome
* does things in its own very special(tm) way, as if it had one * does things in its own very special(tm) way, as if it had one
* extra chromosome over Firefox. * extra chromosome over Firefox.
* *
* Easy chhoice, really. * Easy choice, really.
*/ */
export class ChromeShittinessMitigations { export class ChromeShittinessMitigations {
static port = null; static port = null;
@ -30,4 +30,4 @@ export class ChromeShittinessMitigations {
} }
} }
export default ChromeShittinessMitigations; export default ChromeShittinessMitigations;

View File

@ -44,12 +44,22 @@ export default class UWServer {
}], }],
'replace-css': [{ 'replace-css': [{
function: (message, context) => this.replaceCss(message.oldCssString, message.newCssString, context.comms.sender) function: (message, context) => this.replaceCss(message.oldCssString, message.newCssString, context.comms.sender)
}],
'get-current-site': [{
function: (message, context) => this.getCurrentSite()
}] }]
}; };
private gcTimeout: any; private gcTimeout: any;
uiLoggerInitialized: boolean = false; uiLoggerInitialized: boolean = false;
//#region getters
get activeTab() {
return browser.tabs.query({currentWindow: true, active: true});
}
//#endregion
constructor() { constructor() {
this.setup(); this.setup();
} }
@ -227,10 +237,26 @@ export default class UWServer {
this.selectedSubitem[menu] = subitem; this.selectedSubitem[menu] = subitem;
} }
async getCurrentSite() {
this.eventBus.send(
'set-current-site',
{
site: await this.getVideoTab(),
tabHostname: await this.getCurrentTabHostname(),
},
{
comms: {
forwardTo: 'popup'
}
}
)
}
async getCurrentTab() { async getCurrentTab() {
return (await browser.tabs.query({active: true, currentWindow: true}))[0]; return (await browser.tabs.query({active: true, currentWindow: true}))[0];
} }
async getVideoTab() { async getVideoTab() {
// friendly reminder: if current tab doesn't have a video, // friendly reminder: if current tab doesn't have a video,
// there won't be anything in this.videoTabs[this.currentTabId] // there won't be anything in this.videoTabs[this.currentTabId]
@ -279,8 +305,27 @@ export default class UWServer {
} }
} }
// chrome shitiness mitigation async getCurrentTabHostname() {
sendUnmarkPlayer(message) { const activeTab = await this.activeTab;
this.comms.sendUnmarkPlayer(message);
if (!activeTab || activeTab.length < 1) {
this.logger.log('warn', 'comms', 'There is no active tab for some reason. activeTab:', activeTab);
}
const url = activeTab[0].url;
var hostname;
if (url.indexOf("://") > -1) { //find & remove protocol (http, ftp, etc.) and get hostname
hostname = url.split('/')[2];
}
else {
hostname = url.split('/')[0];
}
hostname = hostname.split(':')[0]; //find & remove port number
hostname = hostname.split('?')[0]; //find & remove "?"
return hostname;
} }
} }

View File

@ -14,7 +14,8 @@ export interface EventBusContext {
comms?: { comms?: {
sender?: any, sender?: any,
port?: any, port?: any,
forwardTo?: 'all' | 'active' | 'contentScript' | 'sameOrigin', frame?: any,
forwardTo?: 'all' | 'active' | 'contentScript' | 'server' | 'sameOrigin' | 'popup',
} }
} }
@ -23,7 +24,7 @@ export default class EventBus {
private commands: { [x: string]: EventBusCommand[]} = {}; private commands: { [x: string]: EventBusCommand[]} = {};
private downstreamBuses: EventBus[] = []; private downstreamBuses: EventBus[] = [];
private upstreamBus?: EventBus; private upstreamBus?: EventBus;
private comms?: CommsClient; private comms?: CommsClient | CommsServer;
//#region lifecycle //#region lifecycle
destroy() { destroy() {
@ -34,7 +35,7 @@ export default class EventBus {
} }
//#endregion //#endregion
setComms(comms: CommsClient) { setComms(comms: CommsClient | CommsServer) {
this.comms = comms; this.comms = comms;
} }
@ -86,7 +87,7 @@ export default class EventBus {
} }
if (this.comms && !context?.fromComms) { if (this.comms && !context?.fromComms) {
this.comms.sendMessage({command, config}); this.comms.sendMessage({command, config}, context);
} }
if (context?.stopPropagation) { if (context?.stopPropagation) {
@ -114,7 +115,7 @@ export default class EventBus {
} }
sendDownstream(command: string, config: any, context?: EventBusContext, sourceEventBus?: EventBus) { private sendDownstream(command: string, config: any, context?: EventBusContext, sourceEventBus?: EventBus) {
for (const eventBus of this.downstreamBuses) { for (const eventBus of this.downstreamBuses) {
if (eventBus !== sourceEventBus) { if (eventBus !== sourceEventBus) {
// prevent eventBus.send from auto-propagating the command // prevent eventBus.send from auto-propagating the command
@ -124,7 +125,7 @@ export default class EventBus {
} }
} }
sendUpstream(command: string, config: any, context?: EventBusContext) { private sendUpstream(command: string, config: any, context?: EventBusContext) {
if (this.upstreamBus) { if (this.upstreamBus) {
// prevent eventBus.send from auto-propagating the command // prevent eventBus.send from auto-propagating the command
this.upstreamBus.send(command, config, {...context, stopPropagation: true}); this.upstreamBus.send(command, config, {...context, stopPropagation: true});

View File

@ -25,22 +25,34 @@ if (process.env.CHANNEL !== 'stable'){
* *
* EventBus is started first. Other components (including commsClient) follow later. * EventBus is started first. Other components (including commsClient) follow later.
* *
* Messages that pass through CommsServer need to define context object with
* context.comms.forwardTo field defined, with one of the following values:
*
* - all : all content scripts of ALL TABS
* - active : all content scripts in CURRENT TAB
* - contentScript : specific content script (requires other EventBusContext fields!)
* - backgroundScript : background script (considered default behaviour)
* - sameOrigin : ???
* - popup : extension popup
*
*
*
* *
* fig 0. ULTRAWIDIFY COMMUNICATION MAP * fig 0. ULTRAWIDIFY COMMUNICATION MAP
* *
* CS EVENT BUS * CS EVENT BUS
* (accessible within tab scripts) * (accessible within tab scripts)
* | NOT EVENT BUS * | BG EVENT BUS
* PageInfo x (accessible within popup) * PageInfo x (accessible within background page)
* x | * x |
* : : x UWServer * : : x UWServer
* x CommsClient <---------------x CommsServer x * x CommsClient <---------------x CommsServer x
* | (Connect to popup) * | (Connect to popup) X POPUP EVENT BUS
* | * | A (accessible within popup) /todo
* x eventBus.sendToTunnel() * x eventBus.sendToTunnel() | |
* <iframe tunnel> * <iframe tunnel> \----------------> ??? X
* A * A |
* | * | X App.vue
* V * V
* x <iframe tunnel> * x <iframe tunnel>
* | * |
@ -99,7 +111,7 @@ class CommsClient {
} }
//#endregion //#endregion
async sendMessage(message){ async sendMessage(message, context?){
message = JSON.parse(JSON.stringify(message)); // vue quirk. We should really use vue store instead message = JSON.parse(JSON.stringify(message)); // vue quirk. We should really use vue store instead
return browser.runtime.sendMessage(null, message, null); return browser.runtime.sendMessage(null, message, null);
} }

View File

View File

@ -13,7 +13,6 @@ class CommsServer {
settings: Settings; settings: Settings;
eventBus: EventBus; eventBus: EventBus;
ports: { ports: {
[tab: string] : { [tab: string] : {
[frame: string] : { [frame: string] : {
@ -23,49 +22,13 @@ class CommsServer {
} = {}; } = {};
popupPort: any; popupPort: any;
/**
* commands functions that handle incoming messages
* functions can have the following arguments, which are,
* in this order:
* message the message we received
* port|sender on persistent channels, second argument is port on which the server
* listens. If the message was sent in non-persistent way, this is the
* sender script/frame/whatever of the message
* sendResponse callback function on messages received via non-persistent channel
*/
commands: {[x: string]: ((a: any, b: any) => void | Promise<void>)[]} = {
'get-current-site': [
async (message, port) => {
port.postMessage({
cmd: 'set-current-site',
site: await this.server.getVideoTab(),
tabHostname: await this.getCurrentTabHostname()
});
},
],
'logging-stop-and-save': [ // TODO: possibly never used/superseded — check
(message, sender) => {
this.logger.log('info', 'comms', "Received command to stop logging and export the received input");
this.logger.addToGlobalHistory(`${message.host}::${sender?.tab?.id ?? '×'}-${sender.frameId ?? '×'}`, JSON.parse(message.history));
this.logger.finish();
}
],
'logging-save': [
(message, sender) => {
this.logger.log('info', 'comms', `Received command to save log for site ${message.host} (tabId ${sender.tab.id}, frameId ${sender.frameId}`);
this.logger.addToGlobalHistory(`${message?.host}::${sender?.tab?.id ?? '×'}-${sender?.frameId ?? '×'}`, JSON.parse(message.history));
}
]
}
//#region getters //#region getters
get activeTab() { get activeTab() {
return browser.tabs.query({currentWindow: true, active: true}); return browser.tabs.query({currentWindow: true, active: true});
} }
//#endregion //#endregion
//#region lifecycle
constructor(server) { constructor(server) {
this.server = server; this.server = server;
this.logger = server.logger; this.logger = server.logger;
@ -76,51 +39,69 @@ class CommsServer {
browser.runtime.onMessage.addListener((m, sender) => this.processReceivedMessage_nonpersistent(m, sender)); browser.runtime.onMessage.addListener((m, sender) => this.processReceivedMessage_nonpersistent(m, sender));
} }
subscribe(command, callback) { private onConnect(port){
if (!this.commands[command]) { // special case
this.commands[command] = [callback]; if (port.name === 'popup-port') {
} else { this.popupPort = port;
this.commands[command].push(callback); this.popupPort.onMessage.addListener( (m,p) => this.processReceivedMessage(m,p));
return;
} }
var tabId = port.sender.tab.id;
var frameId = port.sender.frameId;
if (! this.ports[tabId]){
this.ports[tabId] = {};
}
if (! this.ports[tabId][frameId]) {
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].onDisconnect.addListener( (p) => {
try {
delete this.ports[p.sender.tab.id][p.sender.frameId][port.name];
} catch (e) {
// no biggie if the thing above doesn't exist.
}
if (Object.keys(this.ports[tabId][frameId].length === 0)) {
delete this.ports[tabId][frameId];
if(Object.keys(this.ports[p.sender.tab.id]).length === 0) {
delete this.ports[tabId];
}
}
});
} }
async getCurrentTabHostname() { //#endregion
const activeTab = await this.activeTab;
if (!activeTab || activeTab.length < 1) { sendMessage(message, context?) {
this.logger.log('warn', 'comms', 'There is no active tab for some reason. activeTab:', activeTab); if (context?.comms.forwardTo === 'all') {
}
const url = activeTab[0].url;
var hostname;
if (url.indexOf("://") > -1) { //find & remove protocol (http, ftp, etc.) and get hostname
hostname = url.split('/')[2];
}
else {
hostname = url.split('/')[0];
}
hostname = hostname.split(':')[0]; //find & remove port number
hostname = hostname.split('?')[0]; //find & remove "?"
return hostname;
}
sendMessage(message, context) {
if (context?.forwardTo === 'all') {
return this.sendToAll(message); return this.sendToAll(message);
} }
if (context?.forwardTo === 'active') { if (context?.comms.forwardTo === 'active') {
return this.sendToActive(message); return this.sendToActive(message);
} }
if (context?.forwardTo === 'contentScript') { if (context?.comms.forwardTo === 'contentScript') {
return this.sendToFrame(message, context.tab, context.frame, context.port); return this.sendToFrame(message, context.tab, context.frame, context.port);
} }
if (context?.comms.forwardTo === 'popup') {
return this.sendToPopup(message);
}
} }
sendToAll(message){ /**
* Sends a message to popup script
*/
sendToPopup(message) {
this.popupPort.postMessage(message);
}
/**
* sends a message to ALL **CONTENT SCRIPTS**
* Does NOT send a message to popup.
**/
private sendToAll(message){
for(const tid in this.ports){ for(const tid in this.ports){
const tab = this.ports[tid]; const tab = this.ports[tid];
for(const frame in tab){ for(const frame in tab){
@ -132,7 +113,7 @@ class CommsServer {
} }
/** /**
* Sends a message to addon content scripts. * Sends a message to addon content scripts in a single browser tab.
* @param message message * @param message message
* @param tab the tab we want to send the message to * @param tab the tab we want to send the message to
* @param frame the frame within that tab that we want to send the message to * @param frame the frame within that tab that we want to send the message to
@ -172,7 +153,6 @@ class CommsServer {
} }
} }
private async sendToActive(message) { private async sendToActive(message) {
this.logger.log('info', 'comms', "%c[CommsServer::sendToActive] trying to send a message to active tab. Message:", "background: #dda; color: #11D", message); this.logger.log('info', 'comms', "%c[CommsServer::sendToActive] trying to send a message to active tab. Message:", "background: #dda; color: #11D", message);
@ -188,57 +168,26 @@ class CommsServer {
} }
} }
onConnect(port){ private async processReceivedMessage(message, port){
// special case
if (port.name === 'popup-port') {
this.popupPort = port;
this.popupPort.onMessage.addListener( (m,p) => this.processReceivedMessage(m,p));
return;
}
var tabId = port.sender.tab.id;
var frameId = port.sender.frameId;
if (! this.ports[tabId]){
this.ports[tabId] = {};
}
if (! this.ports[tabId][frameId]) {
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].onDisconnect.addListener( (p) => {
try {
delete this.ports[p.sender.tab.id][p.sender.frameId][port.name];
} catch (e) {
// no biggie if the thing above doesn't exist.
}
if (Object.keys(this.ports[tabId][frameId].length === 0)) {
delete this.ports[tabId][frameId];
if(Object.keys(this.ports[p.sender.tab.id]).length === 0) {
delete this.ports[tabId];
}
}
});
}
async processReceivedMessage(message, port){
this.logger.log('info', 'comms', "[CommsServer.js::processReceivedMessage] Received message from popup/content script!", message, "port", port); this.logger.log('info', 'comms', "[CommsServer.js::processReceivedMessage] Received message from popup/content script!", message, "port", port);
this.eventBus.send(message.command, message.config, {comms: {port}, fromComms: true}); // this triggers events
this.eventBus.send(
message.command,
message.config,
{
...message.context,
comms: {port},
fromComms: true
}
);
} }
processReceivedMessage_nonpersistent(message, sender){ private processReceivedMessage_nonpersistent(message, sender){
this.logger.log('info', 'comms', "%c[CommsServer.js::processMessage_nonpersistent] Received message from background script!", "background-color: #11D; color: #aad", message, sender); this.logger.log('info', 'comms', "%c[CommsServer.js::processMessage_nonpersistent] Received message from background script!", "background-color: #11D; color: #aad", message, sender);
this.eventBus.send(message.command, message.config, {comms: {sender}, fromComms: true}); this.eventBus.send(message.command, message.config, {comms: {sender}, fromComms: true});
} }
// chrome shitiness mitigation
sendUnmarkPlayer(message) {
this.logger.log('info', 'comms', '[CommsServer.js::sendUnmarkPlayer] Chrome is a shit browser that doesn\'t do port.postMessage() in unload events, so we have to resort to inelegant hacks. If you see this, then the workaround method works.');
this.processReceivedMessage(message, this.popupPort);
}
} }
export default CommsServer; export default CommsServer;

View File

@ -25,7 +25,7 @@
</div> </div>
<pre> <pre>
---- site: ---- site:
{{site}} {{site}}
---- ----
</pre> </pre>
@ -93,9 +93,11 @@ export default {
// get info about current site from background script // get info about current site from background script
while (true) { while (true) {
try { try {
console.log('trying to get site ...');
this.getSite(); this.getSite();
console.log('site gottne');
} catch (e) { } catch (e) {
console.warn('failed to load site:', e);
} }
await this.sleep(5000); await this.sleep(5000);
} }