132 lines
3.0 KiB
TypeScript
132 lines
3.0 KiB
TypeScript
"use strict";
|
|
// License: MIT
|
|
|
|
import { EventEmitter } from "./events";
|
|
// eslint-disable-next-line no-unused-vars
|
|
import {runtime, tabs, RawPort, MessageSender} from "./browser";
|
|
|
|
export class Port extends EventEmitter {
|
|
private port: RawPort | null;
|
|
|
|
private disconnected = false;
|
|
|
|
constructor(port: RawPort) {
|
|
super();
|
|
this.port = port;
|
|
|
|
// Nasty firefox bug, thus listen for tab removal explicitly
|
|
if (port.sender && port.sender.tab && port.sender.tab.id) {
|
|
const otherTabId = port.sender.tab.id;
|
|
const tabListener = (tabId: number) => {
|
|
if (tabId !== otherTabId) {
|
|
return;
|
|
}
|
|
this.disconnect();
|
|
};
|
|
tabs.onRemoved.addListener(tabListener);
|
|
}
|
|
port.onMessage.addListener(this.onMessage.bind(this));
|
|
port.onDisconnect.addListener(this.disconnect.bind(this));
|
|
}
|
|
|
|
disconnect() {
|
|
if (this.disconnected) {
|
|
return;
|
|
}
|
|
this.disconnected = true;
|
|
const {port} = this;
|
|
this.port = null; // Break the cycle
|
|
this.emit("disconnect", this, port);
|
|
}
|
|
|
|
get name() {
|
|
if (!this.port) {
|
|
return null;
|
|
}
|
|
return this.port.name;
|
|
}
|
|
|
|
get id() {
|
|
if (!this.port || !this.port.sender) {
|
|
return null;
|
|
}
|
|
return this.port.sender.id;
|
|
}
|
|
|
|
get isSelf() {
|
|
return this.id === runtime.id;
|
|
}
|
|
|
|
post(msg: string, ...data: any[]) {
|
|
if (!this.port) {
|
|
return;
|
|
}
|
|
if (!data) {
|
|
this.port.postMessage({msg});
|
|
return;
|
|
}
|
|
if (data.length === 1) {
|
|
[data] = data;
|
|
}
|
|
this.port.postMessage({msg, data});
|
|
}
|
|
|
|
onMessage(message: any) {
|
|
if (!this.port) {
|
|
return;
|
|
}
|
|
if (Array.isArray(message)) {
|
|
message.forEach(this.onMessage, this);
|
|
return;
|
|
}
|
|
if (Object.keys(message).includes("msg")) {
|
|
this.emit(message.msg, message);
|
|
return;
|
|
}
|
|
if (typeof message === "string") {
|
|
this.emit(message);
|
|
return;
|
|
}
|
|
console.error(`Unhandled message in ${this.port.name}:`, message);
|
|
}
|
|
}
|
|
|
|
export const Bus = new class extends EventEmitter {
|
|
private readonly ports: EventEmitter;
|
|
|
|
public readonly onPort: (event: string, port: (port: Port) => void) => void;
|
|
|
|
public readonly offPort: Function;
|
|
|
|
public readonly oncePort: (event: string, port: (port: Port) => void) => void;
|
|
|
|
constructor() {
|
|
super();
|
|
this.ports = new EventEmitter();
|
|
this.onPort = this.ports.on.bind(this.ports);
|
|
this.offPort = this.ports.off.bind(this.ports);
|
|
this.oncePort = this.ports.once.bind(this.ports);
|
|
runtime.onMessage.addListener(this.onMessage.bind(this));
|
|
runtime.onConnect.addListener(this.onConnect.bind(this));
|
|
}
|
|
|
|
onMessage(msg: any, sender: MessageSender, callback: any) {
|
|
let {type = null} = msg;
|
|
if (!type) {
|
|
type = msg;
|
|
}
|
|
this.emit(type, msg, callback);
|
|
}
|
|
|
|
onConnect(port: RawPort) {
|
|
if (!port.name) {
|
|
port.disconnect();
|
|
return;
|
|
}
|
|
const wrapped = new Port(port);
|
|
if (!this.ports.emit(port.name, wrapped)) {
|
|
wrapped.disconnect();
|
|
}
|
|
}
|
|
}();
|