before rewrite to have callbacks as FIRST parameter
This commit is contained in:
+16
-10
@@ -4,7 +4,7 @@ import { PromiseIOClient, defaultClientConfig } from './PromiseIO/Client'
|
||||
import * as T from './Types';
|
||||
import * as I from './Interfaces';
|
||||
import { stripAfterEquals, appendComma } from './Utils';
|
||||
import { DESTROY_PREFIX, 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';
|
||||
import { DeserializerFactory } from './Decorator';
|
||||
DeserializerFactory
|
||||
|
||||
@@ -21,12 +21,12 @@ export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I
|
||||
|
||||
private socket: I.Socket
|
||||
private handlers: {
|
||||
[name in string]: T.AnyFunction[]
|
||||
[name in string]: T.GenericFunction[]
|
||||
} = {
|
||||
error: [],
|
||||
close: []
|
||||
}
|
||||
private hooks: { [name in string]: T.AnyFunction } = {}
|
||||
private hooks: { [name in string]: T.GenericFunction } = {}
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -82,7 +82,7 @@ export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I
|
||||
* @param type 'error' or 'close'
|
||||
* @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.handlers[type])
|
||||
this.handlers[type] = []
|
||||
@@ -166,7 +166,7 @@ export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I
|
||||
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])
|
||||
})
|
||||
const info: T.ExtendedRpcInfo[] = await this.info(sesame)
|
||||
@@ -205,7 +205,7 @@ export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I
|
||||
* @param fnName The function name
|
||||
* @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 argParams = fnArgs.map(stripAfterEquals).join(",")
|
||||
sesame = appendComma(sesame)
|
||||
@@ -221,7 +221,7 @@ export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I
|
||||
* @param fnName The function name
|
||||
* @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)
|
||||
fnArgs.shift()
|
||||
@@ -234,16 +234,22 @@ export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I
|
||||
const destroy_prefix = DESTROY_PREFIX
|
||||
|
||||
const frontendHookStr = `
|
||||
async (${headerArgs} $__callback__$) => {
|
||||
async (${headerArgs} ${CALLBACK_NAME}) => {
|
||||
const r = await this.call("${fnName}", ${sesame} ${argParams})
|
||||
try{
|
||||
if(r){
|
||||
if(r.uuid){
|
||||
$__callback__$['destroy'] = () => {
|
||||
${CALLBACK_NAME}['destroy'] = () => {
|
||||
this.socket.fire(destroy_prefix+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.map(deserializer.from))
|
||||
})
|
||||
}
|
||||
return deserializer.from(r.return)
|
||||
}else{
|
||||
|
||||
+2
-2
@@ -15,10 +15,10 @@ export interface Socket {
|
||||
id?: string
|
||||
bind: (name: string, listener: T.PioBindListener) => 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>
|
||||
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
|
||||
close(): void
|
||||
}
|
||||
@@ -14,3 +14,4 @@ RPC did not provide a name.
|
||||
\n>------------OFFENDING RPC`
|
||||
export const CLASSNAME_ATTRIBUTE = "$__CLASSNAME__$"
|
||||
export const DESTROY_PREFIX = "$__DESTROY__$_"
|
||||
export const CALLBACK_NAME = "$__CALLBACK__$"
|
||||
+48
-42
@@ -1,12 +1,28 @@
|
||||
import * as I from "./Interfaces";
|
||||
import { RPCSocket } from "./Frontend";
|
||||
import { PromiseIO } from "./PromiseIO/Server";
|
||||
|
||||
export type PioBindListener = (...args: any) => void
|
||||
export type PioHookListener = AnyFunction
|
||||
export type PioHookListener = GenericFunction
|
||||
|
||||
export type GenericFunction<Parameters extends any[] = any[], Result = any> = {(...args: Parameters): Result}
|
||||
|
||||
export type BackendHook<Func extends GenericFunction> =
|
||||
GenericFunction<
|
||||
[
|
||||
...Head<Parameters<Func>>,
|
||||
GenericFunction<
|
||||
Parameters<
|
||||
AsFunction<
|
||||
Last<Parameters<Func>>
|
||||
>
|
||||
>,
|
||||
void
|
||||
>
|
||||
],
|
||||
ReturnType<Func>
|
||||
>
|
||||
|
||||
|
||||
export type AnyFunction = (...args: any[]) => any
|
||||
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 Visibility = "127.0.0.1" | "0.0.0.0"
|
||||
export type ConnectionHandler = (socket: I.Socket) => void
|
||||
@@ -45,24 +61,23 @@ export type ErrorResponse<T = {}> = Respose<T> & { result: "Error", message?: st
|
||||
|
||||
export type RPCType = 'Hook' | 'Unhook' | 'Call'
|
||||
|
||||
export type CallRPC<Name, Func extends AnyFunction> = {
|
||||
export type CallRPC<Name, Func extends GenericFunction> = {
|
||||
name: Name
|
||||
call: Func
|
||||
}
|
||||
|
||||
|
||||
export type HookRPC<Name, Func extends AnyFunction> = {
|
||||
export type HookRPC<Name, Func extends GenericFunction> = {
|
||||
name: Name
|
||||
hook: AnyFunction
|
||||
onCallback?: AnyFunction
|
||||
hook: BackendHook<Func>
|
||||
onCallback?: GenericFunction
|
||||
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 = {}> = {
|
||||
[grp in string]: {
|
||||
[rpc in string]: AnyFunction
|
||||
[rpc in string]: GenericFunction
|
||||
}
|
||||
} & Impl
|
||||
|
||||
@@ -89,7 +104,7 @@ export type HookInfo<SubresT = {}> = BaseInfo & {
|
||||
|
||||
export type CallInfo = BaseInfo & {
|
||||
type: 'Call',
|
||||
call: AnyFunction
|
||||
call: GenericFunction
|
||||
}
|
||||
|
||||
export type RpcInfo = HookInfo | CallInfo
|
||||
@@ -99,39 +114,30 @@ export type OnFunction = <T extends "error" | "close">(type: T, f: FrontEndHandl
|
||||
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>)
|
||||
: Promise<any>
|
||||
|
||||
type DYN_PARAM<
|
||||
A = void,
|
||||
B = void,
|
||||
C = void,
|
||||
D = void,
|
||||
E = void,
|
||||
F = void,
|
||||
G = void,
|
||||
H = void,
|
||||
> = H extends void ?
|
||||
G extends void ?
|
||||
F extends void ?
|
||||
E extends void ?
|
||||
D extends void ?
|
||||
C extends void ?
|
||||
B extends void ?
|
||||
A extends void ?
|
||||
[]
|
||||
: [A]
|
||||
: [A,B]
|
||||
:[A,B,C]
|
||||
:[A,B,C,D]
|
||||
:[A,B,C,D,E]
|
||||
:[A,B,C,D,E,F]
|
||||
:[A,B,C,D,E,F,G]
|
||||
:[A,B,C,D,E,F,G,H]
|
||||
|
||||
type Destroyable = { destroy: () => void }
|
||||
export type Callback<A0 = void, A1 = void, A2 = void, A3 = void, A4 = void, A5 = void, A6 = void, A7 = void> =
|
||||
(this: Destroyable, ...args: DYN_PARAM<A0, A1, A2, A3, A4, A5, A6, A7>) => 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 Head<T extends any[]> = T extends [ ...infer Head, any ] ? Head : any[]
|
||||
type Tail<T extends any[]> = T extends [any, ...infer Tail] ? Tail: []
|
||||
|
||||
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;
|
||||
+4
-2
@@ -76,7 +76,7 @@ export function rpcHooker(socket: I.Socket, exporter: I.RPCExporter<any, any>, e
|
||||
* Decorate an RPC with the error handler
|
||||
* @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 args = argsArr.join(',')
|
||||
const argsStr = argsArr.map(stripAfterEquals).join(',')
|
||||
@@ -108,6 +108,7 @@ export function stripAfterEquals(str: string): string {
|
||||
* @returns A {@link HookFunction}
|
||||
*/
|
||||
const hookGenerator = (rpc: T.HookRPC<any, any>, errorHandler: T.ErrorHandler, sesameFn?: T.SesameFunction, injectSocket?: boolean): T.HookInfo['generator'] => {
|
||||
const deserializer = DeserializerFactory
|
||||
let argsArr = extractArgs(rpc.hook)
|
||||
argsArr.pop() //remove callback param
|
||||
|
||||
@@ -124,6 +125,7 @@ const hookGenerator = (rpc: T.HookRPC<any, any>, errorHandler: T.ErrorHandler, s
|
||||
const uuid = uuidv4()
|
||||
const res = await rpc.hook(${callArgs} (...cbargs) => {
|
||||
${rpc.onCallback ? `rpc.onCallback.apply({}, cbargs)` : ``}
|
||||
cbargs = cbargs.map(deserializer.makeDeserializable)
|
||||
$__socket__$.call.apply($__socket__$, [uuid, ...cbargs])
|
||||
})
|
||||
${rpc.onDestroy ? `$__socket__$.bind(destroy_prefix+uuid, () => {
|
||||
@@ -247,7 +249,7 @@ export const makePioSocket = (socket: any): I.Socket => {
|
||||
res(undefined)
|
||||
}),
|
||||
|
||||
unhook: (name: string, listener?: T.AnyFunction) => {
|
||||
unhook: (name: string, listener?: T.GenericFunction) => {
|
||||
if (listener) {
|
||||
socket.removeListener(name, listener)
|
||||
} else {
|
||||
|
||||
+74
-33
@@ -1,7 +1,7 @@
|
||||
import { describe, it } from "mocha";
|
||||
import { RPCServer, RPCSocket, Serializable } from '../Index'
|
||||
import { RPCExporter, Socket } from "../src/Interfaces";
|
||||
import { ConnectedSocket, Callback } from "../src/Types";
|
||||
import { ConnectedSocket, Callback, GenericFunction } from "../src/Types";
|
||||
import * as log from 'why-is-node-running';
|
||||
import * as http from 'http';
|
||||
import * as express from 'express';
|
||||
@@ -9,7 +9,7 @@ import * as fetch from 'node-fetch';
|
||||
import { PromiseIO } from "../src/PromiseIO/Server";
|
||||
import { PromiseIOClient } from "../src/PromiseIO/Client";
|
||||
import { assert, expect } from 'chai';
|
||||
import { USER_DEFINED_TIMEOUT } from "../src/Strings";
|
||||
import { CLASSNAME_ATTRIBUTE, USER_DEFINED_TIMEOUT } from "../src/Strings";
|
||||
var should = require('chai').should();
|
||||
var chai = require("chai");
|
||||
var chaiAsPromised = require("chai-as-promised");
|
||||
@@ -614,7 +614,7 @@ type topicDTO = { topic: string; }
|
||||
type SesameTestIfc = {
|
||||
test: {
|
||||
checkCandy: () => Promise<string>
|
||||
subscribe: (callback: Callback<string>) => 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]>
|
||||
}
|
||||
}
|
||||
@@ -1034,22 +1034,53 @@ describe("class (de-)serialization", () => {
|
||||
}
|
||||
}
|
||||
|
||||
let myServer: RPCServer;
|
||||
let mySocket: RPCSocket;
|
||||
const verifyObject = (obj: any) => {
|
||||
expect(obj).to.be.an.instanceOf(TestClass)
|
||||
expect(obj.aString).to.be.a('string')
|
||||
expect(obj.aNumber).to.be.a('number')
|
||||
expect(obj.aObject).to.be.a('object')
|
||||
expect(obj.aObject.x).to.be.a('string')
|
||||
expect(obj.aObject.y).to.be.undefined
|
||||
expect(obj.aObject.sub).to.be.an.instanceOf(SubClass)
|
||||
expect(obj.aClassObject).to.be.an.instanceOf(SubClass)
|
||||
expect(obj).to.not.have.key(CLASSNAME_ATTRIBUTE)
|
||||
expect(obj.aObject.sub).to.not.have.key(CLASSNAME_ATTRIBUTE)
|
||||
expect(obj.aClassObject).to.not.have.key(CLASSNAME_ATTRIBUTE)
|
||||
expect(obj.returnOK()).to.be.equal('OK')
|
||||
}
|
||||
|
||||
describe("Responses", () => {
|
||||
type TestIfc = {
|
||||
Test: {
|
||||
returnClass: () => Promise<TestClass>
|
||||
classCallback: (callback: Callback<[TestClass]>) => Promise<TestClass>
|
||||
}
|
||||
}
|
||||
|
||||
let myServer: RPCServer<TestIfc>;
|
||||
let mySocket: ConnectedSocket<TestIfc>;
|
||||
|
||||
before(function (done) {
|
||||
myServer = new RPCServer([{
|
||||
myServer = new RPCServer<TestIfc>([{
|
||||
name: "Test",
|
||||
RPCs: [
|
||||
function returnClass(){
|
||||
async function returnClass() {
|
||||
return new TestClass()
|
||||
}, {
|
||||
name: "classCallback",
|
||||
hook: async function (callback) {
|
||||
setTimeout(_ => callback(new TestClass()), 250)
|
||||
return new TestClass()
|
||||
}
|
||||
}
|
||||
]
|
||||
}])
|
||||
myServer.listen(8084)
|
||||
|
||||
mySocket = new RPCSocket(8084, 'localhost')
|
||||
mySocket.connect().then(() => done())
|
||||
new RPCSocket<TestIfc>(8084, 'localhost').connect().then(connsock => {
|
||||
mySocket = connsock
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
after(function (done) {
|
||||
@@ -1059,33 +1090,43 @@ describe("class (de-)serialization", () => {
|
||||
})
|
||||
|
||||
|
||||
it("receives class in call response", async () => {
|
||||
it("receives class object in call response", async () => {
|
||||
const obj: TestClass = await mySocket['Test'].returnClass()
|
||||
|
||||
expect(obj).to.be.an.instanceOf(TestClass)
|
||||
expect(obj.aString).to.be.a('string')
|
||||
expect(obj.aNumber).to.be.a('number')
|
||||
expect(obj.aObject).to.be.a('object')
|
||||
expect(obj.aObject.x).to.be.a('string')
|
||||
expect(obj.aObject.y).to.be.undefined
|
||||
expect(obj.aObject.sub).to.be.an.instanceOf(SubClass)
|
||||
expect(obj.aClassObject).to.be.an.instanceOf(SubClass)
|
||||
|
||||
expect(obj.returnOK()).to.be.equal('OK')
|
||||
verifyObject(obj)
|
||||
})
|
||||
|
||||
it("receives class in hook response", async () => {
|
||||
const obj: TestClass = await mySocket['Test'].returnClass()
|
||||
it("receives class object in hook response", async function () {
|
||||
const obj: TestClass = await mySocket.Test.classCallback(noop)
|
||||
verifyObject(obj)
|
||||
})
|
||||
|
||||
expect(obj).to.be.an.instanceOf(TestClass)
|
||||
expect(obj.aString).to.be.a('string')
|
||||
expect(obj.aNumber).to.be.a('number')
|
||||
expect(obj.aObject).to.be.a('object')
|
||||
expect(obj.aObject.x).to.be.a('string')
|
||||
expect(obj.aObject.y).to.be.undefined
|
||||
expect(obj.aObject.sub).to.be.an.instanceOf(SubClass)
|
||||
expect(obj.aClassObject).to.be.an.instanceOf(SubClass)
|
||||
|
||||
expect(obj.returnOK()).to.be.equal('OK')
|
||||
it("receives class object in callback", function (done) {
|
||||
mySocket.Test.classCallback(function (cbValue) {
|
||||
verifyObject(cbValue)
|
||||
done()
|
||||
}).then(verifyObject)
|
||||
})
|
||||
})
|
||||
describe("Parameters", () => {
|
||||
it("Class object in call", function(done){
|
||||
const server = new RPCServer([
|
||||
{
|
||||
name: "Test",
|
||||
RPCs: [
|
||||
function callWithClass(testObj: TestClass){
|
||||
verifyObject(testObj)
|
||||
done()
|
||||
}
|
||||
]
|
||||
}
|
||||
]).listen(8086)
|
||||
|
||||
new RPCSocket(8086, 'localhost').connect().then(sock => {
|
||||
sock['Test'].callWithClass(new TestClass()).then(_ => {
|
||||
sock.close()
|
||||
server.close()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user