Added the possibility to connect within an already running TeaWeb instance
parent
9326572342
commit
27e3967ffa
|
@ -1,4 +1,7 @@
|
|||
# Changelog:
|
||||
* **06.10.19**
|
||||
- Added the possibility to connect within an already running TeaWeb instance
|
||||
|
||||
* **30.10.19**
|
||||
- Removed old `files.php` script and replaced it with a more modern `file.ts` script to generate the environment
|
||||
- Some small UI fixed
|
||||
|
|
|
@ -28,8 +28,8 @@ namespace bipc {
|
|||
|
||||
export interface ChannelMessage {
|
||||
channel_id: string;
|
||||
key: string;
|
||||
message: any;
|
||||
type: string;
|
||||
data: any;
|
||||
}
|
||||
|
||||
export interface ProcessQueryResponse {
|
||||
|
@ -63,7 +63,7 @@ namespace bipc {
|
|||
abstract send_message(type: string, data: any, target?: string);
|
||||
|
||||
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.type == "process-query") {
|
||||
|
@ -86,7 +86,8 @@ namespace bipc {
|
|||
log.warn(LogCategory.IPC, tr("Received a query response for an unknown request."));
|
||||
}
|
||||
return;
|
||||
} else if(message.type == "certificate-accept-callback") {
|
||||
}
|
||||
else if(message.type == "certificate-accept-callback") {
|
||||
const data: CertificateAcceptCallback = message.data;
|
||||
if(!this._cert_accept_callbacks[data.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);
|
||||
return;
|
||||
} else if(message.type == "certificate-accept-succeeded") {
|
||||
}
|
||||
else if(message.type == "certificate-accept-succeeded") {
|
||||
if(!this._cert_accept_succeeded[message.sender]) {
|
||||
log.warn(LogCategory.IPC, tr("Received certificate accept succeeded, but haven't a callback."));
|
||||
return;
|
||||
}
|
||||
this._cert_accept_succeeded[message.sender]();
|
||||
return;
|
||||
} else if(message.type === "channel") {
|
||||
const data: ChannelMessage = message.data;
|
||||
}
|
||||
}
|
||||
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);
|
||||
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, message.receiver === BasicIPCHandler.BROADCAST_UNIQUE_ID, data);
|
||||
channel_invoked = true;
|
||||
}
|
||||
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,
|
||||
channel_id: channel_id || uuidv4(),
|
||||
message_handler: undefined,
|
||||
send_message: (key: string, message: any) => {
|
||||
if(!channel.target_id)
|
||||
throw "channel has no target!";
|
||||
send_message: (type: string, data: any, target?: string) => {
|
||||
if(typeof target !== "undefined") {
|
||||
if(typeof channel.target_id === "string" && target != channel.target_id)
|
||||
throw "target id does not match channel target";
|
||||
}
|
||||
|
||||
this.send_message("channel", {
|
||||
key: key,
|
||||
message: message,
|
||||
type: type,
|
||||
data: data,
|
||||
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;
|
||||
target_id?: string;
|
||||
|
||||
message_handler: (remote_id: string, message: ChannelMessage) => any;
|
||||
send_message(key: string, message: any);
|
||||
message_handler: (remote_id: string, broadcast: boolean, message: ChannelMessage) => any;
|
||||
send_message(type: string, message: any, target?: string);
|
||||
}
|
||||
|
||||
class BroadcastChannelIPC extends BasicIPCHandler {
|
||||
|
@ -252,232 +257,460 @@ namespace bipc {
|
|||
}
|
||||
}
|
||||
|
||||
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: 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";
|
||||
export namespace connect {
|
||||
export interface ConnectOffer {
|
||||
request_id: string;
|
||||
data: ConnectRequestData;
|
||||
}
|
||||
|
||||
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", {});
|
||||
}
|
||||
export interface ConnectOfferAnswer {
|
||||
request_id: string;
|
||||
accepted: boolean;
|
||||
}
|
||||
|
||||
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();
|
||||
export interface ConnectExecute {
|
||||
request_id: string;
|
||||
}
|
||||
|
||||
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;
|
||||
export interface ConnectExecuted {
|
||||
request_id: string;
|
||||
succeeded: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/* The connect process:
|
||||
* 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])
|
||||
throw "method is missing in current object";
|
||||
public setup() {
|
||||
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];
|
||||
if(!this._local) {
|
||||
this[method_name] = (...args: any[]) => {
|
||||
if(!this._connected)
|
||||
return Promise.reject("not connected");
|
||||
private on_message(sender: string, broadcast: boolean, message: ChannelMessage) {
|
||||
if(broadcast) {
|
||||
if(message.type == "offer") {
|
||||
const data = message.data as ConnectOffer;
|
||||
|
||||
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;
|
||||
});
|
||||
const response = {
|
||||
accepted: this.callback_available(data.data),
|
||||
request_id: data.request_id
|
||||
} as ConnectOfferAnswer;
|
||||
|
||||
this._ipc_channel.send_message("invoke", {
|
||||
promise_id: proxy_callback.promise_id,
|
||||
arguments: [...args],
|
||||
method_name: method_name
|
||||
} as MethodProxyInvokeData);
|
||||
return proxy_callback.promise;
|
||||
if(response.accepted) {
|
||||
log.debug(LogCategory.IPC, tr("Received new connect offer from %s: %s"), sender, data.request_id);
|
||||
|
||||
const ld = {
|
||||
remote_handler: sender,
|
||||
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) {
|
||||
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);
|
||||
post_connect_request(data: ConnectRequestData, callback_avail: () => Promise<boolean>) : Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const pd = {
|
||||
data: data,
|
||||
id: uuidv4(),
|
||||
timeout: 0,
|
||||
|
||||
callback_success: () => {
|
||||
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() {
|
||||
this.on_disconnected();
|
||||
this.finalize();
|
||||
this._connected = false;
|
||||
export namespace mproxy {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
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 _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 readonly _local: boolean;
|
||||
private readonly _slave: boolean;
|
||||
|
||||
private _handle_invoke(data: MethodProxyInvokeData) {
|
||||
if(this._proxied_methods[data.method_name])
|
||||
throw "we could not invoke a local proxied method!";
|
||||
private _connected: boolean;
|
||||
private _proxied_methods: {[key: string]:() => Promise<any>} = {};
|
||||
private _proxied_callbacks: {[key: string]:MethodProxyCallback} = {};
|
||||
|
||||
if(!this[data.method_name]) {
|
||||
this._send_result(data.promise_id, false, "missing method");
|
||||
return;
|
||||
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";
|
||||
}
|
||||
|
||||
try {
|
||||
log.info(LogCategory.IPC, tr("Invoking method %s with arguments: %o"), data.method_name, data.arguments);
|
||||
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();
|
||||
|
||||
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._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") {
|
||||
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);
|
||||
});
|
||||
} catch(error) {
|
||||
this._send_result(data.promise_id, false, error);
|
||||
return;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _handle_result(data: MethodProxyResultData) {
|
||||
if(!this._proxied_callbacks[data.promise_id]) {
|
||||
console.warn(tr("Received proxy method result for unknown promise"));
|
||||
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);
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
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 connect_handler: connect.ConnectHandler;
|
||||
|
||||
export function setup() {
|
||||
if(!supported())
|
||||
return;
|
||||
/* TODO: test for support */
|
||||
|
||||
handler = new BroadcastChannelIPC();
|
||||
handler.setup();
|
||||
|
||||
connect_handler = new connect.ConnectHandler(handler);
|
||||
connect_handler.setup();
|
||||
}
|
||||
|
||||
export function get_handler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
export function get_connect_handler() {
|
||||
return connect_handler;
|
||||
}
|
||||
|
||||
export function supported() {
|
||||
/* ios does not support this */
|
||||
return typeof(window.BroadcastChannel) !== "undefined";
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
/// <reference path="log.ts" />
|
||||
/// <reference path="PPTListener.ts" />
|
||||
|
||||
import spawnYesNo = Modals.spawnYesNo;
|
||||
|
||||
const js_render = window.jsrender || $;
|
||||
const native_client = window.require !== undefined;
|
||||
|
||||
|
@ -288,34 +290,41 @@ interface Window {
|
|||
}
|
||||
*/
|
||||
|
||||
function execute_default_connect() {
|
||||
if(settings.static(Settings.KEY_FLAG_CONNECT_DEFAULT, false) && settings.static(Settings.KEY_CONNECT_ADDRESS, "")) {
|
||||
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");
|
||||
type ConnectRequestData = {
|
||||
address: string;
|
||||
|
||||
const password = settings.static(Settings.KEY_CONNECT_PASSWORD, "");
|
||||
const password_hashed = settings.static(Settings.KEY_FLAG_CONNECT_PASSWORD, false);
|
||||
profile?: string;
|
||||
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 connection = server_connections.active_connection_handler() || server_connections.spawn_server_connection_handler();
|
||||
connection.startConnection(address, profile, true, {
|
||||
nickname: username,
|
||||
password: password.length > 0 ? {
|
||||
password: password,
|
||||
hashed: password_hashed
|
||||
} : undefined
|
||||
});
|
||||
} else {
|
||||
Modals.spawnConnectModal({},{
|
||||
url: address,
|
||||
enforce: true
|
||||
}, {
|
||||
profile: profile,
|
||||
enforce: true
|
||||
});
|
||||
}
|
||||
const password = properties.password ? properties.password.value : "";
|
||||
const password_hashed = properties.password ? properties.password.hashed : false;
|
||||
|
||||
if(profile && profile.valid()) {
|
||||
connection.startConnection(properties.address, profile, true, {
|
||||
nickname: username,
|
||||
password: password.length > 0 ? {
|
||||
password: password,
|
||||
hashed: password_hashed
|
||||
} : undefined
|
||||
});
|
||||
server_connections.set_active_connection_handler(connection);
|
||||
} else {
|
||||
Modals.spawnConnectModal({},{
|
||||
url: properties.address,
|
||||
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 */
|
||||
setTimeout(execute_default_connect, 5);
|
||||
setTimeout(() => {
|
||||
const connection = server_connections.active_connection_handler();
|
||||
/*
|
||||
|
@ -499,6 +507,74 @@ const task_teaweb_starter: loader.Task = {
|
|||
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 = {
|
||||
name: "certificate accept tester",
|
||||
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."));
|
||||
}
|
||||
|
||||
loader.register_task(loader.Stage.LOADED, task_teaweb_starter);
|
||||
loader.register_task(loader.Stage.LOADED, task_connect_handler);
|
||||
},
|
||||
priority: 10
|
||||
};
|
||||
|
@ -574,8 +650,9 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
|||
|
||||
if(app.is_web()) {
|
||||
loader.register_task(loader.Stage.LOADED, task_certificate_callback);
|
||||
} else
|
||||
} else {
|
||||
loader.register_task(loader.Stage.LOADED, task_teaweb_starter);
|
||||
}
|
||||
} catch (ex) {
|
||||
if(ex instanceof Error || typeof(ex.stack) !== "undefined")
|
||||
console.error((tr || (msg => msg))("Critical error stack trace: %o"), ex.stack);
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
namespace Modals {
|
||||
export function spawnYesNo(header: BodyCreator, body: BodyCreator, callback: (_: boolean) => any, properties?: {
|
||||
text_yes?: string,
|
||||
text_no?: string
|
||||
text_no?: string,
|
||||
|
||||
closeable?: boolean;
|
||||
}) {
|
||||
properties = properties || {};
|
||||
|
||||
|
@ -16,6 +18,7 @@ namespace Modals {
|
|||
props.header = header;
|
||||
props.template_properties.question = ModalFunctions.jqueriefy(body);
|
||||
|
||||
props.closeable = typeof(properties.closeable) !== "boolean" || properties.closeable;
|
||||
const modal = createModal(props);
|
||||
let submited = false;
|
||||
const button_yes = modal.htmlTag.find(".button-yes");
|
||||
|
|
Loading…
Reference in New Issue