TeaWeb/shared/js/ipc/BrowserIPC.ts
2021-02-20 17:46:17 +01:00

223 lines
No EOL
7.2 KiB
TypeScript

import "broadcastchannel-polyfill";
import {LogCategory, logDebug, logError, logTrace, logWarn} from "../log";
import {ConnectHandler} from "../ipc/ConnectHandler";
import {tr} from "tc-shared/i18n/localize";
import {guid} from "tc-shared/crypto/uid";
import {AppParameters} from "tc-shared/settings";
interface IpcRawMessage {
timestampSend: number,
sourcePeerId: string,
targetPeerId: string,
targetChannelId: string,
message: ChannelMessage
}
export interface ChannelMessage {
type: string,
data: any
}
export abstract class BasicIPCHandler {
protected static readonly BROADCAST_UNIQUE_ID = "00000000-0000-4000-0000-000000000000";
protected readonly applicationChannelId: string;
protected readonly localPeerId: string;
protected registeredChannels: IPCChannel[] = [];
protected constructor(applicationChannelId: string) {
this.applicationChannelId = applicationChannelId;
this.localPeerId = guid();
}
setup() { }
getApplicationChannelId() : string { return this.applicationChannelId; }
getLocalPeerId() : string { return this.localPeerId; }
abstract sendMessage(message: IpcRawMessage);
protected handleMessage(message: IpcRawMessage) {
logTrace(LogCategory.IPC, tr("Received message %o"), message);
if(message.targetPeerId !== this.localPeerId && message.targetPeerId !== BasicIPCHandler.BROADCAST_UNIQUE_ID) {
/* The message isn't for us */
return;
}
let channelInvokeCount = 0;
for(const channel of this.registeredChannels) {
if(channel.channelId !== message.targetChannelId) {
continue;
}
if(typeof channel.targetPeerId === "string" && channel.targetPeerId !== message.sourcePeerId) {
continue;
}
if(channel.messageHandler) {
channel.messageHandler(message.sourcePeerId, message.targetPeerId === BasicIPCHandler.BROADCAST_UNIQUE_ID, message.message);
}
channelInvokeCount++;
}
if(!channelInvokeCount) {
/* Seems like we're not the only web/teaclient instance */
/* console.warn(tr("Received channel message for unknown channel (%s)"), data.channelId); */
}
}
/**
* @param channelId
* @param remotePeerId The peer to receive messages from. If empty messages will be broadcasted
*/
createChannel(channelId: string, remotePeerId?: string) : IPCChannel {
let channel: IPCChannel = {
channelId: channelId,
targetPeerId: remotePeerId,
messageHandler: undefined,
sendMessage: (type: string, data: any, remotePeerId?: string) => {
if(typeof remotePeerId !== "undefined") {
if(typeof channel.targetPeerId === "string" && remotePeerId != channel.targetPeerId) {
throw "target id does not match channel target";
}
}
remotePeerId = remotePeerId || channel.targetPeerId || BasicIPCHandler.BROADCAST_UNIQUE_ID;
this.sendMessage({
timestampSend: Date.now(),
sourcePeerId: this.localPeerId,
targetPeerId: remotePeerId,
targetChannelId: channelId,
message: {
data,
type,
}
});
if(remotePeerId === this.localPeerId || remotePeerId === BasicIPCHandler.BROADCAST_UNIQUE_ID) {
for(const localChannel of this.registeredChannels) {
if(localChannel.channelId !== channel.channelId) {
continue;
}
if(typeof localChannel.targetPeerId === "string" && localChannel.targetPeerId !== this.localPeerId) {
continue;
}
if(localChannel === channel) {
continue;
}
if(localChannel.messageHandler) {
localChannel.messageHandler(this.localPeerId, remotePeerId === BasicIPCHandler.BROADCAST_UNIQUE_ID, {
type: type,
data: data,
});
}
}
}
}
};
this.registeredChannels.push(channel);
return channel;
}
/**
* Create a channel which only communicates with the TeaSpeak - Core.
* @param channelId
*/
createCoreControlChannel(channelId: string) : IPCChannel {
return this.createChannel(channelId, AppParameters.getValue(AppParameters.KEY_IPC_CORE_PEER_ADDRESS, this.localPeerId));
}
channels() : IPCChannel[] { return this.registeredChannels; }
deleteChannel(channel: IPCChannel) {
this.registeredChannels.remove(channel);
}
}
export interface IPCChannel {
/** Channel id */
readonly channelId: string;
/** Target peer id. If set only messages from that process will be processed */
targetPeerId?: string;
messageHandler: (sourcePeerId: string, broadcast: boolean, message: ChannelMessage) => void;
sendMessage(type: string, data: any, remotePeerId?: string);
}
class BroadcastChannelIPC extends BasicIPCHandler {
private channel: BroadcastChannel;
constructor(applicationChannelId: string) {
super(applicationChannelId);
}
setup() {
super.setup();
this.channel = new BroadcastChannel(this.applicationChannelId);
this.channel.onmessage = this.onMessage.bind(this);
this.channel.onmessageerror = this.onError.bind(this);
}
private onMessage(event: MessageEvent) {
if(typeof(event.data) !== "string") {
logWarn(LogCategory.IPC, tr("Received message with an invalid type (%s): %o"), typeof(event.data), event.data);
return;
}
let message: IpcRawMessage;
try {
message = JSON.parse(event.data);
} catch(error) {
logError(LogCategory.IPC, tr("Received an invalid encoded message: %o"), event.data);
return;
}
super.handleMessage(message);
}
private onError(event: MessageEvent) {
logWarn(LogCategory.IPC, tr("Received error: %o"), event);
}
sendMessage(message: IpcRawMessage) {
this.channel.postMessage(JSON.stringify(message));
}
}
let handlerInstance: BasicIPCHandler;
let connectHandler: ConnectHandler;
export function setupIpcHandler() {
if(handlerInstance) {
throw "IPC handler already initialized";
}
handlerInstance = new BroadcastChannelIPC(AppParameters.getValue(AppParameters.KEY_IPC_APP_ADDRESS, guid()));
handlerInstance.setup();
logDebug(LogCategory.IPC, tr("Application IPC started for %s. Local peer address: %s"), handlerInstance.getApplicationChannelId(), handlerInstance.getLocalPeerId());
connectHandler = new ConnectHandler(handlerInstance);
connectHandler.setup();
}
export function getIpcInstance() {
return handlerInstance;
}
export function getInstanceConnectHandler() {
return connectHandler;
}