diff --git a/shared/js/devel_main.ts b/shared/js/devel_main.ts index c90f9ba2..38190583 100644 --- a/shared/js/devel_main.ts +++ b/shared/js/devel_main.ts @@ -13,7 +13,7 @@ loader.register_task(Stage.JAVASCRIPT_INITIALIZING, { priority: 10, function: async () => { await i18n.initialize(); - ipc.setup(); + ipc.setupIpcHandler(); } }); diff --git a/shared/js/file/LocalAvatars.ts b/shared/js/file/LocalAvatars.ts index b8cddc2e..cca9d616 100644 --- a/shared/js/file/LocalAvatars.ts +++ b/shared/js/file/LocalAvatars.ts @@ -352,7 +352,7 @@ class LocalAvatarManagerFactory extends AbstractAvatarManagerFactory { constructor() { super(); - this.ipcChannel = ipc.getIpcInstance().createChannel(undefined, kIPCAvatarChannel); + this.ipcChannel = ipc.getIpcInstance().createChannel(kIPCAvatarChannel); this.ipcChannel.messageHandler = this.handleIpcMessage.bind(this); server_connections.events().on("notify_handler_created", event => this.handleHandlerCreated(event.handler)); diff --git a/shared/js/file/LocalIcons.ts b/shared/js/file/LocalIcons.ts index dc3fb820..ede65e7e 100644 --- a/shared/js/file/LocalIcons.ts +++ b/shared/js/file/LocalIcons.ts @@ -68,7 +68,7 @@ class IconManager extends AbstractIconManager { constructor() { super(); - this.ipcChannel = ipc.getIpcInstance().createChannel(undefined, kIPCIconChannel); + this.ipcChannel = ipc.getIpcInstance().createChannel(kIPCIconChannel); this.ipcChannel.messageHandler = this.handleIpcMessage.bind(this); server_connections.events().on("notify_handler_created", event => { diff --git a/shared/js/file/RemoteAvatars.ts b/shared/js/file/RemoteAvatars.ts index 8d8fd771..1dc63aff 100644 --- a/shared/js/file/RemoteAvatars.ts +++ b/shared/js/file/RemoteAvatars.ts @@ -8,7 +8,7 @@ import { kIPCAvatarChannel, setGlobalAvatarManagerFactory, uniqueId2AvatarId } from "../file/Avatars"; -import {IPCChannel} from "../ipc/BrowserIPC"; +import {getIpcInstance, IPCChannel} from "../ipc/BrowserIPC"; import {AppParameters} from "../settings"; import {ChannelMessage} from "../ipc/BrowserIPC"; import {guid} from "../crypto/uid"; @@ -159,7 +159,7 @@ class RemoteAvatarManagerFactory extends AbstractAvatarManagerFactory { constructor() { super(); - this.ipcChannel = ipc.getIpcInstance().createChannel(AppParameters.getValue(AppParameters.KEY_IPC_REMOTE_ADDRESS, "invalid"), kIPCAvatarChannel); + this.ipcChannel = ipc.getIpcInstance().createCoreControlChannel(kIPCAvatarChannel); this.ipcChannel.messageHandler = this.handleIpcMessage.bind(this); } diff --git a/shared/js/file/RemoteIcons.ts b/shared/js/file/RemoteIcons.ts index 7aef5860..82ff40e8 100644 --- a/shared/js/file/RemoteIcons.ts +++ b/shared/js/file/RemoteIcons.ts @@ -3,7 +3,6 @@ import * as loader from "tc-loader"; import {Stage} from "tc-loader"; import {ChannelMessage, IPCChannel} from "tc-shared/ipc/BrowserIPC"; import * as ipc from "tc-shared/ipc/BrowserIPC"; -import {AppParameters} from "tc-shared/settings"; import {LogCategory, logWarn} from "tc-shared/log"; class RemoteRemoteIcon extends RemoteIcon { @@ -33,7 +32,7 @@ class RemoteIconManager extends AbstractIconManager { constructor() { super(); - this.ipcChannel = ipc.getIpcInstance().createChannel(AppParameters.getValue(AppParameters.KEY_IPC_REMOTE_ADDRESS, "invalid"), kIPCIconChannel); + this.ipcChannel = ipc.getIpcInstance().createCoreControlChannel(kIPCIconChannel); this.ipcChannel.messageHandler = this.handleIpcMessage.bind(this); } diff --git a/shared/js/ipc/BrowserIPC.ts b/shared/js/ipc/BrowserIPC.ts index 6d8b928b..845e70fb 100644 --- a/shared/js/ipc/BrowserIPC.ts +++ b/shared/js/ipc/BrowserIPC.ts @@ -1,150 +1,131 @@ import "broadcastchannel-polyfill"; -import {LogCategory, logDebug, logError, logWarn} from "../log"; +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"; -export interface BroadcastMessage { - timestamp: number; - receiver: string; - sender: string; +interface IpcRawMessage { + timestampSend: number, - type: string; - data: any; -} + sourcePeerId: string, + targetPeerId: string, -function uuidv4() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { - const r = Math.random() * 16 | 0; - const v = c == 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); -} + targetChannelId: string, -interface ProcessQuery { - timestamp: number - query_id: string; + message: ChannelMessage } export interface ChannelMessage { - channel_id: string; - type: string; - data: any; + type: string, + data: any } -export interface ProcessQueryResponse { - request_timestamp: number - request_query_id: string; - - device_id: string; - protocol: number; -} - -export interface CertificateAcceptCallback { - request_id: string; -} -export interface CertificateAcceptSucceeded { } - export abstract class BasicIPCHandler { protected static readonly BROADCAST_UNIQUE_ID = "00000000-0000-4000-0000-000000000000"; - protected static readonly PROTOCOL_VERSION = 1; + + protected readonly applicationChannelId: string; + protected readonly localPeerId: string; protected registeredChannels: IPCChannel[] = []; - protected localUniqueId: string; - protected constructor() { } - - setup() { - this.localUniqueId = uuidv4(); + protected constructor(applicationChannelId: string) { + this.applicationChannelId = applicationChannelId; + this.localPeerId = guid(); } - getLocalAddress() : string { return this.localUniqueId; } + setup() { } - abstract sendMessage(type: string, data: any, target?: string); + getApplicationChannelId() : string { return this.applicationChannelId; } - protected handleMessage(message: BroadcastMessage) { - //log.trace(LogCategory.IPC, tr("Received message %o"), message); + getLocalPeerId() : string { return this.localPeerId; } - if(message.receiver === BasicIPCHandler.BROADCAST_UNIQUE_ID) { - if(message.type == "process-query") { - logDebug(LogCategory.IPC, tr("Received a device query from %s."), message.sender); - this.sendMessage("process-query-response", { - request_query_id: (message.data).query_id, - request_timestamp: (message.data).timestamp, + abstract sendMessage(message: IpcRawMessage); - device_id: this.localUniqueId, - protocol: BasicIPCHandler.PROTOCOL_VERSION - } as ProcessQueryResponse, message.sender); - return; - } - } else if(message.receiver === this.localUniqueId) { - if(message.type == "process-query-response") { - const response: ProcessQueryResponse = message.data; - if(this._query_results[response.request_query_id]) - this._query_results[response.request_query_id].push(response); - else { - logWarn(LogCategory.IPC, tr("Received a query response for an unknown request.")); - } - return; - } - else if(message.type == "certificate-accept-callback") { - const data: CertificateAcceptCallback = message.data; - if(!this._cert_accept_callbacks[data.request_id]) { - logWarn(LogCategory.IPC, tr("Received certificate accept callback for an unknown request ID.")); - return; - } - this._cert_accept_callbacks[data.request_id](); - delete this._cert_accept_callbacks[data.request_id]; + protected handleMessage(message: IpcRawMessage) { + logTrace(LogCategory.IPC, tr("Received message %o"), message); - this.sendMessage("certificate-accept-succeeded", { - - } as CertificateAcceptSucceeded, message.sender); - return; - } - else if(message.type == "certificate-accept-succeeded") { - if(!this._cert_accept_succeeded[message.sender]) { - logWarn(LogCategory.IPC, tr("Received certificate accept succeeded, but haven't a callback.")); - return; - } - this._cert_accept_succeeded[message.sender](); - return; - } + if(message.targetPeerId !== this.localPeerId && message.targetPeerId !== BasicIPCHandler.BROADCAST_UNIQUE_ID) { + /* The message isn't for us */ + return; } - if(message.type === "channel") { - const data: ChannelMessage = message.data; - let channel_invoked = false; - for(const channel of this.registeredChannels) { - if(channel.channelId === data.channel_id && (typeof(channel.targetClientId) === "undefined" || channel.targetClientId === message.sender)) { - if(channel.messageHandler) - channel.messageHandler(message.sender, message.receiver === BasicIPCHandler.BROADCAST_UNIQUE_ID, data); - channel_invoked = true; - } + let channelInvokeCount = 0; + for(const channel of this.registeredChannels) { + if(channel.channelId !== message.targetChannelId) { + continue; } - if(!channel_invoked) { - /* Seems like we're not the only web/teaclient instance */ - /* console.warn(tr("Received channel message for unknown channel (%s)"), data.channel_id); */ + 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); */ } } - createChannel(targetId?: string, channelId?: string) : IPCChannel { + /** + * @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 = { - targetClientId: targetId, - channelId: channelId || uuidv4(), + channelId: channelId, + targetPeerId: remotePeerId, messageHandler: undefined, - sendMessage: (type: string, data: any, target?: string) => { - if(typeof target !== "undefined") { - if(typeof channel.targetClientId === "string" && target != channel.targetClientId) { + 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"; } } - this.sendMessage("channel", { - type: type, - data: data, - channel_id: channel.channelId - } as ChannelMessage, target || channel.targetClientId || BasicIPCHandler.BROADCAST_UNIQUE_ID); + 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, + }); + } + } + } } }; @@ -152,77 +133,42 @@ export abstract class BasicIPCHandler { 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 = this.registeredChannels.filter(e => e !== channel); - } - - private _query_results: {[key: string]:ProcessQueryResponse[]} = {}; - async queryProcesses(timeout?: number) : Promise { - const query_id = uuidv4(); - this._query_results[query_id] = []; - - this.sendMessage("process-query", { - query_id: query_id, - timestamp: Date.now() - } as ProcessQuery); - - await new Promise(resolve => setTimeout(resolve, timeout || 250)); - const result = this._query_results[query_id]; - delete this._query_results[query_id]; - return result; - } - - private _cert_accept_callbacks: {[key: string]:(() => any)} = {}; - register_certificate_accept_callback(callback: () => any) : string { - const id = uuidv4(); - this._cert_accept_callbacks[id] = callback; - return this.localUniqueId + ":" + id; - } - - private _cert_accept_succeeded: {[sender: string]:(() => any)} = {}; - post_certificate_accpected(id: string, timeout?: number) : Promise { - return new Promise((resolve, reject) => { - const data = id.split(":"); - const timeout_id = setTimeout(() => { - delete this._cert_accept_succeeded[data[0]]; - clearTimeout(timeout_id); - reject("timeout"); - }, timeout || 250); - this._cert_accept_succeeded[data[0]] = () => { - delete this._cert_accept_succeeded[data[0]]; - clearTimeout(timeout_id); - resolve(); - }; - this.sendMessage("certificate-accept-callback", { - request_id: data[1] - } as CertificateAcceptCallback, data[0]); - }) + this.registeredChannels.remove(channel); } } export interface IPCChannel { + /** Channel id */ readonly channelId: string; - targetClientId?: string; + /** Target peer id. If set only messages from that process will be processed */ + targetPeerId?: string; - messageHandler: (remoteId: string, broadcast: boolean, message: ChannelMessage) => void; - sendMessage(type: string, message: any, target?: string); + messageHandler: (sourcePeerId: string, broadcast: boolean, message: ChannelMessage) => void; + sendMessage(type: string, data: any, remotePeerId?: string); } class BroadcastChannelIPC extends BasicIPCHandler { - private static readonly CHANNEL_NAME = "TeaSpeak-Web"; - private channel: BroadcastChannel; - constructor() { - super(); + constructor(applicationChannelId: string) { + super(applicationChannelId); } setup() { super.setup(); - this.channel = new BroadcastChannel(BroadcastChannelIPC.CHANNEL_NAME); + this.channel = new BroadcastChannel(this.applicationChannelId); this.channel.onmessage = this.onMessage.bind(this); this.channel.onmessageerror = this.onError.bind(this); } @@ -233,7 +179,7 @@ class BroadcastChannelIPC extends BasicIPCHandler { return; } - let message: BroadcastMessage; + let message: IpcRawMessage; try { message = JSON.parse(event.data); } catch(error) { @@ -247,52 +193,31 @@ class BroadcastChannelIPC extends BasicIPCHandler { logWarn(LogCategory.IPC, tr("Received error: %o"), event); } - sendMessage(type: string, data: any, target?: string) { - const message: BroadcastMessage = {} as any; - - message.sender = this.localUniqueId; - message.receiver = target ? target : BasicIPCHandler.BROADCAST_UNIQUE_ID; - message.timestamp = Date.now(); - message.type = type; - message.data = data; - - if(message.receiver === this.localUniqueId) { - this.handleMessage(message); - } else { - this.channel.postMessage(JSON.stringify(message)); - } + sendMessage(message: IpcRawMessage) { + this.channel.postMessage(JSON.stringify(message)); } } -let handler: BasicIPCHandler; -let connect_handler: ConnectHandler; +let handlerInstance: BasicIPCHandler; +let connectHandler: ConnectHandler; -export function setup() { - if(!supported()) - return; +export function setupIpcHandler() { + if(handlerInstance) { + throw "IPC handler already initialized"; + } - if(handler) - throw "bipc already started"; + 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()); - handler = new BroadcastChannelIPC(); - handler.setup(); - - connect_handler = new ConnectHandler(handler); - connect_handler.setup(); + connectHandler = new ConnectHandler(handlerInstance); + connectHandler.setup(); } export function getIpcInstance() { - return handler; + return handlerInstance; } export function getInstanceConnectHandler() { - return connect_handler; -} - -export function supported() { - /* we've a polyfill now */ - return true; - - /* ios does not support this */ - return typeof(window.BroadcastChannel) !== "undefined"; + return connectHandler; } \ No newline at end of file diff --git a/shared/js/ipc/ConnectHandler.ts b/shared/js/ipc/ConnectHandler.ts index ac25755b..df66e0ff 100644 --- a/shared/js/ipc/ConnectHandler.ts +++ b/shared/js/ipc/ConnectHandler.ts @@ -76,7 +76,7 @@ export class ConnectHandler { } public setup() { - this.ipc_channel = this.ipc_handler.createChannel(undefined, ConnectHandler.CHANNEL_NAME); + this.ipc_channel = this.ipc_handler.createChannel(ConnectHandler.CHANNEL_NAME); this.ipc_channel.messageHandler = this.onMessage.bind(this); } diff --git a/shared/js/ipc/MethodProxy.ts b/shared/js/ipc/MethodProxy.ts deleted file mode 100644 index 1c3a5606..00000000 --- a/shared/js/ipc/MethodProxy.ts +++ /dev/null @@ -1,217 +0,0 @@ -import {LogCategory, logDebug, logInfo, logWarn} from "../log"; -import {BasicIPCHandler, ChannelMessage, IPCChannel} from "../ipc/BrowserIPC"; -import {guid} from "../crypto/uid"; -import {tr} from "tc-shared/i18n/localize"; - -export interface MethodProxyInvokeData { - method_name: string; - arguments: any[]; - promise_id: string; -} -export interface MethodProxyResultData { - promise_id: string; - result: any; - success: boolean; -} -export interface MethodProxyCallback { - promise: Promise; - promise_id: string; - - resolve: (object: any) => any; - reject: (object: any) => any; -} - -export type MethodProxyConnectParameters = { - channel_id: string; - client_id: string; -} -export abstract class MethodProxy { - readonly ipc_handler: BasicIPCHandler; - private _ipc_channel: IPCChannel; - private _ipc_parameters: MethodProxyConnectParameters; - - private readonly _local: boolean; - private readonly _slave: boolean; - - private _connected: boolean; - private _proxied_methods: {[key: string]:() => Promise} = {}; - private _proxied_callbacks: {[key: string]:MethodProxyCallback} = {}; - - protected constructor(ipc_handler: BasicIPCHandler, connect_params?: MethodProxyConnectParameters) { - this.ipc_handler = ipc_handler; - this._ipc_parameters = connect_params; - this._connected = false; - this._slave = typeof(connect_params) !== "undefined"; - this._local = typeof(connect_params) !== "undefined" && connect_params.channel_id === "local" && connect_params.client_id === "local"; - } - - protected setup() { - if(this._local) { - this._connected = true; - this.on_connected(); - } else { - if(this._slave) - this._ipc_channel = this.ipc_handler.createChannel(this._ipc_parameters.client_id, this._ipc_parameters.channel_id); - else - this._ipc_channel = this.ipc_handler.createChannel(); - - this._ipc_channel.messageHandler = this._handle_message.bind(this); - if(this._slave) - this._ipc_channel.sendMessage("initialize", {}); - } - } - - protected finalize() { - if(!this._local) { - if(this._connected) - this._ipc_channel.sendMessage("finalize", {}); - - this.ipc_handler.deleteChannel(this._ipc_channel); - this._ipc_channel = undefined; - } - for(const promise of Object.values(this._proxied_callbacks)) - promise.reject("disconnected"); - this._proxied_callbacks = {}; - - this._connected = false; - this.on_disconnected(); - } - - protected register_method(method: (...args: any[]) => Promise | string) { - let method_name: string; - if(typeof method === "function") { - logDebug(LogCategory.IPC, tr("Registering method proxy for %s"), method.name); - method_name = method.name; - } else { - logDebug(LogCategory.IPC, tr("Registering method proxy for %s"), method); - method_name = method; - } - - if(!this[method_name]) - throw "method is missing in current object"; - - this._proxied_methods[method_name] = this[method_name]; - if(!this._local) { - this[method_name] = (...args: any[]) => { - if(!this._connected) - return Promise.reject("not connected"); - - const proxy_callback = { - promise_id: guid() - } as MethodProxyCallback; - this._proxied_callbacks[proxy_callback.promise_id] = proxy_callback; - proxy_callback.promise = new Promise((resolve, reject) => { - proxy_callback.resolve = resolve; - proxy_callback.reject = reject; - }); - - this._ipc_channel.sendMessage("invoke", { - promise_id: proxy_callback.promise_id, - arguments: [...args], - method_name: method_name - } as MethodProxyInvokeData); - return proxy_callback.promise; - } - } - } - - private _handle_message(remote_id: string, boradcast: boolean, message: ChannelMessage) { - if(message.type === "finalize") { - this._handle_finalize(); - } else if(message.type === "initialize") { - this._handle_remote_callback(remote_id); - } else if(message.type === "invoke") { - this._handle_invoke(message.data); - } else if(message.type === "result") { - this._handle_result(message.data); - } - } - - private _handle_finalize() { - this.on_disconnected(); - this.finalize(); - this._connected = false; - } - - private _handle_remote_callback(remote_id: string) { - if(!this._ipc_channel.targetClientId) { - if(this._slave) - throw "initialize wrong state!"; - - this._ipc_channel.targetClientId = remote_id; /* now we're able to send messages */ - this.on_connected(); - this._ipc_channel.sendMessage("initialize", true); - } else { - if(!this._slave) - throw "initialize wrong state!"; - - this.on_connected(); - } - this._connected = true; - } - - private _send_result(promise_id: string, success: boolean, message: any) { - this._ipc_channel.sendMessage("result", { - promise_id: promise_id, - result: message, - success: success - } as MethodProxyResultData); - } - - private _handle_invoke(data: MethodProxyInvokeData) { - if(this._proxied_methods[data.method_name]) - throw "we could not invoke a local proxied method!"; - - if(!this[data.method_name]) { - this._send_result(data.promise_id, false, "missing method"); - return; - } - - try { - logInfo(LogCategory.IPC, tr("Invoking method %s with arguments: %o"), data.method_name, data.arguments); - - const promise = this[data.method_name](...data.arguments); - promise.then(result => { - logInfo(LogCategory.IPC, tr("Result: %o"), result); - this._send_result(data.promise_id, true, result); - }).catch(error => { - this._send_result(data.promise_id, false, error); - }); - } catch(error) { - this._send_result(data.promise_id, false, error); - return; - } - } - - private _handle_result(data: MethodProxyResultData) { - if(!this._proxied_callbacks[data.promise_id]) { - logWarn(LogCategory.IPC, tr("Received proxy method result for unknown promise")); - return; - } - const callback = this._proxied_callbacks[data.promise_id]; - delete this._proxied_callbacks[data.promise_id]; - - if(data.success) - callback.resolve(data.result); - else - callback.reject(data.result); - } - - generate_connect_parameters() : MethodProxyConnectParameters { - if(this._slave) - throw "only masters can generate connect parameters!"; - if(!this._ipc_channel) - throw "please call setup() before"; - - return { - channel_id: this._ipc_channel.channelId, - client_id: this.ipc_handler.getLocalAddress() - }; - } - - is_slave() { return this._local || this._slave; } /* the popout modal */ - is_master() { return this._local || !this._slave; } /* the host (teaweb application) */ - - protected abstract on_connected(); - protected abstract on_disconnected(); -} \ No newline at end of file diff --git a/shared/js/main.tsx b/shared/js/main.tsx index c4b9cad9..6ebd4850 100644 --- a/shared/js/main.tsx +++ b/shared/js/main.tsx @@ -64,7 +64,7 @@ async function initialize() { return; } - bipc.setup(); + bipc.setupIpcHandler(); } async function initializeApp() { diff --git a/shared/js/settings.ts b/shared/js/settings.ts index 9d5f6c0b..df36f6ef 100644 --- a/shared/js/settings.ts +++ b/shared/js/settings.ts @@ -252,16 +252,22 @@ export namespace AppParameters { }; - export const KEY_IPC_REMOTE_ADDRESS: RegistryKey = { + export const KEY_IPC_APP_ADDRESS: RegistryKey = { key: "ipc-address", valueType: "string", description: "Address of the apps IPC channel" }; - export const KEY_IPC_REMOTE_POPOUT_CHANNEL: RegistryKey = { - key: "ipc-channel", + export const KEY_IPC_CORE_PEER_ADDRESS: RegistryKey = { + key: "ipc-core-peer", valueType: "string", - description: "The channel name of the popout channel communication id" + description: "Peer address of the apps core", + }; + + export const KEY_MODAL_IDENTITY_CODE: RegistryKey = { + key: "modal-identify", + valueType: "string", + description: "An authentication code used to register the new process as the modal" }; export const KEY_MODAL_TARGET: RegistryKey = { diff --git a/shared/js/text/bbcode/InviteController.ts b/shared/js/text/bbcode/InviteController.ts index 22eb2759..bb69756b 100644 --- a/shared/js/text/bbcode/InviteController.ts +++ b/shared/js/text/bbcode/InviteController.ts @@ -1,6 +1,6 @@ import * as loader from "tc-loader"; import {ChannelMessage, getIpcInstance, IPCChannel} from "tc-shared/ipc/BrowserIPC"; -import {AppParameters, UrlParameterParser} from "tc-shared/settings"; +import {UrlParameterParser} from "tc-shared/settings"; import {IpcInviteInfo} from "tc-shared/text/bbcode/InviteDefinitions"; import {LogCategory, logError} from "tc-shared/log"; import {clientServiceInvite, clientServices} from "tc-shared/clientservice"; @@ -10,7 +10,7 @@ let ipcChannel: IPCChannel; loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { name: "Invite controller init", function: async () => { - ipcChannel = getIpcInstance().createChannel(AppParameters.getValue(AppParameters.KEY_IPC_REMOTE_ADDRESS, undefined), "invite-info"); + ipcChannel = getIpcInstance().createChannel("invite-info"); ipcChannel.messageHandler = handleIpcMessage; }, priority: 10 diff --git a/shared/js/text/bbcode/InviteRenderer.tsx b/shared/js/text/bbcode/InviteRenderer.tsx index 90e5d079..2b960450 100644 --- a/shared/js/text/bbcode/InviteRenderer.tsx +++ b/shared/js/text/bbcode/InviteRenderer.tsx @@ -208,7 +208,7 @@ let ipcChannel: IPCChannel; loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { name: "Invite controller init", function: async () => { - ipcChannel = getIpcInstance().createChannel(AppParameters.getValue(AppParameters.KEY_IPC_REMOTE_ADDRESS, undefined), "invite-info"); + ipcChannel = getIpcInstance().createCoreControlChannel("invite-info"); ipcChannel.messageHandler = handleIpcMessage; }, priority: 10 diff --git a/shared/js/tree/EntryTagsHandler.ts b/shared/js/tree/EntryTagsHandler.ts index 54f26673..c66fa09d 100644 --- a/shared/js/tree/EntryTagsHandler.ts +++ b/shared/js/tree/EntryTagsHandler.ts @@ -95,7 +95,7 @@ loader.register_task(Stage.JAVASCRIPT_INITIALIZING, { name: "entry tags", priority: 10, function: async () => { - const channel = getIpcInstance().createChannel(undefined, kIpcChannel); + const channel = getIpcInstance().createChannel(kIpcChannel); channel.messageHandler = (_remoteId, _broadcast, message) => handleIpcMessage(message.type, message.data); } }); \ No newline at end of file diff --git a/shared/js/ui/react-elements/external-modal/Controller.ts b/shared/js/ui/react-elements/external-modal/Controller.ts index ca14059d..57e58af2 100644 --- a/shared/js/ui/react-elements/external-modal/Controller.ts +++ b/shared/js/ui/react-elements/external-modal/Controller.ts @@ -1,9 +1,10 @@ -import {LogCategory, logDebug, logTrace, logWarn} from "../../../log"; +import {LogCategory, logDebug, logTrace} from "../../../log"; import * as ipc from "../../../ipc/BrowserIPC"; import {ChannelMessage} from "../../../ipc/BrowserIPC"; -import {Registry} from "../../../events"; +import {Registry} from "tc-events"; import { EventControllerBase, + kPopoutIPCChannelId, Popout2ControllerMessages, PopoutIPCMessage } from "../../../ui/react-elements/external-modal/IPCMessage"; @@ -21,13 +22,13 @@ export abstract class AbstractExternalModalController extends EventControllerBas private callbackWindowInitialized: (error?: string) => void; protected constructor(modalType: string, constructorArguments: any[]) { - super(); + super(guid()); this.modalType = modalType; this.constructorArguments = constructorArguments; this.modalEvents = new Registry(); - this.ipcChannel = ipc.getIpcInstance().createChannel(undefined, "modal-" + guid()); + this.ipcChannel = ipc.getIpcInstance().createChannel(kPopoutIPCChannelId); this.ipcChannel.messageHandler = this.handleIPCMessage.bind(this); this.documentUnloadListener = () => this.destroy(); @@ -121,51 +122,46 @@ export abstract class AbstractExternalModalController extends EventControllerBas } protected handleIPCMessage(remoteId: string, broadcast: boolean, message: ChannelMessage) { - if(broadcast) + if(!broadcast && remoteId !== this.ipcRemotePeerId) { + logDebug(LogCategory.IPC, tr("Received direct IPC message for popout controller from unknown source: %s"), remoteId); return; - - if(this.ipcRemoteId === undefined) { - logDebug(LogCategory.IPC, tr("Remote window connected with id %s"), remoteId); - this.ipcRemoteId = remoteId; - } else if(this.ipcRemoteId !== remoteId) { - this.ipcRemoteId = remoteId; } - super.handleIPCMessage(remoteId, broadcast, message); + this.handleTypedIPCMessage(remoteId, broadcast, message.type as any, message.data); } - protected handleTypedIPCMessage(type: T, payload: PopoutIPCMessage[T]) { - super.handleTypedIPCMessage(type, payload); + protected handleTypedIPCMessage(remoteId: string, isBroadcast: boolean, type: T, payload: PopoutIPCMessage[T]) { + super.handleTypedIPCMessage(remoteId, isBroadcast, type, payload); - switch (type) { - case "hello-popout": { - const tpayload = payload as PopoutIPCMessage["hello-popout"]; - logTrace(LogCategory.IPC, "Received Hello World from popup with version %s (expected %s).", tpayload.version, __build.version); - if(tpayload.version !== __build.version) { - this.sendIPCMessage("hello-controller", { accepted: false, message: tr("version miss match") }); - if(this.callbackWindowInitialized) { - this.callbackWindowInitialized(tr("version miss match")); - this.callbackWindowInitialized = undefined; - } - return; - } - - if(this.callbackWindowInitialized) { - this.callbackWindowInitialized(); - this.callbackWindowInitialized = undefined; - } - - this.sendIPCMessage("hello-controller", { accepted: true, constructorArguments: this.constructorArguments }); - break; + if(type === "hello-popout") { + const messageHello = payload as PopoutIPCMessage["hello-popout"]; + if(messageHello.authenticationCode !== this.ipcAuthenticationCode) { + /* most likely not for us */ + return; } - case "invoke-modal-action": - /* must be handled by the underlying handler */ - break; + if(this.ipcRemotePeerId) { + logTrace(LogCategory.IPC, tr("Modal popout slave changed from %s to %s. Side reload?"), this.ipcRemotePeerId, remoteId); + /* TODO: Send a good by to the old modal */ + } + this.ipcRemotePeerId = remoteId; - default: - logWarn(LogCategory.IPC, "Received unknown message type from popup window: %s", type); + logTrace(LogCategory.IPC, "Received Hello World from popup (peer id %s) with version %s (expected %s).", remoteId, messageHello.version, __build.version); + if(messageHello.version !== __build.version) { + this.sendIPCMessage("hello-controller", { accepted: false, message: tr("version miss match") }); + if(this.callbackWindowInitialized) { + this.callbackWindowInitialized(tr("version miss match")); + this.callbackWindowInitialized = undefined; + } return; + } + + if(this.callbackWindowInitialized) { + this.callbackWindowInitialized(); + this.callbackWindowInitialized = undefined; + } + + this.sendIPCMessage("hello-controller", { accepted: true, constructorArguments: this.constructorArguments }); } } } \ No newline at end of file diff --git a/shared/js/ui/react-elements/external-modal/IPCMessage.ts b/shared/js/ui/react-elements/external-modal/IPCMessage.ts index 282293e4..c3f80c21 100644 --- a/shared/js/ui/react-elements/external-modal/IPCMessage.ts +++ b/shared/js/ui/react-elements/external-modal/IPCMessage.ts @@ -1,7 +1,9 @@ -import {ChannelMessage, IPCChannel} from "../../../ipc/BrowserIPC"; +import {IPCChannel} from "../../../ipc/BrowserIPC"; + +export const kPopoutIPCChannelId = "popout-channel"; export interface PopoutIPCMessage { - "hello-popout": { version: string }, + "hello-popout": { version: string, authenticationCode: string }, "hello-controller": { accepted: boolean, message?: string, constructorArguments?: any[] }, "invoke-modal-action": { action: "close" | "minimize" @@ -22,28 +24,24 @@ export interface ReceivedIPCMessage { } export abstract class EventControllerBase { + protected readonly ipcAuthenticationCode: string; + protected ipcRemotePeerId: string; protected ipcChannel: IPCChannel; - protected ipcRemoteId: string; - protected constructor() { } - - protected handleIPCMessage(remoteId: string, broadcast: boolean, message: ChannelMessage) { - if(this.ipcRemoteId !== remoteId) { - console.warn("Received message from unknown end: %s. Expected: %s", remoteId, this.ipcRemoteId); - return; - } - - this.handleTypedIPCMessage(message.type as any, message.data); + protected constructor(ipcAuthenticationCode: string) { + this.ipcAuthenticationCode = ipcAuthenticationCode; } protected sendIPCMessage(type: T, payload: PopoutIPCMessage[T]) { - this.ipcChannel.sendMessage(type, payload, this.ipcRemoteId); + this.ipcChannel.sendMessage(type, payload, this.ipcRemotePeerId); } - protected handleTypedIPCMessage(type: T, payload: PopoutIPCMessage[T]) {} + protected handleTypedIPCMessage(remoteId: string, isBroadcast: boolean, type: T, payload: PopoutIPCMessage[T]) { + + } protected destroyIPC() { this.ipcChannel = undefined; - this.ipcRemoteId = undefined; + this.ipcRemotePeerId = undefined; } } \ No newline at end of file diff --git a/shared/js/ui/react-elements/external-modal/PopoutController.ts b/shared/js/ui/react-elements/external-modal/PopoutController.ts index 6c528555..8f5e0580 100644 --- a/shared/js/ui/react-elements/external-modal/PopoutController.ts +++ b/shared/js/ui/react-elements/external-modal/PopoutController.ts @@ -2,8 +2,8 @@ import {getIpcInstance as getIPCInstance} from "../../../ipc/BrowserIPC"; import {AppParameters} from "../../../settings"; import { Controller2PopoutMessages, - EventControllerBase, - PopoutIPCMessage + EventControllerBase, kPopoutIPCChannelId, + PopoutIPCMessage, } from "../../../ui/react-elements/external-modal/IPCMessage"; let controller: PopoutController; @@ -21,11 +21,12 @@ class PopoutController extends EventControllerBase<"popout"> { private callbackControllerHello: (accepted: boolean | string) => void; constructor() { - super(); - this.ipcRemoteId = AppParameters.getValue(AppParameters.KEY_IPC_REMOTE_ADDRESS, "invalid"); + super(AppParameters.getValue(AppParameters.KEY_MODAL_IDENTITY_CODE, "invalid")); - this.ipcChannel = getIPCInstance().createChannel(this.ipcRemoteId, AppParameters.getValue(AppParameters.KEY_IPC_REMOTE_POPOUT_CHANNEL, "invalid")); - this.ipcChannel.messageHandler = this.handleIPCMessage.bind(this); + this.ipcChannel = getIPCInstance().createChannel(kPopoutIPCChannelId); + this.ipcChannel.messageHandler = (sourcePeerId, broadcast, message) => { + this.handleTypedIPCMessage(sourcePeerId, broadcast, message.type as any, message.data); + }; } getConstructorArguments() : any[] { @@ -33,7 +34,7 @@ class PopoutController extends EventControllerBase<"popout"> { } async initialize() { - this.sendIPCMessage("hello-popout", { version: __build.version }); + this.sendIPCMessage("hello-popout", { version: __build.version, authenticationCode: this.ipcAuthenticationCode }); await new Promise((resolve, reject) => { const timeout = setTimeout(() => { @@ -55,13 +56,14 @@ class PopoutController extends EventControllerBase<"popout"> { }); } - protected handleTypedIPCMessage(type: T, payload: PopoutIPCMessage[T]) { - super.handleTypedIPCMessage(type, payload); + protected handleTypedIPCMessage(remoteId: string, isBroadcast: boolean, type: T, payload: PopoutIPCMessage[T]) { + super.handleTypedIPCMessage(remoteId, isBroadcast, type, payload); switch (type) { case "hello-controller": { const tpayload = payload as PopoutIPCMessage["hello-controller"]; - console.log("Received Hello World from controller. Window instance accpected: %o", tpayload.accepted); + this.ipcRemotePeerId = remoteId; + console.log("Received Hello World from controller (peer id %s). Window instance accepted: %o", this.ipcRemotePeerId, tpayload.accepted); if(!this.callbackControllerHello) { return; } diff --git a/shared/js/ui/react-elements/external-modal/PopoutEntrypoint.ts b/shared/js/ui/react-elements/external-modal/PopoutEntrypoint.ts index 0954cfd5..07955647 100644 --- a/shared/js/ui/react-elements/external-modal/PopoutEntrypoint.ts +++ b/shared/js/ui/react-elements/external-modal/PopoutEntrypoint.ts @@ -27,7 +27,7 @@ loader.register_task(Stage.JAVASCRIPT_INITIALIZING, { function: async () => { await import("tc-shared/proto"); await i18n.initialize(); - ipc.setup(); + ipc.setupIpcHandler(); setupJSRender(); } diff --git a/shared/js/ui/tree/EntryTags.tsx b/shared/js/ui/tree/EntryTags.tsx index c0a7ab59..68036eb0 100644 --- a/shared/js/ui/tree/EntryTags.tsx +++ b/shared/js/ui/tree/EntryTags.tsx @@ -84,7 +84,6 @@ loader.register_task(Stage.JAVASCRIPT_INITIALIZING, { name: "entry tags", priority: 10, function: async () => { - const ipc = getIpcInstance(); - ipcChannel = ipc.createChannel(AppParameters.getValue(AppParameters.KEY_IPC_REMOTE_ADDRESS, ipc.getLocalAddress()), kIpcChannel); + ipcChannel = getIpcInstance().createCoreControlChannel(kIpcChannel); } }); \ No newline at end of file diff --git a/vendor/xbbcode b/vendor/xbbcode index 33607743..d1a1b51f 160000 --- a/vendor/xbbcode +++ b/vendor/xbbcode @@ -1 +1 @@ -Subproject commit 336077435bbb09bb25f6efdcdac36956288fd3ca +Subproject commit d1a1b51f61c0dce71ebd856208964581ba6fecc7 diff --git a/web/app/ExternalModalFactory.ts b/web/app/ExternalModalFactory.ts index 1b3417b3..bf5595e0 100644 --- a/web/app/ExternalModalFactory.ts +++ b/web/app/ExternalModalFactory.ts @@ -6,6 +6,9 @@ import {LogCategory, logDebug, logWarn} from "tc-shared/log"; import {Popout2ControllerMessages, PopoutIPCMessage} from "tc-shared/ui/react-elements/external-modal/IPCMessage"; import {tr, tra} from "tc-shared/i18n/localize"; import {ModalOptions} from "tc-shared/ui/react-elements/modal/Definitions"; +import {assertMainApplication} from "tc-shared/ui/utils"; + +assertMainApplication(); export class ExternalModalController extends AbstractExternalModalController { private readonly options: ModalOptions; @@ -87,8 +90,9 @@ export class ExternalModalController extends AbstractExternalModalController { "loader-target": "manifest", "chunk": "modal-external", "modal-target": this.modalType, - "ipc-channel": this.ipcChannel.channelId, - "ipc-address": ipc.getIpcInstance().getLocalAddress(), + "modal-identify": this.ipcAuthenticationCode, + "ipc-address": ipc.getIpcInstance().getApplicationChannelId(), + "ipc-core-peer": ipc.getIpcInstance().getLocalPeerId(), "disableGlobalContextMenu": __build.mode === "debug" ? 1 : 0, "loader-abort": __build.mode === "debug" ? 1 : 0, }; @@ -113,7 +117,7 @@ export class ExternalModalController extends AbstractExternalModalController { } protected handleIPCMessage(remoteId: string, broadcast: boolean, message: ChannelMessage) { - if(!broadcast && this.ipcRemoteId !== remoteId) { + if(!broadcast && this.ipcRemotePeerId !== remoteId) { if(this.windowClosedTestInterval > 0) { clearInterval(this.windowClosedTestInterval); this.windowClosedTestInterval = 0; @@ -127,8 +131,12 @@ export class ExternalModalController extends AbstractExternalModalController { super.handleIPCMessage(remoteId, broadcast, message); } - protected handleTypedIPCMessage(type: T, payload: PopoutIPCMessage[T]) { - super.handleTypedIPCMessage(type, payload); + protected handleTypedIPCMessage(remoteId: string, isBroadcast: boolean, type: T, payload: PopoutIPCMessage[T]) { + super.handleTypedIPCMessage(remoteId, isBroadcast, type, payload); + + if(isBroadcast) { + return; + } switch (type) { case "invoke-modal-action": diff --git a/web/app/ui/context-menu/Ipc.ts b/web/app/ui/context-menu/Ipc.ts index 379f5350..a921ada5 100644 --- a/web/app/ui/context-menu/Ipc.ts +++ b/web/app/ui/context-menu/Ipc.ts @@ -26,7 +26,7 @@ class IPCContextMenu implements ContextMenuFactory { private closeCallback: () => void; constructor() { - this.ipcChannel = ipc.getIpcInstance().createChannel(undefined, kIPCContextMenuChannel); + this.ipcChannel = ipc.getIpcInstance().createChannel(kIPCContextMenuChannel); this.ipcChannel.messageHandler = this.handleIpcMessage.bind(this); /* if we're just created we're the focused window ;) */