npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

rpclibrary

v2.5.1

Published

rpclibrary is a websocket RPC library

Downloads

115

Readme

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

Full documentation