Skip to content

RPC universal toolkit with lots of session environment & callbacks support

Notifications You must be signed in to change notification settings

Morglod/rpct-js

Repository files navigation

NPM Version

rpct

RPC toolkit.

Goal: make simpliest for usage, zero-effort remote control library.

Api -> Transport -> { ... session ... } -> Transport -> Api

Api operates with messages.
It can receive message and invoke local method.
It can emit message and send to remote api to invoke remote method.
It can pass callbacks as arguments.
It raises up remote exceptions.

Fully TypeScript'ed.

Api can be easily extended with middlewares & hooks.

Transport operates with data serialization and deserialization. It resend messages over any session to remote connected Transport.

Browser minified sizes:

  • 17kb (without compression)
  • 4kb (gzipped)

Example

Local:

let counter = 0;
setInterval(() => counter++, 1000);

function listenCounter(onChange) {
    watchProperty(() => counter).on('change', onChange);
}

// ...

new Api({
    listenCounter,
}, transport);

Remote:

// ...

api.listenCounter(bindCallback(counter => {
    console.log('counter', counter);
}));

Some stats

json stream without callbacks:
0.1ms per request 9600rps

json stream with callbacks:
0.2ms per request 5000rps

Usage

Install from npm:

npm i rpct

Pick browser minified version and use global RPCT object:

<script src="https://unpkg.com/rpct/browser/rpct.min.js"></script>

Or import browser, lightweight version for bundling:

import * as rpct from 'rpct/browser';

check out browser examples

Transports

as Transport may be implemented any environment, eg:

  • Streams
  • Socket.io
  • DOM Window
  • System sockets
  • Figma UI-plugin
  • WebSockets

check out examples

With stream trasport you can use any streamable format like json or msgpack.

Figma Plugin Example

Define "protocol":

interface PluginMethods {
    createRectangle(width: number, height: number): string;
}

interface UIMethods {}

In ui:

pluginApi = connectToPlugin<PluginMethods, UIMethods>({});

// invoke createRectangle(100, 50)
createdNodeId = await pluginApi.callMethod('createRectangle', 100, 50);

In plugin:

uiApi = connectToUI<PluginMethods, UIMethods>(figma, {
    createRectangle(width, height) {
        const rect = figma.createRectangle();
        rect.resize(width, height);
        figma.currentPage.appendChild(rect);
        return rect.id;
    }
});

You can use minified version from CDN (global RPCT object):

<script src="https://unpkg.com/rpct/browser/rpct.min.js"></script>

Window-Frame Example

Connect to frame, call remote sum method and pass numbers & callbacks.
Remote method do sums and call remote callbacks.

Parent window script:

var frame = document.getElementById('target');

var streamTransport = RPCT.connectToDOM(window, frame.contentWindow);
var api = RPCT.proxyMapRemote(new RPCT.Api({}, streamTransport));

api.sum(
    10, 20, // a, b
    sumResult => console.log('sum:', sumResult), // sumCallback
    mulResult => console.log('mul:', mulResult), // mulCallback
);

Inner frame script:

var streamTransport = RPCT.connectToDOM(window, window.parent);

var remoteApi = new RPCT.Api({
    // api methods here
    sum(a, b, sumCallback, mulCallback) {
        console.log(`called sum(${a}, ${b})`);
        sumCallback(a + b);
        mulCallback(a * b);
    },
}, streamTransport);

Watch remote counter?

In this code we call server's listenCounter method, pass client's callback for onChange event.
Then client will print counter 0, 1, 2, 3....

This is fully working example.

// Open local session for testing
const session = simpleCrossStream<ITransportProtocol>();

type ApiSchema = {
    listenCounter(
        onChange: (x: number) => void,
    ): void;
};

(async function server() {
    // counter
    let counter = 0;
    setInterval(() => counter++, 1000);

    function listenCounter(onChange: (x: number) => void) {
        watchProperty(() => counter).on('change', onChange);
    }

    const remoteStreamTransport = new DuplexStreamTransport(session.a, undefined, 'remote');
    const remoteApi = new Api<{}, ApiSchema>({
        listenCounter,
    }, remoteStreamTransport);
})();

(async function client() {
    const localStreamTransport = new DuplexStreamTransport(session.b, undefined, 'local');
    const localApi = new Api<ApiSchema, {}>({}, localStreamTransport);
    const api = proxyMapRemote(localApi);

    api.listenCounter(bindCallback(counter => {
        console.log('counter', counter);
    }));
})();