Added method to access methods within another window
This commit is contained in:
parent
63a2b3a08a
commit
2b00136e6e
1 changed files with 271 additions and 3 deletions
|
@ -24,6 +24,12 @@ namespace bipc {
|
|||
query_id: string;
|
||||
}
|
||||
|
||||
export interface ChannelMessage {
|
||||
channel_id: string;
|
||||
key: string;
|
||||
message: any;
|
||||
}
|
||||
|
||||
export interface ProcessQueryResponse {
|
||||
request_timestamp: number
|
||||
request_query_id: string;
|
||||
|
@ -35,14 +41,13 @@ namespace bipc {
|
|||
export interface CertificateAcceptCallback {
|
||||
request_id: string;
|
||||
}
|
||||
export interface CertificateAcceptSucceeded {
|
||||
|
||||
}
|
||||
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 _channels: Channel[] = [];
|
||||
protected unique_id;
|
||||
|
||||
protected constructor() { }
|
||||
|
@ -51,6 +56,8 @@ namespace bipc {
|
|||
this.unique_id = uuidv4(); /* lets get an unique identifier */
|
||||
}
|
||||
|
||||
get_local_address() { return this.unique_id; }
|
||||
|
||||
abstract send_message(type: string, data: any, target?: string);
|
||||
|
||||
protected handle_message(message: BroadcastMessage) {
|
||||
|
@ -97,9 +104,49 @@ namespace bipc {
|
|||
}
|
||||
this._cert_accept_succeeded[message.sender]();
|
||||
return;
|
||||
} else if(message.type === "channel") {
|
||||
const data: ChannelMessage = message.data;
|
||||
|
||||
let channel_invoked = false;
|
||||
for(const channel of this._channels)
|
||||
if(channel.channel_id === data.channel_id && (typeof(channel.target_id) === "undefined" || channel.target_id === message.sender)) {
|
||||
if(channel.message_handler)
|
||||
channel.message_handler(message.sender, data);
|
||||
channel_invoked = true;
|
||||
}
|
||||
if(!channel_invoked) {
|
||||
console.warn(tr("Received channel message for unknown channel (%s)"), data.channel_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
create_channel(target_id?: string, channel_id?: string) {
|
||||
let channel: Channel = {
|
||||
target_id: target_id,
|
||||
channel_id: channel_id || uuidv4(),
|
||||
message_handler: undefined,
|
||||
send_message: (key: string, message: any) => {
|
||||
if(!channel.target_id)
|
||||
throw "channel has no target!";
|
||||
|
||||
this.send_message("channel", {
|
||||
key: key,
|
||||
message: message,
|
||||
channel_id: channel.channel_id
|
||||
} as ChannelMessage, channel.target_id)
|
||||
}
|
||||
};
|
||||
|
||||
this._channels.push(channel);
|
||||
return channel;
|
||||
}
|
||||
|
||||
channels() : Channel[] { return this._channels; }
|
||||
|
||||
delete_channel(channel: Channel) {
|
||||
this._channels = this._channels.filter(e => e !== channel);
|
||||
}
|
||||
|
||||
private _query_results: {[key: string]:ProcessQueryResponse[]} = {};
|
||||
async query_processes(timeout?: number) : Promise<ProcessQueryResponse[]> {
|
||||
|
@ -145,6 +192,14 @@ namespace bipc {
|
|||
}
|
||||
}
|
||||
|
||||
export interface Channel {
|
||||
readonly channel_id: string;
|
||||
target_id?: string;
|
||||
|
||||
message_handler: (remote_id: string, message: ChannelMessage) => any;
|
||||
send_message(key: string, message: any);
|
||||
}
|
||||
|
||||
class BroadcastChannelIPC extends BasicIPCHandler {
|
||||
private static readonly CHANNEL_NAME = "TeaSpeak-Web";
|
||||
|
||||
|
@ -195,6 +250,219 @@ namespace bipc {
|
|||
}
|
||||
}
|
||||
|
||||
interface MethodProxyInvokeData {
|
||||
method_name: string;
|
||||
arguments: any[];
|
||||
promise_id: string;
|
||||
}
|
||||
interface MethodProxyResultData {
|
||||
promise_id: string;
|
||||
result: any;
|
||||
success: boolean;
|
||||
}
|
||||
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: Channel;
|
||||
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.create_channel(this._ipc_parameters.client_id, this._ipc_parameters.channel_id);
|
||||
else
|
||||
this._ipc_channel = this.ipc_handler.create_channel();
|
||||
|
||||
this._ipc_channel.message_handler = this._handle_message.bind(this);
|
||||
if(this._slave)
|
||||
this._ipc_channel.send_message("initialize", {});
|
||||
}
|
||||
}
|
||||
|
||||
protected finalize() {
|
||||
if(!this._local) {
|
||||
if(this._connected)
|
||||
this._ipc_channel.send_message("finalize", {});
|
||||
|
||||
this.ipc_handler.delete_channel(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") {
|
||||
console.log("Proxy method: %o", method.name);
|
||||
method_name = method.name;
|
||||
} else {
|
||||
console.log("Proxy method: %o", 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: uuidv4()
|
||||
} 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.send_message("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, message: ChannelMessage) {
|
||||
if(message.key === "finalize") {
|
||||
this._handle_finalize();
|
||||
} else if(message.key === "initialize") {
|
||||
this._handle_remote_callback(remote_id);
|
||||
} else if(message.key === "invoke") {
|
||||
this._handle_invoke(message.message);
|
||||
} else if(message.key === "result") {
|
||||
this._handle_result(message.message);
|
||||
}
|
||||
}
|
||||
|
||||
private _handle_finalize() {
|
||||
this.on_disconnected();
|
||||
this.finalize();
|
||||
this._connected = false;
|
||||
}
|
||||
|
||||
private _handle_remote_callback(remote_id: string) {
|
||||
if(!this._ipc_channel.target_id) {
|
||||
if(this._slave)
|
||||
throw "initialize wrong state!";
|
||||
|
||||
this._ipc_channel.target_id = remote_id; /* now we're able to send messages */
|
||||
this.on_connected();
|
||||
this._ipc_channel.send_message("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.send_message("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 {
|
||||
console.log(tr("Invoking method %s with arguments: %o"), data.method_name, data.arguments);
|
||||
|
||||
const promise = this[data.method_name](...data.arguments);
|
||||
promise.then(result => {
|
||||
console.log(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]) {
|
||||
console.warn(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.channel_id,
|
||||
client_id: this.ipc_handler.get_local_address()
|
||||
};
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
let handler: BasicIPCHandler;
|
||||
export function setup() {
|
||||
if(!supported())
|
||||
|
|
Loading…
Add table
Reference in a new issue