2021-01-10 16:36:57 +00:00
import { LogCategory , logDebug , logError , logWarn } from "../log" ;
import { BasicIPCHandler , ChannelMessage , IPCChannel } from "../ipc/BrowserIPC" ;
2020-09-12 13:49:20 +00:00
import { guid } from "../crypto/uid" ;
2021-01-10 16:36:57 +00:00
import { tr } from "tc-shared/i18n/localize" ;
2020-07-20 17:08:13 +00:00
export type ConnectRequestData = {
address : string ;
profile? : string ;
username? : string ;
2021-02-19 22:05:48 +00:00
2020-07-20 17:08:13 +00:00
password ? : {
value : string ;
hashed : boolean ;
} ;
}
export interface ConnectOffer {
request_id : string ;
data : ConnectRequestData ;
}
export interface ConnectOfferAnswer {
request_id : string ;
accepted : boolean ;
}
export interface ConnectExecute {
request_id : string ;
}
export interface ConnectExecuted {
request_id : string ;
succeeded : boolean ;
message? : string ;
}
/ * T h e c o n n e c t p r o c e s s :
* 1 . Broadcast an offer
* 2 . Wait 50 ms 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 : IPCChannel ;
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 ;
}
public setup() {
2021-02-20 16:46:17 +00:00
this . ipc_channel = this . ipc_handler . createChannel ( ConnectHandler . CHANNEL_NAME ) ;
2020-07-20 17:08:13 +00:00
this . ipc_channel . messageHandler = this . onMessage . bind ( this ) ;
}
private onMessage ( sender : string , broadcast : boolean , message : ChannelMessage ) {
if ( broadcast ) {
if ( message . type == "offer" ) {
const data = message . data as ConnectOffer ;
const response = {
accepted : this.callback_available ( data . data ) ,
2020-07-23 22:43:52 +00:00
request_id : data.request_id
2020-07-20 17:08:13 +00:00
} as ConnectOfferAnswer ;
if ( response . accepted ) {
2021-01-10 16:36:57 +00:00
logDebug ( LogCategory . IPC , tr ( "Received new connect offer from %s: %s" ) , sender , data . request_id ) ;
2020-07-20 17:08:13 +00:00
const ld = {
remote_handler : sender ,
data : data.data ,
id : data.request_id ,
timeout : 0
} ;
this . _pending_connect_offers . push ( ld ) ;
ld . timeout = setTimeout ( ( ) = > {
2021-01-10 16:36:57 +00:00
logDebug ( LogCategory . IPC , tr ( "Dropping connect request %s, because we never received an execute." ) , ld . id ) ;
2020-07-20 17:08:13 +00:00
this . _pending_connect_offers . remove ( ld ) ;
} , 120 * 1000 ) as any ;
}
this . ipc_channel . sendMessage ( "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 ) {
2021-01-10 16:36:57 +00:00
logWarn ( LogCategory . IPC , tr ( "Received connect offer answer with unknown request id (%s)." ) , data . request_id ) ;
2020-07-20 17:08:13 +00:00
return ;
}
if ( ! data . accepted ) {
2021-01-10 16:36:57 +00:00
logDebug ( LogCategory . IPC , tr ( "Client %s rejected the connect offer (%s)." ) , sender , request . id ) ;
2020-07-20 17:08:13 +00:00
return ;
}
if ( request . remote_handler ) {
2021-01-10 16:36:57 +00:00
logDebug ( LogCategory . IPC , tr ( "Client %s accepted the connect offer (%s), but offer has already been accepted." ) , sender , request . id ) ;
2020-07-20 17:08:13 +00:00
return ;
}
2021-01-10 16:36:57 +00:00
logDebug ( LogCategory . IPC , tr ( "Client %s accepted the connect offer (%s). Request local acceptance." ) , sender , request . id ) ;
2020-07-20 17:08:13 +00:00
request . remote_handler = sender ;
clearTimeout ( request . timeout ) ;
request . callback_avail ( ) . then ( flag = > {
if ( ! flag ) {
request . callback_failed ( "local avail rejected" ) ;
return ;
}
2021-01-10 16:36:57 +00:00
logDebug ( LogCategory . IPC , tr ( "Executing connect with client %s" ) , request . remote_handler ) ;
2020-07-20 17:08:13 +00:00
this . ipc_channel . sendMessage ( "execute" , {
2020-07-23 22:43:52 +00:00
request_id : request.id
2020-07-20 17:08:13 +00:00
} as ConnectExecute , request . remote_handler ) ;
request . timeout = setTimeout ( ( ) = > {
request . callback_failed ( "connect execute timeout" ) ;
} , 1000 ) as any ;
} ) . catch ( error = > {
2021-01-10 16:36:57 +00:00
logError ( LogCategory . IPC , tr ( "Local avail callback caused an error: %o" ) , error ) ;
2020-07-20 17:08:13 +00:00
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 ) {
2021-01-10 16:36:57 +00:00
logWarn ( LogCategory . IPC , tr ( "Received connect executed with unknown request id (%s)." ) , data . request_id ) ;
2020-07-20 17:08:13 +00:00
return ;
}
if ( request . remote_handler != sender ) {
2021-01-10 16:36:57 +00:00
logWarn ( LogCategory . IPC , tr ( "Received connect executed for request %s, but from wrong client: %s (expected %s)" ) , data . request_id , sender , request . remote_handler ) ;
2020-07-20 17:08:13 +00:00
return ;
}
2021-01-10 16:36:57 +00:00
logDebug ( LogCategory . IPC , tr ( "Received connect executed response from client %s for request %s. Succeeded: %o (%s)" ) , sender , data . request_id , data . succeeded , data . message ) ;
2020-07-20 17:08:13 +00:00
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 ) {
2021-01-10 16:36:57 +00:00
logWarn ( LogCategory . IPC , tr ( "Received connect execute with unknown request id (%s)." ) , data . request_id ) ;
2020-07-20 17:08:13 +00:00
return ;
}
if ( request . remote_handler != sender ) {
2021-01-10 16:36:57 +00:00
logWarn ( LogCategory . IPC , tr ( "Received connect execute for request %s, but from wrong client: %s (expected %s)" ) , data . request_id , sender , request . remote_handler ) ;
2020-07-20 17:08:13 +00:00
return ;
}
clearTimeout ( request . timeout ) ;
this . _pending_connect_offers . remove ( request ) ;
2021-01-10 16:36:57 +00:00
logDebug ( LogCategory . IPC , tr ( "Executing connect for %s" ) , data . request_id ) ;
2020-07-20 17:08:13 +00:00
const cr = this . callback_execute ( request . data ) ;
const response = {
2020-07-23 22:43:52 +00:00
request_id : data.request_id ,
2020-07-20 17:08:13 +00:00
succeeded : typeof ( cr ) !== "string" && cr ,
message : typeof ( cr ) === "string" ? cr : "" ,
} as ConnectExecuted ;
this . ipc_channel . sendMessage ( "executed" , response , request . remote_handler ) ;
}
}
}
post_connect_request ( data : ConnectRequestData , callback_avail : ( ) = > Promise < boolean > ) : Promise < void > {
return new Promise < void > ( ( resolve , reject ) = > {
const pd = {
data : data ,
id : guid ( ) ,
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 . sendMessage ( "offer" , {
2020-07-23 22:43:52 +00:00
request_id : pd.id ,
2020-07-20 17:08:13 +00:00
data : pd.data
} as ConnectOffer ) ;
pd . timeout = setTimeout ( ( ) = > {
pd . callback_failed ( "received no response to offer" ) ;
} , 50 ) as any ;
} )
}
}