Added the possibility to connect within an already running TeaWeb instance
parent
9326572342
commit
27e3967ffa
|
@ -1,4 +1,7 @@
|
||||||
# Changelog:
|
# Changelog:
|
||||||
|
* **06.10.19**
|
||||||
|
- Added the possibility to connect within an already running TeaWeb instance
|
||||||
|
|
||||||
* **30.10.19**
|
* **30.10.19**
|
||||||
- Removed old `files.php` script and replaced it with a more modern `file.ts` script to generate the environment
|
- Removed old `files.php` script and replaced it with a more modern `file.ts` script to generate the environment
|
||||||
- Some small UI fixed
|
- Some small UI fixed
|
||||||
|
|
|
@ -28,8 +28,8 @@ namespace bipc {
|
||||||
|
|
||||||
export interface ChannelMessage {
|
export interface ChannelMessage {
|
||||||
channel_id: string;
|
channel_id: string;
|
||||||
key: string;
|
type: string;
|
||||||
message: any;
|
data: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProcessQueryResponse {
|
export interface ProcessQueryResponse {
|
||||||
|
@ -63,7 +63,7 @@ namespace bipc {
|
||||||
abstract send_message(type: string, data: any, target?: string);
|
abstract send_message(type: string, data: any, target?: string);
|
||||||
|
|
||||||
protected handle_message(message: BroadcastMessage) {
|
protected handle_message(message: BroadcastMessage) {
|
||||||
log.trace(LogCategory.IPC, tr("Received message %o"), message);
|
//log.trace(LogCategory.IPC, tr("Received message %o"), message);
|
||||||
|
|
||||||
if(message.receiver === BasicIPCHandler.BROADCAST_UNIQUE_ID) {
|
if(message.receiver === BasicIPCHandler.BROADCAST_UNIQUE_ID) {
|
||||||
if(message.type == "process-query") {
|
if(message.type == "process-query") {
|
||||||
|
@ -86,7 +86,8 @@ namespace bipc {
|
||||||
log.warn(LogCategory.IPC, tr("Received a query response for an unknown request."));
|
log.warn(LogCategory.IPC, tr("Received a query response for an unknown request."));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if(message.type == "certificate-accept-callback") {
|
}
|
||||||
|
else if(message.type == "certificate-accept-callback") {
|
||||||
const data: CertificateAcceptCallback = message.data;
|
const data: CertificateAcceptCallback = message.data;
|
||||||
if(!this._cert_accept_callbacks[data.request_id]) {
|
if(!this._cert_accept_callbacks[data.request_id]) {
|
||||||
log.warn(LogCategory.IPC, tr("Received certificate accept callback for an unknown request ID."));
|
log.warn(LogCategory.IPC, tr("Received certificate accept callback for an unknown request ID."));
|
||||||
|
@ -99,26 +100,28 @@ namespace bipc {
|
||||||
|
|
||||||
} as CertificateAcceptSucceeded, message.sender);
|
} as CertificateAcceptSucceeded, message.sender);
|
||||||
return;
|
return;
|
||||||
} else if(message.type == "certificate-accept-succeeded") {
|
}
|
||||||
|
else if(message.type == "certificate-accept-succeeded") {
|
||||||
if(!this._cert_accept_succeeded[message.sender]) {
|
if(!this._cert_accept_succeeded[message.sender]) {
|
||||||
log.warn(LogCategory.IPC, tr("Received certificate accept succeeded, but haven't a callback."));
|
log.warn(LogCategory.IPC, tr("Received certificate accept succeeded, but haven't a callback."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._cert_accept_succeeded[message.sender]();
|
this._cert_accept_succeeded[message.sender]();
|
||||||
return;
|
return;
|
||||||
} else if(message.type === "channel") {
|
}
|
||||||
const data: ChannelMessage = message.data;
|
}
|
||||||
|
if(message.type === "channel") {
|
||||||
|
const data: ChannelMessage = message.data;
|
||||||
|
|
||||||
let channel_invoked = false;
|
let channel_invoked = false;
|
||||||
for(const channel of this._channels)
|
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.channel_id === data.channel_id && (typeof(channel.target_id) === "undefined" || channel.target_id === message.sender)) {
|
||||||
if(channel.message_handler)
|
if(channel.message_handler)
|
||||||
channel.message_handler(message.sender, data);
|
channel.message_handler(message.sender, message.receiver === BasicIPCHandler.BROADCAST_UNIQUE_ID, data);
|
||||||
channel_invoked = true;
|
channel_invoked = true;
|
||||||
}
|
|
||||||
if(!channel_invoked) {
|
|
||||||
console.warn(tr("Received channel message for unknown channel (%s)"), data.channel_id);
|
|
||||||
}
|
}
|
||||||
|
if(!channel_invoked) {
|
||||||
|
console.warn(tr("Received channel message for unknown channel (%s)"), data.channel_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,15 +131,17 @@ namespace bipc {
|
||||||
target_id: target_id,
|
target_id: target_id,
|
||||||
channel_id: channel_id || uuidv4(),
|
channel_id: channel_id || uuidv4(),
|
||||||
message_handler: undefined,
|
message_handler: undefined,
|
||||||
send_message: (key: string, message: any) => {
|
send_message: (type: string, data: any, target?: string) => {
|
||||||
if(!channel.target_id)
|
if(typeof target !== "undefined") {
|
||||||
throw "channel has no target!";
|
if(typeof channel.target_id === "string" && target != channel.target_id)
|
||||||
|
throw "target id does not match channel target";
|
||||||
|
}
|
||||||
|
|
||||||
this.send_message("channel", {
|
this.send_message("channel", {
|
||||||
key: key,
|
type: type,
|
||||||
message: message,
|
data: data,
|
||||||
channel_id: channel.channel_id
|
channel_id: channel.channel_id
|
||||||
} as ChannelMessage, channel.target_id)
|
} as ChannelMessage, target || channel.target_id || BasicIPCHandler.BROADCAST_UNIQUE_ID);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -198,8 +203,8 @@ namespace bipc {
|
||||||
readonly channel_id: string;
|
readonly channel_id: string;
|
||||||
target_id?: string;
|
target_id?: string;
|
||||||
|
|
||||||
message_handler: (remote_id: string, message: ChannelMessage) => any;
|
message_handler: (remote_id: string, broadcast: boolean, message: ChannelMessage) => any;
|
||||||
send_message(key: string, message: any);
|
send_message(type: string, message: any, target?: string);
|
||||||
}
|
}
|
||||||
|
|
||||||
class BroadcastChannelIPC extends BasicIPCHandler {
|
class BroadcastChannelIPC extends BasicIPCHandler {
|
||||||
|
@ -252,232 +257,460 @@ namespace bipc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MethodProxyInvokeData {
|
export namespace connect {
|
||||||
method_name: string;
|
export interface ConnectOffer {
|
||||||
arguments: any[];
|
request_id: string;
|
||||||
promise_id: string;
|
data: ConnectRequestData;
|
||||||
}
|
|
||||||
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: 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() {
|
export interface ConnectOfferAnswer {
|
||||||
if(this._local) {
|
request_id: string;
|
||||||
this._connected = true;
|
accepted: boolean;
|
||||||
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() {
|
export interface ConnectExecute {
|
||||||
if(!this._local) {
|
request_id: string;
|
||||||
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) {
|
export interface ConnectExecuted {
|
||||||
let method_name: string;
|
request_id: string;
|
||||||
if(typeof method === "function") {
|
succeeded: boolean;
|
||||||
log.debug(LogCategory.IPC, tr("Registering method proxy for %s"), method.name);
|
message?: string;
|
||||||
method_name = method.name;
|
}
|
||||||
} else {
|
|
||||||
log.debug(LogCategory.IPC, tr("Registering method proxy for %s"), method);
|
/* The connect process:
|
||||||
method_name = method;
|
* 1. Broadcast an offer
|
||||||
|
* 2. Wait 50ms for all offer responses or until the first one respond with "ok"
|
||||||
|
* 3. Select (if possible) on accepted offer and execute the connect
|
||||||
|
*/
|
||||||
|
export class ConnectHandler {
|
||||||
|
private static readonly CHANNEL_NAME = "connect";
|
||||||
|
|
||||||
|
readonly ipc_handler: BasicIPCHandler;
|
||||||
|
private ipc_channel: Channel;
|
||||||
|
|
||||||
|
public callback_available: (data: ConnectRequestData) => boolean = () => false;
|
||||||
|
public callback_execute: (data: ConnectRequestData) => boolean | string = () => false;
|
||||||
|
|
||||||
|
|
||||||
|
private _pending_connect_offers: {
|
||||||
|
id: string;
|
||||||
|
data: ConnectRequestData;
|
||||||
|
timeout: number;
|
||||||
|
|
||||||
|
remote_handler: string;
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
private _pending_connects_requests: {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
data: ConnectRequestData;
|
||||||
|
timeout: number;
|
||||||
|
|
||||||
|
callback_success: () => any;
|
||||||
|
callback_failed: (message: string) => any;
|
||||||
|
callback_avail: () => Promise<boolean>;
|
||||||
|
|
||||||
|
remote_handler?: string;
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
constructor(ipc_handler: BasicIPCHandler) {
|
||||||
|
this.ipc_handler = ipc_handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!this[method_name])
|
public setup() {
|
||||||
throw "method is missing in current object";
|
this.ipc_channel = this.ipc_handler.create_channel(undefined, ConnectHandler.CHANNEL_NAME);
|
||||||
|
this.ipc_channel.message_handler = this.on_message.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
this._proxied_methods[method_name] = this[method_name];
|
private on_message(sender: string, broadcast: boolean, message: ChannelMessage) {
|
||||||
if(!this._local) {
|
if(broadcast) {
|
||||||
this[method_name] = (...args: any[]) => {
|
if(message.type == "offer") {
|
||||||
if(!this._connected)
|
const data = message.data as ConnectOffer;
|
||||||
return Promise.reject("not connected");
|
|
||||||
|
|
||||||
const proxy_callback = {
|
const response = {
|
||||||
promise_id: uuidv4()
|
accepted: this.callback_available(data.data),
|
||||||
} as MethodProxyCallback;
|
request_id: data.request_id
|
||||||
this._proxied_callbacks[proxy_callback.promise_id] = proxy_callback;
|
} as ConnectOfferAnswer;
|
||||||
proxy_callback.promise = new Promise((resolve, reject) => {
|
|
||||||
proxy_callback.resolve = resolve;
|
|
||||||
proxy_callback.reject = reject;
|
|
||||||
});
|
|
||||||
|
|
||||||
this._ipc_channel.send_message("invoke", {
|
if(response.accepted) {
|
||||||
promise_id: proxy_callback.promise_id,
|
log.debug(LogCategory.IPC, tr("Received new connect offer from %s: %s"), sender, data.request_id);
|
||||||
arguments: [...args],
|
|
||||||
method_name: method_name
|
const ld = {
|
||||||
} as MethodProxyInvokeData);
|
remote_handler: sender,
|
||||||
return proxy_callback.promise;
|
data: data.data,
|
||||||
|
id: data.request_id,
|
||||||
|
timeout: 0
|
||||||
|
};
|
||||||
|
this._pending_connect_offers.push(ld);
|
||||||
|
ld.timeout = setTimeout(() => {
|
||||||
|
log.debug(LogCategory.IPC, tr("Dropping connect request %s, because we never received an execute."), ld.id);
|
||||||
|
this._pending_connect_offers.remove(ld);
|
||||||
|
}, 120 * 1000) as any;
|
||||||
|
}
|
||||||
|
this.ipc_channel.send_message("offer-answer", response, sender);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(message.type == "offer-answer") {
|
||||||
|
const data = message.data as ConnectOfferAnswer;
|
||||||
|
const request = this._pending_connects_requests.find(e => e.id === data.request_id);
|
||||||
|
if(!request) {
|
||||||
|
log.warn(LogCategory.IPC, tr("Received connect offer answer with unknown request id (%s)."), data.request_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!data.accepted) {
|
||||||
|
log.debug(LogCategory.IPC, tr("Client %s rejected the connect offer (%s)."), sender, request.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(request.remote_handler) {
|
||||||
|
log.debug(LogCategory.IPC, tr("Client %s accepted the connect offer (%s), but offer has already been accepted."), sender, request.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug(LogCategory.IPC, tr("Client %s accepted the connect offer (%s). Request local acceptance."), sender, request.id);
|
||||||
|
request.remote_handler = sender;
|
||||||
|
clearTimeout(request.timeout);
|
||||||
|
|
||||||
|
request.callback_avail().then(flag => {
|
||||||
|
if(!flag) {
|
||||||
|
request.callback_failed("local avail rejected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug(LogCategory.IPC, tr("Executing connect with client %s"), request.remote_handler);
|
||||||
|
this.ipc_channel.send_message("execute", {
|
||||||
|
request_id: request.id
|
||||||
|
} as ConnectExecute, request.remote_handler);
|
||||||
|
request.timeout = setTimeout(() => {
|
||||||
|
request.callback_failed("connect execute timeout");
|
||||||
|
}, 1000) as any;
|
||||||
|
}).catch(error => {
|
||||||
|
log.error(LogCategory.IPC, tr("Local avail callback caused an error: %o"), error);
|
||||||
|
request.callback_failed(tr("local avail callback caused an error"));
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
else if(message.type == "executed") {
|
||||||
|
const data = message.data as ConnectExecuted;
|
||||||
|
const request = this._pending_connects_requests.find(e => e.id === data.request_id);
|
||||||
|
if(!request) {
|
||||||
|
log.warn(LogCategory.IPC, tr("Received connect executed with unknown request id (%s)."), data.request_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(request.remote_handler != sender) {
|
||||||
|
log.warn(LogCategory.IPC, tr("Received connect executed for request %s, but from wrong client: %s (expected %s)"), data.request_id, sender, request.remote_handler);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug(LogCategory.IPC, tr("Received connect executed response from client %s for request %s. Succeeded: %o (%s)"), sender, data.request_id, data.succeeded, data.message);
|
||||||
|
clearTimeout(request.timeout);
|
||||||
|
if(data.succeeded)
|
||||||
|
request.callback_success();
|
||||||
|
else
|
||||||
|
request.callback_failed(data.message);
|
||||||
|
}
|
||||||
|
else if(message.type == "execute") {
|
||||||
|
const data = message.data as ConnectExecute;
|
||||||
|
const request = this._pending_connect_offers.find(e => e.id === data.request_id);
|
||||||
|
if(!request) {
|
||||||
|
log.warn(LogCategory.IPC, tr("Received connect execute with unknown request id (%s)."), data.request_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(request.remote_handler != sender) {
|
||||||
|
log.warn(LogCategory.IPC, tr("Received connect execute for request %s, but from wrong client: %s (expected %s)"), data.request_id, sender, request.remote_handler);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clearTimeout(request.timeout);
|
||||||
|
this._pending_connect_offers.remove(request);
|
||||||
|
|
||||||
|
log.debug(LogCategory.IPC, tr("Executing connect for %s"), data.request_id);
|
||||||
|
const cr = this.callback_execute(request.data);
|
||||||
|
|
||||||
|
const response = {
|
||||||
|
request_id: data.request_id,
|
||||||
|
|
||||||
|
succeeded: typeof(cr) !== "string" && cr,
|
||||||
|
message: typeof(cr) === "string" ? cr : "",
|
||||||
|
} as ConnectExecuted;
|
||||||
|
this.ipc_channel.send_message("executed", response, request.remote_handler);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private _handle_message(remote_id: string, message: ChannelMessage) {
|
post_connect_request(data: ConnectRequestData, callback_avail: () => Promise<boolean>) : Promise<void> {
|
||||||
if(message.key === "finalize") {
|
return new Promise<void>((resolve, reject) => {
|
||||||
this._handle_finalize();
|
const pd = {
|
||||||
} else if(message.key === "initialize") {
|
data: data,
|
||||||
this._handle_remote_callback(remote_id);
|
id: uuidv4(),
|
||||||
} else if(message.key === "invoke") {
|
timeout: 0,
|
||||||
this._handle_invoke(message.message);
|
|
||||||
} else if(message.key === "result") {
|
callback_success: () => {
|
||||||
this._handle_result(message.message);
|
this._pending_connects_requests.remove(pd);
|
||||||
|
clearTimeout(pd.timeout);
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
|
||||||
|
callback_failed: error => {
|
||||||
|
this._pending_connects_requests.remove(pd);
|
||||||
|
clearTimeout(pd.timeout);
|
||||||
|
reject(error);
|
||||||
|
},
|
||||||
|
|
||||||
|
callback_avail: callback_avail,
|
||||||
|
};
|
||||||
|
this._pending_connects_requests.push(pd);
|
||||||
|
|
||||||
|
this.ipc_channel.send_message("offer", {
|
||||||
|
request_id: pd.id,
|
||||||
|
data: pd.data
|
||||||
|
} as ConnectOffer);
|
||||||
|
pd.timeout = setTimeout(() => {
|
||||||
|
pd.callback_failed("received no response to offer");
|
||||||
|
}, 50) as any;
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _handle_finalize() {
|
export namespace mproxy {
|
||||||
this.on_disconnected();
|
export interface MethodProxyInvokeData {
|
||||||
this.finalize();
|
method_name: string;
|
||||||
this._connected = false;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handle_remote_callback(remote_id: string) {
|
export type MethodProxyConnectParameters = {
|
||||||
if(!this._ipc_channel.target_id) {
|
channel_id: string;
|
||||||
if(this._slave)
|
client_id: string;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
export abstract class MethodProxy {
|
||||||
|
readonly ipc_handler: BasicIPCHandler;
|
||||||
|
private _ipc_channel: Channel;
|
||||||
|
private _ipc_parameters: MethodProxyConnectParameters;
|
||||||
|
|
||||||
private _send_result(promise_id: string, success: boolean, message: any) {
|
private readonly _local: boolean;
|
||||||
this._ipc_channel.send_message("result", {
|
private readonly _slave: boolean;
|
||||||
promise_id: promise_id,
|
|
||||||
result: message,
|
|
||||||
success: success
|
|
||||||
} as MethodProxyResultData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handle_invoke(data: MethodProxyInvokeData) {
|
private _connected: boolean;
|
||||||
if(this._proxied_methods[data.method_name])
|
private _proxied_methods: {[key: string]:() => Promise<any>} = {};
|
||||||
throw "we could not invoke a local proxied method!";
|
private _proxied_callbacks: {[key: string]:MethodProxyCallback} = {};
|
||||||
|
|
||||||
if(!this[data.method_name]) {
|
protected constructor(ipc_handler: BasicIPCHandler, connect_params?: MethodProxyConnectParameters) {
|
||||||
this._send_result(data.promise_id, false, "missing method");
|
this.ipc_handler = ipc_handler;
|
||||||
return;
|
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";
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
protected setup() {
|
||||||
log.info(LogCategory.IPC, tr("Invoking method %s with arguments: %o"), data.method_name, data.arguments);
|
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();
|
||||||
|
|
||||||
const promise = this[data.method_name](...data.arguments);
|
this._ipc_channel.message_handler = this._handle_message.bind(this);
|
||||||
promise.then(result => {
|
if(this._slave)
|
||||||
log.info(LogCategory.IPC, tr("Result: %o"), result);
|
this._ipc_channel.send_message("initialize", {});
|
||||||
this._send_result(data.promise_id, true, result);
|
}
|
||||||
}).catch(error => {
|
}
|
||||||
|
|
||||||
|
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") {
|
||||||
|
log.debug(LogCategory.IPC, tr("Registering method proxy for %s"), method.name);
|
||||||
|
method_name = method.name;
|
||||||
|
} else {
|
||||||
|
log.debug(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: 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, 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.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 {
|
||||||
|
log.info(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 => {
|
||||||
|
log.info(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);
|
this._send_result(data.promise_id, false, error);
|
||||||
});
|
return;
|
||||||
} catch(error) {
|
}
|
||||||
this._send_result(data.promise_id, false, error);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private _handle_result(data: MethodProxyResultData) {
|
private _handle_result(data: MethodProxyResultData) {
|
||||||
if(!this._proxied_callbacks[data.promise_id]) {
|
if(!this._proxied_callbacks[data.promise_id]) {
|
||||||
console.warn(tr("Received proxy method result for unknown promise"));
|
console.warn(tr("Received proxy method result for unknown promise"));
|
||||||
return;
|
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);
|
||||||
}
|
}
|
||||||
const callback = this._proxied_callbacks[data.promise_id];
|
|
||||||
delete this._proxied_callbacks[data.promise_id];
|
|
||||||
|
|
||||||
if(data.success)
|
generate_connect_parameters() : MethodProxyConnectParameters {
|
||||||
callback.resolve(data.result);
|
if(this._slave)
|
||||||
else
|
throw "only masters can generate connect parameters!";
|
||||||
callback.reject(data.result);
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
let handler: BasicIPCHandler;
|
||||||
|
let connect_handler: connect.ConnectHandler;
|
||||||
|
|
||||||
export function setup() {
|
export function setup() {
|
||||||
if(!supported())
|
if(!supported())
|
||||||
return;
|
return;
|
||||||
/* TODO: test for support */
|
|
||||||
handler = new BroadcastChannelIPC();
|
handler = new BroadcastChannelIPC();
|
||||||
handler.setup();
|
handler.setup();
|
||||||
|
|
||||||
|
connect_handler = new connect.ConnectHandler(handler);
|
||||||
|
connect_handler.setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function get_handler() {
|
export function get_handler() {
|
||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function get_connect_handler() {
|
||||||
|
return connect_handler;
|
||||||
|
}
|
||||||
|
|
||||||
export function supported() {
|
export function supported() {
|
||||||
/* ios does not support this */
|
/* ios does not support this */
|
||||||
return typeof(window.BroadcastChannel) !== "undefined";
|
return typeof(window.BroadcastChannel) !== "undefined";
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
/// <reference path="log.ts" />
|
/// <reference path="log.ts" />
|
||||||
/// <reference path="PPTListener.ts" />
|
/// <reference path="PPTListener.ts" />
|
||||||
|
|
||||||
|
import spawnYesNo = Modals.spawnYesNo;
|
||||||
|
|
||||||
const js_render = window.jsrender || $;
|
const js_render = window.jsrender || $;
|
||||||
const native_client = window.require !== undefined;
|
const native_client = window.require !== undefined;
|
||||||
|
|
||||||
|
@ -288,34 +290,41 @@ interface Window {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function execute_default_connect() {
|
type ConnectRequestData = {
|
||||||
if(settings.static(Settings.KEY_FLAG_CONNECT_DEFAULT, false) && settings.static(Settings.KEY_CONNECT_ADDRESS, "")) {
|
address: string;
|
||||||
const profile_uuid = settings.static(Settings.KEY_CONNECT_PROFILE, (profiles.default_profile() || {id: 'default'}).id);
|
|
||||||
const profile = profiles.find_profile(profile_uuid) || profiles.default_profile();
|
|
||||||
const address = settings.static(Settings.KEY_CONNECT_ADDRESS, "");
|
|
||||||
const username = settings.static(Settings.KEY_CONNECT_USERNAME, "Another TeaSpeak user");
|
|
||||||
|
|
||||||
const password = settings.static(Settings.KEY_CONNECT_PASSWORD, "");
|
profile?: string;
|
||||||
const password_hashed = settings.static(Settings.KEY_FLAG_CONNECT_PASSWORD, false);
|
username?: string;
|
||||||
|
password?: {
|
||||||
|
value: string;
|
||||||
|
hashed: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function handle_connect_request(properties: ConnectRequestData, connection: ConnectionHandler) {
|
||||||
|
const profile_uuid = properties.profile || (profiles.default_profile() || {id: 'default'}).id;
|
||||||
|
const profile = profiles.find_profile(profile_uuid) || profiles.default_profile();
|
||||||
|
const username = properties.username || profile.connect_username();
|
||||||
|
|
||||||
if(profile && profile.valid()) {
|
const password = properties.password ? properties.password.value : "";
|
||||||
const connection = server_connections.active_connection_handler() || server_connections.spawn_server_connection_handler();
|
const password_hashed = properties.password ? properties.password.hashed : false;
|
||||||
connection.startConnection(address, profile, true, {
|
|
||||||
nickname: username,
|
if(profile && profile.valid()) {
|
||||||
password: password.length > 0 ? {
|
connection.startConnection(properties.address, profile, true, {
|
||||||
password: password,
|
nickname: username,
|
||||||
hashed: password_hashed
|
password: password.length > 0 ? {
|
||||||
} : undefined
|
password: password,
|
||||||
});
|
hashed: password_hashed
|
||||||
} else {
|
} : undefined
|
||||||
Modals.spawnConnectModal({},{
|
});
|
||||||
url: address,
|
server_connections.set_active_connection_handler(connection);
|
||||||
enforce: true
|
} else {
|
||||||
}, {
|
Modals.spawnConnectModal({},{
|
||||||
profile: profile,
|
url: properties.address,
|
||||||
enforce: true
|
enforce: true
|
||||||
});
|
}, {
|
||||||
}
|
profile: profile,
|
||||||
|
enforce: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,7 +425,6 @@ function main() {
|
||||||
};
|
};
|
||||||
|
|
||||||
/* schedule it a bit later then the main because the main function is still within the loader */
|
/* schedule it a bit later then the main because the main function is still within the loader */
|
||||||
setTimeout(execute_default_connect, 5);
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const connection = server_connections.active_connection_handler();
|
const connection = server_connections.active_connection_handler();
|
||||||
/*
|
/*
|
||||||
|
@ -499,6 +507,74 @@ const task_teaweb_starter: loader.Task = {
|
||||||
priority: 10
|
priority: 10
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const task_connect_handler: loader.Task = {
|
||||||
|
name: "Connect handler",
|
||||||
|
function: async () => {
|
||||||
|
const address = settings.static(Settings.KEY_CONNECT_ADDRESS, "");
|
||||||
|
const chandler = bipc.get_connect_handler();
|
||||||
|
if(settings.static(Settings.KEY_FLAG_CONNECT_DEFAULT, false) && address) {
|
||||||
|
const connect_data = {
|
||||||
|
address: address,
|
||||||
|
|
||||||
|
profile: settings.static(Settings.KEY_CONNECT_PROFILE, ""),
|
||||||
|
username: settings.static(Settings.KEY_CONNECT_USERNAME, ""),
|
||||||
|
|
||||||
|
password: {
|
||||||
|
value: settings.static(Settings.KEY_CONNECT_PASSWORD, ""),
|
||||||
|
hashed: settings.static(Settings.KEY_FLAG_CONNECT_PASSWORD, false)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if(chandler) {
|
||||||
|
try {
|
||||||
|
await chandler.post_connect_request(connect_data, () => new Promise<boolean>((resolve, reject) => {
|
||||||
|
spawnYesNo(tr("Another TeaWeb instance is already running"), tra("Another TeaWeb instance is already running.{:br:}Would you like to connect there?"), response => {
|
||||||
|
resolve(response);
|
||||||
|
}, {
|
||||||
|
closeable: false
|
||||||
|
}).open();
|
||||||
|
}));
|
||||||
|
log.info(LogCategory.CLIENT, tr("Executed connect successfully in another browser window. Closing this window"));
|
||||||
|
|
||||||
|
const message =
|
||||||
|
"You're connecting to {0} within the other TeaWeb instance.{:br:}" +
|
||||||
|
"You could now close this page.";
|
||||||
|
createInfoModal(
|
||||||
|
tr("Connecting successfully within other instance"),
|
||||||
|
MessageHelper.formatMessage(tr(message), connect_data.address),
|
||||||
|
{
|
||||||
|
closeable: false,
|
||||||
|
footer: undefined
|
||||||
|
}
|
||||||
|
).open();
|
||||||
|
return;
|
||||||
|
} catch(error) {
|
||||||
|
log.info(LogCategory.CLIENT, tr("Failed to execute connect within other TeaWeb instance. Using this one. Error: %o"), error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.register_task(loader.Stage.LOADED, {
|
||||||
|
priority: 0,
|
||||||
|
function: async () => handle_connect_request(connect_data, server_connections.active_connection_handler() || server_connections.spawn_server_connection_handler()),
|
||||||
|
name: tr("default url connect")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if(chandler) {
|
||||||
|
/* no instance avail, so lets make us avail */
|
||||||
|
chandler.callback_available = data => {
|
||||||
|
return !settings.static_global(Settings.KEY_DISABLE_MULTI_SESSION);
|
||||||
|
};
|
||||||
|
|
||||||
|
chandler.callback_execute = data => {
|
||||||
|
handle_connect_request(data, server_connections.spawn_server_connection_handler());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loader.register_task(loader.Stage.LOADED, task_teaweb_starter);
|
||||||
|
},
|
||||||
|
priority: 10
|
||||||
|
};
|
||||||
|
|
||||||
const task_certificate_callback: loader.Task = {
|
const task_certificate_callback: loader.Task = {
|
||||||
name: "certificate accept tester",
|
name: "certificate accept tester",
|
||||||
function: async () => {
|
function: async () => {
|
||||||
|
@ -546,7 +622,7 @@ const task_certificate_callback: loader.Task = {
|
||||||
log.info(LogCategory.IPC, tr("We're not used to accept certificated. Booting app."));
|
log.info(LogCategory.IPC, tr("We're not used to accept certificated. Booting app."));
|
||||||
}
|
}
|
||||||
|
|
||||||
loader.register_task(loader.Stage.LOADED, task_teaweb_starter);
|
loader.register_task(loader.Stage.LOADED, task_connect_handler);
|
||||||
},
|
},
|
||||||
priority: 10
|
priority: 10
|
||||||
};
|
};
|
||||||
|
@ -574,8 +650,9 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
||||||
|
|
||||||
if(app.is_web()) {
|
if(app.is_web()) {
|
||||||
loader.register_task(loader.Stage.LOADED, task_certificate_callback);
|
loader.register_task(loader.Stage.LOADED, task_certificate_callback);
|
||||||
} else
|
} else {
|
||||||
loader.register_task(loader.Stage.LOADED, task_teaweb_starter);
|
loader.register_task(loader.Stage.LOADED, task_teaweb_starter);
|
||||||
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
if(ex instanceof Error || typeof(ex.stack) !== "undefined")
|
if(ex instanceof Error || typeof(ex.stack) !== "undefined")
|
||||||
console.error((tr || (msg => msg))("Critical error stack trace: %o"), ex.stack);
|
console.error((tr || (msg => msg))("Critical error stack trace: %o"), ex.stack);
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
namespace Modals {
|
namespace Modals {
|
||||||
export function spawnYesNo(header: BodyCreator, body: BodyCreator, callback: (_: boolean) => any, properties?: {
|
export function spawnYesNo(header: BodyCreator, body: BodyCreator, callback: (_: boolean) => any, properties?: {
|
||||||
text_yes?: string,
|
text_yes?: string,
|
||||||
text_no?: string
|
text_no?: string,
|
||||||
|
|
||||||
|
closeable?: boolean;
|
||||||
}) {
|
}) {
|
||||||
properties = properties || {};
|
properties = properties || {};
|
||||||
|
|
||||||
|
@ -16,6 +18,7 @@ namespace Modals {
|
||||||
props.header = header;
|
props.header = header;
|
||||||
props.template_properties.question = ModalFunctions.jqueriefy(body);
|
props.template_properties.question = ModalFunctions.jqueriefy(body);
|
||||||
|
|
||||||
|
props.closeable = typeof(properties.closeable) !== "boolean" || properties.closeable;
|
||||||
const modal = createModal(props);
|
const modal = createModal(props);
|
||||||
let submited = false;
|
let submited = false;
|
||||||
const button_yes = modal.htmlTag.find(".button-yes");
|
const button_yes = modal.htmlTag.find(".button-yes");
|
||||||
|
|
Loading…
Reference in New Issue