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

psionic

v0.2.0

Published

json-rpc communication utility

Downloads

7

Readme

psionic rpc

not stable.

psionic is a bidirectional rpc system for node, glued together with json-rpc and a promise-based workflow.

getting started

to create a websocket-server (using ws):

all these examples use babel-node, see the examples folder for config. using babel is optional, but it does make working with promises nicer.

// server
import psionic from 'psionic';

psionic.webSocket.createServer({ port: 3000 }, function (client) {
  // you have to call 'describe' once for the client to start.
  client.describe({
    factor: 'doubling'
    // functions can return promises if needed,
    // they'll always be promises for the caller.
    multiply(x) { return x * 2; }
  });
});

and a corresponding client:

// client
import psionic from 'psionic';

(async function () {
  let client = await psionic.webSocket.connect('ws://localhost:3000');
  let doubled = await client.multiply(5);
  console.log(client.factor + '5 gives ' + doubled + '! amazing!');
  // doubling 5 gives 10! amazing!
})().catch(ex => console.error(ex.stack));

server to client communication

the client can also call describe to send state to the server.

// client
import psionic from 'psionic';

(async function () {
  let client = await psionic.webSocket.connect('ws://localhost:3000', {
    // using connect options, your state will be sent right as the server socket is created.
    describe: { name: "joe" }
  });

  let result = await client.test("jdp");
  if (!result) {
    // you can also call describe later to send state whenever needed,
    // which gives a promise to be sure it makes it to the server.
    await client.describe({
      name: "jdp",
      welcome(x) { console.log('Server says: ' + x); }
    });
  }

  console.log(await client.test("jdp"));
})().catch(ex => console.error(ex.stack));

corresponding server:

// server
import psionic from 'psionic';

psionic.webSocket.createServer({ port: 3000 }, function (client) {
  // the 'client' objects data is sent from the client,
  // it is user input and can't be trusted.
  let name = client.name; // joe

  client.describe({
    async test(testName) {
      if (client.welcome instanceof Function) {
        await client.welcome('Welcome, ' + client.name);
      }
      return testName === client.name;
    }
  });
});

emitting events

if you need ad-hoc message passing, you can use the event emitter:

// server
import psionic from 'psionic';

psionic.webSocket.createServer({ port: 3000 }, function (client) {
  client.describe({}); // describe still has to be called.
  var pings = 0;
  
  client.events.on('pong', function (i) {
    console.log('client sent a pong: ' + i);
  });
  
  setInterval(function () {
    client.emit('ping', ++pings);
  }, 1000);
});

and a corresponding client:

// client
import psionic from 'psionic';

(async function () {
  let client = await psionic.webSocket.connect('ws://localhost:3000');
  
  client.events.on('ping', function (i) {
    console.log(i + ' pings since we connected');
    // the client can trigger events on the server, as well.
    client.emit('pong', i);
  });
})().catch(ex => console.error(ex.stack));

communication protocols

So far there is no implementation with fallbacks (using say, socket.io or primus,) so each server/client pair is specific to a single protocol.

socket

Plain Node.js net sockets.

It can be accessed using require('psionic').socket, or require('psionic/lib/socket')

// server
import psionic from 'psionic';

// all server options are passed to net.createServer
// if a `port` is passed, net.listen will be called during construction.
var opts = { port: 9000 };

var server = psionic.socket.createServer(opts, function (client) { });

// createServer returns the underlying net server
server.listen(9000);

// client
var promise = psionic.socket.connect(
	// the first argument is passed to net.connect
	{ port: 9000 },
    
    // the second argument is used to configure the psionic client.
    { describe: {} } 
);

promise.then(function (client) {
	// you can get the underlying socket after connecting.
    // note that the client auto-reconnects, so this can change.
	var underlyingSocket = client.state.socket;
});

websocket

Websockets are created using the websockets/ws library, or natively on the browser.

It can be accessed using require('psionic').webSocket, or require('psionic/lib/websocket')

// server
import psionic from 'psionic';

// all server options are passed to the ws.Server constructor
var opts = { port: 9000 };

var server = psionic.webSocket.createServer(opts, function (client) { });

// createServer returns the underlying ws server
server.listen(9000);

// client
var promise = psionic.webSocket.connect(
	// the first argument is passed to the ws WebSocket constructor
    'ws://127.0.0.1:9000',
    
    // the second argument is used to configure the psionic client.
    { describe: {} } 
);

promise.then(function (client) {
	// you can get the underlying WebSocket after connecting.
    // note that the client auto-reconnects, so this can change.
	var underlyingSocket = client.state.socket;
});

browser support

The WebSocket protocol does not use any polyfills at this time, so it's limited to IE10+. If that's not a problem, you can require('psionic') with browserify or webpack and use psionic.webSocket.connect.

client object

The client object is used on both the server- and client-side to send and receive messages.

// Default client structure
client = {
  events: EventEmitter,
  state: EventEmitter + {
    connected: true,
    
    // callId stores the id of the previous rpc-call,
    // this is incremented for every call.
    callId: 0,
    
    // describe is a reference to the functions that
    // can be called from the remote. it is replaced
    // by calling `client.describe`.
    describe: { ... },
    
    // @emit is called when the remote triggers an event.
    "@emit": Function
    
    // @describe is called when the remote is replacing its description.
    "@describe": Function
  },
  
  // the describe function calls @describe on the remote,
  // to tell it which functions are available on this client.
  describe: Function,
  
  // emit calls @emit on the remote, which triggers an event.
  emit: Function
  
  // any other functions that are added using the describe function
  // on the remote are found on this object as well.
}

client.state events

Client.state has a few events that can be used to detect changes in the connection.

  • open - connected, but no service description has been receieved
  • describe - an updated service description has been received
  • connect - connected, and a service description has been received
  • send - a message is ready to be sent to the remote
  • message - a message has been received from the remote
  • result:{id} - a remote procedure call has returned a value
  • disconnect - the transport has disconnect, but might reconnect
  • close - the transport is shutting down, and will not reconnect

client.events events

Client.events is only triggered by calling Client.emit(name, args), and can have any event names.

creation options

When connecting to a server using socket.connect or webSocket.connect, you can supply options (well, option..) to configure the client.

  • describe - state object that is sent to the server when connecting, can be used to pass state needed for initializing the remote service.

messaging protocol

Psionic uses JSON-RPC messages, with single line JSON. It does not support notification requests at this time, all requests must be responded to.

example messages

In order to begin communicating, both client and server must send their description. The client starts this, by calling the "@describe" function.

{"id":1,"name":"@describe","args":[{"test":"psionic!function","example":"value"}]}

This tells the server to create an rpc-function called "test", and an extra value to add to the client object. The object can be arbitrarily nested.. When the server is ready, it responds to the message and sends its own description.

{"id":1}
{"id":5,"name":"@describe","args":[{"login":"psionic!function"}]}

The client then responds to the message, and calls its login function.

{"id":5}
{"id":2,"name":"logim","args":["username","password"]}

After the server is done processing the request, it will respond with its result:

{"id":2,"error":{"code":-32601,"message":"Function not found: logim"}}

Different error codes are sent based on the JSON-RPC spec. code and friendlyMessage are used from any thrown Error objects. If no friendlyMessage is found, "Unhandled error" will be used. Let's try that again..

{"id":3,"name":"login","args":["username","password"]}

There we go, everything's spelled right, now we'll get a response:

{"id":3,"result":true}

And that's it!