TeaWeb/shared/js/ipc/BrowserIPC.ts

223 lines
7.2 KiB
TypeScript
Raw Normal View History

2020-09-07 19:07:14 +02:00
import "broadcastchannel-polyfill";
2021-02-20 17:46:17 +01:00
import {LogCategory, logDebug, logError, logTrace, logWarn} from "../log";
import {ConnectHandler} from "../ipc/ConnectHandler";
import {tr} from "tc-shared/i18n/localize";
2021-02-20 17:46:17 +01:00
import {guid} from "tc-shared/crypto/uid";
import {AppParameters} from "tc-shared/settings";
2021-02-20 17:46:17 +01:00
interface IpcRawMessage {
timestampSend: number,
2021-02-20 17:46:17 +01:00
sourcePeerId: string,
targetPeerId: string,
2021-02-20 17:46:17 +01:00
targetChannelId: string,
2021-02-20 17:46:17 +01:00
message: ChannelMessage
}
export interface ChannelMessage {
2021-02-20 17:46:17 +01:00
type: string,
data: any
}
export abstract class BasicIPCHandler {
protected static readonly BROADCAST_UNIQUE_ID = "00000000-0000-4000-0000-000000000000";
2021-02-20 17:46:17 +01:00
protected readonly applicationChannelId: string;
protected readonly localPeerId: string;
2021-02-20 17:46:17 +01:00
protected registeredChannels: IPCChannel[] = [];
2021-02-20 17:46:17 +01:00
protected constructor(applicationChannelId: string) {
this.applicationChannelId = applicationChannelId;
this.localPeerId = guid();
}
2021-02-20 17:46:17 +01:00
setup() { }
2021-02-20 17:46:17 +01:00
getApplicationChannelId() : string { return this.applicationChannelId; }
2021-02-20 17:46:17 +01:00
getLocalPeerId() : string { return this.localPeerId; }
2021-02-20 17:46:17 +01:00
abstract sendMessage(message: IpcRawMessage);
2021-02-20 17:46:17 +01:00
protected handleMessage(message: IpcRawMessage) {
logTrace(LogCategory.IPC, tr("Received message %o"), message);
2021-02-20 17:46:17 +01:00
if(message.targetPeerId !== this.localPeerId && message.targetPeerId !== BasicIPCHandler.BROADCAST_UNIQUE_ID) {
/* The message isn't for us */
return;
}
2021-02-20 17:46:17 +01:00
let channelInvokeCount = 0;
for(const channel of this.registeredChannels) {
if(channel.channelId !== message.targetChannelId) {
continue;
}
2021-02-20 17:46:17 +01:00
if(typeof channel.targetPeerId === "string" && channel.targetPeerId !== message.sourcePeerId) {
continue;
}
2021-02-20 17:46:17 +01:00
if(channel.messageHandler) {
channel.messageHandler(message.sourcePeerId, message.targetPeerId === BasicIPCHandler.BROADCAST_UNIQUE_ID, message.message);
}
2021-02-20 17:46:17 +01:00
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); */
}
}
2021-02-20 17:46:17 +01:00
/**
* @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 = {
2021-02-20 17:46:17 +01:00
channelId: channelId,
targetPeerId: remotePeerId,
messageHandler: undefined,
2021-02-20 17:46:17 +01:00
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";
}
}
2021-02-20 17:46:17 +01:00
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;
}
2021-02-20 17:46:17 +01:00
/**
* 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));
}
2021-02-20 17:46:17 +01:00
channels() : IPCChannel[] { return this.registeredChannels; }
2021-02-20 17:46:17 +01:00
deleteChannel(channel: IPCChannel) {
this.registeredChannels.remove(channel);
}
}
export interface IPCChannel {
2021-02-20 17:46:17 +01:00
/** Channel id */
readonly channelId: string;
2021-02-20 17:46:17 +01:00
/** Target peer id. If set only messages from that process will be processed */
targetPeerId?: string;
2021-02-20 17:46:17 +01:00
messageHandler: (sourcePeerId: string, broadcast: boolean, message: ChannelMessage) => void;
sendMessage(type: string, data: any, remotePeerId?: string);
}
class BroadcastChannelIPC extends BasicIPCHandler {
private channel: BroadcastChannel;
2021-02-20 17:46:17 +01:00
constructor(applicationChannelId: string) {
super(applicationChannelId);
}
setup() {
super.setup();
2021-02-20 17:46:17 +01:00
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;
}
2021-02-20 17:46:17 +01:00
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);
}
2021-02-20 17:46:17 +01:00
sendMessage(message: IpcRawMessage) {
this.channel.postMessage(JSON.stringify(message));
}
}
2021-02-20 17:46:17 +01:00
let handlerInstance: BasicIPCHandler;
let connectHandler: ConnectHandler;
2021-02-20 17:46:17 +01:00
export function setupIpcHandler() {
if(handlerInstance) {
throw "IPC handler already initialized";
}
2021-02-20 17:46:17 +01:00
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());
2021-02-20 17:46:17 +01:00
connectHandler = new ConnectHandler(handlerInstance);
connectHandler.setup();
}
export function getIpcInstance() {
2021-02-20 17:46:17 +01:00
return handlerInstance;
}
export function getInstanceConnectHandler() {
2021-02-20 17:46:17 +01:00
return connectHandler;
}