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
* methods are wrapped inside a ES6 proxy object. If 'port' is a
* 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
* excuses, e.g.
*
*
* | TypeError: Illegal invocation. Function must be called on
* | an object of type Port
*
*
* which is some grade A bullshit because Firefox can handle that just
* fine.
*
*
* There's two ways how I could handle this:
* * Find out how to get the original object from the proxy Vue3
* 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
* does things in its own very special(tm) way, as if it had one
* extra chromosome over Firefox.
*
* Easy chhoice, really.
*
* Easy choice, really.
*/
export class ChromeShittinessMitigations {
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': [{
function: (message, context) => this.replaceCss(message.oldCssString, message.newCssString, context.comms.sender)
}],
'get-current-site': [{
function: (message, context) => this.getCurrentSite()
}]
};
private gcTimeout: any;
uiLoggerInitialized: boolean = false;
//#region getters
get activeTab() {
return browser.tabs.query({currentWindow: true, active: true});
}
//#endregion
constructor() {
this.setup();
}
@ -227,10 +237,26 @@ export default class UWServer {
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() {
return (await browser.tabs.query({active: true, currentWindow: true}))[0];
}
async getVideoTab() {
// friendly reminder: if current tab doesn't have a video,
// there won't be anything in this.videoTabs[this.currentTabId]
@ -279,8 +305,27 @@ export default class UWServer {
}
}
// chrome shitiness mitigation
sendUnmarkPlayer(message) {
this.comms.sendUnmarkPlayer(message);
async getCurrentTabHostname() {
const activeTab = await this.activeTab;
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?: {
sender?: 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 downstreamBuses: EventBus[] = [];
private upstreamBus?: EventBus;
private comms?: CommsClient;
private comms?: CommsClient | CommsServer;
//#region lifecycle
destroy() {
@ -34,7 +35,7 @@ export default class EventBus {
}
//#endregion
setComms(comms: CommsClient) {
setComms(comms: CommsClient | CommsServer) {
this.comms = comms;
}
@ -86,7 +87,7 @@ export default class EventBus {
}
if (this.comms && !context?.fromComms) {
this.comms.sendMessage({command, config});
this.comms.sendMessage({command, config}, context);
}
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) {
if (eventBus !== sourceEventBus) {
// 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) {
// prevent eventBus.send from auto-propagating the command
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.
*
* 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
*
* CS EVENT BUS
* (accessible within tab scripts)
* | NOT EVENT BUS
* PageInfo x (accessible within popup)
* | BG EVENT BUS
* PageInfo x (accessible within background page)
* x |
* : : x UWServer
* x CommsClient <---------------x CommsServer x
* | (Connect to popup)
* |
* x eventBus.sendToTunnel()
* <iframe tunnel>
* A
* |
* | (Connect to popup) X POPUP EVENT BUS
* | A (accessible within popup) /todo
* x eventBus.sendToTunnel() | |
* <iframe tunnel> \----------------> ??? X
* A |
* | X App.vue
* V
* x <iframe tunnel>
* |
@ -99,7 +111,7 @@ class CommsClient {
}
//#endregion
async sendMessage(message){
async sendMessage(message, context?){
message = JSON.parse(JSON.stringify(message)); // vue quirk. We should really use vue store instead
return browser.runtime.sendMessage(null, message, null);
}

View File

View File

@ -13,7 +13,6 @@ class CommsServer {
settings: Settings;
eventBus: EventBus;
ports: {
[tab: string] : {
[frame: string] : {
@ -23,49 +22,13 @@ class CommsServer {
} = {};
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
get activeTab() {
return browser.tabs.query({currentWindow: true, active: true});
}
//#endregion
//#region lifecycle
constructor(server) {
this.server = server;
this.logger = server.logger;
@ -76,51 +39,69 @@ class CommsServer {
browser.runtime.onMessage.addListener((m, sender) => this.processReceivedMessage_nonpersistent(m, sender));
}
subscribe(command, callback) {
if (!this.commands[command]) {
this.commands[command] = [callback];
} else {
this.commands[command].push(callback);
private onConnect(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 getCurrentTabHostname() {
const activeTab = await this.activeTab;
//#endregion
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;
}
sendMessage(message, context) {
if (context?.forwardTo === 'all') {
sendMessage(message, context?) {
if (context?.comms.forwardTo === 'all') {
return this.sendToAll(message);
}
if (context?.forwardTo === 'active') {
if (context?.comms.forwardTo === 'active') {
return this.sendToActive(message);
}
if (context?.forwardTo === 'contentScript') {
if (context?.comms.forwardTo === 'contentScript') {
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){
const tab = this.ports[tid];
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 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
@ -172,7 +153,6 @@ class CommsServer {
}
}
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);
@ -188,57 +168,26 @@ class CommsServer {
}
}
onConnect(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){
private async processReceivedMessage(message, 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.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;

View File

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