Compare commits

...
10 Commits
Author SHA1 Message Date
nitowa 217bb4a549 Remove tests from npm package. Oops 2023-01-10 07:06:11 +01:00
nitowa cbe7f6eb1a npm version bump 2023-01-10 07:02:14 +01:00
nitowa dbed0081db update to use socket.io 4.5.4 2023-01-10 07:01:49 +01:00
nitowa c138a2b9af removed serialization, was a security risk 2023-01-10 06:33:25 +01:00
nitowa ec158e6090 npm version bump(again) 2023-01-09 15:31:05 +01:00
nitowa c01a44ff3b npm version bump 2023-01-09 15:02:54 +01:00
nitowa 928d2662fd add getter for internal PromiseIO socket in RPCServer 2023-01-09 15:02:32 +01:00
nitowa b2ffbbfa48 before rewrite to have callbacks as FIRST parameter 2022-04-07 00:01:41 +02:00
nitowa a1f4055194 probably a working typecheck for callbacks 2022-04-05 05:41:08 +02:00
nitowa 7e7ec0deb8 npm version bump 2021-07-28 02:07:44 +02:00
12 changed files with 682 additions and 10860 deletions
+493 -10727
View File
File diff suppressed because it is too large Load Diff
+15 -12
View File
@@ -1,6 +1,6 @@
{ {
"name": "rpclibrary", "name": "rpclibrary",
"version": "2.4.1", "version": "2.5.1",
"description": "rpclibrary is a websocket RPC library", "description": "rpclibrary is a websocket RPC library",
"main": "./js/Index.js", "main": "./js/Index.js",
"repository": { "repository": {
@@ -46,24 +46,27 @@
"ts-mocha": "^6.0.0", "ts-mocha": "^6.0.0",
"typedoc": "^0.15.0", "typedoc": "^0.15.0",
"typedoc-plugin-markdown": "^2.2.6", "typedoc-plugin-markdown": "^2.2.6",
"typescript": "^3.5.3", "typescript": "^4.6.3",
"webpack": "^4.40.2", "webpack": "^5.71.0",
"webpack-cli": "^3.3.9", "webpack-cli": "^3.3.9",
"why-is-node-running": "^2.1.2" "why-is-node-running": "^2.1.2",
"@types/chai": "^4.2.21",
"@types/socket.io": "^3.0.2",
"@types/socket.io-client": "^3.0.0",
"chai": "^4.3.4",
"chai-as-promised": "^7.1.1"
}, },
"dependencies": { "dependencies": {
"@types/chai": "^4.2.21",
"@types/socket.io": "^2.1.8",
"@types/socket.io-client": "^1.4.33",
"chai": "^4.3.4",
"chai-as-promised": "^7.1.1",
"http": "0.0.0", "http": "0.0.0",
"socket.io": "^2.3.0", "socket.io": "^4.5.4",
"socket.io-client": "^2.3.0", "socket.io-client": "^4.5.4",
"socketio-wildcard": "^2.0.0", "socketio-wildcard": "^2.0.0",
"uuid": "^3.3.3" "uuid": "^3.3.3"
}, },
"files": [ "files": [
"js" "js/browser",
"js/src",
"js/Index.js",
"js/Index.d.ts"
] ]
} }
+9 -12
View File
@@ -5,7 +5,8 @@ import { PromiseIO } from "./PromiseIO/Server";
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 { BAD_CONFIG_PARAM, UNKNOWN_RPC_IDENTIFIER, UNKNOWN_RPC_SERVER } from './Strings'; import { BAD_CONFIG_PARAM, DESTROY_PREFIX, RPC_NO_NAME, UNKNOWN_RPC_IDENTIFIER, UNKNOWN_RPC_SERVER } from './Strings';
import { ServerOptions } from 'socket.io';
export class RPCServer< export class RPCServer<
InterfaceT extends T.RPCInterface = T.RPCInterface, InterfaceT extends T.RPCInterface = T.RPCInterface,
@@ -65,13 +66,7 @@ export class RPCServer<
let badRPC = exporters.flatMap(ex => typeof ex.RPCs === "function" ? ex.RPCs() : (ex as any)).find(rpc => !rpc.name) let badRPC = exporters.flatMap(ex => typeof ex.RPCs === "function" ? ex.RPCs() : (ex as any)).find(rpc => !rpc.name)
if (badRPC) { if (badRPC) {
throw new Error(` throw new Error(RPC_NO_NAME(badRPC.toString()))
RPC did not provide a name.
\nUse 'funtion name(..){ .. }' syntax instead.
\n
\n<------------OFFENDING RPC:
\n`+ badRPC.toString() + `
\n>------------OFFENDING RPC`)
} }
try { try {
@@ -86,13 +81,13 @@ export class RPCServer<
} }
} }
public attach = (httpServer = new http.Server(), options?: SocketIO.ServerOptions): RPCServer<InterfaceT> => { public attach = (httpServer = new http.Server(), options?: T.SomeOf<ServerOptions>): RPCServer<InterfaceT> => {
this.pio.attach(httpServer, options) this.pio.attach(httpServer, options)
this.attached = true this.attached = true
return this return this
} }
public listen(port: number, options?: SocketIO.ServerOptions): RPCServer<InterfaceT> { public listen(port: number, options?: T.SomeOf<ServerOptions>): RPCServer<InterfaceT> {
if (!this.attached) { if (!this.attached) {
this.attach(undefined, options) this.attach(undefined, options)
} else { } else {
@@ -114,15 +109,17 @@ export class RPCServer<
if (this.conf.throwOnUnknownRPC) { if (this.conf.throwOnUnknownRPC) {
clientSocket.on("*", (packet) => { clientSocket.on("*", (packet) => {
if (!infos.some(i => i.uniqueName === packet.data[0])) { if (!infos.some(i => i.uniqueName === packet.data[0])) {
if (packet.data[0].startsWith('destroy_')) return if (packet.data[0].startsWith(DESTROY_PREFIX)) return
this.errorHandler(clientSocket, new Error(UNKNOWN_RPC_SERVER(packet.data[0])), packet.data[0], [...packet.data].splice(1), true) this.errorHandler(clientSocket, new Error(UNKNOWN_RPC_SERVER(packet.data[0])), packet.data[0], [...packet.data].splice(1), true)
} }
}) })
} }
return infos return infos
}) })
}
public getSocket(): PromiseIO{
return this.pio
} }
close(): void { close(): void {
+31 -20
View File
@@ -4,7 +4,7 @@ import { PromiseIOClient, defaultClientConfig } from './PromiseIO/Client'
import * as T from './Types'; import * as T from './Types';
import * as I from './Interfaces'; import * as I from './Interfaces';
import { stripAfterEquals, appendComma } from './Utils'; import { stripAfterEquals, appendComma } from './Utils';
import { SOCKET_NOT_CONNECTED, UNKNOWN_RPC_IDENTIFIER, USER_DEFINED_TIMEOUT } from './Strings'; import { CALLBACK_NAME, DESTROY_PREFIX, SOCKET_NOT_CONNECTED, UNKNOWN_RPC_IDENTIFIER, USER_DEFINED_TIMEOUT } from './Strings';
/** /**
@@ -19,12 +19,12 @@ export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I
private socket: I.Socket private socket: I.Socket
private handlers: { private handlers: {
[name in string]: T.AnyFunction[] [name in string]: T.GenericFunction[]
} = { } = {
error: [], error: [],
close: [] close: []
} }
private hooks: { [name in string]: T.AnyFunction } = {} private hooks: { [name in string]: T.GenericFunction } = {}
/** /**
* *
@@ -80,7 +80,7 @@ export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I
* @param type 'error' or 'close' * @param type 'error' or 'close'
* @param f The listener to attach * @param f The listener to attach
*/ */
public on(type: string, f: T.AnyFunction) { public on(type: string, f: T.GenericFunction) {
if (!this.socket) { if (!this.socket) {
if (!this.handlers[type]) if (!this.handlers[type])
this.handlers[type] = [] this.handlers[type] = []
@@ -117,17 +117,21 @@ export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I
public async call(rpcname: string, ...args: any[]): Promise<any> { public async call(rpcname: string, ...args: any[]): Promise<any> {
if (!this.socket) throw new Error(SOCKET_NOT_CONNECTED) if (!this.socket) throw new Error(SOCKET_NOT_CONNECTED)
try { try {
if(!this.conf.callTimeoutMs || this.conf.callTimeoutMs <= 0) if(!this.conf.callTimeoutMs || this.conf.callTimeoutMs <= 0)
return await this.socket.call.apply(this.socket, [rpcname, ...args]) return await this.socket.call.apply(this.socket, [rpcname, ...args])
else else
return await Promise.race([ if(this.conf.callTimeoutMs){
this.socket.call.apply(this.socket, [rpcname, ...args]), return await Promise.race([
new Promise((_, rej) => { this.socket.call.apply(this.socket, [rpcname, ...args]),
setTimeout(_ => rej(USER_DEFINED_TIMEOUT(this.conf.callTimeoutMs)), this.conf.callTimeoutMs) new Promise((_, rej) => {
}) setTimeout(_ => rej(USER_DEFINED_TIMEOUT(this.conf.callTimeoutMs)), this.conf.callTimeoutMs)
]) })
])
}else{
return await this.socket.call.apply(this.socket, [rpcname, ...args])
}
} catch (e) { } catch (e) {
this.emit('error', e) this.emit('error', e)
throw e throw e
@@ -160,7 +164,7 @@ export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I
v.forEach(h => this.socket.on(k, h)) v.forEach(h => this.socket.on(k, h))
}) })
Object.entries(this.hooks).forEach((kv: [string, T.AnyFunction]) => { Object.entries(this.hooks).forEach((kv: [string, T.GenericFunction]) => {
this.socket.hook(kv[0], kv[1]) this.socket.hook(kv[0], kv[1])
}) })
const info: T.ExtendedRpcInfo[] = await this.info(sesame) const info: T.ExtendedRpcInfo[] = await this.info(sesame)
@@ -199,13 +203,13 @@ export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I
* @param fnName The function name * @param fnName The function name
* @param fnArgs A string-list of parameters * @param fnArgs A string-list of parameters
*/ */
private callGenerator(fnName: string, fnArgs: string[], sesame?: string): T.AnyFunction { private callGenerator(fnName: string, fnArgs: string[], sesame?: string): T.GenericFunction {
const headerArgs = fnArgs.join(",") const headerArgs = fnArgs.join(",")
const argParams = fnArgs.map(stripAfterEquals).join(",") const argParams = fnArgs.map(stripAfterEquals).join(",")
sesame = appendComma(sesame) sesame = appendComma(sesame)
return eval(`async (${headerArgs}) => { return eval(`async (${headerArgs}) => {
return await this.call("${fnName}", ${sesame} ${argParams}) const returnvalue = await this.call("${fnName}", ${sesame} ${argParams})
return returnvalue
}`) }`)
} }
@@ -214,7 +218,7 @@ export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I
* @param fnName The function name * @param fnName The function name
* @param fnArgs A string-list of parameters * @param fnArgs A string-list of parameters
*/ */
private frontEndHookGenerator(fnName: string, fnArgs: string[], sesame?: string): T.HookFunction { private frontEndHookGenerator(fnName: string, fnArgs: string[], sesame?: string): T.GenericFunction {
if (sesame) if (sesame)
fnArgs.shift() fnArgs.shift()
@@ -223,18 +227,25 @@ export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I
const argParams = fnArgs.map(stripAfterEquals).join(",") const argParams = fnArgs.map(stripAfterEquals).join(",")
sesame = appendComma(sesame, true) sesame = appendComma(sesame, true)
headerArgs = fnArgs.length > 0 ? headerArgs + "," : headerArgs headerArgs = fnArgs.length > 0 ? headerArgs + "," : headerArgs
const destroy_prefix = DESTROY_PREFIX
const frontendHookStr = ` const frontendHookStr = `
async (${headerArgs} $__callback__$) => { async (${CALLBACK_NAME}, ${headerArgs}) => {
const r = await this.call("${fnName}", ${sesame} ${argParams}) const r = await this.call("${fnName}", ${sesame} ${argParams})
try{ try{
if(r){ if(r){
if(r.uuid){ if(r.uuid){
$__callback__$['destroy'] = () => { ${CALLBACK_NAME}['destroy'] = () => {
this.socket.fire('destroy_'+r.uuid) this.socket.fire(destroy_prefix+r.uuid)
this.socket.unhook(r.uuid) this.socket.unhook(r.uuid)
} }
this.socket.hook(r.uuid, $__callback__$) ${CALLBACK_NAME} = ${CALLBACK_NAME}.bind({
destroy: ${CALLBACK_NAME}['destroy']
})
this.socket.hook(r.uuid, (...args) => {
${CALLBACK_NAME}.apply(${CALLBACK_NAME}, args)
})
} }
return r.return return r.return
}else{ }else{
+2 -2
View File
@@ -15,10 +15,10 @@ export interface Socket {
id?: string id?: string
bind: (name: string, listener: T.PioBindListener) => void bind: (name: string, listener: T.PioBindListener) => void
hook: (rpcname: string, handler: T.PioHookListener) => void hook: (rpcname: string, handler: T.PioHookListener) => void
unhook: (rpcname: string, listener?:T.AnyFunction) => void unhook: (rpcname: string, listener?:T.GenericFunction) => void
call: (rpcname: string, ...args: any[]) => Promise<any> call: (rpcname: string, ...args: any[]) => Promise<any>
fire: (rpcname: string, ...args: any[]) => Promise<any> fire: (rpcname: string, ...args: any[]) => Promise<any>
on: (type: string, f: T.AnyFunction)=>any on: (type: string, f: T.GenericFunction)=>any
emit: (eventName: string, ...args: any[]) => void emit: (eventName: string, ...args: any[]) => void
close(): void close(): void
} }
+1 -1
View File
@@ -22,7 +22,7 @@ export class PromiseIOClient {
} }
const address = `${host}:${port}` const address = `${host}:${port}`
const socket = socketio(`${options.protocol?options.protocol:'http'}://${address}`, options) const socket = socketio.io(`${options.protocol?options.protocol:'http'}://${address}`, options)
socket.on('connect_error', e => { socket.on('connect_error', e => {
sock.emit('error', e) sock.emit('error', e)
+5 -7
View File
@@ -5,10 +5,8 @@ import * as T from '../Types'
import socketio = require('socket.io') import socketio = require('socket.io')
import middleware = require('socketio-wildcard'); import middleware = require('socketio-wildcard');
const defaultConfig : socketio.ServerOptions = {
cookie: false, const defaultConfig : T.SomeOf<socketio.ServerOptions> = {}
path: '/socket.io',
}
export class PromiseIO { export class PromiseIO {
io?: Server io?: Server
@@ -18,12 +16,12 @@ export class PromiseIO {
connect: [] connect: []
} }
attach(httpServer: httpServer, options: socketio.ServerOptions = defaultConfig) { attach(httpServer: httpServer, options: T.SomeOf<socketio.ServerOptions> = defaultConfig) {
if(options.path && !options.path.startsWith('/')){ if(options.path && !options.path.startsWith('/')){
options.path = "/"+options.path options.path = "/"+options.path
} }
this.httpServer = httpServer this.httpServer = httpServer
this.io = socketio(httpServer, options) this.io = new Server(httpServer, options)
this.io!.use(middleware()) this.io!.use(middleware())
this.io!.on('connection', (clientSocket: Socket) => { this.io!.on('connection', (clientSocket: Socket) => {
@@ -50,7 +48,7 @@ export class PromiseIO {
this.httpServer!.listen(port) this.httpServer!.listen(port)
} }
on(eventName: string, listener: T.AnyFunction) { on(eventName: string, listener: T.GenericFunction) {
if (this.listeners[eventName] == null) { if (this.listeners[eventName] == null) {
this.listeners[eventName] = [] this.listeners[eventName] = []
} }
+3
View File
@@ -12,3 +12,6 @@ RPC did not provide a name.
\n<------------OFFENDING RPC: \n<------------OFFENDING RPC:
\n${name} \n${name}
\n>------------OFFENDING RPC` \n>------------OFFENDING RPC`
export const CLASSNAME_ATTRIBUTE = "$__CLASSNAME__$"
export const DESTROY_PREFIX = "$__DESTROY__$_"
export const CALLBACK_NAME = "$__CALLBACK__$"
+73 -30
View File
@@ -1,29 +1,51 @@
import * as I from "./Interfaces"; import * as I from "./Interfaces";
import { RPCSocket } from "./Frontend"; import { RPCSocket } from "./Frontend";
import { PromiseIO } from "./PromiseIO/Server"; import { ManagerOptions, SocketOptions } from 'socket.io-client'
export type PioBindListener = (...args: any) => void export type PioBindListener = (...args: any) => void
export type PioHookListener = AnyFunction export type PioHookListener = GenericFunction
export type AnyFunction = (...args:any) => any export type GenericFunction<Parameters extends any[] = any[], Result = any> = {(...args: Parameters): Result}
export type HookFunction = AnyFunction
export type AccessFilter<InterfaceT extends RPCInterface = RPCInterface> = (sesame:string|undefined, exporter: I.RPCExporter<InterfaceT, keyof InterfaceT>) => Promise<boolean> | boolean export type BackendHook<Func extends GenericFunction> =
GenericFunction<
[
GenericFunction<
Parameters<
AsFunction<
Parameters<Func>[0]
>
>,
void
>,
...Tail<Parameters<Func>>
],
ReturnType<Func>
>
export type AccessFilter<InterfaceT extends RPCInterface = RPCInterface> = (sesame: string | undefined, exporter: I.RPCExporter<InterfaceT, keyof InterfaceT>) => Promise<boolean> | 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
export type CloseHandler = (socket:I.Socket) => void export type CloseHandler = (socket: I.Socket) => void
export type SesameFunction = (sesame : string) => boolean export type SesameFunction = (sesame: string) => boolean
export type SesameConf = { export type SesameConf = {
sesame?: string | SesameFunction sesame?: string | SesameFunction
} }
export type FrontEndHandlerType = { export type FrontEndHandlerType = {
'error' : (e: any) => void 'error': (e: any) => void
'close' : () => void 'close': () => void
} }
export type ClientConfig = SocketIOClient.ConnectOpts & { export type ClientConfig = Partial<ManagerOptions & SocketOptions> & {
protocol?: 'http' | 'https', protocol?: 'http' | 'https',
callTimeoutMs?: number callTimeoutMs?: number
} }
export type SomeOf<T> = {
[key in keyof T]?: T[key]
}
export type ExporterArray<InterfaceT extends RPCInterface = RPCInterface> = I.RPCExporter<RPCInterface<InterfaceT>, keyof InterfaceT>[] export type ExporterArray<InterfaceT extends RPCInterface = RPCInterface> = I.RPCExporter<RPCInterface<InterfaceT>, keyof InterfaceT>[]
export type ConnectedSocket<T extends RPCInterface = RPCInterface> = RPCSocket & AsyncIfc<T> export type ConnectedSocket<T extends RPCInterface = RPCInterface> = RPCSocket & AsyncIfc<T>
@@ -41,28 +63,27 @@ 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 RPCType = 'Hook' | 'Unhook' | 'Call' export type RPCType = 'Hook' | 'Unhook' | 'Call'
export type CallRPC<Name, Func extends AnyFunction> = { export type CallRPC<Name, Func extends GenericFunction> = {
name: Name name: Name
call: Func call: Func
} }
export type HookRPC<Name, Func extends GenericFunction> = {
export type HookRPC<Name, Func extends AnyFunction> = {
name: Name name: Name
hook: Func hook: BackendHook<Func>
onCallback?: AnyFunction onCallback?: GenericFunction
onDestroy?: HookCloseFunction<ReturnType<Func> extends Promise<infer T> ? T : ReturnType<Func>> onDestroy?: HookCloseFunction<ReturnType<Func> extends Promise<infer T> ? T : ReturnType<Func>>
} }
export type RPC<Name, Func extends AnyFunction> = HookRPC<Name, Func> | CallRPC<Name,Func> | Func export type RPC<Name, Func extends GenericFunction> = 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]: {
[rpc in string] : AnyFunction [rpc in string]: GenericFunction
} }
} & Impl } & Impl
@@ -71,7 +92,9 @@ export type exportT = {
} }
export type RPCDefinitions<Ifc extends RPCInterface> = { export type RPCDefinitions<Ifc extends RPCInterface> = {
[grp in keyof Ifc]:( { [rpc in keyof Ifc[grp]]: RPC<rpc, Ifc[grp][rpc]> }[keyof Ifc[grp]] )[] [grp in keyof Ifc]: ({
[rpc in keyof Ifc[grp]]: RPC<rpc, Ifc[grp][rpc]>
}[keyof Ifc[grp]])[]
} }
export type BaseInfo = { export type BaseInfo = {
@@ -82,23 +105,43 @@ export type BaseInfo = {
export type HookInfo<SubresT = {}> = BaseInfo & { export type HookInfo<SubresT = {}> = BaseInfo & {
type: 'Hook', type: 'Hook',
generator: (socket?:I.Socket) => (...args:any[]) => SubresT generator: (socket?: I.Socket) => (...args: any[]) => SubresT
} }
export type CallInfo = BaseInfo & { export type CallInfo = BaseInfo & {
type: 'Call', type: 'Call',
call: AnyFunction call: GenericFunction
} }
export type RpcInfo = HookInfo | CallInfo export type RpcInfo = HookInfo | CallInfo
export type ExtendedRpcInfo = RpcInfo & { uniqueName: string } export type ExtendedRpcInfo = RpcInfo & { uniqueName: string }
export type OnFunction = <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: T, rpc:HookRPC<any, any>) => any export type HookCloseFunction<T> = (res: T, rpc: HookRPC<any, any>) => any
export type AsyncIfc<Ifc extends RPCInterface> = { [grp in keyof Ifc]: {[rpcname in keyof Ifc[grp]] : AsyncAnyFunction<Ifc[grp][rpcname]> } } export type AsyncIfc<Ifc extends RPCInterface> = { [grp in keyof Ifc]: { [rpcname in keyof Ifc[grp]]: AsyncGenericFunction<Ifc[grp][rpcname]> } }
export type AsyncAnyFunction<F extends AnyFunction = AnyFunction> = F extends (...args: Parameters<F>) => infer R export type AsyncGenericFunction<F extends GenericFunction = GenericFunction> = F extends (...args: Parameters<F>) => infer R
? ((...args: Parameters<F>) => R extends Promise<any> ? R : Promise<R> ) ? ((...args: Parameters<F>) => R extends Promise<any> ? R : Promise<R>)
: Promise<any> : Promise<any>
type Destroyable = { destroy: () => void }
export type Callback<Params extends any[] = []> =
(this: Destroyable, ...args: Params) => void
type AsFunction<F> = F extends GenericFunction ? F : GenericFunction
type Last<Tuple extends any[]> = Tuple[ Subtract<Length<Tuple>, 1> ]
type Tail<T extends any[]> = T extends [any, ...infer R] ? R : any[]
type Length<T extends any[]> =
T extends { length: infer L } ? L : never;
type BuildTuple<L extends number, T extends any[] = []> =
T extends { length: L } ? T : BuildTuple<L, [...T, any]>;
type Subtract<A extends number, B extends number> =
BuildTuple<A> extends [...(infer U), ...BuildTuple<B>]
? Length<U>
: never;
+11 -10
View File
@@ -3,7 +3,7 @@ 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 { Socket } from "socket.io" import { Socket } from "socket.io"
import { CALL_NOT_FOUND, RPC_BAD_TYPE, RPC_NO_NAME } from "./Strings"; import { CALL_NOT_FOUND, DESTROY_PREFIX, RPC_BAD_TYPE, RPC_NO_NAME } from "./Strings";
/** /**
* Translate an RPC to RPCInfo for serialization. * Translate an RPC to RPCInfo for serialization.
@@ -75,14 +75,15 @@ export function rpcHooker(socket: I.Socket, exporter: I.RPCExporter<any, any>, e
* Decorate an RPC with the error handler * Decorate an RPC with the error handler
* @param rpcFunction the function to decorate * @param rpcFunction the function to decorate
*/ */
const callGenerator = (rpcName: string, $__socket__$: I.Socket, rpcFunction: T.AnyFunction, errorHandler: T.ErrorHandler): T.AnyFunction => { const callGenerator = (rpcName: string, $__socket__$: I.Socket, rpcFunction: T.GenericFunction, errorHandler: T.ErrorHandler): T.GenericFunction => {
const argsArr = extractArgs(rpcFunction) const argsArr = extractArgs(rpcFunction)
const args = argsArr.join(',') const args = argsArr.join(',')
const argsStr = argsArr.map(stripAfterEquals).join(',') const argsStr = argsArr.map(stripAfterEquals).join(',')
const callStr = `async (${args}) => { const callStr = `async (${args}) => {
try{ try{
return await rpcFunction(${argsStr}) const res = await rpcFunction(${argsStr})
return res
}catch(e){ }catch(e){
errorHandler($__socket__$, e, rpcName, [${args}]) errorHandler($__socket__$, e, rpcName, [${args}])
} }
@@ -106,24 +107,24 @@ export function stripAfterEquals(str: string): string {
*/ */
const hookGenerator = (rpc: T.HookRPC<any, any>, errorHandler: T.ErrorHandler, sesameFn?: T.SesameFunction, injectSocket?: boolean): T.HookInfo['generator'] => { const hookGenerator = (rpc: T.HookRPC<any, any>, errorHandler: T.ErrorHandler, sesameFn?: T.SesameFunction, injectSocket?: boolean): T.HookInfo['generator'] => {
let argsArr = extractArgs(rpc.hook) let argsArr = extractArgs(rpc.hook)
argsArr.pop() //remove callback param argsArr.shift()//remove callback param
let callArgs = argsArr.join(',') let callArgs = argsArr.join(',')
const args = sesameFn ? (['sesame', ...argsArr].join(',')) const args = sesameFn ? (['sesame', ...argsArr].join(','))
: callArgs : callArgs
callArgs = appendComma(callArgs, false) callArgs = appendComma(callArgs, false)
const destroy_prefix = DESTROY_PREFIX
const hookStr = ` const hookStr = `
($__socket__$) => async (${args}) => { ($__socket__$) => async (${args}) => {
try{ try{
if(sesameFn && !sesameFn(sesame)) return if(sesameFn && !sesameFn(sesame)) return
const uuid = uuidv4() const uuid = uuidv4()
const res = await rpc.hook(${callArgs} (...cbargs) => { const res = await rpc.hook((...cbargs) => {
${rpc.onCallback ? `rpc.onCallback.apply({}, cbargs)` : ``} ${rpc.onCallback ? `rpc.onCallback.apply({}, cbargs)` : ``}
$__socket__$.call.apply($__socket__$, [uuid, ...cbargs]) $__socket__$.call.apply($__socket__$, [uuid, ...cbargs])
}) },${callArgs})
${rpc.onDestroy ? `$__socket__$.bind('destroy_'+uuid, () => { ${rpc.onDestroy ? `$__socket__$.bind(destroy_prefix+uuid, () => {
rpc.onDestroy(res, rpc) rpc.onDestroy(res, rpc)
})` : ``} })` : ``}
return {'uuid': uuid, 'return': res} return {'uuid': uuid, 'return': res}
@@ -144,7 +145,7 @@ const makeError = (callName: string) => new Error(CALL_NOT_FOUND(callName))
*/ */
const extractArgs = (f: Function): string[] => { const extractArgs = (f: Function): string[] => {
let fn: string let fn: string
fn = (fn = String(f)).substr(0, fn.indexOf(")")).substr(fn.indexOf("(") + 1) fn = (fn = String(f)).substring(0, fn.indexOf(")")).substring(fn.indexOf("(") + 1)
return fn !== "" ? fn.split(',') : [] return fn !== "" ? fn.split(',') : []
} }
@@ -244,7 +245,7 @@ export const makePioSocket = (socket: any): I.Socket => {
res(undefined) res(undefined)
}), }),
unhook: (name: string, listener?: T.AnyFunction) => { unhook: (name: string, listener?: T.GenericFunction) => {
if (listener) { if (listener) {
socket.removeListener(name, listener) socket.removeListener(name, listener)
} else { } else {
+29 -29
View File
@@ -1,7 +1,6 @@
import { describe, it } from "mocha"; import { describe, it } from "mocha";
import { RPCServer, RPCSocket } from '../Index'
import { RPCExporter, Socket } from "../src/Interfaces"; import { RPCExporter, Socket } from "../src/Interfaces";
import { ConnectedSocket } from "../src/Types"; import { ConnectedSocket, Callback, GenericFunction } from "../src/Types";
import * as log from 'why-is-node-running'; import * as log from 'why-is-node-running';
import * as http from 'http'; import * as http from 'http';
import * as express from 'express'; import * as express from 'express';
@@ -9,17 +8,18 @@ import * as fetch from 'node-fetch';
import { PromiseIO } from "../src/PromiseIO/Server"; import { PromiseIO } from "../src/PromiseIO/Server";
import { PromiseIOClient } from "../src/PromiseIO/Client"; import { PromiseIOClient } from "../src/PromiseIO/Client";
import { assert, expect } from 'chai'; import { assert, expect } from 'chai';
import { USER_DEFINED_TIMEOUT } from "../src/Strings"; import { CLASSNAME_ATTRIBUTE, USER_DEFINED_TIMEOUT } from "../src/Strings";
import { RPCServer, RPCSocket } from "../Index";
var should = require('chai').should(); var should = require('chai').should();
var chai = require("chai"); var chai = require("chai");
var chaiAsPromised = require("chai-as-promised"); var chaiAsPromised = require("chai-as-promised");
chai.use(chaiAsPromised); chai.use(chaiAsPromised);
const noop = (...args) => { } const noop = (...args: any[]) => { }
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(onCallback = noop, connectionHandler = noop, hookCloseHandler = noop, closeHandler = noop, errorHandler = noop) { function makeServer(onCallback = noop, connectionHandler = noop, hookCloseHandler = noop, closeHandler = noop, errorHandler = noop) {
let subcallback let subcallback: GenericFunction
const serv = new RPCServer([{ const serv = new RPCServer([{
name: 'test', name: 'test',
RPCs: [ RPCs: [
@@ -68,7 +68,7 @@ describe('PromiseIO', () => {
const server = new PromiseIO() const server = new PromiseIO()
server.attach(new http.Server()) server.attach(new http.Server())
server.on("socket", clientSocket => { server.on("socket", clientSocket => {
clientSocket.bind("test123", (p1, p2) => { clientSocket.bind("test123", (p1: string, p2:string) => {
server.close() server.close()
if (p1 === "p1" && p2 === "p2") if (p1 === "p1" && p2 === "p2")
done() done()
@@ -489,12 +489,12 @@ describe('RPCSocket', () => {
}) })
it('should have rpc echo', async() => { it('should have rpc echo', async () => {
const x = await client['test'].echo("x") const x = await client['test'].echo("x")
expect(x).to.be.equal('x') expect(x).to.be.equal('x')
}) })
it('should add up to 6', async() => { it('should add up to 6', async () => {
const sum = await client['test'].add(1, 2, 3) const sum = await client['test'].add(1, 2, 3)
expect(sum).to.be.equal(6) expect(sum).to.be.equal(6)
}) })
@@ -549,7 +549,7 @@ describe('It should do unhook', () => {
}, },
{ {
name: 'subscribeWithParam', name: 'subscribeWithParam',
hook: async (param, callback): Promise<{ uuid: string }> => { hook: async (callback, param): Promise<{ uuid: string }> => {
if (param != "OK") { if (param != "OK") {
console.log("param was" + param); console.log("param was" + param);
@@ -583,14 +583,14 @@ describe('It should do unhook', () => {
}) })
it('Subscribe with param', async () => { it('Subscribe with param', async () => {
const res = await client['test'].subscribeWithParam("OK", noop) const res = await client['test'].subscribeWithParam(noop, "OK")
expect(res.uuid).to.be.equal(candy) expect(res.uuid).to.be.equal(candy)
}) })
let run = 0 let run = 0
const expected = [yesCandy, noCandy, noCandy, noCandy] const expected = [yesCandy, noCandy, noCandy, noCandy]
it('Unhook+unsubscribe should stop callbacks', async() => { it('Unhook+unsubscribe should stop callbacks', async () => {
await client['test'].subscribe(function myCallback(c) { await client['test'].subscribe(function myCallback(c) {
if (run == 1) if (run == 1)
(myCallback as any).destroy() (myCallback as any).destroy()
@@ -614,7 +614,7 @@ type topicDTO = { topic: string; }
type SesameTestIfc = { type SesameTestIfc = {
test: { test: {
checkCandy: () => Promise<string> checkCandy: () => Promise<string>
subscribe: (callback: Function) => Promise<topicDTO> subscribe: (callback: Callback<[string]>) => Promise<topicDTO>
manyParams: <A = string, B = number, C = boolean, D = Object>(a: A, b: B, c: C, d: D) => Promise<[A, B, C, D]> manyParams: <A = string, B = number, C = boolean, D = Object>(a: A, b: B, c: C, d: D) => Promise<[A, B, C, D]>
} }
} }
@@ -625,6 +625,7 @@ describe('Sesame should unlock the socket', () => {
let server: RPCServer<SesameTestIfc> let server: RPCServer<SesameTestIfc>
let cb: Function = (...args) => { } let cb: Function = (...args) => { }
before((done) => { before((done) => {
server = new RPCServer<SesameTestIfc>([{ server = new RPCServer<SesameTestIfc>([{
name: "test", name: "test",
@@ -688,7 +689,7 @@ 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(function (c) {
if (c === candy) { if (c === candy) {
done() done()
} }
@@ -725,21 +726,21 @@ describe('Error handling', () => {
a: 'a', a: 'a',
b: 'b' b: 'b'
}) })
.then(r => { .then(r => {
if (r != null) if (r != null)
done(new Error("UNEXPECTED RESULT " + r)) done(new Error("UNEXPECTED RESULT " + r))
}) })
.catch((e) => { .catch((e) => {
if (e.message === errtxt) if (e.message === errtxt)
done() done()
else else
done(e) done(e)
}) })
.finally(() => { .finally(() => {
cli.close() cli.close()
sock.close() sock.close()
server.close() server.close()
}) })
}) })
}) })
@@ -1009,5 +1010,4 @@ describe("attaching handlers before connecting", () => {
done(e) done(e)
}) })
}) })
}) })
+1 -1
View File
@@ -9,6 +9,6 @@
"strict": true, "strict": true,
"experimentalDecorators": true "experimentalDecorators": true
}, },
"include": ["src/**/*.ts", "test/**/*.ts", "Index.ts", "demo.ts", "scratchpad.ts"], "include": ["src/**/*.ts", "test/**/*.ts", "Index.ts"],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }