You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nitowa 2915a19af4 generated from tag 2.4.2 1 year ago
classes generated from tag 2.4.0 2 years ago
interfaces generated from tag 2.3.1 2 years ago
LICENSE.md generated from tag 1.4.5 3 years ago
README.md generated from tag 2.4.2 1 year ago
globals.md generated from tag 2.4.2 1 year ago

README.md

Overview

Build Status Current Version Weekly Downloads License Type

rpclibrary is a simple to use websocket RPC library.

How to install

npm i rpclibrary

Quickstart (TL;DR)

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))
    }
})

Working with http.Server and express

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)

Async and Callbacks?

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))
    }
})

Hooks and Events

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(_ => { /* ... */})

Restricting access

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)

Typescript support

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) 
})

A class-based pattern for modular APIs

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 => {
    
})

Version

2.4.2

Classes

Interfaces

Type aliases

Variables

Functions

Object literals

Type aliases

AccessFilter

Ƭ AccessFilter: function

Type declaration:

▸ (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

Ƭ AnyFunction: function

Type declaration:

▸ (…args: any): any

Parameters:

Name Type
...args any

AsyncAnyFunction

Ƭ AsyncAnyFunction: AsyncAnyFunction


AsyncIfc

Ƭ AsyncIfc: object

Type declaration:


BaseInfo

Ƭ BaseInfo: object

Type declaration:

  • argNames: string[]

  • name: string

  • owner: string


CallInfo

Ƭ CallInfo: BaseInfo & object


CallRPC

Ƭ CallRPC: object

Type declaration:

  • call: Func

  • name: Name


ClientConfig

Ƭ ClientConfig: ConnectOpts & object


CloseHandler

Ƭ CloseHandler: function

Type declaration:

▸ (socket: Socket): void

Parameters:

Name Type
socket Socket

ConnectedSocket

Ƭ ConnectedSocket: RPCSocket & AsyncIfc‹T›


ConnectionHandler

Ƭ ConnectionHandler: function

Type declaration:

▸ (socket: Socket): void

Parameters:

Name Type
socket Socket

ErrorHandler

Ƭ ErrorHandler: function

Type declaration:

▸ (socket: Socket, error: any, rpcName: string, args: any[]): void

Parameters:

Name Type
socket Socket
error any
rpcName string
args any[]

ErrorResponse

Ƭ ErrorResponse: Respose‹T› & object


ExporterArray

Ƭ ExporterArray: I.RPCExporter‹RPCInterface‹InterfaceT›, keyof InterfaceT›[]


ExtendedRpcInfo

Ƭ ExtendedRpcInfo: RpcInfo & object


FrontEndHandlerType

Ƭ FrontEndHandlerType: object

Type declaration:

  • close(): function

    • (): void
  • error(): function

    • (e: any): void

HookCloseFunction

Ƭ HookCloseFunction: function

Type declaration:

▸ (res: T, rpc: HookRPC‹any, any›): any

Parameters:

Name Type
res T
rpc HookRPC‹any, any›

HookFunction

Ƭ HookFunction: AnyFunction


HookInfo

Ƭ HookInfo: BaseInfo & object


HookRPC

Ƭ HookRPC: object

Type declaration:


OnFunction

Ƭ OnFunction: function

Type declaration:

▸ <T>(type: T, f: FrontEndHandlerType[T]): void

Type parameters:

T: “error” | “close”

Parameters:

Name Type
type T
f FrontEndHandlerType[T]

Outcome

Ƭ Outcome: “Success” | “Error”


PioBindListener

Ƭ PioBindListener: function

Type declaration:

▸ (…args: any): void

Parameters:

Name Type
...args any

PioHookListener

Ƭ PioHookListener: AnyFunction


RPC

Ƭ RPC: HookRPC‹Name, Func› | CallRPC‹Name, Func› | Func


RPCDefinitions

Ƭ RPCDefinitions: object

Type declaration:


RPCExporter

Ƭ RPCExporter: object

Type declaration:

  • RPCs: RPCDefinitions[Name] | function

  • name: Name


RPCInterface

Ƭ RPCInterface: object & Impl


RPCType

Ƭ RPCType: “Hook” | “Unhook” | “Call”


ResponseType

Ƭ ResponseType: “Subscribe” | “Success” | “Error”


Respose

Ƭ Respose: T & object


RpcInfo

Ƭ RpcInfo: HookInfo | CallInfo


ServerConf

Ƭ ServerConf: object & SesameConf


SesameConf

Ƭ SesameConf: object

Type declaration:


SesameFunction

Ƭ SesameFunction: function

Type declaration:

▸ (sesame: string): boolean

Parameters:

Name Type
sesame string

SuccessResponse

Ƭ SuccessResponse: Respose‹T› & object


Visibility

Ƭ Visibility: “127.0.0.1” | “0.0.0.0”


exportT

Ƭ exportT: object

Type declaration:

Variables

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$”

Functions

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

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

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

A HookFunction

▸ (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

makeSesameFunction(sesame: T.SesameFunction | string): T.SesameFunction

Parameters:

Name Type
sesame T.SesameFunction | string

Returns: T.SesameFunction


rpcHooker

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

stripAfterEquals(str: string): string

Parameters:

Name Type Description
str string The parameter to modify

Returns: string

Object literals

Const defaultClientConfig

defaultClientConfig: object

callTimeoutMs

callTimeoutMs: number = 0

protocol

protocol: “http” = “http”

reconnection

reconnection: false = false

reconnectionAttempts

reconnectionAttempts: number = 2

reconnectionDelay

reconnectionDelay: number = 200

timeout

timeout: number = 450


Const defaultConfig

defaultConfig: object

cookie

cookie: false = false

path

path: string = “/socket.io”