clean up type system, remove subres

This commit is contained in:
2020-03-17 23:35:47 +01:00
parent bb94b1c405
commit 7d580c4c23
9 changed files with 459 additions and 462 deletions
+55 -35
View File
@@ -1,6 +1,6 @@
{
"name": "rpclibrary",
"version": "1.6.1",
"version": "1.9.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -467,9 +467,9 @@
"dev": true
},
"acorn": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz",
"integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==",
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
"integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
"dev": true
},
"aggregate-error": {
@@ -753,9 +753,9 @@
"dev": true
},
"bluebird": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz",
"integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==",
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
"dev": true
},
"bn.js": {
@@ -952,9 +952,9 @@
},
"dependencies": {
"glob": {
"version": "7.1.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz",
"integrity": "sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ==",
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
@@ -1062,9 +1062,9 @@
}
},
"chownr": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz",
"integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==",
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"dev": true
},
"chrome-trace-event": {
@@ -1331,6 +1331,11 @@
"randomfill": "^1.0.3"
}
},
"crypto-js": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz",
"integrity": "sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg=="
},
"cyclist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
@@ -2661,9 +2666,9 @@
"dev": true
},
"handlebars": {
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz",
"integrity": "sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA==",
"version": "4.7.3",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.3.tgz",
"integrity": "sha512-SRGwSYuNfx8DwHD/6InAPzD6RgeruWLT+B8e8a7gGs8FWgHzlExpTFMEq2IA6QpAfOClpKHy6+8IqTjeBCu6Kg==",
"dev": true,
"requires": {
"neo-async": "^2.6.0",
@@ -3388,9 +3393,9 @@
}
},
"kind-of": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true
},
"lcid": {
@@ -4707,9 +4712,9 @@
"dev": true
},
"serialize-javascript": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz",
"integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==",
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
"integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==",
"dev": true
},
"set-blocking": {
@@ -5039,6 +5044,12 @@
"figgy-pudding": "^3.5.1"
}
},
"stackback": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
"integrity": "sha1-Gsig2Ug4SNFpXkGLbQMaPDzmjjs=",
"dev": true
},
"static-extend": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
@@ -5094,9 +5105,9 @@
}
},
"stream-shift": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
"integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
"integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
"dev": true
},
"string-width": {
@@ -5182,9 +5193,9 @@
"dev": true
},
"terser": {
"version": "4.3.9",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.3.9.tgz",
"integrity": "sha512-NFGMpHjlzmyOtPL+fDw3G7+6Ueh/sz4mkaUYa4lJCxOPTNzd0Uj0aZJOmsDYoSQyfuVoWDMSWTPU3huyOm2zdA==",
"version": "4.6.6",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.6.6.tgz",
"integrity": "sha512-4lYPyeNmstjIIESr/ysHg2vUPRGf2tzF9z2yYwnowXVuVzLEamPN1Gfrz7f8I9uEPuHcbFlW4PLIAsJoxXyJ1g==",
"dev": true,
"requires": {
"commander": "^2.20.0",
@@ -5201,16 +5212,16 @@
}
},
"terser-webpack-plugin": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz",
"integrity": "sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==",
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz",
"integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==",
"dev": true,
"requires": {
"cacache": "^12.0.2",
"find-cache-dir": "^2.1.0",
"is-wsl": "^1.1.0",
"schema-utils": "^1.0.0",
"serialize-javascript": "^1.7.0",
"serialize-javascript": "^2.1.2",
"source-map": "^0.6.1",
"terser": "^4.1.2",
"webpack-sources": "^1.4.0",
@@ -5488,9 +5499,9 @@
"dev": true
},
"uglify-js": {
"version": "3.6.7",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.7.tgz",
"integrity": "sha512-4sXQDzmdnoXiO+xvmTzQsfIiwrjUCSA95rSP4SEd8tDb51W2TiDOlL76Hl+Kw0Ie42PSItCW8/t6pBNCF2R48A==",
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.0.tgz",
"integrity": "sha512-ugNSTT8ierCsDHso2jkBHXYrU8Y5/fY2ZUprfrJUiD7YpuFvV4jODLFmb3h4btQjqr5Nh4TX4XtgDfCU1WdioQ==",
"dev": true,
"optional": true,
"requires": {
@@ -5857,6 +5868,15 @@
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
"dev": true
},
"why-is-node-running": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.1.2.tgz",
"integrity": "sha512-TwUeoRNMAWy8jAD8oFLtgmYKecZkH3yCtbQ17CYVCxd1WaPJAEB6oqkNgm0o+wIzxJi1oHUkOxMk0M/t5jCGeA==",
"dev": true,
"requires": {
"stackback": "0.0.2"
}
},
"wide-align": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+4 -2
View File
@@ -1,6 +1,6 @@
{
"name": "rpclibrary",
"version": "1.9.2",
"version": "1.10.0",
"description": "rpclibrary is a websocket on steroids!",
"main": "./js/Index.js",
"repository": {
@@ -46,10 +46,12 @@
"typedoc-plugin-markdown": "^2.2.6",
"typescript": "^3.5.3",
"webpack": "^4.40.2",
"webpack-cli": "^3.3.9"
"webpack-cli": "^3.3.9",
"why-is-node-running": "^2.1.2"
},
"dependencies": {
"bsock": "^0.1.9",
"crypto-js": "^4.0.0",
"http": "0.0.0",
"uuid": "^3.3.3"
},
+7 -11
View File
@@ -2,19 +2,15 @@
import http = require('http');
import bsock = require('bsock');
import * as T from './Types';
import * as U from './Utils';
import * as I from './Interfaces';
import { Socket } from 'dgram';
type Exporters<InterfaceT extends T.RPCInterface = T.RPCInterface, SubResType = {}> = I.RPCExporter<T.RPCInterface<InterfaceT>, keyof InterfaceT, SubResType>[]
/**
* A Websocket-server-on-steroids with built-in RPC capabilities
*/
export class RPCServer<
SubResType = {},
InterfaceT extends T.RPCInterface = T.RPCInterface,
> implements I.Destroyable {
@@ -25,7 +21,7 @@ export class RPCServer<
private errorHandler: T.ErrorHandler
private connectionHandler: T.ConnectionHandler
private sesame?: T.SesameFunction
private accessFilter: T.AccessFilter<InterfaceT, SubResType>
private accessFilter: T.AccessFilter<InterfaceT>
/**
* @throws On RPC with no name
@@ -35,12 +31,11 @@ export class RPCServer<
*/
constructor(
private port: number,
private exporters: Exporters<InterfaceT, SubResType> = [],
conf: T.ServerConf<InterfaceT, SubResType> = {}
private exporters: T.ExporterArray<InterfaceT> = [],
conf: T.ServerConf<InterfaceT> = {}
) {
if (!conf.visibility) this.visibility = "0.0.0.0"
if (conf.sesame) {
this.sesame = U.makeSesameFunction(conf.sesame)
}
@@ -88,7 +83,7 @@ export class RPCServer<
this.connectionHandler(socket)
this.initRPCs(socket)
})
this.ws.listen(this.port, this.visibility)
this.ws = this.ws.listen(this.port, this.visibility)
} catch (e) {
this.errorHandler(this.io, e, 'system', [])
}
@@ -108,8 +103,7 @@ export class RPCServer<
/**
* Publishes a new list of Exporters. This destroys and restarts the socket
* @param exporters the exporters to publish
*/
public setExporters(exporters: Exporters<InterfaceT, SubResType>):any{
public setExporters(exporters: T.ExporterArray<InterfaceT>): any {
exporters.forEach(U.fixNames)
this.destroy()
this.ws = http.createServer()
@@ -117,6 +111,8 @@ export class RPCServer<
this.exporters = exporters
this.startWebsocket()
}
*/
destroy(): void {
this.io.close()
+15 -6
View File
@@ -7,12 +7,12 @@ import * as I from './Interfaces';
import { stripAfterEquals, appendComma } from './Utils';
/**
* 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, sesame?:string, conf?:T.SocketConf): Promise<RPCSocket & T> {
export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I.Socket{
static async makeSocket<T extends T.RPCInterface = T.RPCInterface>(port:number, server: string, sesame?:string, conf?:T.SocketConf): Promise<T.ConnectedSocket<T>> {
const socket = new RPCSocket(port, server, conf)
return await socket.connect<T>(sesame)
}
@@ -128,7 +128,7 @@ export class RPCSocket implements I.Socket{
/**
* Connects to the server and attaches available RPCs to this object
*/
public async connect<T extends T.RPCInterface= T.RPCInterface>( sesame?: string ) : Promise<RPCSocket & T>{
public async connect<T extends T.RPCInterface= Ifc>( sesame?: string ) : Promise<T.ConnectedSocket<T>> {
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))
@@ -197,10 +197,19 @@ export class RPCSocket implements I.Socket{
return eval( `
async (${headerArgs} callback) => {
const r = await this.call("${fnName}", ${sesame} ${argParams})
if(r && r.result === 'Success'){
try{
if(r){
if(r.uuid){
callback['destroy'] = () => { this.socket.unhook(r.uuid) }
this.socket.hook(r.uuid, callback)
}
return r
return r.return
}else{
throw new Error("Empty response")
}
}catch(e){
throw e
}
}`)
}
}
+4 -5
View File
@@ -4,13 +4,12 @@ import * as I from "./Interfaces"
/**
* Interface for all classes that export RPCs
*/
export interface RPCExporter<
export type RPCExporter<
Ifc extends T.RPCInterface = T.RPCInterface,
Name extends keyof Ifc = keyof Ifc,
SubresT = {}
>{
Name extends keyof Ifc = string,
> = {
name: Name
exportRPCs() : T.RPCInterfaceArray<Ifc, SubresT>[Name]
exportRPCs() : T.RPCDefinitions<Ifc>[Name]
}
/**
+26 -18
View File
@@ -1,8 +1,9 @@
import * as I from "./Interfaces";
import { RPCSocket } from "./Frontend";
export type AnyFunction = (...args:any) => any
export type HookFunction<F extends AnyFunction = AnyFunction, SubresT = {}> = (...args: Parameters<F>) => Promise<SubscriptionResponse<SubresT> | ErrorResponse>
export type AccessFilter<InterfaceT extends RPCInterface, SubresT> = (sesame:string|undefined, exporter: I.RPCExporter<RPCInterface<InterfaceT>, keyof InterfaceT, SubresT>) => Promise<boolean>
export type HookFunction = AnyFunction
export type AccessFilter<InterfaceT extends RPCInterface = RPCInterface> = (sesame:string|undefined, exporter: I.RPCExporter<InterfaceT, keyof InterfaceT>) => Promise<boolean>
export type Visibility = "127.0.0.1" | "0.0.0.0"
export type ConnectionHandler = (socket:I.Socket) => void
export type ErrorHandler = (socket:I.Socket, error:any, rpcName: string, args: any[]) => void
@@ -17,8 +18,12 @@ export type FrontEndHandlerType = {
'close' : () => void
}
export type ServerConf<InterfaceT extends RPCInterface, SubresT> = {
accessFilter?: AccessFilter<InterfaceT, SubresT>
export type ExporterArray<InterfaceT extends RPCInterface = RPCInterface> = I.RPCExporter<RPCInterface<InterfaceT>, keyof InterfaceT>[]
export type ConnectedSocket<T extends RPCInterface = RPCInterface> = RPCSocket & T
export type ServerConf<InterfaceT extends RPCInterface> = {
accessFilter?: AccessFilter<InterfaceT>
connectionHandler?: ConnectionHandler
errorHandler?: ErrorHandler
closeHandler?: CloseHandler
@@ -36,23 +41,23 @@ export type Outcome = "Success" | "Error"
export type Respose<T> = T & { result: Outcome }
export type SuccessResponse<T = {}> = Respose<T> & { result: "Success" }
export type ErrorResponse<T = {}> = Respose<T> & { result: "Error", message?:string }
export type SubscriptionResponse<T = {}> = Respose<T> & { result: "Success"; uuid: string }
export type RPCType = 'Hook' | 'Unhook' | 'Call'
export type CallRPC<N, F> = {
name: N
call: F
export type CallRPC<Name, Func extends AnyFunction> = {
name: Name
call: Func
}
export type HookRPC<N, F extends AnyFunction, SubresT = {}> = {
name: N
hook: HookFunction<F, SubresT>
export type HookRPC<Name, Func extends AnyFunction> = {
name: Name
hook: Func
onCallback?: AnyFunction
onClose?: HookCloseFunction<SubresT>
onClose?: HookCloseFunction<ReturnType<Func> extends Promise<infer T> ? T : ReturnType<Func>>
}
export type RPC<N, F extends AnyFunction, SubresT = {}> = HookRPC<N, F, SubresT> | CallRPC<N,F> | F
export type RPC<Name, Func extends AnyFunction> = HookRPC<Name, Func> | CallRPC<Name,Func> | Func
export type RPCInterface<Impl extends RPCInterface = {}> = {
[grp in string] : {
@@ -60,10 +65,13 @@ export type RPCInterface<Impl extends RPCInterface = {}> = {
}
} & Impl
export type RPCInterfaceArray<Itfc extends RPCInterface, SubresT = {}> = {
[grp in keyof Itfc]: Array<
{ [rpc in keyof Itfc[grp]]: RPC<rpc, Itfc[grp][rpc], SubresT> }[keyof Itfc[grp]]
>
export type exportT = {
[group in string]: {}
}
//This probably has lots of issues
export type RPCDefinitions<Ifc extends RPCInterface> = {
[grp in keyof Ifc]:( { [rpc in keyof Ifc[grp]]: RPC<rpc, Ifc[grp][rpc]> }[keyof Ifc[grp]] )[]
}
export type BaseInfo = {
@@ -86,4 +94,4 @@ export type RpcInfo = HookInfo | CallInfo
export type ExtendedRpcInfo = RpcInfo & { uniqueName: string }
export type OnFunction = <T extends "error" | "close">(type: T, f: FrontEndHandlerType[T]) => void
export type HookCloseFunction<T = {}> = (res:SubscriptionResponse<T>, rpc:HookRPC<any, any, T>) => any
export type HookCloseFunction<T> = (res: T, rpc:HookRPC<any, any>) => any
+17 -24
View File
@@ -2,7 +2,6 @@ import * as uuidv4 from "uuid/v4"
import * as T from "./Types";
import * as I from "./Interfaces";
import { SubscriptionResponse } from "./Types";
/**
* Translate an RPC to RPCInfo for serialization.
@@ -12,7 +11,7 @@ import { SubscriptionResponse } from "./Types";
* @param sesame optional sesame phrase to prepend before all RPC arguments
* @throws Error on RPC without name property
*/
export const rpcToRpcinfo = <SubResT = {}>(socket: I.Socket, rpc : T.RPC<any, any, SubResT>, owner: string, errorHandler: T.ErrorHandler, sesame?:T.SesameFunction):T.RpcInfo => {
export const rpcToRpcinfo = (socket: I.Socket, rpc : T.RPC<any, any>, owner: string, errorHandler: T.ErrorHandler, sesame?:T.SesameFunction):T.RpcInfo => {
switch (typeof rpc){
case "object":
if(rpc['call']){
@@ -24,7 +23,7 @@ export const rpcToRpcinfo = <SubResT = {}>(socket: I.Socket, rpc : T.RPC<any, an
call: sesame?async (_sesame, ...args) => {if(sesame(_sesame)) return await rpc['call'].apply({}, args); socket.destroy()}:rpc['call'], // check & remove sesame
}
}else{
const generator = hookGenerator(<T.HookRPC<any, any, any>>rpc, errorHandler, sesame)
const generator = hookGenerator(<T.HookRPC<any, any>>rpc, errorHandler, sesame)
return {
owner: owner,
argNames: extractArgs(generator(undefined)),
@@ -58,10 +57,9 @@ 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<any, any, SubResT>, errorHandler: T.ErrorHandler, sesame?:T.SesameFunction, makeUnique = true):T.ExtendedRpcInfo[]{
export function rpcHooker(socket: I.Socket, exporter:I.RPCExporter<any, any>, errorHandler: T.ErrorHandler, sesame?:T.SesameFunction, makeUnique = true):T.ExtendedRpcInfo[]{
const owner = exporter.name
const RPCs = [...exporter.exportRPCs()]
const RPCs = exporter.exportRPCs()
return RPCs.map(rpc => rpcToRpcinfo(socket, rpc, owner, errorHandler, sesame))
.map(info => {
@@ -107,7 +105,7 @@ export function stripAfterEquals(str:string):string{
* @param rpc The RPC to transform
* @returns A {@link HookFunction}
*/
const hookGenerator = (rpc:T.HookRPC<any, any, any>, errorHandler: T.ErrorHandler, sesameFn?: T.SesameFunction): T.HookInfo['generator'] => {
const hookGenerator = (rpc:T.HookRPC<any, any>, /*not unused!*/ errorHandler: T.ErrorHandler, sesameFn?: T.SesameFunction): T.HookInfo['generator'] => {
let argsArr = extractArgs(rpc.hook)
argsArr.pop() //remove 'callback' from the end
let callArgs = argsArr.join(',')
@@ -119,16 +117,23 @@ const hookGenerator = (rpc:T.HookRPC<any, any, any>, errorHandler: T.ErrorHandle
//note rpc.hook is the associated RPC, not a socket.hook
return eval(`
(socket) => async (${args}) => {
(clientSocket) => async (${args}) => {
try{
if(sesameFn && !sesameFn(sesame)) return
const uuid = uuidv4()
const res = await rpc.hook(${callArgs} (...cbargs) => {
if(rpc.onCallback) rpc.onCallback.apply({}, cbargs)
socket.call.apply(socket, [res.uuid, ...cbargs])
if(rpc.onCallback){
rpc.onCallback.apply({}, cbargs)
}
clientSocket.call.apply(clientSocket, [uuid, ...cbargs])
})
return res
if(rpc.onClose){
clientSocket.on('close', () => rpc.onClose(res, rpc))
}
return {'uuid': uuid, 'return': res}
}catch(e){
errorHandler(socket)(e, ${rpc.name}, [${args}])
//can throw to pass exception to client or swallow to keep it local
errorHandler(clientSocket)(e, ${rpc.name}, [${args}])
}
}`)
}
@@ -147,18 +152,6 @@ const extractArgs = (f:Function):string[] => {
return fn!==""?fn.split(',') : []
}
/**
* Simple utility function to create basic {@link SubscriptionResponse}
* @param uuid optional uuid to use, otherwise defaults to uuid/v4
*/
export function makeSubResponse<T extends {} = {}>(extension:T):SubscriptionResponse & T{
return {
result: "Success",
uuid: uuidv4(),
...extension
}
}
export function makeSesameFunction (sesame : T.SesameFunction | string) : T.SesameFunction {
if(typeof sesame === 'function'){
+142 -99
View File
@@ -1,16 +1,14 @@
import { describe, it, Func } from "mocha";
import { RPCServer, RPCSocket, SubscriptionResponse, makeSubResponse } from '../Index'
import * as uuidv4 from "uuid/v4"
import { doesNotReject } from "assert";
import { Socket } from "dgram";
import { describe, it } from "mocha";
import { RPCServer, RPCSocket } from '../Index'
import { RPCExporter } from "../src/Interfaces";
import { ConnectedSocket } from "../src/Types";
import * as log from 'why-is-node-running';
const add = (...args: number[]) => { return args.reduce((a, b) => a + b, 0) }
function makeServer() {
let subcallback
return new RPCServer<{ topic: string }>(21010, [{
name: "test",
return new RPCServer(21010, [{
name: 'test',
exportRPCs: () => [
{
name: 'echo',
@@ -19,13 +17,14 @@ function makeServer(){
name: 'simpleSubscribe',
hook: async (callback) => {
subcallback = callback
return makeSubResponse<{topic: string}>({topic: "test"})
}
return { topic: "test" }
},
onClose: (res) => { }
}, {
name: 'subscribe',
hook: async (callback) => {
subcallback = callback
return makeSubResponse<{topic: string}>({topic: "test"})
return { topic: "test" }
},
onClose: (res, rpc) => {
console.log("onClose", rpc.name === 'subscribe' && res ? "OK" : "")
@@ -47,21 +46,11 @@ function makeServer(){
describe('RPCServer', () => {
let server: RPCServer<{ topic: string }, any>
before(() => {
server = makeServer()
})
after(() => {
server.destroy()
})
it('should be able to use all kinds of RPC definitions', (done) => {
let client, server
const echo = (x) => x
const server = new RPCServer(21003, [{
before(done => {
server = new RPCServer(21003, [{
name: 'HelloWorldRPCGroup',
exportRPCs: () => [
echo, //named function variable
@@ -73,17 +62,28 @@ describe('RPCServer', () => {
]
}])
const client = new RPCSocket(21003, 'localhost')
client = new RPCSocket(21003, 'localhost')
done()
})
after(done => {
client.destroy()
server.destroy()
done()
})
it('should be able to use all kinds of RPC definitions', (done) => {
client.connect().then(async () => {
const r0 = await client['HelloWorldRPCGroup'].echo('Hello')
const r1 = await client['HelloWorldRPCGroup'].echof('World')
const r2 = await client['HelloWorldRPCGroup'].echoExplicit('R', 'P', 'C!')
if (r0 === 'Hello' && r1 === 'World' && r2.join('') === 'RPC!') {
client.destroy()
server.destroy()
done()
}else{
done(new Error("Bad response"))
}
})
})
@@ -91,7 +91,7 @@ describe('RPCServer', () => {
it('new RPCServer() should fail on bad RPC', (done) => {
try {
new RPCServer(20001, [{
name: "bad",
name: 'bad',
exportRPCs: () => [
(aaa, bbb, ccc) => { return aaa + bbb + ccc }
]
@@ -106,7 +106,7 @@ describe('RPCServer', () => {
describe('RPCSocket', () => {
let client: RPCSocket
let server: RPCServer<{topic: string}>
let server: RPCServer
before(async () => {
server = makeServer()
@@ -140,7 +140,7 @@ describe('RPCSocket', () => {
it('should subscribe with success', (done) => {
client['test'].simpleSubscribe(console.log).then(res => {
if(res.result === 'Success'){
if (res.topic === 'test') {
done()
} else {
console.error(res)
@@ -173,48 +173,42 @@ describe('RPCSocket', () => {
})
describe('It should do unhook', () => {
let candy = "OK"
const yesCandy = "OK"
const noCandy = "stolen"
let candy = yesCandy
let cb: Function
let cb2: Function
let client: RPCSocket
let server: RPCServer<{topic: string}>
let server: RPCServer
before(async () => {
server = new RPCServer<{ topic: string }>(21010, [{
server = new RPCServer(21010, [{
name: "test",
exportRPCs: () => [{
name: 'subscribe',
hook: async(callback):Promise<SubscriptionResponse<{topic:string}>> => {
hook: async (callback): Promise<void> => {
cb = <Function>callback
return {
result: "Success",
uuid: uuidv4(),
topic: "test"
}
return
}
},
{
name: 'subscribeWithParam',
hook: async(param, callback):Promise<SubscriptionResponse<{topic:string}>> => {
hook: async (param, callback): Promise<{ uuid: string }> => {
if (param != "OK") {
console.log("param was" + param);
return {
result: "Success",
uuid: "no",
topic: "test"
}
}
cb2 = <Function>callback
return {
result: "Success",
uuid: "OK",
topic: "test"
}
}
},
function checkCandy():string { cb(candy); return candy },
function stealCandy():string { candy = "_OK"; cb(candy); cb = () => {}; return candy }
function publish(): string { cb(candy); return candy },
function unsubscribe(): string { candy = noCandy; cb(candy); cb = () => { }; return candy }
]
}], {
connectionHandler: (socket) => { },
@@ -231,23 +225,33 @@ describe('It should do unhook', () => {
})
it('Subscribe with param', (done) => {
client['test'].subscribeWithParam("OK", c => {}).then( async (res: SubscriptionResponse) => {
if(res.uuid === "OK"){
client['test'].subscribeWithParam("OK", c => { }).then(async (res) => {
if (res.uuid === candy) {
done()
} else
done(new Error("Results did not match " + res.uuid))
})
})
it('Unhook+unsubscribe should stop callbacks', (done) => {
client['test'].subscribe(c => {}).then( async (res: SubscriptionResponse) => {
const r1 = await client['test'].checkCandy()
const r3 = await client['test'].stealCandy()
client.unhook(res.uuid)
const r2 = await client['test'].checkCandy()
const r4 = await client['test'].checkCandy()
let run = 0
const expected = [yesCandy, noCandy, noCandy, noCandy]
if(r1 === "OK" && r3 === "_OK" && r2 === "_OK" && r4 === "_OK")
it('Unhook+unsubscribe should stop callbacks', (done) => {
client['test'].subscribe(function myCallback(c){
if(run == 1)
(myCallback as any).destroy()
if (c !== expected[run++]) {
done(new Error(`Wrong candy '${c}' in iteration '${run - 1}'`))
}
}).then(async function(res){
const r1 = await client['test'].publish()
const r3 = await client['test'].unsubscribe()
const r2 = await client['test'].publish()
const r4 = await client['test'].publish()
if (r1 === yesCandy && r3 === noCandy && r2 === noCandy && r4 === noCandy)
done()
else
done(new Error("Results did not match: " + [r1, r2, r3, r4]))
@@ -255,43 +259,54 @@ describe('It should do unhook', () => {
})
})
type topicDTO = { topic: string; }
type SesameTestIfc = {
test: {
checkCandy: () => Promise<string>
subscribe: (callback) => Promise<SubscriptionResponse<{ topic: string; }>>
subscribe: (callback: Function) => Promise<topicDTO>
manyParams: <A=string,B=number,C=boolean,D=Object>(a:A, b:B, c:C, d:D) => Promise<[A, B, C, D]>
}
other: {
echo: (x:any) => Promise<any>
}
}
describe('Sesame should unlock the socket', () => {
let candy = "OK"
let client: RPCSocket & SesameTestIfc
let server: RPCServer
let cb = (...args) => {}
let client: ConnectedSocket<SesameTestIfc>
let server: RPCServer<SesameTestIfc>
let cb: Function = (...args) => { }
before((done) => {
server = new RPCServer(21004, [{
server = new RPCServer<SesameTestIfc>(21004, [{
name: "test",
exportRPCs: () => [
{
name: 'subscribe',
hook: async (callback) => {
cb = callback
return <SubscriptionResponse>{
result: "Success",
uuid: uuidv4(),
return {
topic: 'test'
}
}
},
async function checkCandy():Promise<string> { cb(candy); cb=()=>{}; return candy },
onClose: (a) => { }
},
async function checkCandy() { cb(candy); cb = () => { }; return candy },
async function manyParams(a, b, c, d) { return [a, b, c, d] }
]}
],{
],
},{
name: 'other',
exportRPCs: () => [
async function echo(x){return x}
]
}], {
sesame: (_sesame) => _sesame === 'sesame!'
})
const sock = new RPCSocket(21004, "localhost")
sock.connect<SesameTestIfc>('sesame!').then(cli => {
const sock = new RPCSocket<SesameTestIfc>(21004, "localhost")
sock.connect('sesame!').then(cli => {
client = cli
done()
})
@@ -345,8 +360,8 @@ describe('Sesame should unlock the socket', () => {
done()
}
}).then(d => {
if(d.result !== 'Success')
done('unexpected valid response')
if (d.topic !== 'test')
done('unexpected invalid response')
client.test.checkCandy()
})
@@ -355,18 +370,20 @@ describe('Sesame should unlock the socket', () => {
describe('Error handling', () => {
const errtxt = "BAD BAD BAD"
let createUser = async (user: { a: any, b: any }) => {
throw new Error("BAD BAD BAD")
throw new Error(errtxt)
}
it("RPC throws on client without handler", (done) => {
let server = new RPCServer(21004, [{
name: 'createUser' as 'createUser',
name: "createUser",
exportRPCs: () => [{
name: 'createUser' as 'createUser',
call: createUser
}]}], {
}]
}], {
})
@@ -378,11 +395,13 @@ describe('Error handling', ()=>{
})
.then(r => {
if (r != null)
done("UNEXPECTED RESULT " + r)
done(new Error("UNEXPECTED RESULT " + r))
})
.catch((e) => {
//console.log("EXPECTED CLIENT EXCEPTION", String(e));
if (e.message === errtxt)
done()
else
done(e)
})
.finally(() => {
cli.destroy()
@@ -394,11 +413,12 @@ describe('Error handling', ()=>{
it("RPC throws on server with handler", (done) => {
let server = new RPCServer(21004, [{
name: 'createUser' as 'createUser',
name: "createUser",
exportRPCs: () => [{
name: 'createUser' as 'createUser',
call: createUser
}]}], {
}]
}], {
errorHandler: (socket, e, rpcName, args) => {
done()
}
@@ -429,19 +449,22 @@ describe('Error handling', ()=>{
describe("Errorhandler functionality", () => {
const errtxt = "BAD BAD BAD"
let createUser = async (user: { a: any, b: any }) => {
throw new Error("BAD BAD BAD")
throw new Error(errtxt)
}
it("correct values are passed to the handler", (done) => {
let server = new RPCServer(21004, [{
name: 'createUser' as 'createUser',
name: "createUser",
exportRPCs: () => [{
name: 'createUser' as 'createUser',
call: createUser
}]}], {
}]
}], {
errorHandler: (socket, e, rpcName, args) => {
if(e.message === "BAD BAD BAD" && rpcName === "createUser" && args[0]['a'] === 'a' && args[0]['b'] === 'b')
if (e.message === errtxt && rpcName === "createUser" && args[0]['a'] === 'a' && args[0]['b'] === 'b')
done()
}
})
@@ -457,8 +480,7 @@ describe("Errorhandler functionality", ()=>{
done("UNEXPECTED RESULT " + r)
})
.catch((e) => {
done("UNEXPECTED CLIENT ERROR " + e)
done(e)
done(new Error("UNEXPECTED CLIENT ERROR " + e.message))
})
.finally(() => {
cli.destroy()
@@ -471,14 +493,15 @@ describe("Errorhandler functionality", ()=>{
it("handler sees sesame", (done) => {
let sesame = "AAAAAAAAAAAAAAA"
let server = new RPCServer(21004, [{
name: 'createUser' as 'createUser',
name: "createUser" as "createUser",
exportRPCs: () => [{
name: 'createUser' as 'createUser',
call: createUser
}]}], {
}]
}], {
sesame: sesame,
errorHandler: (socket, e, rpcName, args) => {
if(e.message === "BAD BAD BAD" && rpcName === "createUser" && args[0] === sesame && args[1]['a'] === 'a' && args[1]['b'] === 'b')
if (e.message === errtxt && rpcName === "createUser" && args[0] === sesame && args[1]['a'] === 'a' && args[1]['b'] === 'b')
done()
}
@@ -517,24 +540,24 @@ type myExporterIfc = {
describe("Class binding", () => {
let exporter1: MyExporter
let serv : RPCServer<{}, myExporterIfc>
let serv: RPCServer<myExporterIfc>
let sock: RPCSocket & myExporterIfc
let allowed = true
class MyExporter implements RPCExporter<myExporterIfc>{
name = "MyExporter" as "MyExporter";
name = "MyExporter" as "MyExporter"
exportRPCs = () => [
this.myRPC
]
myRPC = async () => {
serv.setExporters([new MyOtherExporter])
//serv.setExporters([new MyOtherExporter])
return "Hello World"
}
}
class MyOtherExporter implements RPCExporter<myExporterIfc>{
name = "MyExporter" as "MyExporter";
name = "MyExporter" as "MyExporter"
exportRPCs = () => [
this.myRPC
]
@@ -547,14 +570,13 @@ describe("Class binding", ()=>{
before(done => {
exporter1 = new MyExporter()
serv = new RPCServer<{}, myExporterIfc>(21004, [exporter1], {
serv = new RPCServer<myExporterIfc>(21004, [exporter1], {
accessFilter: async (sesame, exporter) => {
switch(exporter.name){
case "MyExporter":
if(exporter.name === 'MyExporter'){
if (!allowed) return false
allowed = false
return sesame === 'xxx';
default:
}else{
return false
}
},
@@ -564,7 +586,6 @@ describe("Class binding", ()=>{
})
beforeEach((done) => {
const s = new RPCSocket(21004, 'localhost')
s.connect<myExporterIfc>("xxx").then(conn => {
sock = conn
@@ -572,7 +593,7 @@ describe("Class binding", ()=>{
})
})
afterEach(done => {
afterEach((done) => {
sock.destroy()
done()
})
@@ -581,7 +602,11 @@ describe("Class binding", ()=>{
serv.destroy()
})
it("binds correctly", (done)=>{
/* The server-side socket will enter a 30s timeout if destroyed by a RPC.
to mitigate the impact on testing time these are not run.
it("binds correctly", function(done){
this.timeout(1000)
sock['MyExporter'].myRPC().then((res) => {
done(new Error(res))
}).catch(e => {
@@ -592,6 +617,7 @@ describe("Class binding", ()=>{
})
it("changes exporters", (done) => {
sock['MyExporter'].myRPC().then((res) => {
if (res === "Hello Borld")
done()
@@ -599,6 +625,16 @@ describe("Class binding", ()=>{
done(new Error(res))
})
})
*/
it("use sesameFilter for available", (done) => {
if (sock['MyExporter']){
allowed = false
done()
}
else done(new Error("RPC supposed to be here"))
})
it("use sesameFilter", (done) => {
if (!sock['MyExporter']) done()
@@ -667,4 +703,11 @@ describe("attaching handlers before connecting", ()=>{
done(e)
})
})
})
describe('finally', () => {
it('print open handles (Ignore `DNSCHANNEL` and `Immediate`)', () => {
log()
})
})
-73
View File
@@ -1,73 +0,0 @@
import { RPCServer } from "../src/Backend";
import { SubscriptionResponse, RPCInterface } from "../src/Types";
import { RPCSocket } from "../src/Frontend";
import { makeSubResponse } from "../src/Utils";
type SubresExtension = {a:string}
type MyInterface = {
Group1: {
triggerCallbacks: (...args:any[]) => Promise<void>,
subscribe: (param:string, callback:Function) => Promise<SubscriptionResponse<SubresExtension>>,
unsubscribe: (uuid:string) => Promise<void>
},
Group2: {
echo: (x:string) => Promise<string>
}
}
new RPCServer<SubresExtension, MyInterface>(20000,
[{
name: 'Group1',
exportRPCs: () => [{
name: 'triggerCallbacks',
call: async () => { /*...*/ }
},{
name: 'subscribe',
hook: async (param, callback) => { return makeSubResponse<SubresExtension>({a: "test"}) }
},{
name: 'unsubscribe',
call: async(uuid) => { }
}
]
},{
name: 'Group2',
exportRPCs: () => [{
name: 'echo',
call: async (x) => "..."
}]
}]
)
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")
}))
const srv = new RPCServer(30000, [{
name: 'Group2',
exportRPCs: () => [{
name: 'echo',
call: async (x) => x
}]
}], {
sesame: 'open'
})
const s = new RPCSocket(30000, 'localhost')
s.connect("open").then(async() => {
s['Group2']['echo']('open', 'dfgfg').then(console.log)
s['Group2']['echo']('dfgfg').then(console.log)
s['Group2']['echo']('dfgfg').then(console.log)
})