test123
This commit is contained in:
+3
-3
@@ -11,9 +11,9 @@ import * as I from './Interfaces';
|
||||
* A Websocket-server-on-steroids with built-in RPC capabilities
|
||||
*/
|
||||
export class RPCServer<
|
||||
SubResType = {}
|
||||
SubResType = {},
|
||||
InterfaceT extends T.RPCInterface = T.RPCInterface,
|
||||
> implements I.Destroyable{
|
||||
|
||||
private ws = http.createServer()
|
||||
private io = bsock.createServer()
|
||||
private visibility:T.Visibility
|
||||
@@ -29,7 +29,7 @@ export class RPCServer<
|
||||
*/
|
||||
constructor(
|
||||
private port:number,
|
||||
private exporters: I.RPCExporter<SubResType>[] = [],
|
||||
private exporters: I.RPCExporter<T.RPCInterface<InterfaceT>, keyof T.RPCInterface<InterfaceT>, SubResType>[] = [],
|
||||
conf: T.SocketConf = {}
|
||||
){
|
||||
|
||||
|
||||
+13
-7
@@ -17,6 +17,11 @@ function stripAfterEquals(str:string):string{
|
||||
* A websocket-on-steroids with built-in RPC capabilities
|
||||
*/
|
||||
export class RPCSocket implements I.Socket{
|
||||
static async makeSocket<T extends T.RPCInterface= T.RPCInterface>(port:number, server: string, tls: boolean = false): Promise<RPCSocket & T.RPCInterface<T>> {
|
||||
const socket = <RPCSocket & T> new RPCSocket(port, server, tls)
|
||||
return await socket.connect<T>()
|
||||
}
|
||||
|
||||
private socket: I.Socket
|
||||
|
||||
/**
|
||||
@@ -34,7 +39,7 @@ export class RPCSocket implements I.Socket{
|
||||
* @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>){
|
||||
public hook(name: string, handler: (...args:any[]) => any | Promise<any>){
|
||||
return this.socket.hook(name, handler)
|
||||
}
|
||||
|
||||
@@ -42,7 +47,7 @@ export class RPCSocket implements I.Socket{
|
||||
* Removes a {@link hook} listener by name.
|
||||
* @param name The function name
|
||||
*/
|
||||
public unhook(name: T.Name){
|
||||
public unhook(name: string){
|
||||
return this.socket.unhook(name)
|
||||
}
|
||||
|
||||
@@ -74,7 +79,7 @@ export class RPCSocket implements I.Socket{
|
||||
* @param rpcname The function to call
|
||||
* @param args other arguments
|
||||
*/
|
||||
public async call (rpcname: T.Name, ...args: T.Any[]) : Promise<T.Any>{
|
||||
public async call (rpcname: string, ...args: any[]) : Promise<any>{
|
||||
return await this.socket.call.apply(this.socket, [rpcname, ...args])
|
||||
}
|
||||
|
||||
@@ -83,14 +88,14 @@ export class RPCSocket implements I.Socket{
|
||||
* @param rpcname The function to call
|
||||
* @param args other arguments
|
||||
*/
|
||||
public async fire(rpcname: T.Name, ...args: T.Any[]) : Promise<void>{
|
||||
public async fire(rpcname: string, ...args: 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(){
|
||||
public async connect<T extends T.RPCInterface= T.RPCInterface>() : Promise<RPCSocket & T.RPCInterface<T>>{
|
||||
this.socket = await bsock.connect(this.port, this.server, this.tls)
|
||||
|
||||
const info:T.ExtendedRpcInfo[] = await this.info()
|
||||
@@ -109,6 +114,7 @@ export class RPCSocket implements I.Socket{
|
||||
this[i.owner][i.name] = f
|
||||
this[i.owner][i.name].bind(this)
|
||||
})
|
||||
return <RPCSocket & T.RPCInterface<T>> <any> this
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,7 +129,7 @@ export class RPCSocket implements I.Socket{
|
||||
* @param fnName The function name
|
||||
* @param fnArgs A string-list of parameters
|
||||
*/
|
||||
private callGenerator(fnName: T.Name, fnArgs:T.Arg[]): T.AsyncFunction{
|
||||
private callGenerator(fnName: string, fnArgs:string[]): T.AsyncFunction{
|
||||
const headerArgs = fnArgs.join(",")
|
||||
const argParams = fnArgs.map(stripAfterEquals).join(",")
|
||||
return eval( '( () => async ('+headerArgs+') => { return await this.socket.call("'+fnName+'", '+argParams+')} )()' )
|
||||
@@ -134,7 +140,7 @@ export class RPCSocket implements I.Socket{
|
||||
* @param fnName The function name
|
||||
* @param fnArgs A string-list of parameters
|
||||
*/
|
||||
private hookGenerator(fnName: T.Name, fnArgs:T.Arg[]): T.HookFunction{
|
||||
private hookGenerator(fnName: string, fnArgs:string[]): T.HookFunction{
|
||||
const headerArgs = fnArgs.join(",")
|
||||
const argParams = fnArgs.map(stripAfterEquals).join(",")
|
||||
return eval( `( () => async (`+headerArgs+(headerArgs.length!==0?",":"")+` callback) => {
|
||||
|
||||
+11
-7
@@ -4,9 +4,13 @@ import * as I from "./Interfaces"
|
||||
/**
|
||||
* Interface for all classes that may export RPCs
|
||||
*/
|
||||
export interface RPCExporter<T = {}>{
|
||||
name: T.Name
|
||||
exportRPCs() : T.RPC<T>[]
|
||||
export interface RPCExporter<
|
||||
Ifc extends T.RPCInterface,
|
||||
K extends keyof Ifc,
|
||||
SubresT = {}
|
||||
>{
|
||||
name: K
|
||||
exportRPCs() : T.RPC<Ifc[K], keyof Ifc[K], SubresT>[]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -14,10 +18,10 @@ export interface RPCExporter<T = {}>{
|
||||
*/
|
||||
export interface Socket extends Destroyable {
|
||||
port: number
|
||||
hook: (rpcname: T.Name, handler: (...args:any[]) => any | Promise<any>) => I.Socket
|
||||
unhook: (rpcname:T.Name) => I.Socket
|
||||
call: (rpcname:T.Name, ...args: T.Any[]) => Promise<T.Any>
|
||||
fire: (rpcname:T.Name, ...args: T.Any[]) => Promise<T.Any>
|
||||
hook: (rpcname: string, handler: T.AnyFunction) => I.Socket
|
||||
unhook: (rpcname:string) => I.Socket
|
||||
call: (rpcname:string, ...args: any[]) => Promise<any>
|
||||
fire: (rpcname:string, ...args: any[]) => Promise<any>
|
||||
on: T.OnFunction
|
||||
close() : void
|
||||
}
|
||||
|
||||
+24
-21
@@ -1,10 +1,11 @@
|
||||
import * as I from "./Interfaces";
|
||||
|
||||
export type AnyFunction = (...any:any)=>any
|
||||
export type AsyncFunction<Fn extends AnyFunction = AnyFunction> = ReturnType<Fn> extends Promise<any> ? Fn : (...any:Parameters<Fn>) => Promise<ReturnType<Fn>>
|
||||
export type RPCGroup = { [name in string] : AsyncFunction }
|
||||
export type RPCInterface<Impl extends RPCInterface = {}> = { [groupName in string]: RPCGroup } & Impl
|
||||
|
||||
export type Visibility = "127.0.0.1" | "0.0.0.0"
|
||||
export type Any = any
|
||||
export type Arg = string
|
||||
export type Name = Arg
|
||||
export type Owner = Name
|
||||
export type ConnectionHandler = (socket:I.Socket) => void
|
||||
export type ErrorHandler = (socket:I.Socket, error:any) => void
|
||||
export type CloseHandler = (socket:I.Socket) => void
|
||||
@@ -26,29 +27,32 @@ export type SubscriptionResponse<T = {}> = Respose<T> & { result: "Success"; uui
|
||||
|
||||
export type RPCType = 'Hook' | 'Unhook' | 'Call'
|
||||
|
||||
export type HookRPC<T = {}> = {
|
||||
name: Name
|
||||
hook: HookFunction<T>
|
||||
export type HookT<G extends RPCGroup, K extends keyof G, SubresT> = AsyncFunction<HookFunction<G[K], SubresT>>
|
||||
export type CallT<G extends RPCGroup, K extends keyof G> = AsyncFunction<G[K]>
|
||||
|
||||
export type HookRPC<G extends RPCGroup, K extends keyof G, SubresT = {}> = {
|
||||
name: K
|
||||
hook: HookT<G, K, SubresT>
|
||||
onCallback?: CallbackFunction,
|
||||
onClose?: HookCloseFunction<T>
|
||||
onClose?: HookCloseFunction<SubresT>
|
||||
}
|
||||
|
||||
export type CallRPC = {
|
||||
name: Name
|
||||
call: AsyncFunction
|
||||
} | Function
|
||||
export type CallRPC<G extends RPCGroup, K extends keyof G> = {
|
||||
name: K
|
||||
call: CallT<G,K>
|
||||
}
|
||||
|
||||
export type RPC<T = {}> = CallRPC | HookRPC<T>
|
||||
export type RPC<G extends RPCGroup, Kg extends keyof G, SubresT> = CallRPC<G, Kg> | HookRPC<G, Kg, SubresT>
|
||||
|
||||
export type BaseInfo = {
|
||||
name: Name,
|
||||
owner: Name,
|
||||
argNames: Name[],
|
||||
name: string,
|
||||
owner: string,
|
||||
argNames: string[],
|
||||
}
|
||||
|
||||
export type HookInfo<T = {}> = BaseInfo & {
|
||||
type: 'Hook',
|
||||
generator: (socket?:I.Socket) => HookFunction<T>
|
||||
generator: (socket?:I.Socket) => HookFunction<AnyFunction, T>
|
||||
}
|
||||
|
||||
export type CallInfo = BaseInfo & {
|
||||
@@ -60,7 +64,6 @@ export type RpcInfo = HookInfo | CallInfo
|
||||
export type ExtendedRpcInfo = RpcInfo & { uniqueName: string }
|
||||
|
||||
export type OnFunction = (type: 'error' | 'close', f: (e?:any)=>void) => I.Socket
|
||||
export type HookCloseFunction<T = {}> = (res:SubscriptionResponse<T>, rpc:HookRPC<T>) => any
|
||||
export type HookFunction<T = {}> = (...args:any) => Promise<SubscriptionResponse<T> | ErrorResponse>
|
||||
export type AsyncFunction = (...args:any) => Promise<any>
|
||||
export type CallbackFunction = (...args:any) => void
|
||||
export type HookCloseFunction<T = {}> = (res:SubscriptionResponse<T>, rpc:HookRPC<any, any, T>) => any
|
||||
export type HookFunction<F extends AnyFunction = AnyFunction, SubResT = {}> = AsyncFunction<(...args:Parameters<F>) => SubscriptionResponse<SubResT> | ErrorResponse>
|
||||
export type CallbackFunction = (callback:AnyFunction, ...args:any) => any
|
||||
+7
-7
@@ -10,7 +10,7 @@ import { SubscriptionResponse } from "./Types";
|
||||
* @param owner The owning RPC group's name
|
||||
* @throws Error on RPC without name property
|
||||
*/
|
||||
export const rpcToRpcinfo = <SubResT = {}>(rpc : T.RPC<SubResT>, owner: T.Owner):T.RpcInfo => {
|
||||
export const rpcToRpcinfo = <SubResT = {}>(rpc : T.RPC<any, any, SubResT>, owner: string):T.RpcInfo => {
|
||||
switch (typeof rpc){
|
||||
case "object":
|
||||
if(rpc['call']){
|
||||
@@ -22,7 +22,7 @@ export const rpcToRpcinfo = <SubResT = {}>(rpc : T.RPC<SubResT>, owner: T.Owner)
|
||||
call: rpc['call'],
|
||||
}
|
||||
}else{
|
||||
const generator = hookGenerator(<T.HookRPC<any>>rpc)
|
||||
const generator = hookGenerator(<T.HookRPC<any, any, any>>rpc)
|
||||
return {
|
||||
owner: owner,
|
||||
argNames: extractArgs(generator(undefined)),
|
||||
@@ -44,7 +44,7 @@ RPC did not provide a name.
|
||||
argNames: extractArgs(rpc),
|
||||
type: "Call",
|
||||
name: rpc.name,
|
||||
call: async(...args) => rpc.apply({}, args),
|
||||
call: async(...args) => (<Function>rpc).apply({}, args),
|
||||
}
|
||||
}
|
||||
throw new Error("Bad socketIORPC type "+ typeof rpc)
|
||||
@@ -56,7 +56,7 @@ RPC did not provide a name.
|
||||
* @param exporter The exporter
|
||||
* @param makeUnique @default true Attach a suffix to RPC names
|
||||
*/
|
||||
export function rpcHooker<SubResT = {}>(socket: I.Socket, exporter:I.RPCExporter<SubResT>, makeUnique = true):T.ExtendedRpcInfo[]{
|
||||
export function rpcHooker<SubResT = {}>(socket: I.Socket, exporter:I.RPCExporter<any, any, SubResT>, makeUnique = true):T.ExtendedRpcInfo[]{
|
||||
const owner = exporter.name
|
||||
const RPCs = [...exporter.exportRPCs()]
|
||||
const suffix = makeUnique?"-"+uuidv4().substr(0,4):""
|
||||
@@ -82,7 +82,7 @@ export function rpcHooker<SubResT = {}>(socket: I.Socket, exporter:I.RPCExporter
|
||||
* @param rpc The RPC to transform
|
||||
* @returns A {@link HookFunction}
|
||||
*/
|
||||
const hookGenerator = (rpc:T.HookRPC<any>): T.HookInfo['generator'] => {
|
||||
const hookGenerator = (rpc:T.HookRPC<any, any, any>): T.HookInfo['generator'] => {
|
||||
const argsArr = extractArgs(rpc.hook)
|
||||
argsArr.pop()
|
||||
const args = argsArr.join(',')
|
||||
@@ -107,8 +107,8 @@ 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
|
||||
const extractArgs = (f:Function):string[] => {
|
||||
let fn:string
|
||||
return (fn = String(f)).substr(0, fn.indexOf(")")).substr(fn.indexOf("(")+1).split(",")
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -54,7 +54,7 @@ function makeServer(){
|
||||
|
||||
|
||||
describe('RPCServer', () => {
|
||||
let server: RPCServer<{ topic: string}>
|
||||
let server: RPCServer<{ topic: string }, any>
|
||||
|
||||
before((done) => {
|
||||
server = makeServer()
|
||||
@@ -160,8 +160,8 @@ describe('It should do unhook', () => {
|
||||
name: "test",
|
||||
exportRPCs: () => [{
|
||||
name: 'subscribe',
|
||||
hook: async(callback) => {
|
||||
cb = callback
|
||||
hook: async(callback):Promise<SubscriptionResponse<{topic:string}>> => {
|
||||
cb = <Function> callback
|
||||
return {
|
||||
result: "Success",
|
||||
uuid: uuidv4(),
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { RPCServer } from "../src/Backend";
|
||||
import { SubscriptionResponse, CallRPC, HookRPC, RPCInterface } from "../src/Types";
|
||||
import { RPCSocket } from "../src/Frontend";
|
||||
|
||||
type MyInterface = RPCInterface<{
|
||||
Group1: {
|
||||
triggerCallbacks: (...args:any[]) => Promise<void>,
|
||||
subscribe: (param:string, callback:Function) => Promise<SubscriptionResponse<{a: string}>>,
|
||||
unsubscribe: (uuid:string) => Promise<void>
|
||||
},
|
||||
Group2: {
|
||||
echo: (x:string) => Promise<string>
|
||||
}
|
||||
}>
|
||||
|
||||
const callbacks:Map<string, Function> = new Map()
|
||||
|
||||
new RPCServer<{a:string}, MyInterface>(20000,
|
||||
[{
|
||||
name: "Group1",
|
||||
exportRPCs: () => [
|
||||
<HookRPC<MyInterface['Group1'], 'subscribe', {a:string}>>{
|
||||
name: 'subscribe',
|
||||
hook: async (param, callback) => { const _uuid = ""+Math.random(); console.log(param); callbacks.set(_uuid, callback); return { result: 'Success', a: '3', uuid: _uuid} }
|
||||
},
|
||||
<CallRPC<MyInterface['Group1'], 'unsubscribe'>>{
|
||||
name: 'unsubscribe',
|
||||
call: async (uuid) => { callbacks.delete(uuid) }
|
||||
},
|
||||
<CallRPC<MyInterface['Group1'], 'triggerCallbacks'>>{
|
||||
name: 'triggerCallbacks',
|
||||
call: async (...args) => { callbacks.forEach(cb => cb.apply({}, args)) }
|
||||
}]
|
||||
},{
|
||||
name: 'Group2',
|
||||
exportRPCs: () => [
|
||||
<CallRPC<MyInterface['Group2'], 'echo'>>{
|
||||
name: 'echo',
|
||||
call: async (x) => x
|
||||
}]
|
||||
}]
|
||||
)
|
||||
|
||||
new RPCServer<{a:string}, MyInterface>(20001,
|
||||
[{
|
||||
name: "Group1",
|
||||
exportRPCs: () => []
|
||||
},{
|
||||
name: 'Group2',
|
||||
exportRPCs: () => [{
|
||||
name: 'echo',
|
||||
call: async (x) => x+" lol"
|
||||
}]
|
||||
}]
|
||||
)
|
||||
|
||||
RPCSocket.makeSocket<MyInterface>(20000, 'localhost').then((async (client) => {
|
||||
console.log(client)
|
||||
const res = await client.Group1.subscribe('test', async (...args:any) => {
|
||||
console.log.apply(console, args)
|
||||
|
||||
/* close the callbacks once you're done */
|
||||
await client.Group1.unsubscribe(res.uuid)
|
||||
client.unhook(res.uuid)
|
||||
})
|
||||
|
||||
await client.Group1.triggerCallbacks("Hello", "World", "Callbacks")
|
||||
}))
|
||||
|
||||
RPCSocket.makeSocket<MyInterface>(20001, 'localhost').then((async (client) => {
|
||||
console.log(client)
|
||||
const r = await client.Group2.echo("hee")
|
||||
console.log(r)
|
||||
}))
|
||||
Reference in New Issue
Block a user