Reworked the BrowserIPC module

master
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,
function: async () => {
await i18n.initialize();
ipc.setup();
ipc.setupIpcHandler();
}
});

View File

@ -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));

View File

@ -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 => {

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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: (<ProcessQuery>message.data).query_id,
request_timestamp: (<ProcessQuery>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<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]);
})
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;
}

View File

@ -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);
}

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;
}
bipc.setup();
bipc.setupIpcHandler();
}
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",
valueType: "string",
description: "Address of the apps IPC channel"
};
export const KEY_IPC_REMOTE_POPOUT_CHANNEL: RegistryKey<string> = {
key: "ipc-channel",
export const KEY_IPC_CORE_PEER_ADDRESS: RegistryKey<string> = {
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<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> = {

View File

@ -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

View File

@ -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

View File

@ -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);
}
});

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 {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<ModalEvents>();
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<T extends Popout2ControllerMessages>(type: T, payload: PopoutIPCMessage[T]) {
super.handleTypedIPCMessage(type, payload);
protected handleTypedIPCMessage<T extends Popout2ControllerMessages>(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 });
}
}
}

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 {
"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<Type extends "controller" | "popout"> {
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<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() {
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 {
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<T extends Controller2PopoutMessages>(type: T, payload: PopoutIPCMessage[T]) {
super.handleTypedIPCMessage(type, payload);
protected handleTypedIPCMessage<T extends Controller2PopoutMessages>(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;
}

View File

@ -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();
}

View File

@ -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);
}
});

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 {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<T extends Popout2ControllerMessages>(type: T, payload: PopoutIPCMessage[T]) {
super.handleTypedIPCMessage(type, payload);
protected handleTypedIPCMessage<T extends Popout2ControllerMessages>(remoteId: string, isBroadcast: boolean, type: T, payload: PopoutIPCMessage[T]) {
super.handleTypedIPCMessage(remoteId, isBroadcast, type, payload);
if(isBroadcast) {
return;
}
switch (type) {
case "invoke-modal-action":

View File

@ -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 ;) */