fix attaching listeners before connecting

This commit is contained in:
2020-02-25 15:20:45 +01:00
parent 96976974c7
commit b97d88b9e1
6 changed files with 76 additions and 15 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "rpclibrary", "name": "rpclibrary",
"version": "1.7.1", "version": "1.8.0",
"description": "rpclibrary is a websocket on steroids!", "description": "rpclibrary is a websocket on steroids!",
"main": "./js/Index.js", "main": "./js/Index.js",
"repository": { "repository": {
+1 -4
View File
@@ -53,13 +53,10 @@ export class RPCServer<
if(conf.connectionHandler) conf.connectionHandler(socket) if(conf.connectionHandler) conf.connectionHandler(socket)
} }
exporters.forEach(U.fixNames) exporters.forEach(U.fixNames) //TSC for some reason doesn't preserve name properties of methods
let badRPC = exporters.flatMap(ex => ex.exportRPCs()).find(rpc => !rpc.name) let badRPC = exporters.flatMap(ex => ex.exportRPCs()).find(rpc => !rpc.name)
if(badRPC){ if(badRPC){
console.log(badRPC);
throw new Error(` throw new Error(`
RPC did not provide a name. RPC did not provide a name.
\nUse 'funtion name(..){ .. }' syntax instead. \nUse 'funtion name(..){ .. }' syntax instead.
+35 -7
View File
@@ -18,6 +18,9 @@ export class RPCSocket implements I.Socket{
} }
private socket: I.Socket private socket: I.Socket
private closeHandlers: T.CloseHandler[] = []
private errorHandlers: T.ErrorHandler[] = []
private hooks : {[name in string]: T.AnyFunction} = {}
/** /**
* *
@@ -35,7 +38,11 @@ export class RPCSocket implements I.Socket{
* @param handler The handler to attach * @param handler The handler to attach
*/ */
public hook(name: string, handler: (...args:any[]) => any | Promise<any>){ public hook(name: string, handler: (...args:any[]) => any | Promise<any>){
return this.socket.hook(name, handler) if(!this.socket){
this.hooks[name] = handler
}else{
this.socket.hook(name, handler)
}
} }
/** /**
@@ -43,7 +50,11 @@ export class RPCSocket implements I.Socket{
* @param name The function name * @param name The function name
*/ */
public unhook(name: string){ public unhook(name: string){
return this.socket.unhook(name) if(!this.socket){
delete this.hooks[name]
}else{
this.socket.unhook(name)
}
} }
/** /**
@@ -51,22 +62,32 @@ export class RPCSocket implements I.Socket{
* @param type 'error' or 'close' * @param type 'error' or 'close'
* @param f The listener to attach * @param f The listener to attach
*/ */
public on(type: "error" | "close", f: (e?: any) => void){ public on<T extends "error" | "close">(type: T, f: T.HandlerType[T]){
return this.socket.on(type, f) if(!this.socket){
switch(type){
case "error": this.errorHandlers.push(<T.HandlerType['error']> f); break;
case "close": this.closeHandlers.push(<T.HandlerType['close']> f); break;
default: throw new Error('socket.on only supports ´error´ and ´close´ as first parameter. Got: ´'+type+'´')
}
}else{
this.socket.on(type, f)
}
} }
/** /**
* Destroys the socket * Destroys the socket
*/ */
public destroy(){ public destroy(){
return this.socket.destroy() if(!this.socket) return;
this.socket.destroy()
} }
/** /**
* Closes the socket. It may attempt to reconnect. * Closes the socket. It may attempt to reconnect.
*/ */
public close(){ public close(){
return this.socket.close() if(!this.socket) return;
this.socket.close()
} }
/** /**
@@ -75,6 +96,7 @@ export class RPCSocket implements I.Socket{
* @param args other arguments * @param args other arguments
*/ */
public async call (rpcname: string, ...args: any[]) : Promise<any>{ public async call (rpcname: string, ...args: any[]) : Promise<any>{
if(!this.socket) throw new Error("The socket is not connected! Use socket.connect() first")
return await this.socket.call.apply(this.socket, [rpcname, ...args]) return await this.socket.call.apply(this.socket, [rpcname, ...args])
} }
@@ -84,6 +106,7 @@ export class RPCSocket implements I.Socket{
* @param args other arguments * @param args other arguments
*/ */
public async fire(rpcname: string, ...args: any[]) : Promise<void>{ public async fire(rpcname: string, ...args: any[]) : Promise<void>{
if(!this.socket) throw new Error("The socket is not connected! Use socket.connect() first")
await this.socket.fire.apply(this.socket, [rpcname, ...args]) await this.socket.fire.apply(this.socket, [rpcname, ...args])
} }
@@ -92,6 +115,11 @@ export class RPCSocket implements I.Socket{
*/ */
public async connect<T extends T.RPCInterface= T.RPCInterface>( sesame?: string ) : Promise<RPCSocket & T>{ public async connect<T extends T.RPCInterface= T.RPCInterface>( sesame?: string ) : Promise<RPCSocket & T>{
this.socket = await bsock.connect(this.port, this.server, this.conf.tls?this.conf.tls:false) this.socket = await bsock.connect(this.port, this.server, this.conf.tls?this.conf.tls:false)
this.errorHandlers.forEach(h => this.socket.on('error', h))
this.closeHandlers.forEach(h => this.socket.on('close', h))
Object.entries(this.hooks).forEach((kv: [string, T.AnyFunction]) => {
this.socket.hook(kv[0], kv[1])
})
const info:T.ExtendedRpcInfo[] = await this.info() const info:T.ExtendedRpcInfo[] = await this.info()
info.forEach(i => { info.forEach(i => {
@@ -117,6 +145,7 @@ export class RPCSocket implements I.Socket{
* Get a list of available RPCs from the server * Get a list of available RPCs from the server
*/ */
public async info(){ public async info(){
if(!this.socket) throw new Error("The socket is not connected! Use socket.connect() first")
return await this.socket.call('info') return await this.socket.call('info')
} }
@@ -154,7 +183,6 @@ export class RPCSocket implements I.Socket{
const r = await this.socket.call("${fnName}", ${sesame} ${argParams}) const r = await this.socket.call("${fnName}", ${sesame} ${argParams})
if(r && r.result === 'Success'){ if(r && r.result === 'Success'){
this.socket.hook(r.uuid, callback) this.socket.hook(r.uuid, callback)
this.socket.on('error', e => this.socket.unhook(r.uuid))
} }
return r return r
}`) }`)
+2 -2
View File
@@ -18,8 +18,8 @@ export interface RPCExporter<
*/ */
export interface Socket extends Destroyable { export interface Socket extends Destroyable {
port: number port: number
hook: (rpcname: string, handler: T.AnyFunction) => I.Socket hook: (rpcname: string, handler: T.AnyFunction) => void
unhook: (rpcname:string) => I.Socket unhook: (rpcname:string) => void
call: (rpcname:string, ...args: any[]) => Promise<any> call: (rpcname:string, ...args: any[]) => Promise<any>
fire: (rpcname:string, ...args: any[]) => Promise<any> fire: (rpcname:string, ...args: any[]) => Promise<any>
on: T.OnFunction on: T.OnFunction
+6 -1
View File
@@ -12,6 +12,11 @@ export type ExceptionHandling = 'local' | 'remote'
export type SesameConf = { export type SesameConf = {
sesame?: string | SesameFunction sesame?: string | SesameFunction
} }
export type HandlerType = {
'error' : ErrorHandler
'close' : CloseHandler
}
export type ServerConf = { export type ServerConf = {
connectionHandler?: ConnectionHandler connectionHandler?: ConnectionHandler
errorHandler?: ErrorHandler errorHandler?: ErrorHandler
@@ -79,5 +84,5 @@ export type CallInfo = BaseInfo & {
export type RpcInfo = HookInfo | CallInfo export type RpcInfo = HookInfo | CallInfo
export type ExtendedRpcInfo = RpcInfo & { uniqueName: string } export type ExtendedRpcInfo = RpcInfo & { uniqueName: string }
export type OnFunction = (type: 'error' | 'close', f: (e?:any)=>void) => I.Socket export type OnFunction = <T extends "error" | "close">(type: T, f: HandlerType[T]) => void
export type HookCloseFunction<T = {}> = (res:SubscriptionResponse<T>, rpc:HookRPC<any, any, T>) => any export type HookCloseFunction<T = {}> = (res:SubscriptionResponse<T>, rpc:HookRPC<any, any, T>) => any
+31
View File
@@ -571,3 +571,34 @@ describe("Class binding", ()=>{
}) })
}) })
}) })
describe("attaching handlers before connecting", ()=>{
it("fires error if server is unreachable", (done)=>{
const sock = new RPCSocket(21004, 'localhost')
let errorHandleCount = 0
sock.on('error', (socket) => {
//attached listener fires first
if(errorHandleCount != 0){
console.log("Error handler didn't fire first");
}else{
errorHandleCount++
}
})
sock.connect().then(_ => {
console.log("Unexpected successful connect")
}).catch(e => {
//catch clause fires second
if(errorHandleCount != 1){
console.log("catch clause didn't fire second");
}else{
sock.destroy()
done()
}
})
})
})