Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
peter dde95027bc npm version 4 gadus atpakaļ
src use external http 4 gadus atpakaļ
test socketio drop in for bsock 4 gadus atpakaļ
.drone.yml retry 5 gadus atpakaļ
.gitignore automate documentation 5 gadus atpakaļ
Index.ts moved RPCLibrary to Index 5 gadus atpakaļ
LICENSE.md added mit license 5 gadus atpakaļ
README.md new readme and some small fixes 4 gadus atpakaļ
demo.ts Before PioSocket dropin replacement 4 gadus atpakaļ
package-lock.json socketio drop in for bsock 4 gadus atpakaļ
package.json npm version 4 gadus atpakaļ
scratchpad.ts socketio drop in for bsock 4 gadus atpakaļ
tsconfig.json socketio drop in for bsock 4 gadus atpakaļ

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

import {RPCServer, RPCSocket} from 'rpclibrary' 

// TL;DR
const echo = (text) => text
const add = (a, b) => a + b

new RPCServer(20000, [{
    name: 'MyRPCGroup1',
    exportRPCs: () => [
        echo,
        add,
    ]
}])

new RPCSocket(20000, 'localhost').connect().then(async sock => {
    try{
        const RPCs = sock['MyRPCGroup1']
        await RPCs.echo("hello!").then(console.log)
        await RPCs.add(1, Math.PI).then(console.log)
    }catch(e){
        console.log(String(e))
    }
})

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(20000, [{
    name: 'MyRPCGroup1',
    exportRPCs: () => [
        getAsync,
        {
            name: 'getCallback',
            hook: getCallback,
        }
    ]
}])

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(20001, [{
    name: 'MyRPCGroup1',
    exportRPCs: () => [
        echo,
        add,
        getAsync,
        {
            name: 'getCallback',
            hook: getCallback,
            onClose: (response, rpc) => { /* client disconnected */ },
            onCallback: (...callbackArgs) => { /* callback triggered */ }
        }
    ],
}], {
    visibility: '127.0.0.1', //0.0.0.0
    closeHandler: (socket) => { /* global close handler */ },
    connectionHandler: (socket) => { /* new connection made */ },
    errorHandler: (socket, error, rpcname, argArr) => { /* An error occured inside a RPC */ },
})

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


//Restricting access
new RPCServer(20002, [{
    name: 'MyRPCGroup1',
    exportRPCs: () => [
        echo,
        add,
        getAsync,
        {
            name: 'getCallback',
            hook: getCallback,
        }
    ],
}], {
    sesame: "sesame open",
    /* 
    OR check sesame dynamically
    and refine permissioning with accessfilter (optional)
    */

    //sesame: (sesame) => true
    //accessFilter: (sesame, exporter) => { return exporter.name === "MyRPCGroup1" && sesame === "sesame open" },
})

new RPCSocket(20002, 'localhost').connect("sesame open").then(async sock => {
    try{
        const RPCs = sock['MyRPCGroup1']
        await RPCs.echo("hello!").then(console.log)
        await RPCs.add(1, Math.PI).then(console.log)
        await RPCs.getAsync().then(console.log)
        await RPCs.getCallback(console.log).then(console.log)
    }catch(e){
        console.log(String(e))
    }
})


Typescript support

rpclibrary is a typescript-first project and offers full support for typing your RPCs. NOTE that your function implementations have to be currectly typed to make the compiler agree. Explicit typing is recommended.

Example:

echo = (x) => x
    /*becomes*/
echo = (x:string) : string => x
type MyInterface = {
    MyRPCGroup1: {
        echo: (x: string) => string
        add: (a: number, b: number) => number
        getAsync: () => Promise<{ topic: string, message: string }>
        getCallback: (callback:Function) => string
    }
};


/*
exportRPCs is now type safe. Try swapping echo for badEcho.
Sadly TSC's stack traces aren't the best, but try to scroll to the bottom of them to find useful info like

Type '(x: boolean) => number' is not assignable to type '(x: string) => string'
*/

const badEcho = (x: boolean) : number => 3

new RPCServer<MyInterface>(20003, [{
    name: 'MyRPCGroup1',
    exportRPCs: () => [
        //badEcho,
        echo,
        add,
        getAsync,
        {
            name: 'getCallback',
            hook: getCallback,
        }
    ],
}])

new RPCSocket<MyInterface>(20003, 'localhost').connect().then(async sock => {
    try{
        await sock.MyRPCGroup1.echo("hello!").then(console.log)
        await sock.MyRPCGroup1.add(1, Math.PI).then(console.log)
        await sock.MyRPCGroup1.getAsync().then(console.log)
        await sock.MyRPCGroup1.getCallback(console.log).then(console.log)
    }catch(e){
        console.log(String(e))
    }
})

A class-based scalable pattern for APIs

because long lists of functions quickly become unwieldy, it is smart to break up the RPCs into chunks or features. A pattern I found to be useful is as follows:

interface IMyImplementation {
    echo: (x: string) => string
    add: (a: number, b: number) => number
    getAsync: () => Promise<{ topic: string, message: string }>
    getCallback: (callback:Function) => string
}

type MyInterface = {
    MyRPCGroup1: {
        echo: IMyImplementation['echo']
        add: IMyImplementation['add']
        getAsync: IMyImplementation['getAsync']
        getCallback: IMyImplementation['getCallback']
    }
}

class MyImplementation implements IMyImplementation, RPCExporter<MyInterface>{
    //"X" as "X" syntax is required to satisfy the type system (as it assumes string to be the true type)
    name = "MyRpcGroup11" as "MyRPCGroup1"

    //List the functions you declared in MyInterface
    exportRPCs = () => [
        this.echo,
        this.add,
        this.getAsync,
        this.getCallback
    ]

    //Write your implementations as you normally would
    echo = (text: string) => text

    add = (a: number, b: number) : number => a + b

    getAsync = async () : Promise<{topic: string, message:string}>=> await new Promise((res, _) => {
        setTimeout(() => {
            res({
                topic: "Hey!!",
                message: "Hello World Async!"
            })
        }, 250)
    })

    getCallback = (callback: Function) : string => {
        setTimeout(() => {
            try{
                callback({
                    topic: "Hey!!",
                    message: "Hello World Callback!"
                })
            }catch(e){
                console.log(String(e))
            }
        }, 250)
        return "Please wait for a callback :)"
    }
}

type ProjectInterface = MyInterface
                    //& MyOtherInterface
                    //& MyOtherOtherInterface
                    // ...
;

new RPCServer<ProjectInterface>(20004, [new MyImplementation() /*, new MyOtherImplementation(), new MyOtherOtherImplementation() */])

new RPCSocket<ProjectInterface>(20004, 'localhost').connect().then(async sock => {
    // ...
})

Full documentation