This commit is contained in:
2019-09-22 10:52:33 +02:00
parent ab6121c8c8
commit 2bab836faf
12 changed files with 244 additions and 140 deletions
+10 -1
View File
@@ -7,6 +7,9 @@ import * as T from './Types';
import * as U from './Utils';
import * as I from './Interfaces';
/**
* A Websocket-server-on-steroids with built-in RPC capabilities
*/
export class RPCServer<
SubResType = {}
> implements I.Destroyable{
@@ -18,9 +21,15 @@ export class RPCServer<
private errorHandler: T.ErrorHandler
private connectionHandler: T.ConnectionHandler
/**
* @throws On RPC with no name
* @param port The port to listen on
* @param exporters A list of {@link RPCExporter} to publish
* @param conf A {@link SocketConf} object with optional settings
*/
constructor(
private port:number,
private exporters: I.Exporter<SubResType>[] = [],
private exporters: I.RPCExporter<SubResType>[] = [],
conf: T.SocketConf = {}
){
+63 -5
View File
@@ -5,46 +5,91 @@ import bsock = require('bsock');
import * as T from './Types';
import * as I from './Interfaces';
//fix args with defaults like "force = true" -> "force"
function stripAfterEquals(str:string){
/**
* Utility function to strip parameters like "a = 3" of their defaults
* @param str The parameter to modify
*/
function stripAfterEquals(str:string):string{
return str.split("=")[0]
}
/**
* A websocket-on-steroids with built-in RPC capabilities
*/
export class RPCSocket implements I.Socket{
private socket: I.Socket
/**
*
* @param port Port to connect to
* @param server Server address
* @param tls @default false use TLS
*/
constructor(public port:number, private server: string, private tls: boolean = false){
Object.defineProperty(this, 'socket', {value: undefined, writable: true})
}
/**
* Hooks a handler to a function name. Use {@link call} to trigger it.
* @param name The function name to listen on
* @param handler The handler to attach
*/
public hook(name: T.Name, handler: (...args:any[]) => any | Promise<any>){
return this.socket.hook(name, handler)
}
/**
* Removes a {@link hook} listener by name.
* @param name The function name
*/
public unhook(name: T.Name){
return this.socket.unhook(name)
}
/**
* Attach a listener to error or close events
* @param type 'error' or 'close'
* @param f The listener to attach
*/
public on(type: "error" | "close", f: (e?: any) => void){
return this.socket.on(type, f)
}
/**
* Destroys the socket
*/
public destroy(){
return this.socket.destroy()
}
/**
* Closes the socket. It may attempt to reconnect.
*/
public close(){
return this.socket.close()
}
/**
* Trigger a hooked handler on the server
* @param rpcname The function to call
* @param args other arguments
*/
public async call (rpcname: T.Name, ...args: T.Any[]) : Promise<T.Any>{
return await this.socket.call.apply(this.socket, [rpcname, ...args])
}
public async fire(rpcname: T.Name, ...args: T.Any[]) : Promise<T.Any>{
return await this.socket.fire.apply(this.socket, [rpcname, ...args])
/**
* An alternative to call that does not wait for confirmation and doesn't return a value.
* @param rpcname The function to call
* @param args other arguments
*/
public async fire(rpcname: T.Name, ...args: T.Any[]) : Promise<void>{
await this.socket.fire.apply(this.socket, [rpcname, ...args])
}
/**
* Connects to the server and attaches available RPCs to this object
*/
public async connect(){
this.socket = await bsock.connect(this.port, this.server, this.tls)
@@ -66,16 +111,29 @@ export class RPCSocket implements I.Socket{
})
}
/**
* Get a list of available RPCs from the server
*/
public async info(){
return await this.socket.call('info')
}
/**
* Utility {@link AsyncFunction} generator
* @param fnName The function name
* @param fnArgs A string-list of parameters
*/
private callGenerator(fnName: T.Name, fnArgs:T.Arg[]): T.AsyncFunction{
const headerArgs = fnArgs.join(",")
const argParams = fnArgs.map(stripAfterEquals).join(",")
return eval( '( () => async ('+headerArgs+') => { return await this.socket.call("'+fnName+'", '+argParams+')} )()' )
}
/**
* Utility {@link HookFunction} generator
* @param fnName The function name
* @param fnArgs A string-list of parameters
*/
private hookGenerator(fnName: T.Name, fnArgs:T.Arg[]): T.HookFunction{
const headerArgs = fnArgs.join(",")
const argParams = fnArgs.map(stripAfterEquals).join(",")
+7 -1
View File
@@ -1,11 +1,17 @@
import * as T from "./Types";
import * as I from "./Interfaces"
export interface Exporter<T = {}>{
/**
* Interface for all classes that may export RPCs
*/
export interface RPCExporter<T = {}>{
name: T.Name
exportRPCs() : T.RPC<T>[]
}
/**
* Generic socket interface that can apply to bsock as well as RPCSocket
*/
export interface Socket extends Destroyable {
port: number
hook: (rpcname: T.Name, handler: (...args:any[]) => any | Promise<any>) => I.Socket
+16 -4
View File
@@ -8,7 +8,7 @@ import { SubscriptionResponse } from "./Types";
* Translate an RPC to RPCInfo for serialization.
* @param rpc The RPC to transform
* @param owner The owning RPC group's name
* @throws {Errror} Error on RPC without name property
* @throws Error on RPC without name property
*/
export const rpcToRpcinfo = <SubResT = {}>(rpc : T.RPC<SubResT>, owner: T.Owner):T.RpcInfo => {
switch (typeof rpc){
@@ -51,12 +51,12 @@ RPC did not provide a name.
}
/**
* Utility function to apply the RPCs of an {@link Exporter}.
* Utility function to apply the RPCs of an {@link RPCExporter}.
* @param socket The websocket (implementation: bsock) to hook on
* @param exporter The exporter
* @param makeUnique @default true Attach a suffix to RPC names
*/
export function rpcHooker<SubResT = {}>(socket: I.Socket, exporter:I.Exporter<SubResT>, makeUnique = true):T.ExtendedRpcInfo[]{
export function rpcHooker<SubResT = {}>(socket: I.Socket, exporter:I.RPCExporter<SubResT>, makeUnique = true):T.ExtendedRpcInfo[]{
const owner = exporter.name
const RPCs = [...exporter.exportRPCs()]
const suffix = makeUnique?"-"+uuidv4().substr(0,4):""
@@ -77,7 +77,11 @@ export function rpcHooker<SubResT = {}>(socket: I.Socket, exporter:I.Exporter<Su
return ret
})
}
//
/**
* Utility function to generate {@link HookFunction} from a RPC
* @param rpc The RPC to transform
* @returns A {@link HookFunction}
*/
const hookGenerator = (rpc:T.HookRPC<any>): T.HookInfo['generator'] => {
const argsArr = extractArgs(rpc.hook)
argsArr.pop()
@@ -99,11 +103,19 @@ const hookGenerator = (rpc:T.HookRPC<any>): T.HookInfo['generator'] => {
}`)
}
/**
* Extract a string list of parameters from a function
* @param f The source function
*/
const extractArgs = (f:Function):T.Arg[] => {
let fn
return (fn = String(f)).substr(0, fn.indexOf(")")).substr(fn.indexOf("(")+1).split(",")
}
/**
* Simple utility function to create basic {@link SubscriptionResponse}
* @param uuid optional uuid to use, otherwise defaults to uuid/v4
*/
export function makeSubResponse(uuid?:string):SubscriptionResponse{
return {
result: "Success",