BrowserRPC
This library allows you exchange messages beetween BrowserRPC instances using window.postMessage API. You can call remote procedures as promises.
Installing
Using npm:
$ npm install @fleekhq/browser-rpc
Using yarn
$ yarn add @fleekhq/browser-rpc
Usage
RPC requires that you setup a client (who calls remote procedures and wait for responses) and a server (who process the calls and send back the responses).
Let's say that your client is going to be placed in your webpage and need to call a remote procedure method called sum
that receives 2 arguments (2 number values to sum).
import { BrowserRPC } from '@fleekhq/browser-rpc';
const client = new BrowserRPC(window, {
name: 'browser-client',
target: 'rpc-server',
timeout: 10000,
});
client.start();
client
.call('sum', [1, 2])
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error(error);
});
client.stop();
We've already setup the client, now we have to setup the server. The server listen for methods calls, process the calls and send the responses back to the client. Take in mind that the server only process the calls coming from the target
specified in the initial config object.
import { BrowserRPC } from '@fleekhq/browser-rpc';
const server = new BrowserRPC(window, {
name: 'rpc-server',
target: 'browser-client',
});
server.exposeHandler('sum', ({ callback, message }, val1, val2) => {
console.log(message);
const result = val1 + val2;
callback(null, result);
});
server.start();
API
For types info please refer to the types.ts
file: src/types.ts
New BrowserRPC instance:
new BrowserRPC(window, config)
window
: the window object used by the instance to post messages and add the event listeners.config
: [object] config required by the instance
name
: [string] name of the instance. This name is injected in every message emitted by the instance to identify the caller.target
: [string] the name of the rpc instance that has to handle the call requeststimeout?
: [number] timeout for call methods. 0 means no timeout. by default this value is 5000 mshandlers?
{[name: string]: Handler} object with handlers functions. object key is taken as the name identifier for the every handler function defined in the object
BrowserRPC.start(): void
Add the event listeners required by the instance
BrowserRPC.stop(): void
Remove the event listeners and rejects all the pending calls.
BrowserRPC.exposeHandler(name: string, handler: Handler): void
Add a new handler to the instance
BrowserRPC.removeHandler(name: string): boolean
Remove a handler by its name
BrowserRPC.call(handler: string, args: any[], config?: CallConfigObject): Promise
Call a new remote procedure method. Returns a promise resolving to the response or rejecting with an error.
ErrorRes Type
Your handlers functions receive as first argument a callback function that has to be called with the response (second argument) or with an ErrorRes object as first argument if there is any error.
ErrorRes Type:
export type ErrorRes = {
code: number,
message: string,
data?: any,
};
This error type represents a JSON RPC error object. Please refer to the JSON RPC documentation to get information about error codes, message and data:
https://www.jsonrpc.org/specification#error_object
ProxyRPC
This class is meant to be used on browser extensions content script. It allow proxy all the calls that arn't handled by the instance to the background script controller (see BackgroundController class). Same way, all the responses received from the BackgroundScript controller are redirected to the requester. (This class implements window.postMessage for the communication between webpages and content script, and browser.runtime.port for the cummunication between the content script and the background script).
import { ProxyRPC } from '@fleekhq/browser-rpc';
const server = new ProxyRPC(window, {
name: 'rpc-server',
target: 'browser-client',
});
server.exposeHandler('sum', ({ callback, message }, val1, val2) => {
console.log(message);
const result = val1 + val2;
callback(null, result);
});
server.start();
now if you try to call the method sum
from your client, you'll receive the response from the ProxyRPC instance, but if you try to call a method not defined into the Proxy, the call is going to be redirected to the background script using Port
communication in order to be processed. If the method also doesn't exists in the background script controller, the call is rejected with an error.
BackgroundController
This class implements Port communication between the content script and other resources with access to the Port API. Similar to the BorwserRPC and ProxyRPC, you can expose handlers to process the requests.
import { BackgroundController } from '@fleekhq/browser-rpc';
const backgroundController = new BackgroundController({
name: 'bg-script',
trustedSources: [
'rpc-server',
'another-port-source',
],
});
backgroundController.exposeController('hello', (opts, name) => {
const { message, sender, callback } = opts;
console.log(message);
console.log(sender);
callback(null, `hello ${name}!!!`);
});
backgroundController.exposeController('remoteCall', (opts, myNumber, callId, portId) => {
const { callback } = opts;
callback(null, myNumber);
callback(null, myNumber, [{ portId, callId }]);
});
backgroundController.start();
BackgroundController.exposeHandler(name: string, handler: ControllerHandler): void
Add a new handler to the instance
handler: (opts, ...args) => void
- opts (object):
- sender: Sender information object
- id: port id assigned by the instance
- name: port name
- port: port
- ports: Map with all the ports handled by the instance: Map<id: number, port>;
- message: The full message
- callback: callback used to send a response back to the sender port or to another port
- err: ErrorRes type if you want to send an error or null if is not required
- res: any, the respose that you want to send back to the sender
- targetPorts: optional, this is an array of objects with a portId and a callId (message call id). If you want to send a response to a different port, you have to pass this information, otherwise the callback is going to respond directly to the sender
- portId: number, the target port id
- callId: number | string, the call id
type HandlerProps = {
sender: {
id: number,
name: string,
port: chrome.runtime.Port,
},
ports: Map<number, chrome.runtime.Port>,
message: Message,
callback: (
err: ErrorRes | null,
res: any,
targetPorts?: {
portId: number,
callId: CallID,
}[],
) => void,
};
type ControllerHandler = (
props: HandlerProps,
...args: any[]
) => void;
PortRPC
Similar to BrowserRPC, this one is used to send requests to the BackgroundController using Port communication
import { PortRPC } from '@fleekhq/browser-rpc';
const client = new PortRPC({
name: 'browser-port-rpc',
target: 'bg-script',
timeout: 10000,
});
client.start();
client
.call('sum', [1, 2])
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error(error);
});
Full Example
Here is a full implementation of BrowserRPC, ProxyRPC, PortRPC and BackgroundController
webpage
import { BrowserRPC } from '@fleekhq/browser-rpc';
const client = new BrowserRPC(window, {
name: 'browser-client',
target: 'rpc-server',
timeout: 10000,
});
client.start();
client
.call('sum', [1, 2])
.then((result) => {
console.log(result);
});
client
.call('hello', ['Jhon'])
.then((result) => {
console.log(result);
});
content script
import { ProxyRPC } from '@fleekhq/browser-rpc';
const server = new ProxyRPC(window, {
name: 'rpc-server',
target: 'browser-client',
});
server.exposeHandler('sum', ({ callback, message }, val1, val2) => {
console.log(message);
const result = val1 + val2;
callback(null, result);
});
server.start();
background script
import { BackgroundController } from '@fleekhq/browser-rpc';
const backgroundController = new BackgroundController({
name: 'bg-script',
trustedSources: [
'rpc-server',
'another-port-source',
],
});
backgroundController.exposeController('hello', (opts, name) => {
const { message, sender, callback } = opts;
console.log(message);
console.log(sender);
callback(null, `hello ${name}!!!`);
});
backgroundController.exposeController('remoteCall', (opts, myNumber, callId, portId) => {
const { callback } = opts;
callback(null, myNumber);
callback(null, myNumber, [{ portId, callId }]);
});
backgroundController.start();