|
1 year ago | |
---|---|---|
classes | 2 years ago | |
interfaces | 2 years ago | |
LICENSE.md | 3 years ago | |
README.md | 1 year ago | |
globals.md | 1 year ago |
rpclibrary is a simple to use websocket RPC library.
npm i rpclibrary
import {RPCServer, RPCSocket} from 'rpclibrary'
const echo = (text) => text
const add = (a, b) => a + b
const subtract = (a, b) => a - b
new RPCServer([
{
name: 'MyRPCGroup1',
RPCs: [
echo,
add,
]
},
name: 'MyOtherRPCGroup',
RPCs: [
subtract
]
}
]).listen(20000)
new RPCSocket(20000, 'localhost').connect().then(async sock => {
try{
await sock['MyRPCGroup1'].echo("hello!").then(console.log)
const sum = await sock['MyRPCGroup1'].add(1, Math.PI)
const result = await sock['MyOtherRPCGroup'].subtract(sum, 4)
}catch(e){
console.log(String(e))
}
})
import {RPCServer, RPCSocket} from 'rpclibrary'
import {Server as httpServer} from 'http'
import * as express from 'express'
const app = express()
//...do express things...
const httpServer = new http.Server(app)
const rpcServer = new RPCServer([ ... ])
rpcServer.attach(httpServer)
rpcServer.listen(8080) //identical to httpServer.listen(8080)
rpclibrary offers full support for callbacks and Promises. Please note that there may only be one callback per RPC and it has to be the last parameter
const getAsync = async () => await new Promise((res, _) => {
setTimeout(() => {
res({
topic: "Hey!!",
message: "Hello World Async!"
})
}, 250)
})
const getCallback = (callback) => {
setTimeout(() => {
try{
callback({
topic: "Hey!!",
message: "Hello World Callback!"
})
}catch(e){
console.log(String(e))
}
}, 250)
return "Please wait for a callback :)"
}
new RPCServer([{
name: 'MyRPCGroup1',
RPCs: [
getAsync,
{
name: 'getCallback',
hook: getCallback,
}
]
}]).listen(20000)
new RPCSocket(20000, 'localhost').connect().then(async sock => {
try{
const RPCs = sock['MyRPCGroup1']
await RPCs.getAsync().then(console.log)
await RPCs.getCallback(console.log).then(console.log)
}catch(e){
console.log(String(e))
}
})
There are a many things you can hook into to manage your connections
new RPCServer([{
name: 'MyRPCGroup1',
RPCs: [
echo,
{
name: 'getCallback',
hook: getCallback,
onClose: (response, rpc) => { /* client disconnected */ },
onCallback: (...callbackArgs) => { /* callback triggered */ }
}
],
}], {
closeHandler: (socket) => { /* global close handler */ },
connectionHandler: (socket) => { /* new connection made */ },
errorHandler: (socket, error, rpcname, argArr) => { /* An error occured inside a RPC */ },
}).listen(20001)
const sock = new RPCSocket(20001, 'localhost')
sock.on('error', (e) => { /* handle error */ })
sock.on('close', () => { /* handle close event */ })
sock.hook('RPCName', (/* arg0, arg1, ..., argN */) => { /* bind client-side RPCs */ })
//Retrieve the socket from connectionHandler (Server-side) and trigger with
//socket.call('RPCName', arg0, arg1, ..., argN)
sock.connect().then(_ => { /* ... */})
rpclibrary offers some minimalistic permission management in the form of a parameter prefix.
The sesame
option will decorate all functions with a check for the sesame password. If the wrong string is provided, the RPC will not be executed or respond.
new RPCServer([ ... ], {
sesame: "sesame open",
}).listen(20002)
new RPCSocket(20002, 'localhost').connect("sesame open").then(async sock => {
/* use normally */
})
new RPCSocket(20002, 'localhost').connect("WRONG SESAME").then(async sock => {
const RPCs = sock['MyRPCGroup1'] //undefined
})
The permissioning can also be performed dynamically with the accessfilter
option and sesame
function.
new RPCServer([ ... ], {
sesame: (sesame) => checkIfSesameTokenShouldHaveAnyAccess(sesame),
accessFilter: (sesame, rpcGroupName) => checkIfSesameTokenShouldSeeRPCGroup(sesame, rpcGroupName),
}).listen(20002)
rpclibrary is a typescript-first project and offers full support for typing your RPCs. NOTE that your function implementations and Group Names will usually have to be manually typed to make the compiler infer the correct types.
First apply types to your functions:
echo = (x) => x
/*becomes*/
echo = (x:string) : string => x
/* A faulty implementation */
badEcho = (x: boolean) : number => 3
Next define a type matching the desired Group-to-RPC structure. This kind of type will be called pseudo-interface
:
type MyType = {
MyRPCGroup1: {
echo: (x: string) => string
//...further RPC declarations
},
//... further Group declarations
};
If MyType
is provided as generic parameter to RPCServer
, RPCs
becomes type-safe.
TSC’s stack traces may become very long when errors happen in this section. Don’t be intimidated by this.
The relevant information is usually found at the end in a clear manner.
new RPCServer<MyType>([{
name: 'MyRPCGroup1' as 'MyRPCGroup1', //Otherwise the compiler may infer type 'string'
RPCs: [ badEcho ], //Type '(x: boolean) => number' is not assignable to type '(x: string) => string'
}]).listen(20003)
Finally also provide MyType
to the client RPCSocket
to gain type safety for the socket object.
new RPCSocket<MyType>(20003, 'localhost').connect().then(async sock => {
//TSC knows about MyRPCGroup1 and its members
await sock.MyRPCGroup1.echo("hello!").then(console.log)
})
Define a regular Interface. This will function as the source of truth for type definitions.
interface IComponentA {
echo: (x: string) => string
add: (a: number, b: number) => number
}
interface IComponentB {
getAsync: () => Promise<{ topic: string, message: string }>
getCallback: (callback:Function) => string
}
Create an RPC pseudo-interface type that references the proper interface. This will make sure the types declared by your RPCs are consistent with their implementations.
type TComponentA = {
ComponentA: {
echo: IComponentA['echo']
add: IComponentA['add']
}
}
type TComponentB = {
ComponentB: {
getAsync: IComponentB['getAsync']
getCallback: IComponentB['getCallback']
}
}
Create classes that implement the interfaces you declared alongside RPCExporter<MyType>
, where MyType is the RPC pseudo-interface.
class ComponentA implements IComponentA, RPCExporter<TComponentA> {
name = "ComponentA" as "ComponentA"
RPCs = [
this.echo,
this.add,
]
echo = (text: string) => text
add = (a: number, b: number) : number => a + b
}
class ComponentB implements IComponentB, RPCExporter<TComponentB> {
name = "ComponentB" as "ComponentB"
RPCs = [
this.getAsync,
{
name: 'getCallback',
hook: this.getCallback
}
]
getAsync = async () : Promise<{topic: string, message:string}> => {...}
getCallback = (callback: Function) : string => {...}
}
Finally, merge all RPC pseudo-interfaces into a project-wide one:
type TProject = TComponentA
& TComponentB
new RPCServer<TProject>([new ComponentA(), new ComponentB()]).listen(port)
new RPCSocket<TProject>(port, host).connect().then(async sock => {
})
2.4.2
Ƭ AccessFilter: function
▸ (sesame
: string | undefined, exporter
: I.RPCExporter‹InterfaceT, keyof InterfaceT›): Promise‹boolean› | boolean
Parameters:
Name | Type |
---|---|
sesame |
string | undefined |
exporter |
I.RPCExporter‹InterfaceT, keyof InterfaceT› |
Ƭ AnyFunction: function
▸ (…args
: any): any
Parameters:
Name | Type |
---|---|
...args |
any |
Ƭ AsyncAnyFunction: AsyncAnyFunction
Ƭ AsyncIfc: object
Ƭ BaseInfo: object
argNames: string[]
name: string
owner: string
Ƭ CallInfo: BaseInfo & object
Ƭ CallRPC: object
call: Func
name: Name
Ƭ ClientConfig: ConnectOpts & object
Ƭ CloseHandler: function
▸ (socket
: Socket): void
Parameters:
Name | Type |
---|---|
socket |
Socket |
Ƭ ConnectedSocket: RPCSocket & AsyncIfc‹T›
Ƭ ConnectionHandler: function
▸ (socket
: Socket): void
Parameters:
Name | Type |
---|---|
socket |
Socket |
Ƭ ErrorHandler: function
▸ (socket
: Socket, error
: any, rpcName
: string, args
: any[]): void
Parameters:
Name | Type |
---|---|
socket |
Socket |
error |
any |
rpcName |
string |
args |
any[] |
Ƭ ErrorResponse: Respose‹T› & object
Ƭ ExporterArray: I.RPCExporter‹RPCInterface‹InterfaceT›, keyof InterfaceT›[]
Ƭ ExtendedRpcInfo: RpcInfo & object
Ƭ FrontEndHandlerType: object
close(): function
error(): function
e
: any): voidƬ HookCloseFunction: function
▸ (res
: T, rpc
: HookRPC‹any, any›): any
Parameters:
Name | Type |
---|---|
res |
T |
rpc |
HookRPC‹any, any› |
Ƭ HookFunction: AnyFunction
Ƭ HookInfo: BaseInfo & object
Ƭ HookRPC: object
hook: Func
name: Name
onCallback? : AnyFunction
onDestroy? : HookCloseFunction‹ReturnType extends Promise ? T : ReturnType›
Ƭ OnFunction: function
▸ <T>(type
: T, f
: FrontEndHandlerType[T]): void
Type parameters:
▪ T: “error” | “close”
Parameters:
Name | Type |
---|---|
type |
T |
f |
FrontEndHandlerType[T] |
Ƭ Outcome: “Success” | “Error”
Ƭ PioBindListener: function
▸ (…args
: any): void
Parameters:
Name | Type |
---|---|
...args |
any |
Ƭ PioHookListener: AnyFunction
Ƭ RPC: HookRPC‹Name, Func› | CallRPC‹Name, Func› | Func
Ƭ RPCDefinitions: object
Ƭ RPCExporter: object
RPCs: RPCDefinitions[Name] | function
name: Name
Ƭ RPCInterface: object & Impl
Ƭ RPCType: “Hook” | “Unhook” | “Call”
Ƭ ResponseType: “Subscribe” | “Success” | “Error”
Ƭ Respose: T & object
Ƭ RpcInfo: HookInfo | CallInfo
Ƭ ServerConf: object & SesameConf
Ƭ SesameConf: object
Ƭ SesameFunction: function
▸ (sesame
: string): boolean
Parameters:
Name | Type |
---|---|
sesame |
string |
Ƭ SuccessResponse: Respose‹T› & object
Ƭ Visibility: “127.0.0.1” | “0.0.0.0”
Ƭ exportT: object
Const
BAD_CONFIG_PARAM• BAD_CONFIG_PARAM: “RPCServer options were passed to listen(..) after attach(..) was called. Please pass them to attach(..) instead. Ignoring.” = “RPCServer options were passed to listen(..) after attach(..) was called. Please pass them to attach(..) instead. Ignoring.”
Const
SOCKET_NOT_CONNECTED• SOCKET_NOT_CONNECTED: “The socket is not connected! Use socket.connect() first” = “The socket is not connected! Use socket.connect() first”
Const
UNKNOWN_RPC_IDENTIFIER• UNKNOWN_RPC_IDENTIFIER: ”$UNKNOWNRPC$” = “$UNKNOWNRPC$”
Const
CALL_NOT_FOUND▸ CALL_NOT_FOUND(callName
: any): string
Parameters:
Name | Type |
---|---|
callName |
any |
Returns: string
Const
RPC_BAD_TYPE▸ RPC_BAD_TYPE(type
: any): string
Parameters:
Name | Type |
---|---|
type |
any |
Returns: string
Const
RPC_NO_NAME▸ RPC_NO_NAME(name
: any): string
Parameters:
Name | Type |
---|---|
name |
any |
Returns: string
Const
UNKNOWN_RPC_SERVER▸ UNKNOWN_RPC_SERVER(name
: any): string
Parameters:
Name | Type |
---|---|
name |
any |
Returns: string
Const
USER_DEFINED_TIMEOUT▸ USER_DEFINED_TIMEOUT(ms
: any): string
Parameters:
Name | Type |
---|---|
ms |
any |
Returns: string
▸ appendComma(s?
: undefined | string, turnToString
: boolean): string
Parameters:
Name | Type | Default |
---|---|---|
s? |
undefined | string | - |
turnToString |
boolean | true |
Returns: string
Const
callGenerator▸ callGenerator(rpcName
: string, $__socket__$
: Socket, rpcFunction
: T.AnyFunction, errorHandler
: T.ErrorHandler): T.AnyFunction
Parameters:
Name | Type | Description |
---|---|---|
rpcName |
string | - |
$__socket__$ |
Socket | - |
rpcFunction |
T.AnyFunction | the function to decorate |
errorHandler |
T.ErrorHandler | - |
Returns: T.AnyFunction
Const
extractArgs▸ extractArgs(f
: Function): string[]
Parameters:
Name | Type | Description |
---|---|---|
f |
Function | The source function |
Returns: string[]
▸ fixNames(o
: Object): void
Parameters:
Name | Type |
---|---|
o |
Object |
Returns: void
Const
hookGenerator▸ hookGenerator(rpc
: T.HookRPC‹any, any›, errorHandler
: T.ErrorHandler, sesameFn?
: T.SesameFunction, injectSocket?
: undefined | false | true): function
Parameters:
Name | Type | Description |
---|---|---|
rpc |
T.HookRPC‹any, any› | The RPC to transform |
errorHandler |
T.ErrorHandler | - |
sesameFn? |
T.SesameFunction | - |
injectSocket? |
undefined | false | true | - |
Returns: function
▸ (socket?
: I.Socket): function
Parameters:
Name | Type |
---|---|
socket? |
I.Socket |
▸ (…args
: any[]): SubresT
Parameters:
Name | Type |
---|---|
...args |
any[] |
Const
isError▸ isError(e
: any): boolean
Parameters:
Name | Type |
---|---|
e |
any |
Returns: boolean
Const
makeError▸ makeError(callName
: string): Error
Parameters:
Name | Type |
---|---|
callName |
string |
Returns: Error
Const
makePioSocket▸ makePioSocket(socket
: any): Socket
Parameters:
Name | Type | Description |
---|---|---|
socket |
any | A socket.io socket |
Returns: Socket
▸ makeSesameFunction(sesame
: T.SesameFunction | string): T.SesameFunction
Parameters:
Name | Type |
---|---|
sesame |
T.SesameFunction | string |
Returns: T.SesameFunction
▸ rpcHooker(socket
: Socket, exporter
: I.RPCExporter‹any, any›, errorHandler
: T.ErrorHandler, sesame?
: T.SesameFunction, makeUnique
: boolean): T.ExtendedRpcInfo[]
Parameters:
Name | Type | Default | Description |
---|---|---|---|
socket |
Socket | - | The websocket (implementation: socket.io) to hook on |
exporter |
I.RPCExporter‹any, any› | - | The exporter |
errorHandler |
T.ErrorHandler | - | - |
sesame? |
T.SesameFunction | - | - |
makeUnique |
boolean | true | @default true Attach a suffix to RPC names |
Returns: T.ExtendedRpcInfo[]
Const
rpcToRpcinfo▸ rpcToRpcinfo(socket
: Socket, rpc
: T.RPC‹any, any›, owner
: string, errorHandler
: T.ErrorHandler, sesame?
: T.SesameFunction): T.RpcInfo
Parameters:
Name | Type | Description |
---|---|---|
socket |
Socket | - |
rpc |
T.RPC‹any, any› | The RPC to transform |
owner |
string | The owning RPC group’s name |
errorHandler |
T.ErrorHandler | The error handler to invoke when something goes wrong |
sesame? |
T.SesameFunction | optional sesame phrase to prepend before all RPC arguments |
Returns: T.RpcInfo
▸ stripAfterEquals(str
: string): string
Parameters:
Name | Type | Description |
---|---|---|
str |
string | The parameter to modify |
Returns: string
Const
defaultClientConfig• callTimeoutMs: number = 0
• protocol: “http” = “http”
• reconnection: false = false
• reconnectionAttempts: number = 2
• reconnectionDelay: number = 200
• timeout: number = 450
Const
defaultConfig• cookie: false = false
• path: string = “/socket.io”