Skip to content

JSON-RPC inspired server and client with HTTP and WebSocket transports and JSON Schema IDL

License

Notifications You must be signed in to change notification settings

mhingston/jayson

Repository files navigation

Jayson

JSON-RPC inspired server and client with HTTP and WebSocket transports and JSON Schema IDL.

Installation

npm install mhingston/jayson

Introduction

Format

Jayson uses a custom version of JSON-RPC for messages. The specification is based on JSON-RPC 2.0 with the following changes:

  • The request and response objects must contain the property jayson with the value set to the version of the API being used. This property replaces the jsonrpc property.

    e.g.

    {
        "jayson": "1.0"
    }
  • A response object without an error property and without a result property indicates that the remote method returned undefined.

  • The following additional properties can be defined within a request object:

    • auth {String} A JWT providing the authentication context.

The reason for baking auth into JSON-RPC was that I felt it should be part of the protocol and not reliant on the different authentication mechanisms available within the transport layers (i.e. HTTP, WebSocket).

Method Properties

Remote methods may have attached to them the following properties:

  • schema {Object} A JSON schema which is used as the IDL. The schema must verify against the method schema. If a method doesn't provide a full schema then any calls to the method won't have their auth or params properties validated. Similarly if the returns schema isn't defined then the return value won't be validated.

    Example

    function hello(name)
    {
        return 'Hello ' + name;
    }
    
    hello.schema =
    {
        params:
        {
            type: 'string'
        },
        returns:
        {
            type: 'string'
        }
    }

    If you need to reference schema definitions you should pass in your definitions schema to the server config. Example.

  • timeout {Number} How long to wait (in milliseconds) before timing out the request. If not provided then the timeout value will be used from the server config.

Server Usage

Note: The server is intended to be run behind a reverse proxy, unless you're using electron ofcourse.

// Import the module
const Jayson = require('jayson');

// Declare an object literal with all the methods you want to expose...
const methods =
{
    foo: () => 'hello',
    bar: (a, b) => a + b,
    baz: ({name}) => 'hello ' + name 
};

// ...or pass in an instance of a class with the methods you want to expose.
class Method
{
    foo()
    {
        return 'hello';
    }

    bar(a, b)
    {
        return a + b
    }

    baz({name})
    {
        return 'hello ' + name;
    }
}

const methods = new Method();

Note: The Jayson server passes a context object argument to every method that's called. If the params property of a request is an object then the property context will be added to that object. If the params property is any other type then the first argument passed to the method will be the context object.

context {Object} Method context.

  • headers {Object} Request headers.
  • auth {String} A JWT providing the authentication context.

Define your config (default values shown below):

const config =
{
    title: 'Jayson Server API',
    methods,
    logger: false,
    jsonLimit: '1mb',
    timeout: 60000,
    http:
    {
        port: 3000,
        cors: {},
        helmet: {noCache: true},
        compress: {}
    },
    ws:
    {
        port: 3001,
        heartbeat: 30000
    },
    jwt:
    {
        secret: 'sauce'
    }
}
  • title {String} Name of the API instance. Default = 'Jayson Server API'.
  • description {String} Description of the API instance. Default = undefined.
  • $id {String} JSON Schema ID. Default = undefined.
  • methods {Object} (Required) Object containing the methods exposed to the RPC server. Default = undefined.
  • definitions {Object} Schema definitions. Use this when you need to reference shared definitions from method schemas. See the schema-definitions example. Default = undefined.
  • logger {Boolean|Function} Set to true to have debug log written to the console or pass in a function to receive the log messages. Default = undefined.
  • jsonLimit {String} Maximum size of the message payload. Default = '1mb'.
  • timeout {Number|Null} Default timeout for all RPC calls (in milliseconds). Set to null to disable default timeout. Default = 60000.
  • http {Object}. Default = undefined.
    • port {Number} Port to listen to HTTP connections on. Default = 3000.
    • cors {Object} CORS options, see koa2-cors. Default = {}.
    • helmet {Object} Helmet options, see koa-helmet. Default = {noCache: true}.
    • compress {Object} Compress options, see compress. Default = {}.
  • ws {Object}. Default = undefined.
    • port {Number} Port to listen to WebSocket connections on. Default = 3001.
    • heartbeat {Number} How often to send pings to clients (in milliseconds). Default = 30000.
  • electron {Boolean} Whether the server is running in electron. Default = undefined.
  • jwt {Object}. Default = {}.
    • secret {String|Buffer|Object} See jwt.sign. Default = 'sauce'.
    • options {Object} See jwt.sign.

