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", "name": "rpclibrary",
"version": "1.6.1", "version": "1.9.2",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@@ -467,9 +467,9 @@
"dev": true "dev": true
}, },
"acorn": { "acorn": {
"version": "6.3.0", "version": "6.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
"integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==", "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
"dev": true "dev": true
}, },
"aggregate-error": { "aggregate-error": {
@@ -753,9 +753,9 @@
"dev": true "dev": true
}, },
"bluebird": { "bluebird": {
"version": "3.7.1", "version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
"integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
"dev": true "dev": true
}, },
"bn.js": { "bn.js": {
@@ -952,9 +952,9 @@
}, },
"dependencies": { "dependencies": {
"glob": { "glob": {
"version": "7.1.5", "version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ==", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"dev": true, "dev": true,
"requires": { "requires": {
"fs.realpath": "^1.0.0", "fs.realpath": "^1.0.0",
@@ -1062,9 +1062,9 @@
} }
}, },
"chownr": { "chownr": {
"version": "1.1.3", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"dev": true "dev": true
}, },
"chrome-trace-event": { "chrome-trace-event": {
@@ -1331,6 +1331,11 @@
"randomfill": "^1.0.3" "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": { "cyclist": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
@@ -2661,9 +2666,9 @@
"dev": true "dev": true
}, },
"handlebars": { "handlebars": {
"version": "4.5.1", "version": "4.7.3",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.3.tgz",
"integrity": "sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA==", "integrity": "sha512-SRGwSYuNfx8DwHD/6InAPzD6RgeruWLT+B8e8a7gGs8FWgHzlExpTFMEq2IA6QpAfOClpKHy6+8IqTjeBCu6Kg==",
"dev": true, "dev": true,
"requires": { "requires": {
"neo-async": "^2.6.0", "neo-async": "^2.6.0",
@@ -3388,9 +3393,9 @@
} }
}, },
"kind-of": { "kind-of": {
"version": "6.0.2", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true "dev": true
}, },
"lcid": { "lcid": {
@@ -4707,9 +4712,9 @@
"dev": true "dev": true
}, },
"serialize-javascript": { "serialize-javascript": {
"version": "1.9.1", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
"integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==", "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==",
"dev": true "dev": true
}, },
"set-blocking": { "set-blocking": {
@@ -5039,6 +5044,12 @@
"figgy-pudding": "^3.5.1" "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": { "static-extend": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
@@ -5094,9 +5105,9 @@
} }
}, },
"stream-shift": { "stream-shift": {
"version": "1.0.0", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
"integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
"dev": true "dev": true
}, },
"string-width": { "string-width": {
@@ -5182,9 +5193,9 @@
"dev": true "dev": true
}, },
"terser": { "terser": {
"version": "4.3.9", "version": "4.6.6",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.3.9.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.6.tgz",
"integrity": "sha512-NFGMpHjlzmyOtPL+fDw3G7+6Ueh/sz4mkaUYa4lJCxOPTNzd0Uj0aZJOmsDYoSQyfuVoWDMSWTPU3huyOm2zdA==", "integrity": "sha512-4lYPyeNmstjIIESr/ysHg2vUPRGf2tzF9z2yYwnowXVuVzLEamPN1Gfrz7f8I9uEPuHcbFlW4PLIAsJoxXyJ1g==",
"dev": true, "dev": true,
"requires": { "requires": {
"commander": "^2.20.0", "commander": "^2.20.0",
@@ -5201,16 +5212,16 @@
} }
}, },
"terser-webpack-plugin": { "terser-webpack-plugin": {
"version": "1.4.1", "version": "1.4.3",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz",
"integrity": "sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==", "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==",
"dev": true, "dev": true,
"requires": { "requires": {
"cacache": "^12.0.2", "cacache": "^12.0.2",
"find-cache-dir": "^2.1.0", "find-cache-dir": "^2.1.0",
"is-wsl": "^1.1.0", "is-wsl": "^1.1.0",
"schema-utils": "^1.0.0", "schema-utils": "^1.0.0",
"serialize-javascript": "^1.7.0", "serialize-javascript": "^2.1.2",
"source-map": "^0.6.1", "source-map": "^0.6.1",
"terser": "^4.1.2", "terser": "^4.1.2",
"webpack-sources": "^1.4.0", "webpack-sources": "^1.4.0",
@@ -5488,9 +5499,9 @@
"dev": true "dev": true
}, },
"uglify-js": { "uglify-js": {
"version": "3.6.7", "version": "3.8.0",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.7.tgz", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.0.tgz",
"integrity": "sha512-4sXQDzmdnoXiO+xvmTzQsfIiwrjUCSA95rSP4SEd8tDb51W2TiDOlL76Hl+Kw0Ie42PSItCW8/t6pBNCF2R48A==", "integrity": "sha512-ugNSTT8ierCsDHso2jkBHXYrU8Y5/fY2ZUprfrJUiD7YpuFvV4jODLFmb3h4btQjqr5Nh4TX4XtgDfCU1WdioQ==",
"dev": true, "dev": true,
"optional": true, "optional": true,
"requires": { "requires": {
@@ -5857,6 +5868,15 @@
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
"dev": true "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": { "wide-align": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+4 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "rpclibrary", "name": "rpclibrary",
"version": "1.9.2", "version": "1.10.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": {
@@ -46,10 +46,12 @@
"typedoc-plugin-markdown": "^2.2.6", "typedoc-plugin-markdown": "^2.2.6",
"typescript": "^3.5.3", "typescript": "^3.5.3",
"webpack": "^4.40.2", "webpack": "^4.40.2",
"webpack-cli": "^3.3.9" "webpack-cli": "^3.3.9",
"why-is-node-running": "^2.1.2"
}, },
"dependencies": { "dependencies": {
"bsock": "^0.1.9", "bsock": "^0.1.9",
"crypto-js": "^4.0.0",
"http": "0.0.0", "http": "0.0.0",
"uuid": "^3.3.3" "uuid": "^3.3.3"
}, },
+31 -35
View File
@@ -2,30 +2,26 @@
import http = require('http'); import http = require('http');
import bsock = require('bsock'); import bsock = require('bsock');
import * as T from './Types'; import * as T from './Types';
import * as U from './Utils'; import * as U from './Utils';
import * as I from './Interfaces'; 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 * A Websocket-server-on-steroids with built-in RPC capabilities
*/ */
export class RPCServer< export class RPCServer<
SubResType = {},
InterfaceT extends T.RPCInterface = T.RPCInterface, InterfaceT extends T.RPCInterface = T.RPCInterface,
> implements I.Destroyable{ > implements I.Destroyable {
private ws = http.createServer() private ws = http.createServer()
private io = bsock.createServer() private io = bsock.createServer()
private visibility:T.Visibility private visibility: T.Visibility
private closeHandler:T.CloseHandler private closeHandler: T.CloseHandler
private errorHandler: T.ErrorHandler private errorHandler: T.ErrorHandler
private connectionHandler: T.ConnectionHandler private connectionHandler: T.ConnectionHandler
private sesame? : T.SesameFunction private sesame?: T.SesameFunction
private accessFilter: T.AccessFilter<InterfaceT, SubResType> private accessFilter: T.AccessFilter<InterfaceT>
/** /**
* @throws On RPC with no name * @throws On RPC with no name
@@ -34,71 +30,70 @@ export class RPCServer<
* @param conf A {@link SocketConf} object with optional settings * @param conf A {@link SocketConf} object with optional settings
*/ */
constructor( constructor(
private port:number, private port: number,
private exporters: Exporters<InterfaceT, SubResType> = [], private exporters: T.ExporterArray<InterfaceT> = [],
conf: T.ServerConf<InterfaceT, SubResType> = {} conf: T.ServerConf<InterfaceT> = {}
){ ) {
if(!conf.visibility) this.visibility = "0.0.0.0" if (!conf.visibility) this.visibility = "0.0.0.0"
if (conf.sesame) {
if(conf.sesame){
this.sesame = U.makeSesameFunction(conf.sesame) this.sesame = U.makeSesameFunction(conf.sesame)
} }
this.accessFilter = conf.accessFilter || (async (sesame) => { this.accessFilter = conf.accessFilter || (async (sesame) => {
if(!this.sesame) return true if (!this.sesame) return true
return this.sesame!(sesame!) return this.sesame!(sesame!)
}) })
this.errorHandler = (socket:I.Socket) => (error:any, rpcName:string, args: any[]) => { this.errorHandler = (socket: I.Socket) => (error: any, rpcName: string, args: any[]) => {
if(conf.errorHandler) conf.errorHandler(socket, error, rpcName, args) if (conf.errorHandler) conf.errorHandler(socket, error, rpcName, args)
else throw error else throw error
} }
this.closeHandler = (socket:I.Socket) => { this.closeHandler = (socket: I.Socket) => {
if(conf.closeHandler) conf.closeHandler(socket) if (conf.closeHandler) conf.closeHandler(socket)
} }
this.connectionHandler = (socket:I.Socket) => { this.connectionHandler = (socket: I.Socket) => {
if(conf.connectionHandler) conf.connectionHandler(socket) if (conf.connectionHandler) conf.connectionHandler(socket)
} }
exporters.forEach(U.fixNames) //TSC for some reason doesn't preserve name properties of methods 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) {
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.
\n \n
\n<------------OFFENDING RPC: \n<------------OFFENDING RPC:
\n`+badRPC.toString()+` \n`+ badRPC.toString() + `
\n>------------OFFENDING RPC`) \n>------------OFFENDING RPC`)
} }
this.startWebsocket() this.startWebsocket()
} }
private startWebsocket(){ private startWebsocket() {
try{ try {
this.io.attach(this.ws) this.io.attach(this.ws)
this.io.on('socket', (socket:I.Socket) => { this.io.on('socket', (socket: I.Socket) => {
socket.on('error', (err) => this.errorHandler(socket, err, "system", [])) socket.on('error', (err) => this.errorHandler(socket, err, "system", []))
socket.on('close', () => this.closeHandler(socket)) socket.on('close', () => this.closeHandler(socket))
this.connectionHandler(socket) this.connectionHandler(socket)
this.initRPCs(socket) this.initRPCs(socket)
}) })
this.ws.listen(this.port, this.visibility) this.ws = this.ws.listen(this.port, this.visibility)
}catch(e){ } catch (e) {
this.errorHandler(this.io, e, 'system', []) this.errorHandler(this.io, e, 'system', [])
} }
} }
protected initRPCs(socket:I.Socket){ protected initRPCs(socket: I.Socket) {
socket.hook('info', async (sesame? : string) => { socket.hook('info', async (sesame?: string) => {
const rpcs = await Promise.all(this.exporters.map(async exp => { const rpcs = await Promise.all(this.exporters.map(async exp => {
const allowed = await this.accessFilter(sesame, exp) const allowed = await this.accessFilter(sesame, exp)
if(!allowed) return [] if (!allowed) return []
return U.rpcHooker(socket, exp, this.errorHandler, this.sesame) return U.rpcHooker(socket, exp, this.errorHandler, this.sesame)
})) }))
return rpcs.flat() return rpcs.flat()
@@ -108,8 +103,7 @@ export class RPCServer<
/** /**
* Publishes a new list of Exporters. This destroys and restarts the socket * Publishes a new list of Exporters. This destroys and restarts the socket
* @param exporters the exporters to publish * @param exporters the exporters to publish
*/ public setExporters(exporters: T.ExporterArray<InterfaceT>): any {
public setExporters(exporters: Exporters<InterfaceT, SubResType>):any{
exporters.forEach(U.fixNames) exporters.forEach(U.fixNames)
this.destroy() this.destroy()
this.ws = http.createServer() this.ws = http.createServer()
@@ -117,6 +111,8 @@ export class RPCServer<
this.exporters = exporters this.exporters = exporters
this.startWebsocket() this.startWebsocket()
} }
*/
destroy(): void { destroy(): void {
this.io.close() this.io.close()
+16 -7
View File
@@ -7,12 +7,12 @@ import * as I from './Interfaces';
import { stripAfterEquals, appendComma } from './Utils'; import { stripAfterEquals, appendComma } from './Utils';
/** /**
* A websocket-on-steroids with built-in RPC capabilities * A websocket-on-steroids with built-in RPC capabilities
*/ */
export class RPCSocket implements I.Socket{ 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<RPCSocket & T> {
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) const socket = new RPCSocket(port, server, conf)
return await socket.connect<T>(sesame) 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 * 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.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.errorHandlers.forEach(h => this.socket.on('error', h))
this.closeHandlers.forEach(h => this.socket.on('close', h)) this.closeHandlers.forEach(h => this.socket.on('close', h))
@@ -197,10 +197,19 @@ export class RPCSocket implements I.Socket{
return eval( ` return eval( `
async (${headerArgs} callback) => { async (${headerArgs} callback) => {
const r = await this.call("${fnName}", ${sesame} ${argParams}) const r = await this.call("${fnName}", ${sesame} ${argParams})
if(r && r.result === 'Success'){ try{
this.socket.hook(r.uuid, callback) if(r){
if(r.uuid){
callback['destroy'] = () => { this.socket.unhook(r.uuid) }
this.socket.hook(r.uuid, callback)
}
return r.return
}else{
throw new Error("Empty response")
}
}catch(e){
throw e
} }
return r
}`) }`)
} }
} }
+4 -5
View File
@@ -4,13 +4,12 @@ import * as I from "./Interfaces"
/** /**
* Interface for all classes that export RPCs * Interface for all classes that export RPCs
*/ */
export interface RPCExporter< export type RPCExporter<
Ifc extends T.RPCInterface = T.RPCInterface, Ifc extends T.RPCInterface = T.RPCInterface,
Name extends keyof Ifc = keyof Ifc, Name extends keyof Ifc = string,
SubresT = {} > = {
>{
name: Name 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 * as I from "./Interfaces";
import { RPCSocket } from "./Frontend";
export type AnyFunction = (...args:any) => any export type AnyFunction = (...args:any) => any
export type HookFunction<F extends AnyFunction = AnyFunction, SubresT = {}> = (...args: Parameters<F>) => Promise<SubscriptionResponse<SubresT> | ErrorResponse> export type HookFunction = AnyFunction
export type AccessFilter<InterfaceT extends RPCInterface, SubresT> = (sesame:string|undefined, exporter: I.RPCExporter<RPCInterface<InterfaceT>, keyof InterfaceT, SubresT>) => Promise<boolean> 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 Visibility = "127.0.0.1" | "0.0.0.0"
export type ConnectionHandler = (socket:I.Socket) => void export type ConnectionHandler = (socket:I.Socket) => void
export type ErrorHandler = (socket:I.Socket, error:any, rpcName: string, args: any[]) => void export type ErrorHandler = (socket:I.Socket, error:any, rpcName: string, args: any[]) => void
@@ -17,8 +18,12 @@ export type FrontEndHandlerType = {
'close' : () => void 'close' : () => void
} }
export type ServerConf<InterfaceT extends RPCInterface, SubresT> = { export type ExporterArray<InterfaceT extends RPCInterface = RPCInterface> = I.RPCExporter<RPCInterface<InterfaceT>, keyof InterfaceT>[]
accessFilter?: AccessFilter<InterfaceT, SubresT>
export type ConnectedSocket<T extends RPCInterface = RPCInterface> = RPCSocket & T
export type ServerConf<InterfaceT extends RPCInterface> = {
accessFilter?: AccessFilter<InterfaceT>
connectionHandler?: ConnectionHandler connectionHandler?: ConnectionHandler
errorHandler?: ErrorHandler errorHandler?: ErrorHandler
closeHandler?: CloseHandler closeHandler?: CloseHandler
@@ -36,23 +41,23 @@ export type Outcome = "Success" | "Error"
export type Respose<T> = T & { result: Outcome } export type Respose<T> = T & { result: Outcome }
export type SuccessResponse<T = {}> = Respose<T> & { result: "Success" } export type SuccessResponse<T = {}> = Respose<T> & { result: "Success" }
export type ErrorResponse<T = {}> = Respose<T> & { result: "Error", message?:string } 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 RPCType = 'Hook' | 'Unhook' | 'Call'
export type CallRPC<N, F> = { export type CallRPC<Name, Func extends AnyFunction> = {
name: N name: Name
call: F call: Func
} }
export type HookRPC<N, F extends AnyFunction, SubresT = {}> = {
name: N export type HookRPC<Name, Func extends AnyFunction> = {
hook: HookFunction<F, SubresT> name: Name
hook: Func
onCallback?: AnyFunction 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 = {}> = { export type RPCInterface<Impl extends RPCInterface = {}> = {
[grp in string] : { [grp in string] : {
@@ -60,10 +65,13 @@ export type RPCInterface<Impl extends RPCInterface = {}> = {
} }
} & Impl } & Impl
export type RPCInterfaceArray<Itfc extends RPCInterface, SubresT = {}> = { export type exportT = {
[grp in keyof Itfc]: Array< [group in string]: {}
{ [rpc in keyof Itfc[grp]]: RPC<rpc, Itfc[grp][rpc], SubresT> }[keyof Itfc[grp]] }
>
//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 = { export type BaseInfo = {
@@ -86,4 +94,4 @@ export type RpcInfo = HookInfo | CallInfo
export type ExtendedRpcInfo = RpcInfo & { uniqueName: string } export type ExtendedRpcInfo = RpcInfo & { uniqueName: string }
export type OnFunction = <T extends "error" | "close">(type: T, f: FrontEndHandlerType[T]) => void 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 T from "./Types";
import * as I from "./Interfaces"; import * as I from "./Interfaces";
import { SubscriptionResponse } from "./Types";
/** /**
* Translate an RPC to RPCInfo for serialization. * 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 * @param sesame optional sesame phrase to prepend before all RPC arguments
* @throws Error on RPC without name property * @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){ switch (typeof rpc){
case "object": case "object":
if(rpc['call']){ 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 call: sesame?async (_sesame, ...args) => {if(sesame(_sesame)) return await rpc['call'].apply({}, args); socket.destroy()}:rpc['call'], // check & remove sesame
} }
}else{ }else{
const generator = hookGenerator(<T.HookRPC<any, any, any>>rpc, errorHandler, sesame) const generator = hookGenerator(<T.HookRPC<any, any>>rpc, errorHandler, sesame)
return { return {
owner: owner, owner: owner,
argNames: extractArgs(generator(undefined)), argNames: extractArgs(generator(undefined)),
@@ -58,10 +57,9 @@ RPC did not provide a name.
* @param exporter The exporter * @param exporter The exporter
* @param makeUnique @default true Attach a suffix to RPC names * @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 owner = exporter.name
const RPCs = [...exporter.exportRPCs()] const RPCs = exporter.exportRPCs()
return RPCs.map(rpc => rpcToRpcinfo(socket, rpc, owner, errorHandler, sesame)) return RPCs.map(rpc => rpcToRpcinfo(socket, rpc, owner, errorHandler, sesame))
.map(info => { .map(info => {
@@ -107,7 +105,7 @@ export function stripAfterEquals(str:string):string{
* @param rpc The RPC to transform * @param rpc The RPC to transform
* @returns A {@link HookFunction} * @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) let argsArr = extractArgs(rpc.hook)
argsArr.pop() //remove 'callback' from the end argsArr.pop() //remove 'callback' from the end
let callArgs = argsArr.join(',') 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 //note rpc.hook is the associated RPC, not a socket.hook
return eval(` return eval(`
(socket) => async (${args}) => { (clientSocket) => async (${args}) => {
try{ try{
if(sesameFn && !sesameFn(sesame)) return if(sesameFn && !sesameFn(sesame)) return
const uuid = uuidv4()
const res = await rpc.hook(${callArgs} (...cbargs) => { const res = await rpc.hook(${callArgs} (...cbargs) => {
if(rpc.onCallback) rpc.onCallback.apply({}, cbargs) if(rpc.onCallback){
socket.call.apply(socket, [res.uuid, ...cbargs]) 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){ }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(',') : [] 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 { export function makeSesameFunction (sesame : T.SesameFunction | string) : T.SesameFunction {
if(typeof sesame === 'function'){ if(typeof sesame === 'function'){
+284 -241
View File
@@ -1,45 +1,44 @@
import { describe, it, Func } from "mocha"; import { describe, it } from "mocha";
import { RPCServer, RPCSocket } from '../Index'
import { RPCServer, RPCSocket, SubscriptionResponse, makeSubResponse } from '../Index'
import * as uuidv4 from "uuid/v4"
import { doesNotReject } from "assert";
import { Socket } from "dgram";
import { RPCExporter } from "../src/Interfaces"; 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)} const add = (...args: number[]) => { return args.reduce((a, b) => a + b, 0) }
function makeServer(){ function makeServer() {
let subcallback let subcallback
return new RPCServer<{ topic: string }>(21010, [{ return new RPCServer(21010, [{
name: "test", name: 'test',
exportRPCs: () => [ exportRPCs: () => [
{ {
name: 'echo', name: 'echo',
call: async (s:string) => s, call: async (s: string) => s,
},{ }, {
name: 'simpleSubscribe', name: 'simpleSubscribe',
hook: async(callback) => { hook: async (callback) => {
subcallback = callback subcallback = callback
return makeSubResponse<{topic: string}>({topic: "test"}) return { topic: "test" }
} },
},{ onClose: (res) => { }
}, {
name: 'subscribe', name: 'subscribe',
hook: async (callback) => { hook: async (callback) => {
subcallback = callback subcallback = callback
return makeSubResponse<{topic: string}>({topic: "test"}) return { topic: "test" }
}, },
onClose: (res, rpc) => { onClose: (res, rpc) => {
console.log("onClose", rpc.name === 'subscribe' && res?"OK":"") console.log("onClose", rpc.name === 'subscribe' && res ? "OK" : "")
subcallback = null subcallback = null
}, },
onCallback: (...args:any) => { onCallback: (...args: any) => {
console.log("onCallback", args[0] === "test" && args[1] === "callback"?"OK":"") console.log("onCallback", args[0] === "test" && args[1] === "callback" ? "OK" : "")
} }
}, },
add, add,
function triggerCallback(...messages:any[]):number {return subcallback.apply({}, messages)}, function triggerCallback(...messages: any[]): number { return subcallback.apply({}, messages) },
] ]
}],{ }], {
connectionHandler: (socket) => { }, connectionHandler: (socket) => { },
closeHandler: (socket) => { }, closeHandler: (socket) => { },
errorHandler: (socket, err) => { throw err } errorHandler: (socket, err) => { throw err }
}) })
@@ -47,57 +46,58 @@ function makeServer(){
describe('RPCServer', () => { describe('RPCServer', () => {
let server: RPCServer<{ topic: string }, any> let client, server
const echo = (x) => x
before(() => { before(done => {
server = makeServer() server = new RPCServer(21003, [{
})
after(() => {
server.destroy()
})
it('should be able to use all kinds of RPC definitions', (done) => {
const echo = (x) => x
const server = new RPCServer(21003, [{
name: 'HelloWorldRPCGroup', name: 'HelloWorldRPCGroup',
exportRPCs: () => [ exportRPCs: () => [
echo, //named function variable echo, //named function variable
function echof(x){ return x }, //named function function echof(x) { return x }, //named function
{ {
name: 'echoExplicit', //describing object name: 'echoExplicit', //describing object
call: async (x,y,z) => [x,y,z] call: async (x, y, z) => [x, y, z]
} }
] ]
}]) }])
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 () => { client.connect().then(async () => {
const r0 = await client['HelloWorldRPCGroup'].echo('Hello') const r0 = await client['HelloWorldRPCGroup'].echo('Hello')
const r1 = await client['HelloWorldRPCGroup'].echof('World') const r1 = await client['HelloWorldRPCGroup'].echof('World')
const r2 = await client['HelloWorldRPCGroup'].echoExplicit('R','P','C!') const r2 = await client['HelloWorldRPCGroup'].echoExplicit('R', 'P', 'C!')
if(r0 === 'Hello' && r1 === 'World' && r2.join('') ==='RPC!'){
client.destroy() if (r0 === 'Hello' && r1 === 'World' && r2.join('') === 'RPC!') {
server.destroy()
done() done()
}else{
done(new Error("Bad response"))
} }
}) })
}) })
it('new RPCServer() should fail on bad RPC', (done) => { it('new RPCServer() should fail on bad RPC', (done) => {
try{ try {
new RPCServer(20001, [{ new RPCServer(20001, [{
name: "bad", name: 'bad',
exportRPCs: () => [ exportRPCs: () => [
(aaa,bbb,ccc) => { return aaa+bbb+ccc } (aaa, bbb, ccc) => { return aaa + bbb + ccc }
] ]
}]) }])
done(new Error("Didn't fail with bad RPC")) done(new Error("Didn't fail with bad RPC"))
}catch(badRPCError){ } catch (badRPCError) {
done() done()
} }
}) })
@@ -106,9 +106,9 @@ describe('RPCServer', () => {
describe('RPCSocket', () => { describe('RPCSocket', () => {
let client: RPCSocket let client: RPCSocket
let server: RPCServer<{topic: string}> let server: RPCServer
before(async() => { before(async () => {
server = makeServer() server = makeServer()
client = new RPCSocket(21010, "localhost") client = new RPCSocket(21010, "localhost")
return await client.connect() return await client.connect()
@@ -122,7 +122,7 @@ describe('RPCSocket', () => {
it('should have rpc echo', (done) => { it('should have rpc echo', (done) => {
client['test'].echo("x").then(x => { client['test'].echo("x").then(x => {
if(x === 'x') if (x === 'x')
done() done()
else else
done(new Error('echo RPC response did not match')) done(new Error('echo RPC response did not match'))
@@ -130,8 +130,8 @@ describe('RPCSocket', () => {
}) })
it('should add up to 6', (done) => { it('should add up to 6', (done) => {
client['test'].add(1,2,3).then(x => { client['test'].add(1, 2, 3).then(x => {
if(x === 6) if (x === 6)
done() done()
else else
done(new Error('add RPC response did not match')) done(new Error('add RPC response did not match'))
@@ -140,9 +140,9 @@ describe('RPCSocket', () => {
it('should subscribe with success', (done) => { it('should subscribe with success', (done) => {
client['test'].simpleSubscribe(console.log).then(res => { client['test'].simpleSubscribe(console.log).then(res => {
if(res.result === 'Success'){ if (res.topic === 'test') {
done() done()
}else{ } else {
console.error(res) console.error(res)
done(new Error('Subscribe did not return success')) done(new Error('Subscribe did not return success'))
} }
@@ -151,72 +151,66 @@ describe('RPCSocket', () => {
it('subscribe should call back', (done) => { it('subscribe should call back', (done) => {
client['test'].subscribe((...args: any) => { client['test'].subscribe((...args: any) => {
if(args[0] === "test" && args[1] === "callback") if (args[0] === "test" && args[1] === "callback")
done() done()
else else
done(new Error("Bad callback value "+ args)) done(new Error("Bad callback value " + args))
}).then( async () => { }).then(async () => {
await client['test'].triggerCallback("test", "callback") await client['test'].triggerCallback("test", "callback")
}) })
}) })
it('simpleSubscribe should call back', (done) => { it('simpleSubscribe should call back', (done) => {
client['test'].simpleSubscribe((...args: any) => { client['test'].simpleSubscribe((...args: any) => {
if(args[0] === "test_" && args[1] === "callback_") if (args[0] === "test_" && args[1] === "callback_")
done() done()
else else
done(new Error("Bad callback value "+ args)) done(new Error("Bad callback value " + args))
}).then( async () => { }).then(async () => {
await client['test'].triggerCallback("test_", "callback_") await client['test'].triggerCallback("test_", "callback_")
}) })
}) })
}) })
describe('It should do unhook', () => { describe('It should do unhook', () => {
let candy = "OK" const yesCandy = "OK"
const noCandy = "stolen"
let candy = yesCandy
let cb: Function let cb: Function
let cb2: Function let cb2: Function
let client: RPCSocket let client: RPCSocket
let server: RPCServer<{topic: string}> let server: RPCServer
before(async() => { before(async () => {
server = new RPCServer<{ topic: string }>(21010, [{ server = new RPCServer(21010, [{
name: "test", name: "test",
exportRPCs: () => [{ exportRPCs: () => [{
name: 'subscribe', name: 'subscribe',
hook: async(callback):Promise<SubscriptionResponse<{topic:string}>> => { hook: async (callback): Promise<void> => {
cb = <Function> callback cb = <Function>callback
return { return
result: "Success",
uuid: uuidv4(),
topic: "test"
}
} }
}, },
{ {
name: 'subscribeWithParam', name: 'subscribeWithParam',
hook: async(param, callback):Promise<SubscriptionResponse<{topic:string}>> => { hook: async (param, callback): Promise<{ uuid: string }> => {
if(param != "OK"){ if (param != "OK") {
console.log("param was"+ param); console.log("param was" + param);
return { return {
result: "Success",
uuid: "no", uuid: "no",
topic: "test"
} }
} }
cb2 = <Function> callback cb2 = <Function>callback
return { return {
result: "Success",
uuid: "OK", uuid: "OK",
topic: "test"
} }
} }
}, },
function checkCandy():string { cb(candy); return candy }, function publish(): string { cb(candy); return candy },
function stealCandy():string { candy = "_OK"; cb(candy); cb = () => {}; return candy } function unsubscribe(): string { candy = noCandy; cb(candy); cb = () => { }; return candy }
] ]
}],{ }], {
connectionHandler: (socket) => { }, connectionHandler: (socket) => { },
closeHandler: (socket) => { }, closeHandler: (socket) => { },
errorHandler: (socket, err) => { throw err } errorHandler: (socket, err) => { throw err }
@@ -231,67 +225,88 @@ describe('It should do unhook', () => {
}) })
it('Subscribe with param', (done) => { it('Subscribe with param', (done) => {
client['test'].subscribeWithParam("OK", c => {}).then( async (res: SubscriptionResponse) => { client['test'].subscribeWithParam("OK", c => { }).then(async (res) => {
if(res.uuid === "OK"){ if (res.uuid === candy) {
done() done()
}else } else
done(new Error("Results did not match "+res.uuid)) done(new Error("Results did not match " + res.uuid))
}) })
}) })
it('Unhook+unsubscribe should stop callbacks', (done) => { let run = 0
client['test'].subscribe(c => {}).then( async (res: SubscriptionResponse) => { const expected = [yesCandy, noCandy, noCandy, noCandy]
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()
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() done()
else else
done(new Error("Results did not match: "+[r1,r2,r3,r4])) done(new Error("Results did not match: " + [r1, r2, r3, r4]))
}) })
}) })
}) })
type topicDTO = { topic: string; }
type SesameTestIfc = { type SesameTestIfc = {
test: { test: {
checkCandy: ()=>Promise<string> 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', () => { describe('Sesame should unlock the socket', () => {
let candy = "OK" let candy = "OK"
let client: RPCSocket & SesameTestIfc let client: ConnectedSocket<SesameTestIfc>
let server: RPCServer let server: RPCServer<SesameTestIfc>
let cb = (...args) => {} let cb: Function = (...args) => { }
before((done) => { before((done) => {
server = new RPCServer(21004, [{ server = new RPCServer<SesameTestIfc>(21004, [{
name: "test", name: "test",
exportRPCs: () => [ exportRPCs: () => [
{ {
name: 'subscribe', name: 'subscribe',
hook: async(callback) => { hook: async (callback) => {
cb = callback cb = callback
return <SubscriptionResponse>{ return {
result: "Success",
uuid: uuidv4(),
topic: 'test' topic: 'test'
} }
} },
onClose: (a) => { }
}, },
async function checkCandy():Promise<string> { cb(candy); cb=()=>{}; return candy }, async function checkCandy() { cb(candy); cb = () => { }; return candy },
async function manyParams(a,b,c,d) {return [a,b,c,d]} 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!' sesame: (_sesame) => _sesame === 'sesame!'
}) })
const sock = new RPCSocket(21004, "localhost") const sock = new RPCSocket<SesameTestIfc>(21004, "localhost")
sock.connect<SesameTestIfc>('sesame!').then(cli => { sock.connect('sesame!').then(cli => {
client = cli client = cli
done() done()
}) })
@@ -307,8 +322,8 @@ describe('Sesame should unlock the socket', () => {
}) })
it('should work with multiple params', (done) => { it('should work with multiple params', (done) => {
client.test['manyParams']('a','b','c','d').then(c => { client.test['manyParams']('a', 'b', 'c', 'd').then(c => {
if(c[0] == 'a' && c[1] === 'b' && c[2] === 'c' && c[3] === 'd') if (c[0] == 'a' && c[1] === 'b' && c[2] === 'c' && c[3] === 'd')
done() done()
}) })
}) })
@@ -316,9 +331,9 @@ describe('Sesame should unlock the socket', () => {
it('should not work without sesame', (done) => { it('should not work without sesame', (done) => {
const sock = new RPCSocket(21004, "localhost") const sock = new RPCSocket(21004, "localhost")
sock.connect<SesameTestIfc>( /* no sesame */).then(async (cli) => { sock.connect<SesameTestIfc>( /* no sesame */).then(async (cli) => {
if(!cli.test) if (!cli.test)
done() done()
else{ else {
done(new Error("Function supposed to be removed without sesame")) done(new Error("Function supposed to be removed without sesame"))
} }
cli.destroy() cli.destroy()
@@ -329,9 +344,9 @@ describe('Sesame should unlock the socket', () => {
it('should fail with wrong sesame', (done) => { it('should fail with wrong sesame', (done) => {
const sock = new RPCSocket(21004, "localhost") const sock = new RPCSocket(21004, "localhost")
sock.connect<SesameTestIfc>('abasd').then(async (cli) => { sock.connect<SesameTestIfc>('abasd').then(async (cli) => {
if(!cli.test) if (!cli.test)
done() done()
else{ else {
done(new Error("Function supposed to be removed without sesame")) done(new Error("Function supposed to be removed without sesame"))
} }
cli.destroy() cli.destroy()
@@ -341,12 +356,12 @@ describe('Sesame should unlock the socket', () => {
it('callback should work with sesame', (done) => { it('callback should work with sesame', (done) => {
client.test.subscribe((c) => { client.test.subscribe((c) => {
if(c === candy){ if (c === candy) {
done() done()
} }
}).then(d => { }).then(d => {
if(d.result !== 'Success') if (d.topic !== 'test')
done('unexpected valid response') done('unexpected invalid response')
client.test.checkCandy() client.test.checkCandy()
}) })
@@ -354,51 +369,56 @@ describe('Sesame should unlock the socket', () => {
}) })
describe('Error handling', ()=>{ describe('Error handling', () => {
const errtxt = "BAD BAD BAD"
let createUser = async( user: {a:any,b:any}) => { 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)=>{ it("RPC throws on client without handler", (done) => {
let server = new RPCServer(21004, [ { let server = new RPCServer(21004, [{
name: 'createUser' as 'createUser', name: "createUser",
exportRPCs: () => [{ exportRPCs: () => [{
name: 'createUser' as 'createUser', name: 'createUser' as 'createUser',
call: createUser call: createUser
}]}], { }]
}], {
}) })
let sock = new RPCSocket(21004, 'localhost') let sock = new RPCSocket(21004, 'localhost')
sock.connect().then((cli) => { sock.connect().then((cli) => {
cli["createUser"]["createUser"]({ cli["createUser"]["createUser"]({
a:'a', a: 'a',
b:'b' b: 'b'
})
.then(r => {
if(r != null)
done("UNEXPECTED RESULT " + r)
})
.catch((e) => {
//console.log("EXPECTED CLIENT EXCEPTION", String(e));
done()
})
.finally(() => {
cli.destroy()
sock.destroy()
server.destroy()
}) })
.then(r => {
if (r != null)
done(new Error("UNEXPECTED RESULT " + r))
})
.catch((e) => {
if (e.message === errtxt)
done()
else
done(e)
})
.finally(() => {
cli.destroy()
sock.destroy()
server.destroy()
})
}) })
}) })
it("RPC throws on server with handler", (done)=>{ it("RPC throws on server with handler", (done) => {
let server = new RPCServer(21004, [ { let server = new RPCServer(21004, [{
name: 'createUser' as 'createUser', name: "createUser",
exportRPCs: () => [{ exportRPCs: () => [{
name: 'createUser' as 'createUser', name: 'createUser' as 'createUser',
call: createUser call: createUser
}]}], { }]
}], {
errorHandler: (socket, e, rpcName, args) => { errorHandler: (socket, e, rpcName, args) => {
done() done()
} }
@@ -407,41 +427,44 @@ describe('Error handling', ()=>{
let sock = new RPCSocket(21004, 'localhost') let sock = new RPCSocket(21004, 'localhost')
sock.connect().then((cli) => { sock.connect().then((cli) => {
cli["createUser"]["createUser"]({ cli["createUser"]["createUser"]({
a:'a', a: 'a',
b:'b' b: 'b'
})
.then(r => {
if(r != null)
done("UNEXPECTED RESULT " + r)
})
.catch((e) => {
done("UNEXPECTED CLIENT ERROR " + e)
done(e)
})
.finally(() => {
cli.destroy()
sock.destroy()
server.destroy()
}) })
.then(r => {
if (r != null)
done("UNEXPECTED RESULT " + r)
})
.catch((e) => {
done("UNEXPECTED CLIENT ERROR " + e)
done(e)
})
.finally(() => {
cli.destroy()
sock.destroy()
server.destroy()
})
}) })
}) })
}) })
describe("Errorhandler functionality", ()=>{ describe("Errorhandler functionality", () => {
let createUser = async( user: {a:any,b:any}) => { const errtxt = "BAD BAD BAD"
throw new Error("BAD BAD BAD")
let createUser = async (user: { a: any, b: any }) => {
throw new Error(errtxt)
} }
it("correct values are passed to the handler", (done)=>{ it("correct values are passed to the handler", (done) => {
let server = new RPCServer(21004, [ { let server = new RPCServer(21004, [{
name: 'createUser' as 'createUser', name: "createUser",
exportRPCs: () => [{ exportRPCs: () => [{
name: 'createUser' as 'createUser', name: 'createUser' as 'createUser',
call: createUser call: createUser
}]}], { }]
}], {
errorHandler: (socket, e, rpcName, args) => { 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() done()
} }
}) })
@@ -449,36 +472,36 @@ describe("Errorhandler functionality", ()=>{
let sock = new RPCSocket(21004, 'localhost') let sock = new RPCSocket(21004, 'localhost')
sock.connect().then((cli) => { sock.connect().then((cli) => {
cli["createUser"]["createUser"]({ cli["createUser"]["createUser"]({
a:'a', a: 'a',
b:'b' b: 'b'
})
.then(r => {
if(r != null)
done("UNEXPECTED RESULT " + r)
})
.catch((e) => {
done("UNEXPECTED CLIENT ERROR " + e)
done(e)
})
.finally(() => {
cli.destroy()
sock.destroy()
server.destroy()
}) })
.then(r => {
if (r != null)
done("UNEXPECTED RESULT " + r)
})
.catch((e) => {
done(new Error("UNEXPECTED CLIENT ERROR " + e.message))
})
.finally(() => {
cli.destroy()
sock.destroy()
server.destroy()
})
}) })
}) })
it("handler sees sesame", (done)=>{ it("handler sees sesame", (done) => {
let sesame = "AAAAAAAAAAAAAAA" let sesame = "AAAAAAAAAAAAAAA"
let server = new RPCServer(21004, [ { let server = new RPCServer(21004, [{
name: 'createUser' as 'createUser', name: "createUser" as "createUser",
exportRPCs: () => [{ exportRPCs: () => [{
name: 'createUser' as 'createUser', name: 'createUser' as 'createUser',
call: createUser call: createUser
}]}], { }]
}], {
sesame: sesame, sesame: sesame,
errorHandler: (socket, e, rpcName, args) => { 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() done()
} }
@@ -487,54 +510,54 @@ describe("Errorhandler functionality", ()=>{
let sock = new RPCSocket(21004, 'localhost') let sock = new RPCSocket(21004, 'localhost')
sock.connect(sesame).then((cli) => { sock.connect(sesame).then((cli) => {
cli["createUser"]["createUser"]({ cli["createUser"]["createUser"]({
a:'a', a: 'a',
b:'b' b: 'b'
})
.then(r => {
if(r != null)
done("UNEXPECTED RESULT " + r)
})
.catch((e) => {
done("UNEXPECTED CLIENT ERROR " + e)
done(e)
})
.finally(() => {
cli.destroy()
sock.destroy()
server.destroy()
}) })
.then(r => {
if (r != null)
done("UNEXPECTED RESULT " + r)
})
.catch((e) => {
done("UNEXPECTED CLIENT ERROR " + e)
done(e)
})
.finally(() => {
cli.destroy()
sock.destroy()
server.destroy()
})
}) })
}) })
}) })
type myExporterIfc = { type myExporterIfc = {
MyExporter: { MyExporter: {
myRPC: ()=>Promise<string> myRPC: () => Promise<string>
} }
} }
describe("Class binding", ()=>{ describe("Class binding", () => {
let exporter1 : MyExporter let exporter1: MyExporter
let serv : RPCServer<{}, myExporterIfc> let serv: RPCServer<myExporterIfc>
let sock: RPCSocket & myExporterIfc let sock: RPCSocket & myExporterIfc
let allowed = true let allowed = true
class MyExporter implements RPCExporter<myExporterIfc>{ class MyExporter implements RPCExporter<myExporterIfc>{
name = "MyExporter" as "MyExporter"; name = "MyExporter" as "MyExporter"
exportRPCs = () => [ exportRPCs = () => [
this.myRPC this.myRPC
] ]
myRPC = async () => { myRPC = async () => {
serv.setExporters([new MyOtherExporter]) //serv.setExporters([new MyOtherExporter])
return "Hello World" return "Hello World"
} }
} }
class MyOtherExporter implements RPCExporter<myExporterIfc>{ class MyOtherExporter implements RPCExporter<myExporterIfc>{
name = "MyExporter" as "MyExporter"; name = "MyExporter" as "MyExporter"
exportRPCs = () => [ exportRPCs = () => [
this.myRPC this.myRPC
] ]
@@ -547,15 +570,14 @@ describe("Class binding", ()=>{
before(done => { before(done => {
exporter1 = new MyExporter() exporter1 = new MyExporter()
serv = new RPCServer<{}, myExporterIfc>(21004, [exporter1], { serv = new RPCServer<myExporterIfc>(21004, [exporter1], {
accessFilter: async (sesame,exporter) => { accessFilter: async (sesame, exporter) => {
switch(exporter.name){ if(exporter.name === 'MyExporter'){
case "MyExporter": if (!allowed) return false
if(!allowed) return false allowed = false
allowed = false return sesame === 'xxx';
return sesame==='xxx'; }else{
default: return false
return false
} }
}, },
sesame: "xxx" sesame: "xxx"
@@ -563,8 +585,7 @@ describe("Class binding", ()=>{
done() done()
}) })
beforeEach((done)=>{ beforeEach((done) => {
const s = new RPCSocket(21004, 'localhost') const s = new RPCSocket(21004, 'localhost')
s.connect<myExporterIfc>("xxx").then(conn => { s.connect<myExporterIfc>("xxx").then(conn => {
sock = conn sock = conn
@@ -572,7 +593,7 @@ describe("Class binding", ()=>{
}) })
}) })
afterEach(done => { afterEach((done) => {
sock.destroy() sock.destroy()
done() done()
}) })
@@ -581,7 +602,11 @@ describe("Class binding", ()=>{
serv.destroy() 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) => { sock['MyExporter'].myRPC().then((res) => {
done(new Error(res)) done(new Error(res))
}).catch(e => { }).catch(e => {
@@ -592,31 +617,42 @@ describe("Class binding", ()=>{
}) })
it("changes exporters", (done) => { it("changes exporters", (done) => {
sock['MyExporter'].myRPC().then((res) => { sock['MyExporter'].myRPC().then((res) => {
if(res === "Hello Borld") if (res === "Hello Borld")
done() done()
else else
done(new Error(res)) 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) => { it("use sesameFilter", (done) => {
if(!sock['MyExporter']) done() if (!sock['MyExporter']) done()
else done(new Error("RPC supposed to be gone")) else done(new Error("RPC supposed to be gone"))
}) })
}) })
describe("attaching handlers before connecting", ()=>{ describe("attaching handlers before connecting", () => {
it("fires error if server is unreachable", (done)=>{ it("fires error if server is unreachable", (done) => {
const sock = new RPCSocket(21004, 'localhost') const sock = new RPCSocket(21004, 'localhost')
let errorHandleCount = 0 let errorHandleCount = 0
sock.on('error', (err) => { sock.on('error', (err) => {
//attached listener fires first //attached listener fires first
if(errorHandleCount != 0){ if (errorHandleCount != 0) {
console.log("Error handler didn't fire first"); console.log("Error handler didn't fire first");
}else{ } else {
errorHandleCount++ errorHandleCount++
} }
}) })
@@ -625,16 +661,16 @@ describe("attaching handlers before connecting", ()=>{
console.log("Unexpected successful connect") console.log("Unexpected successful connect")
}).catch(e => { }).catch(e => {
//catch clause fires second //catch clause fires second
if(errorHandleCount != 1){ if (errorHandleCount != 1) {
console.log("catch clause didn't fire second"); console.log("catch clause didn't fire second");
}else{ } else {
sock.destroy() sock.destroy()
done() done()
} }
}) })
}) })
it("fires error if call is unknown", (done)=>{ it("fires error if call is unknown", (done) => {
const serv = new RPCServer(21004) const serv = new RPCServer(21004)
const sock = new RPCSocket(21004, 'localhost') const sock = new RPCSocket(21004, 'localhost')
@@ -645,14 +681,14 @@ describe("attaching handlers before connecting", ()=>{
}) })
sock.connect().then(_ => { sock.connect().then(_ => {
sock.call("unknownRPC123", "AAAAA").catch(e => { /* ignore */}) sock.call("unknownRPC123", "AAAAA").catch(e => { /* ignore */ })
}).catch(e => { }).catch(e => {
console.log("unexpected connect catch clause"); console.log("unexpected connect catch clause");
done(e) done(e)
}) })
}) })
it("demands catch on method invocation if call is unknown", (done)=>{ it("demands catch on method invocation if call is unknown", (done) => {
const serv = new RPCServer(21004) const serv = new RPCServer(21004)
const sock = new RPCSocket(21004, 'localhost') const sock = new RPCSocket(21004, 'localhost')
@@ -667,4 +703,11 @@ describe("attaching handlers before connecting", ()=>{
done(e) 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)
})