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

@ki1r0y/jsonrpc

v1.0.1

Published

Easy setup jsonrpc using postMessage between frames or workers.

Downloads

12

Readme

jsonrpc

Easy setup, two-way jsonrpc using postMessage between frames or workers.

// In worker.js
import dispatch from '@ki1r0y/jsonrpc/index.mjs';
const send = dispatch({ target: self }); // Returns a function.
async function sum(a, b) {               // Callable by other end.
  let offset = await send('getOffset');  // Call getOffset() at other end, returning promise.
  return a + b + offset;
}

// In app.js
import dispatch from '@ki1r0y/jsonrpc/index.mjs';
const worker = new Worker('worker.mjs', {type: 'module'});
const send = dispatch({ target: worker, receiver: self })}; // Returns a function.
function getOffset() {               // Callable by other end.
  return 42;
}
async function demo() {
  return await send('sum', 1, 2);    // Calls sum(1, 2) at other end.
}

The default export from this package is a

function({
   target = self,
   receiver = target,
   namespace = receiver,
   origin = ((target !== receiver) && target.location.origin),

   log = null,
   info = console.info.bind(console),
   warn = console.warn.bind(console),
   error = console.error.bind(console),
      
   targetLabel = target.name || origin || target.location?.href || target,
   dispatherLabel = namespace.name || receiver.name || receiver.location?.href || receiver
})

that does two things:

  1. It adds a handler for message events on receiver(*). The handler processes jsonrpc requests or responses and ignores non-jsonprc messages. The target can be anything that defines postMessage, such as port, worker, the contentWindow for an iframe, or top-level self. When a jsonrpc request comes in from target, the handler will call namespace[method](...params) and send the result or error back to target.
  2. Returns a function(methodName, ...arguments) that can be used to make requests to target. A call to this function returns a Promise that will be resolved or rejected with the response from target. (All such requests are sent as a jsonrpc request that expects a response, and not as jsonrpc 1.0 notifications which have no way to indicate errors.)

Although the above example shows @ki1r0y/jsonrpc being used on both sides, any JSON-RPC 2.0 conforming implementation can be used on the other side. Although @ki1r0y/jsonrpc always sends array params, it will accept a single object as params as well for compatability with other jsonrpc implementations.

The origin argument is used the second argument to target.postMessage(message, targetOrigin), and is used in the receiver.onmessage handler to ignore messages that are not from the spected origin.

The log, info, warn, and error arguments are used to log sending/receiving, setup, non-jsonrpc messages, and origin or source mismatches, respectively, using targetLabel and dispatcherLabel. A falsy value for a logger is allowed.

Errors

When handling an incoming request, we call namespace[method](...params), await the response, and post the jsonrpc result using the original request id. (See spec.) If the call throws an error or is rejected, an error response is send back on the request id.

Error objects (or more generally thrown and rejected values) are not necessarily transferable by postMessage. We transfer them as a POJO containing the properties name, code, message, and data from the rejection.

When receiving a request error response, the promise is rejected with this object as value.

A Complex Example

The ki1r0y distributed-security package implements a separate secure browsing context for cryptographic work. Applications load the module from an origin that is distinct from the rest of the application. The module then dynamically creates an iframe, which itself launches a web worker. This creates the following:

   app origin                  distributed-security origin
     foo.com                         security.foo.com
+--------------+         +--------------+         +-------------+
| application  |         | iframe       |         | worker      |
|.             | jsonrpc |              | jsonrpc |             |     
|            --+---------+->          --+---------+->           |
|  index.mjs <-+---------+- vault.mjs <-+---------+- worker.mjs |
+--------------+         +--------------+         +-------------+

On the right-hand side, the connection from worker.mjs is a straightforward two-direction jsonrpc to the vault.mjs code in in the iframe. worker.mjs imports the Security API and jsonrpc dispatch. Only a single line is needed to arrange for postClient to be a function that uses the worker's postMessage to send requests to vault.mjs, and to answer requests from vault.mjs by calling the named method in the Security api.

(targeLabel helps jsonrpc emit clear and specific logging when there are multiple jsonrpc pathways. The worker knows it's own name, but does not know the name of the other end.)

The worker instance is specifically created by vault.mjs, so the other end of the connection in worker.mjs is unambigous. Within vault.mjs, the postWorker definition at the bottom of the file is not much more complicated, needing only to additionally specify the worker object as the target.

Between index.mjs and vault.mjs, there are another pair of jsonrpc connections. In most cases, these could both be specified using the origin parameter to dispatch, which would arrange for messages to be sent only to the specified target, and noisily ignoring messages that are not from the specified target. However, in distributed-security, it is possible, and even encouraged, for multiple components of the application to separately include their own versions of distributed-security. The could all import the same index.mjs from security.foo.com (in this example app), in which case they all communicate with the same vault.

However, they could also import their own copies of index.mjs hosted at different domains. That will also work just fine, with each vault only accessible by the code using the import.mjs loaded from that domain.

Although not correct production usage, the various components could all load copies from the same domain -- e.g., a developer's localhost. If the various components that reference index.mjs are each bundled into their own separate component module, then the different modules will create different vaults. Even this will correctly distinguish between communications to different vaults, but jsonrpc will log that it is rejecting messages from other modules. To be obsolutely clear that no cross-module communication is happening, distributed-security uses MessagePorts for communcation, which were created for this situation. In index.mjs and the postClient definition in vault.mjs, we see these MessagePorts used as targets. The initialization of the vault.mjs message port from index.mjs is done in an additional message outside of the jsonrpc implementation.


Notes

  • The 'message' handler is added using receiver.addEventListener, and not receiver.onmessage. If the application creates a MessagePort and passes this as the receiver, the application must call start on the message port, as addEventListener does not automatically do this the way onmessage does.