ultrawidify/src/ext/lib/comms/CommsServer.ts

345 lines
13 KiB
TypeScript
Raw Normal View History

import Debug from '../../conf/Debug';
import BrowserDetect from '../../conf/BrowserDetect';
2021-03-06 01:27:28 +01:00
import Logger from '../Logger';
import Settings from '../Settings';
import { browser } from 'webextension-polyfill-ts';
import ExtensionMode from '../../../common/enums/ExtensionMode.enum';
class CommsServer {
2021-03-06 01:27:28 +01:00
server: any;
logger: Logger;
settings: Settings;
2021-03-06 02:22:56 +01:00
ports: {
[frame: string] : {
[port: string]: any
}
}[] = [];
2021-03-06 01:27:28 +01:00
popupPort: any;
commands: {[x: string]: ((a: any, b: any) => void | Promise<void>)[]}
constructor(server) {
this.server = server;
this.logger = server.logger;
2018-08-07 23:31:28 +02:00
this.settings = server.settings;
2020-01-30 01:06:21 +01:00
this.popupPort = null;
2021-03-06 01:27:28 +01:00
browser.runtime.onConnect.addListener(p => this.onConnect(p));
browser.runtime.onMessage.addListener((m, sender) => this.processReceivedMessage_nonpersistent(m, sender));
2020-01-30 01:06:21 +01:00
// 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
this.commands = {
'announce-zoom': [
(message) => {
try {
// forward message to the popup
this.popupPort.postMessage({cmd: 'set-current-zoom', zoom: message.zoom});
} catch (e) {
// if popup is closed, this will/may fail. This is okay, so we just ignore this error
}
},
],
'get-current-zoom': [
(message) => this.sendToActive(message),
],
'get-current-site': [
async (message, port) => {
port.postMessage({
cmd: 'set-current-site',
site: await this.server.getVideoTab(),
tabHostname: await this.getCurrentTabHostname()
});
},
],
'popup-set-selected-tab': [
(message) => this.server.setSelectedTab(message.selectedMenu, message.selectedSubitem),
],
'has-video': [
(message, port) => this.server.registerVideo(port.sender),
],
'noVideo': [
(message, port) => this.server.unregisterVideo(port.sender),
],
'inject-css': [
(message, sender) => this.server.injectCss(message.cssString, sender),
],
'eject-css': [
(message, sender) => this.server.removeCss(message.cssString, sender),
],
'replace-css': [
(message, sender) => this.server.replaceCss(message.oldCssString, message.newCssString, sender),
],
2021-03-06 01:27:28 +01:00
// 'get-config': [
// (message, port) => {
// this.logger.log('info', 'comms', "CommsServer: received get-config. Active settings?", this.settings.active, "\n(settings:", this.settings, ")");
// port.postMessage(
// {cmd: "set-config", conf: this.settings.active, site: this.server.currentSite}
// );
// },
// ],
2020-01-30 01:06:21 +01:00
'get-config': [
2021-03-06 01:27:28 +01:00
(message, sender) => {
var ret = {extensionConf: JSON.stringify(this.settings.active)};
this.logger.log('info', 'comms', "%c[CommsServer.js::processMessage_nonpersistent] Returning this:", "background-color: #11D; color: #aad", ret);
Promise.resolve(ret);
2020-01-30 01:06:21 +01:00
}
],
'autoar-enable': [
() => {
2021-03-06 01:27:28 +01:00
this.settings.active.sites['@global'].autoar = ExtensionMode.Enabled;
2020-01-30 01:06:21 +01:00
this.settings.save();
2020-01-30 23:17:43 +01:00
this.logger.log('info', 'comms', "[uw-bg] autoar set to enabled (blacklist). evidenz:", this.settings.active);
2020-01-30 01:06:21 +01:00
}
],
'autoar-disable': [
(message) => {
2021-03-06 01:27:28 +01:00
this.settings.active.sites['@global'].autoar = ExtensionMode.Disabled;
2020-01-30 01:06:21 +01:00
if (message.reason){
this.settings.active.arDetect.disabledReason = message.reason;
} else {
this.settings.active.arDetect.disabledReason = 'User disabled';
}
this.settings.save();
2020-01-30 23:17:43 +01:00
this.logger.log('info', 'comms', "[uw-bg] autoar set to disabled. evidenz:", this.settings.active);
2020-01-30 01:06:21 +01:00
}
],
'autoar-set-interval': [
(message) => {
2020-01-30 23:17:43 +01:00
this.logger.log('info', 'comms', `[uw-bg] trying to set new interval for autoAr. New interval is, ${message.timeout} ms`);
2020-01-30 01:06:21 +01:00
// set fairly liberal limit
var timeout = message.timeout < 4 ? 4 : message.timeout;
2021-03-06 01:27:28 +01:00
this.settings.active.arDetect.timers.playing = timeout;
2020-01-30 01:06:21 +01:00
this.settings.save();
}
],
'logging-stop-and-save': [ // TODO: possibly never used/superseded — check
(message, sender) => {
2020-01-30 23:17:43 +01:00
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));
2020-01-30 01:06:21 +01:00
this.logger.finish();
}
],
'logging-save': [
(message, sender) => {
2020-01-30 23:17:43 +01:00
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));
2020-01-30 01:06:21 +01:00
}
]
}
}
subscribe(command, callback) {
if (!this.commands[command]) {
this.commands[command] = [callback];
} else {
this.commands[command].push(callback);
}
}
async getCurrentTabHostname() {
2021-03-06 01:27:28 +01:00
const activeTab = await this.activeTab;
2020-02-08 00:41:10 +01:00
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;
}
sendToAll(message){
for(const tab of this.ports){
for(const frame in tab){
for (const port in tab[frame]) {
tab[frame][port].postMessage(message);
}
}
}
}
2021-03-06 01:27:28 +01:00
get activeTab() {
return browser.tabs.query({currentWindow: true, active: true});
2018-07-16 22:30:52 +02:00
}
2021-03-06 01:27:28 +01:00
/**
* Sends a message to addon content scripts.
* @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
* @param port if defined, message will only be sent to that specific script, otherwise it gets sent to all scripts of a given frame
*/
async sendToFrameContentScripts(message, tab, frame, port?) {
if (port !== undefined) {
this.ports[tab][frame][port].postMessage(message);
return;
}
2021-03-06 01:27:28 +01:00
for (const framePort in this.ports[tab][frame]) {
this.ports[tab][frame][framePort].postMessage(message);
}
}
2021-03-06 01:27:28 +01:00
async sendToFrame(message, tab, frame, port?) {
2020-01-30 23:17:43 +01:00
this.logger.log('info', 'comms', `%c[CommsServer::sendToFrame] attempting to send message to tab ${tab}, frame ${frame}`, "background: #dda; color: #11D", message);
2018-11-21 20:41:15 +01:00
if (isNaN(tab)) {
2020-02-08 00:41:10 +01:00
if (frame === '__playing') {
2018-11-21 20:41:15 +01:00
message['playing'] = true;
this.sendToAll(message);
return;
2020-02-08 00:41:10 +01:00
} else if (frame === '__all') {
2018-11-21 20:41:15 +01:00
this.sendToAll(message);
return;
}
2020-05-16 22:02:06 +02:00
[tab, frame] = frame.split('-');
2018-11-21 20:41:15 +01:00
}
2020-01-30 23:17:43 +01:00
this.logger.log('info', 'comms', `%c[CommsServer::sendToFrame] attempting to send message to tab ${tab}, frame ${frame}`, "background: #dda; color: #11D", message);
2018-11-21 20:41:15 +01:00
try {
this.sendToFrameContentScripts(message, tab, frame, port);
2018-11-21 20:41:15 +01:00
} catch (e) {
2020-01-30 23:17:43 +01:00
this.logger.log('error', 'comms', `%c[CommsServer::sendToFrame] Sending message failed. Reason:`, "background: #dda; color: #11D", e);
2018-11-21 20:41:15 +01:00
}
}
async sendToAllFrames(message, tab, port) {
for (const frame in this.ports[tab]) {
this.sendToFrameContentScripts(message, tab, frame, port);
}
}
2018-07-16 22:30:52 +02:00
async sendToActive(message) {
2020-01-30 23:17:43 +01:00
this.logger.log('info', 'comms', "%c[CommsServer::sendToActive] trying to send a message to active tab. Message:", "background: #dda; color: #11D", message);
2021-03-06 01:27:28 +01:00
const tabs = await this.activeTab;
2020-01-30 23:17:43 +01:00
this.logger.log('info', 'comms', "[CommsServer::_sendToActive] currently active tab(s)?", tabs);
for (const frame in this.ports[tabs[0].id]) {
this.logger.log('info', 'comms', "key?", frame, this.ports[tabs[0].id]);
}
for (const frame in this.ports[tabs[0].id]) {
this.sendToFrameContentScripts(message, tabs[0].id, frame);
}
}
onConnect(port){
// poseben primer | special case
if (port.name === 'popup-port') {
this.popupPort = port;
2020-03-09 21:29:39 +01:00
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;
2020-03-09 21:29:39 +01:00
this.ports[tabId][frameId][port.name].onMessage.addListener( (m,p) => this.processReceivedMessage(m, p));
2020-03-09 21:29:39 +01:00
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];
}
}
});
}
2020-01-30 01:06:21 +01:00
2021-03-06 01:27:28 +01:00
// TODO: sendResponse seems redundant — it used to be a callback for
// chrome-based browsers, but browser polyfill doesn't do callback. Just
// awaits.
async execCmd(message, portOrSender, sendResponse?) {
2020-01-30 23:17:43 +01:00
this.logger.log(
'info', 'comms', '[CommsServer.js::execCmd] Received message', message,
2020-02-04 00:56:31 +01:00
". Port/sender:", portOrSender, "sendResponse:", sendResponse, "\nThere is ", this.commands[message.cmd]?.length ?? 0,
2020-01-30 23:17:43 +01:00
" command(s) for action", message.cmd
);
if (this.commands[message.cmd]) {
for (const c of this.commands[message.cmd]) {
2020-03-09 21:29:39 +01:00
try {
2021-03-06 01:27:28 +01:00
await c(message, portOrSender);
2020-03-09 21:29:39 +01:00
} catch (e) {
this.logger.log('error', 'debug', "[CommsServer.js::execCmd] failed to execute command.", e)
}
}
2020-01-30 01:06:21 +01:00
}
}
2021-03-06 01:27:28 +01:00
async handleMessage(message, portOrSender) {
await this.execCmd(message, portOrSender);
if (message.forwardToSameFramePort) {
this.sendToFrameContentScripts(message, portOrSender.tab.id, portOrSender.frameId, message.port)
}
if (message.forwardToContentScript) {
2020-01-30 23:17:43 +01:00
this.logger.log('info', 'comms', "[CommsServer.js::processReceivedMessage] Message has 'forward to content script' flag set. Forwarding message as is. Message:", message);
2019-04-13 03:09:29 +02:00
this.sendToFrame(message, message.targetTab, message.targetFrame);
}
if (message.forwardToAll) {
2020-01-30 23:17:43 +01:00
this.logger.log('info', 'comms', "[CommsServer.js::processReceivedMessage] Message has 'forward to all' flag set. Forwarding message as is. Message:", message);
2019-04-13 03:09:29 +02:00
this.sendToAll(message);
}
if (message.forwardToActive) {
2020-01-30 23:17:43 +01:00
this.logger.log('info', 'comms', "[CommsServer.js::processReceivedMessage] Message has 'forward to active' flag set. Forwarding message as is. Message:", message);
2018-09-21 00:26:08 +02:00
this.sendToActive(message);
}
2020-02-04 00:56:31 +01:00
}
async processReceivedMessage(message, port){
this.logger.log('info', 'comms', "[CommsServer.js::processReceivedMessage] Received message from popup/content script!", message, "port", port);
this.handleMessage(message, port)
}
2021-03-06 01:27:28 +01:00
processReceivedMessage_nonpersistent(message, sender){
2020-01-30 23:17:43 +01:00
this.logger.log('info', 'comms', "%c[CommsServer.js::processMessage_nonpersistent] Received message from background script!", "background-color: #11D; color: #aad", message, sender);
2019-06-14 21:53:48 +02:00
2021-03-06 01:27:28 +01:00
this.handleMessage(message, sender);
}
// 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;