Reworked the BrowserIPC module

This commit is contained in:
WolverinDEV 2021-02-20 17:46:17 +01:00
parent a1e1df6a2d
commit b9310b3fec
21 changed files with 222 additions and 506 deletions

View file

@ -13,7 +13,7 @@ loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
priority: 10, priority: 10,
function: async () => { function: async () => {
await i18n.initialize(); await i18n.initialize();
ipc.setup(); ipc.setupIpcHandler();
} }
}); });

View file

@ -352,7 +352,7 @@ class LocalAvatarManagerFactory extends AbstractAvatarManagerFactory {
constructor() { constructor() {
super(); super();
this.ipcChannel = ipc.getIpcInstance().createChannel(undefined, kIPCAvatarChannel); this.ipcChannel = ipc.getIpcInstance().createChannel(kIPCAvatarChannel);
this.ipcChannel.messageHandler = this.handleIpcMessage.bind(this); this.ipcChannel.messageHandler = this.handleIpcMessage.bind(this);
server_connections.events().on("notify_handler_created", event => this.handleHandlerCreated(event.handler)); server_connections.events().on("notify_handler_created", event => this.handleHandlerCreated(event.handler));

View file

@ -68,7 +68,7 @@ class IconManager extends AbstractIconManager {
constructor() { constructor() {
super(); super();
this.ipcChannel = ipc.getIpcInstance().createChannel(undefined, kIPCIconChannel); this.ipcChannel = ipc.getIpcInstance().createChannel(kIPCIconChannel);
this.ipcChannel.messageHandler = this.handleIpcMessage.bind(this); this.ipcChannel.messageHandler = this.handleIpcMessage.bind(this);
server_connections.events().on("notify_handler_created", event => { server_connections.events().on("notify_handler_created", event => {

View file

@ -8,7 +8,7 @@ import {
kIPCAvatarChannel, kIPCAvatarChannel,
setGlobalAvatarManagerFactory, uniqueId2AvatarId setGlobalAvatarManagerFactory, uniqueId2AvatarId
} from "../file/Avatars"; } from "../file/Avatars";
import {IPCChannel} from "../ipc/BrowserIPC"; import {getIpcInstance, IPCChannel} from "../ipc/BrowserIPC";
import {AppParameters} from "../settings"; import {AppParameters} from "../settings";
import {ChannelMessage} from "../ipc/BrowserIPC"; import {ChannelMessage} from "../ipc/BrowserIPC";
import {guid} from "../crypto/uid"; import {guid} from "../crypto/uid";
@ -159,7 +159,7 @@ class RemoteAvatarManagerFactory extends AbstractAvatarManagerFactory {
constructor() { constructor() {
super(); 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); this.ipcChannel.messageHandler = this.handleIpcMessage.bind(this);
} }

View file

@ -3,7 +3,6 @@ import * as loader from "tc-loader";
import {Stage} from "tc-loader"; import {Stage} from "tc-loader";
import {ChannelMessage, IPCChannel} from "tc-shared/ipc/BrowserIPC"; import {ChannelMessage, IPCChannel} from "tc-shared/ipc/BrowserIPC";
import * as ipc 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"; import {LogCategory, logWarn} from "tc-shared/log";
class RemoteRemoteIcon extends RemoteIcon { class RemoteRemoteIcon extends RemoteIcon {
@ -33,7 +32,7 @@ class RemoteIconManager extends AbstractIconManager {
constructor() { constructor() {
super(); 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); this.ipcChannel.messageHandler = this.handleIpcMessage.bind(this);
} }

View file

@ -1,150 +1,131 @@
import "broadcastchannel-polyfill"; import "broadcastchannel-polyfill";
import {LogCategory, logDebug, logError, logWarn} from "../log"; import {LogCategory, logDebug, logError, logTrace, logWarn} from "../log";
import {ConnectHandler} from "../ipc/ConnectHandler"; import {ConnectHandler} from "../ipc/ConnectHandler";
import {tr} from "tc-shared/i18n/localize"; import {tr} from "tc-shared/i18n/localize";
import {guid} from "tc-shared/crypto/uid";
import {AppParameters} from "tc-shared/settings";
export interface BroadcastMessage { interface IpcRawMessage {
timestamp: number; timestampSend: number,
receiver: string;
sender: string;
type: string; sourcePeerId: string,
data: any; targetPeerId: string,
}
function uuidv4() { targetChannelId: string,
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);
});
}
interface ProcessQuery { message: ChannelMessage
timestamp: number
query_id: string;
} }
export interface ChannelMessage { export interface ChannelMessage {
channel_id: string; type: string,
type: string; data: any
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 { export abstract class BasicIPCHandler {
protected static readonly BROADCAST_UNIQUE_ID = "00000000-0000-4000-0000-000000000000"; 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 registeredChannels: IPCChannel[] = [];
protected localUniqueId: string;
protected constructor() { } protected constructor(applicationChannelId: string) {
this.applicationChannelId = applicationChannelId;
setup() { this.localPeerId = guid();
this.localUniqueId = uuidv4();
} }
getLocalAddress() : string { return this.localUniqueId; } setup() { }
abstract sendMessage(type: string, data: any, target?: string); getApplicationChannelId() : string { return this.applicationChannelId; }
protected handleMessage(message: BroadcastMessage) { getLocalPeerId() : string { return this.localPeerId; }
//log.trace(LogCategory.IPC, tr("Received message %o"), message);
if(message.receiver === BasicIPCHandler.BROADCAST_UNIQUE_ID) { abstract sendMessage(message: IpcRawMessage);
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: (<ProcessQuery>message.data).query_id,
request_timestamp: (<ProcessQuery>message.data).timestamp,
device_id: this.localUniqueId, protected handleMessage(message: IpcRawMessage) {
protocol: BasicIPCHandler.PROTOCOL_VERSION logTrace(LogCategory.IPC, tr("Received message %o"), message);
} 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];
this.sendMessage("certificate-accept-succeeded", { if(message.targetPeerId !== this.localPeerId && message.targetPeerId !== BasicIPCHandler.BROADCAST_UNIQUE_ID) {
/* The message isn't for us */
} as CertificateAcceptSucceeded, message.sender); return;
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.type === "channel") {
const data: ChannelMessage = message.data;
let channel_invoked = false; let channelInvokeCount = 0;
for(const channel of this.registeredChannels) { for(const channel of this.registeredChannels) {
if(channel.channelId === data.channel_id && (typeof(channel.targetClientId) === "undefined" || channel.targetClientId === message.sender)) { if(channel.channelId !== message.targetChannelId) {
if(channel.messageHandler) continue;
channel.messageHandler(message.sender, message.receiver === BasicIPCHandler.BROADCAST_UNIQUE_ID, data);
channel_invoked = true;
}
} }
if(!channel_invoked) { if(typeof channel.targetPeerId === "string" && channel.targetPeerId !== message.sourcePeerId) {
/* Seems like we're not the only web/teaclient instance */ continue;
/* console.warn(tr("Received channel message for unknown channel (%s)"), data.channel_id); */
} }
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 = { let channel: IPCChannel = {
targetClientId: targetId, channelId: channelId,
channelId: channelId || uuidv4(), targetPeerId: remotePeerId,
messageHandler: undefined, messageHandler: undefined,
sendMessage: (type: string, data: any, target?: string) => { sendMessage: (type: string, data: any, remotePeerId?: string) => {
if(typeof target !== "undefined") { if(typeof remotePeerId !== "undefined") {
if(typeof channel.targetClientId === "string" && target != channel.targetClientId) { if(typeof channel.targetPeerId === "string" && remotePeerId != channel.targetPeerId) {
throw "target id does not match channel target"; throw "target id does not match channel target";
} }
} }
this.sendMessage("channel", { remotePeerId = remotePeerId || channel.targetPeerId || BasicIPCHandler.BROADCAST_UNIQUE_ID;
type: type, this.sendMessage({
data: data, timestampSend: Date.now(),
channel_id: channel.channelId
} as ChannelMessage, target || channel.targetClientId || BasicIPCHandler.BROADCAST_UNIQUE_ID); 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; 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; } channels() : IPCChannel[] { return this.registeredChannels; }
deleteChannel(channel: IPCChannel) { deleteChannel(channel: IPCChannel) {
this.registeredChannels = this.registeredChannels.filter(e => e !== channel); this.registeredChannels.remove(channel);
}
private _query_results: {[key: string]:ProcessQueryResponse[]} = {};
async queryProcesses(timeout?: number) : Promise<ProcessQueryResponse[]> {
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<void> {
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]);
})
} }
} }
export interface IPCChannel { export interface IPCChannel {
/** Channel id */
readonly channelId: string; 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; messageHandler: (sourcePeerId: string, broadcast: boolean, message: ChannelMessage) => void;
sendMessage(type: string, message: any, target?: string); sendMessage(type: string, data: any, remotePeerId?: string);
} }
class BroadcastChannelIPC extends BasicIPCHandler { class BroadcastChannelIPC extends BasicIPCHandler {
private static readonly CHANNEL_NAME = "TeaSpeak-Web";
private channel: BroadcastChannel; private channel: BroadcastChannel;
constructor() { constructor(applicationChannelId: string) {
super(); super(applicationChannelId);
} }
setup() { setup() {
super.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.onmessage = this.onMessage.bind(this);
this.channel.onmessageerror = this.onError.bind(this); this.channel.onmessageerror = this.onError.bind(this);
} }
@ -233,7 +179,7 @@ class BroadcastChannelIPC extends BasicIPCHandler {
return; return;
} }
let message: BroadcastMessage; let message: IpcRawMessage;
try { try {
message = JSON.parse(event.data); message = JSON.parse(event.data);
} catch(error) { } catch(error) {
@ -247,52 +193,31 @@ class BroadcastChannelIPC extends BasicIPCHandler {
logWarn(LogCategory.IPC, tr("Received error: %o"), event); logWarn(LogCategory.IPC, tr("Received error: %o"), event);
} }
sendMessage(type: string, data: any, target?: string) { sendMessage(message: IpcRawMessage) {
const message: BroadcastMessage = {} as any; this.channel.postMessage(JSON.stringify(message));
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));
}
} }
} }
let handler: BasicIPCHandler; let handlerInstance: BasicIPCHandler;
let connect_handler: ConnectHandler; let connectHandler: ConnectHandler;
export function setup() { export function setupIpcHandler() {
if(!supported()) if(handlerInstance) {
return; throw "IPC handler already initialized";
}
if(handler) handlerInstance = new BroadcastChannelIPC(AppParameters.getValue(AppParameters.KEY_IPC_APP_ADDRESS, guid()));
throw "bipc already started"; handlerInstance.setup();
logDebug(LogCategory.IPC, tr("Application IPC started for %s. Local peer address: %s"), handlerInstance.getApplicationChannelId(), handlerInstance.getLocalPeerId());
handler = new BroadcastChannelIPC(); connectHandler = new ConnectHandler(handlerInstance);
handler.setup(); connectHandler.setup();
connect_handler = new ConnectHandler(handler);
connect_handler.setup();
} }
export function getIpcInstance() { export function getIpcInstance() {
return handler; return handlerInstance;
} }
export function getInstanceConnectHandler() { export function getInstanceConnectHandler() {
return connect_handler; return connectHandler;
}
export function supported() {
/* we've a polyfill now */
return true;
/* ios does not support this */
return typeof(window.BroadcastChannel) !== "undefined";
} }

View file

@ -76,7 +76,7 @@ export class ConnectHandler {
} }
public setup() { 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); this.ipc_channel.messageHandler = this.onMessage.bind(this);
} }

View file

@ -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<any>;
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<any>} = {};
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<R>(method: (...args: any[]) => Promise<R> | 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();
}

View file

@ -64,7 +64,7 @@ async function initialize() {
return; return;
} }
bipc.setup(); bipc.setupIpcHandler();
} }
async function initializeApp() { async function initializeApp() {

View file

@ -252,16 +252,22 @@ export namespace AppParameters {
}; };
export const KEY_IPC_REMOTE_ADDRESS: RegistryKey<string> = { export const KEY_IPC_APP_ADDRESS: RegistryKey<string> = {
key: "ipc-address", key: "ipc-address",
valueType: "string", valueType: "string",
description: "Address of the apps IPC channel" description: "Address of the apps IPC channel"
}; };
export const KEY_IPC_REMOTE_POPOUT_CHANNEL: RegistryKey<string> = { export const KEY_IPC_CORE_PEER_ADDRESS: RegistryKey<string> = {
key: "ipc-channel", key: "ipc-core-peer",
valueType: "string", 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<string> = {
key: "modal-identify",
valueType: "string",
description: "An authentication code used to register the new process as the modal"
}; };
export const KEY_MODAL_TARGET: RegistryKey<string> = { export const KEY_MODAL_TARGET: RegistryKey<string> = {

View file

@ -1,6 +1,6 @@
import * as loader from "tc-loader"; import * as loader from "tc-loader";
import {ChannelMessage, getIpcInstance, IPCChannel} from "tc-shared/ipc/BrowserIPC"; 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 {IpcInviteInfo} from "tc-shared/text/bbcode/InviteDefinitions";
import {LogCategory, logError} from "tc-shared/log"; import {LogCategory, logError} from "tc-shared/log";
import {clientServiceInvite, clientServices} from "tc-shared/clientservice"; import {clientServiceInvite, clientServices} from "tc-shared/clientservice";
@ -10,7 +10,7 @@ let ipcChannel: IPCChannel;
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
name: "Invite controller init", name: "Invite controller init",
function: async () => { function: async () => {
ipcChannel = getIpcInstance().createChannel(AppParameters.getValue(AppParameters.KEY_IPC_REMOTE_ADDRESS, undefined), "invite-info"); ipcChannel = getIpcInstance().createChannel("invite-info");
ipcChannel.messageHandler = handleIpcMessage; ipcChannel.messageHandler = handleIpcMessage;
}, },
priority: 10 priority: 10

View file

@ -208,7 +208,7 @@ let ipcChannel: IPCChannel;
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
name: "Invite controller init", name: "Invite controller init",
function: async () => { function: async () => {
ipcChannel = getIpcInstance().createChannel(AppParameters.getValue(AppParameters.KEY_IPC_REMOTE_ADDRESS, undefined), "invite-info"); ipcChannel = getIpcInstance().createCoreControlChannel("invite-info");
ipcChannel.messageHandler = handleIpcMessage; ipcChannel.messageHandler = handleIpcMessage;
}, },
priority: 10 priority: 10

View file

@ -95,7 +95,7 @@ loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
name: "entry tags", name: "entry tags",
priority: 10, priority: 10,
function: async () => { function: async () => {
const channel = getIpcInstance().createChannel(undefined, kIpcChannel); const channel = getIpcInstance().createChannel(kIpcChannel);
channel.messageHandler = (_remoteId, _broadcast, message) => handleIpcMessage(message.type, message.data); channel.messageHandler = (_remoteId, _broadcast, message) => handleIpcMessage(message.type, message.data);
} }
}); });

View file

@ -1,9 +1,10 @@
import {LogCategory, logDebug, logTrace, logWarn} from "../../../log"; import {LogCategory, logDebug, logTrace} from "../../../log";
import * as ipc from "../../../ipc/BrowserIPC"; import * as ipc from "../../../ipc/BrowserIPC";
import {ChannelMessage} from "../../../ipc/BrowserIPC"; import {ChannelMessage} from "../../../ipc/BrowserIPC";
import {Registry} from "../../../events"; import {Registry} from "tc-events";
import { import {
EventControllerBase, EventControllerBase,
kPopoutIPCChannelId,
Popout2ControllerMessages, Popout2ControllerMessages,
PopoutIPCMessage PopoutIPCMessage
} from "../../../ui/react-elements/external-modal/IPCMessage"; } from "../../../ui/react-elements/external-modal/IPCMessage";
@ -21,13 +22,13 @@ export abstract class AbstractExternalModalController extends EventControllerBas
private callbackWindowInitialized: (error?: string) => void; private callbackWindowInitialized: (error?: string) => void;
protected constructor(modalType: string, constructorArguments: any[]) { protected constructor(modalType: string, constructorArguments: any[]) {
super(); super(guid());
this.modalType = modalType; this.modalType = modalType;
this.constructorArguments = constructorArguments; this.constructorArguments = constructorArguments;
this.modalEvents = new Registry<ModalEvents>(); this.modalEvents = new Registry<ModalEvents>();
this.ipcChannel = ipc.getIpcInstance().createChannel(undefined, "modal-" + guid()); this.ipcChannel = ipc.getIpcInstance().createChannel(kPopoutIPCChannelId);
this.ipcChannel.messageHandler = this.handleIPCMessage.bind(this); this.ipcChannel.messageHandler = this.handleIPCMessage.bind(this);
this.documentUnloadListener = () => this.destroy(); this.documentUnloadListener = () => this.destroy();
@ -121,51 +122,46 @@ export abstract class AbstractExternalModalController extends EventControllerBas
} }
protected handleIPCMessage(remoteId: string, broadcast: boolean, message: ChannelMessage) { 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; 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<T extends Popout2ControllerMessages>(type: T, payload: PopoutIPCMessage[T]) { protected handleTypedIPCMessage<T extends Popout2ControllerMessages>(remoteId: string, isBroadcast: boolean, type: T, payload: PopoutIPCMessage[T]) {
super.handleTypedIPCMessage(type, payload); super.handleTypedIPCMessage(remoteId, isBroadcast, type, payload);
switch (type) { if(type === "hello-popout") {
case "hello-popout": { const messageHello = payload as PopoutIPCMessage["hello-popout"];
const tpayload = payload as PopoutIPCMessage["hello-popout"]; if(messageHello.authenticationCode !== this.ipcAuthenticationCode) {
logTrace(LogCategory.IPC, "Received Hello World from popup with version %s (expected %s).", tpayload.version, __build.version); /* most likely not for us */
if(tpayload.version !== __build.version) { return;
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;
} }
case "invoke-modal-action": if(this.ipcRemotePeerId) {
/* must be handled by the underlying handler */ logTrace(LogCategory.IPC, tr("Modal popout slave changed from %s to %s. Side reload?"), this.ipcRemotePeerId, remoteId);
break; /* TODO: Send a good by to the old modal */
}
this.ipcRemotePeerId = remoteId;
default: logTrace(LogCategory.IPC, "Received Hello World from popup (peer id %s) with version %s (expected %s).", remoteId, messageHello.version, __build.version);
logWarn(LogCategory.IPC, "Received unknown message type from popup window: %s", type); 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; return;
}
if(this.callbackWindowInitialized) {
this.callbackWindowInitialized();
this.callbackWindowInitialized = undefined;
}
this.sendIPCMessage("hello-controller", { accepted: true, constructorArguments: this.constructorArguments });
} }
} }
} }

View file

@ -1,7 +1,9 @@
import {ChannelMessage, IPCChannel} from "../../../ipc/BrowserIPC"; import {IPCChannel} from "../../../ipc/BrowserIPC";
export const kPopoutIPCChannelId = "popout-channel";
export interface PopoutIPCMessage { export interface PopoutIPCMessage {
"hello-popout": { version: string }, "hello-popout": { version: string, authenticationCode: string },
"hello-controller": { accepted: boolean, message?: string, constructorArguments?: any[] }, "hello-controller": { accepted: boolean, message?: string, constructorArguments?: any[] },
"invoke-modal-action": { "invoke-modal-action": {
action: "close" | "minimize" action: "close" | "minimize"
@ -22,28 +24,24 @@ export interface ReceivedIPCMessage {
} }
export abstract class EventControllerBase<Type extends "controller" | "popout"> { export abstract class EventControllerBase<Type extends "controller" | "popout"> {
protected readonly ipcAuthenticationCode: string;
protected ipcRemotePeerId: string;
protected ipcChannel: IPCChannel; protected ipcChannel: IPCChannel;
protected ipcRemoteId: string;
protected constructor() { } protected constructor(ipcAuthenticationCode: string) {
this.ipcAuthenticationCode = ipcAuthenticationCode;
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 sendIPCMessage<T extends SendIPCMessage[Type]>(type: T, payload: PopoutIPCMessage[T]) { protected sendIPCMessage<T extends SendIPCMessage[Type]>(type: T, payload: PopoutIPCMessage[T]) {
this.ipcChannel.sendMessage(type, payload, this.ipcRemoteId); this.ipcChannel.sendMessage(type, payload, this.ipcRemotePeerId);
} }
protected handleTypedIPCMessage<T extends ReceivedIPCMessage[Type]>(type: T, payload: PopoutIPCMessage[T]) {} protected handleTypedIPCMessage<T extends ReceivedIPCMessage[Type]>(remoteId: string, isBroadcast: boolean, type: T, payload: PopoutIPCMessage[T]) {
}
protected destroyIPC() { protected destroyIPC() {
this.ipcChannel = undefined; this.ipcChannel = undefined;
this.ipcRemoteId = undefined; this.ipcRemotePeerId = undefined;
} }
} }

View file

@ -2,8 +2,8 @@ import {getIpcInstance as getIPCInstance} from "../../../ipc/BrowserIPC";
import {AppParameters} from "../../../settings"; import {AppParameters} from "../../../settings";
import { import {
Controller2PopoutMessages, Controller2PopoutMessages,
EventControllerBase, EventControllerBase, kPopoutIPCChannelId,
PopoutIPCMessage PopoutIPCMessage,
} from "../../../ui/react-elements/external-modal/IPCMessage"; } from "../../../ui/react-elements/external-modal/IPCMessage";
let controller: PopoutController; let controller: PopoutController;
@ -21,11 +21,12 @@ class PopoutController extends EventControllerBase<"popout"> {
private callbackControllerHello: (accepted: boolean | string) => void; private callbackControllerHello: (accepted: boolean | string) => void;
constructor() { constructor() {
super(); super(AppParameters.getValue(AppParameters.KEY_MODAL_IDENTITY_CODE, "invalid"));
this.ipcRemoteId = AppParameters.getValue(AppParameters.KEY_IPC_REMOTE_ADDRESS, "invalid");
this.ipcChannel = getIPCInstance().createChannel(this.ipcRemoteId, AppParameters.getValue(AppParameters.KEY_IPC_REMOTE_POPOUT_CHANNEL, "invalid")); this.ipcChannel = getIPCInstance().createChannel(kPopoutIPCChannelId);
this.ipcChannel.messageHandler = this.handleIPCMessage.bind(this); this.ipcChannel.messageHandler = (sourcePeerId, broadcast, message) => {
this.handleTypedIPCMessage(sourcePeerId, broadcast, message.type as any, message.data);
};
} }
getConstructorArguments() : any[] { getConstructorArguments() : any[] {
@ -33,7 +34,7 @@ class PopoutController extends EventControllerBase<"popout"> {
} }
async initialize() { async initialize() {
this.sendIPCMessage("hello-popout", { version: __build.version }); this.sendIPCMessage("hello-popout", { version: __build.version, authenticationCode: this.ipcAuthenticationCode });
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
@ -55,13 +56,14 @@ class PopoutController extends EventControllerBase<"popout"> {
}); });
} }
protected handleTypedIPCMessage<T extends Controller2PopoutMessages>(type: T, payload: PopoutIPCMessage[T]) { protected handleTypedIPCMessage<T extends Controller2PopoutMessages>(remoteId: string, isBroadcast: boolean, type: T, payload: PopoutIPCMessage[T]) {
super.handleTypedIPCMessage(type, payload); super.handleTypedIPCMessage(remoteId, isBroadcast, type, payload);
switch (type) { switch (type) {
case "hello-controller": { case "hello-controller": {
const tpayload = payload as PopoutIPCMessage["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) { if(!this.callbackControllerHello) {
return; return;
} }

View file

@ -27,7 +27,7 @@ loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
function: async () => { function: async () => {
await import("tc-shared/proto"); await import("tc-shared/proto");
await i18n.initialize(); await i18n.initialize();
ipc.setup(); ipc.setupIpcHandler();
setupJSRender(); setupJSRender();
} }

View file

@ -84,7 +84,6 @@ loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
name: "entry tags", name: "entry tags",
priority: 10, priority: 10,
function: async () => { function: async () => {
const ipc = getIpcInstance(); ipcChannel = getIpcInstance().createCoreControlChannel(kIpcChannel);
ipcChannel = ipc.createChannel(AppParameters.getValue(AppParameters.KEY_IPC_REMOTE_ADDRESS, ipc.getLocalAddress()), kIpcChannel);
} }
}); });

2
vendor/xbbcode vendored

@ -1 +1 @@
Subproject commit 336077435bbb09bb25f6efdcdac36956288fd3ca Subproject commit d1a1b51f61c0dce71ebd856208964581ba6fecc7

View file

@ -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 {Popout2ControllerMessages, PopoutIPCMessage} from "tc-shared/ui/react-elements/external-modal/IPCMessage";
import {tr, tra} from "tc-shared/i18n/localize"; import {tr, tra} from "tc-shared/i18n/localize";
import {ModalOptions} from "tc-shared/ui/react-elements/modal/Definitions"; import {ModalOptions} from "tc-shared/ui/react-elements/modal/Definitions";
import {assertMainApplication} from "tc-shared/ui/utils";
assertMainApplication();
export class ExternalModalController extends AbstractExternalModalController { export class ExternalModalController extends AbstractExternalModalController {
private readonly options: ModalOptions; private readonly options: ModalOptions;
@ -87,8 +90,9 @@ export class ExternalModalController extends AbstractExternalModalController {
"loader-target": "manifest", "loader-target": "manifest",
"chunk": "modal-external", "chunk": "modal-external",
"modal-target": this.modalType, "modal-target": this.modalType,
"ipc-channel": this.ipcChannel.channelId, "modal-identify": this.ipcAuthenticationCode,
"ipc-address": ipc.getIpcInstance().getLocalAddress(), "ipc-address": ipc.getIpcInstance().getApplicationChannelId(),
"ipc-core-peer": ipc.getIpcInstance().getLocalPeerId(),
"disableGlobalContextMenu": __build.mode === "debug" ? 1 : 0, "disableGlobalContextMenu": __build.mode === "debug" ? 1 : 0,
"loader-abort": __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) { protected handleIPCMessage(remoteId: string, broadcast: boolean, message: ChannelMessage) {
if(!broadcast && this.ipcRemoteId !== remoteId) { if(!broadcast && this.ipcRemotePeerId !== remoteId) {
if(this.windowClosedTestInterval > 0) { if(this.windowClosedTestInterval > 0) {
clearInterval(this.windowClosedTestInterval); clearInterval(this.windowClosedTestInterval);
this.windowClosedTestInterval = 0; this.windowClosedTestInterval = 0;
@ -127,8 +131,12 @@ export class ExternalModalController extends AbstractExternalModalController {
super.handleIPCMessage(remoteId, broadcast, message); super.handleIPCMessage(remoteId, broadcast, message);
} }
protected handleTypedIPCMessage<T extends Popout2ControllerMessages>(type: T, payload: PopoutIPCMessage[T]) { protected handleTypedIPCMessage<T extends Popout2ControllerMessages>(remoteId: string, isBroadcast: boolean, type: T, payload: PopoutIPCMessage[T]) {
super.handleTypedIPCMessage(type, payload); super.handleTypedIPCMessage(remoteId, isBroadcast, type, payload);
if(isBroadcast) {
return;
}
switch (type) { switch (type) {
case "invoke-modal-action": case "invoke-modal-action":

View file

@ -26,7 +26,7 @@ class IPCContextMenu implements ContextMenuFactory {
private closeCallback: () => void; private closeCallback: () => void;
constructor() { constructor() {
this.ipcChannel = ipc.getIpcInstance().createChannel(undefined, kIPCContextMenuChannel); this.ipcChannel = ipc.getIpcInstance().createChannel(kIPCContextMenuChannel);
this.ipcChannel.messageHandler = this.handleIpcMessage.bind(this); this.ipcChannel.messageHandler = this.handleIpcMessage.bind(this);
/* if we're just created we're the focused window ;) */ /* if we're just created we're the focused window ;) */