Note: The config must include a http and/or a ws property unless you're using electron in which case just set electron: true.

Instantiate a new RPC server:

new Jayson.Server(config);

Client Usage

For use in a browser you can either include the bundle dist/jayson.min.js or you can import the module using a module loader.

Note: When used in a browser the global variable window.Jayson is set.

// Import the module
const Jayson = require('jayson');

Define your config (default values shown below):

const config =
{
    retryDelay: 3000,
    timeout: 60000,
    logger: false,
    url: 'http://127.0.0.1:3000'
}
  • retryDelay {Number} If the connection to the WebSocket server is lost how often should the client attempt to reconnect (in milliseconds). Default = 3000.
  • timeout {Number} How long to wait for a response for every RPC call (in milliseconds). Default = 60000.
  • logger {Boolean|Function} Set to true to have debug log written to the console or pass in a function to receive the log messages. Default = undefined.
  • url {String} The URL of the Jayson server. To connect to a WebSocket server use a WebSocket protocol i.e. ws:// or wss://. If you're using electron this isn't required. Default = undefined.
  • electron {Boolean} Whether the client is running in electron. Default = undefined.

Instantiate a new RPC client:

const client = new Jayson.Client(config);

Class: Client

client.connect(callback) [async]

Connect to the RPC server.

  • callback(error, client) {Function} Callback function (optional).
    • error {Object|Null} Error object.
    • client {Object} The client instance.

client.discover() [async]

Retrieve the RPC methods schema from the RPC server. This is necessary to validate future RPC calls. If you don't call this method then schema validation will be disabled.

  • callback(error, result) {Function} Callback function.
    • error {Object|Null} Error object.
    • result {Object} Method schema.

client.call(args) [async]

Call a method on the RPC server.

  • args {Object|Array<Object>}.
    • method {String} (Required) Name of the RPC method to call.
    • params {Array|Object} Arguments to pass to the RPC method. Be aware that your params will be serialized as JSON (i.e. JSON.stringify).
    • auth {String} A JWT providing the authentication context.
    • timeout {Number} How long to wait for a response for the RPC call (in milliseconds).
    • notification {Boolean} Whether the call is a notification or not (i.e. expects a response).
    • callback {Function} Callback function.
      • error {Object} Error object.
      • result {String|Number|Boolean|Null|Undefined|Array|Object} Result from the RPC call.

Example

client.connect()
.then(() => client.discover())
.then(() => client.call(
{
    method: 'foo'
}))
.then(response =>
{
    console.log(response);
})
.catch(error =>
{
    console.log(error.message);
})

See the examples folder for more.

Generating Documentation

Once you have an API server up and running and have provided a schema for some/all of your methods you can generate a HTML file using docson.

From your project directory:

node node_modules/.bin/docson --server <Server URL> --output <Output File>

e.g.

node node_modules/.bin/docson --server http://127.0.0.1:3000 --output index.html

Notes

  • Jayson supports calling async methods, i.e. functions returning a promise.
  • Rate limiting requests is not supported and is beyond the scope of this project. It's better handled by a reverse proxy and/or firewall.

About

JSON-RPC inspired server and client with HTTP and WebSocket transports and JSON Schema IDL

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published