@pubkeeper/client

Pubkeeper javascript SDK

Usage no npm install needed!

<script type="module">
  import pubkeeperClient from 'https://cdn.skypack.dev/@pubkeeper/client';
</script>

README

Pubkeeper Client

@pubkeeper/client enables web browsers to join, consume, and produce content on a Pubkeeper network.

Concepts & Terms

  • Brew : A protocol/method of delivery, e.g. WebSocket, SocketIO, WebRTC, HTTP
  • Brewer : An object bound to a specific topic that accepts a data transports it to matched patrons.
  • Patron : A consumer bound to one or more topics, it receives data from matched brewers.

Installation

In a browser:

<script src="pubkeeper-client.js"></script>

Using npm:

$ npm install --save @pubkeeper/client

Then, load using ES5 require() syntax…

var PubkeeperClient = require('@pubkeeper/client').PubkeeperClient;

…or with ES2015+ import syntax:

import { PubkeeperClient } from '@pubkeeper/client';

Basic Syntax

Client Configuration

import { PubkeeperClient } from '@pubkeeper/client';
import { WebSocketBrew } from '@pubkeeper/brew-websocket';
import { PubkeeperAesCbcCrypto } from '@pubkeeper/crypto-cjs-aes-cbc';

// Create a client
const client = new PubkeeperClient({
  server: 'wss://192.168.100.191:9898/ws',
  jwt: "[your JWT]",
  crypto: new Map([
    PubkeeperAesCbcCrypto,
  ]),
  brews: [
    new WebSocketBrew({
      brewerConfig: {
        hostname: '192.168.100.191',
        port: 8000,
        secure: true,
      },
    }),
  ],
});

client.connect();

Note: This client is the core pubkeeper client, as such it does not include any crypto or brews. If you want a pre-packaged version suitable for general usage see @pubkeeper/browser-client

Brewing Setup

To produce data into the Pubkeeper network you will need to create a brewer. The example below will setup a Brewer with the topic of topic.information

client.addBrewer('topic.information', (brewer) => {
  // brewer has been *matched* by pubkeeper, i.e. there is
  // at least one patron of this topic.
  let counter = 0;

  const id = setInterval(() => {
    brewer.brewJSON({ counter });
    counter += 1;
  }, 1000);

  return function teardown() {
    // teardown function will be called when there are
    // _no_ more patrons or this brewer has been removed.
    clearInterval(id);
  };
});

Patron Setup

To listen to someone who is brewing information, you need to subscribe to the topic, and configure a callback to occur when there is data available for processing.

client.addPatron('weather.*', (patron) => {
  // patron has been *matched* by pubkeeper, i.e. there is
  // at least one brewer of this topic.
  function handler(bytes, meta) {
    console.log(meta.from, meta.topic, patron.id, bytes);
  };

  patron.on('message', handler);

  return function teardown() {
    // teardown function will be called when there are
    // _no_ more brewers or this patron has been removed.
    patron.off('message', handler);
  };
});

Brewer/Patron Connections

The Patron message event is always fired with a Uint8Array as data, no matter which brew*() method was called on the brewing side. You need to handle parsing/casting those bytes.

For instance, given a brewer:

client.addBrewer('tick', (b) => {
  let id, count = 0;
  (function tick() {
    id = setTimeout(tick, 1000);
    b.brewJSON({ count });
  })();
  return () => { clearTimeout(id); };
});

Then on the patron side, it would look like this;

client.addPatron('tick', (p) => {
  const handler = (bytes) => {
    const text = new TextDecoder().decode(bytes);
    const value = JSON.parse(text);
    console.log(value.count);
  };
  p.on('message', handler);
  return () => { p.off('message', handler); };
});

NOTE This will be resolved in future versions of the protocol.

State Change Events

Brewers and Patrons will be notified when thier couterparts are added or removed:

client.addBrewer('topic.information', (brewer) => {
  const handleAdded = ({ id, topic }) => {
    console.log('patron (%s) added', id);
  };

  const handleRemoved = ({ id }) => {
    console.log('patron (%s) removed', id);
  };

  // setup
  brewer.on('added', handleAdded);
  brewer.on('removed', handleRemoved);

  return function teardown() {
    // cleanup
    brewer.off('added', handleAdded);
    brewer.off('removed', handleRemoved);
  };
});

PubkeeperClient Class

The PubkeeperClient manages the connection to a pubkeeper server.

new PubkeeperClient(options)

| Name | Type | Description | |-|-|-| | options | ClientOptions | The options for this pubkeeper client |

ClientOptions : object

These options can be given to a PubkeeperClient to customize its behavior.

| Name | Type | Default | Description | |-|-|-|-| | server | string | - | Websocket URL of the pubkeeper server | | jwt | string | - | Auth token for the pubkeeper server | | brews | Array<Brew>| [] |Supported brews of this client | | crypto | Map<*,Crypto> | new Map | Supported crypto mappings for this client | | protocols | Map<*,Protocol> | new Map | Supported protocol mappings for this client. Defaults to the legacy protocol | | maxRetries | number | Infinity | Maximum number of connection retries | | maxTimeout | number | 10000 | Maximum timeout between retries (in milliseconds) |

Examples

Using ES2015 Promise:

const client = new PubkeeperClient(config)

client.connect().then(() => {
  console.log('ready');
});

Using ES2017 async/await:

(async () => {
  const client = new PubkeeperClient(config)
  await client.connect();
  console.log('ready');
})();

Members

.status : string

Returns the status of this client. Can be one of the following:

| Value | Description | |-|-| | 'connecting' | Attempting to connect. | | 'connected' | Connected | | 'reconnecting' | Has lost the connection, and has queued a reconnection attempt. | | 'disconnected' | Disconnected | | 'dead' | Given up retrying to connect |

Methods

.connect({ wait = true } = {}) : Promise?

Connect to the configured pubkeeper server. If { wait: true } is specified, this function will return a Promise that will be resolved when the connection to the pubkeeper server has been finalized. Passing { wait: false } will immediately return undefined and the server may or may not be connected yet.


.disconnect()

Disconnect from the server.


.addBrewer(topic[,[ options,] matched]) : Brewer

Create a new brewer of topic.

| Name | Type | Description | |-|-|-| | topic | string | The topic | | options | BrewerOptions | | | matched | Function| Callback that is invoked when the brewer has been matched with a patron |

BrewerOptions : object

These options can be given to the PubkeeperClient#addBrewer method to customize the brewers behavior.

| Name | Type | Default | Description | |-|-|-|-| | autoRemoveListeners | boolean | false | Automatically remove all event listeners on unmatch | | brews | Array<(string\|Brew)> | - | A subset of brews to use for this brewer, defaults to all registered brews. | | id | string| - | The UUID of this brewer, defaults to a random UUIDv4 | | waitForBrews | (number\|boolean) | 250 | The maximum wait time to wait when brewing data for brew connections to stablize. |

Examples

Using the matched callback:

client.addBrewer('topic', (brewer) => {
  brewer.brewText('somebody is out there!');
});

With extended options:

// Do not wait for brew connections to stablize before sending.
client.addBrewer(
  'topic',
  { waitForBrews: false },
  (brewer) => {
    brewer.brewText('somebody is out there!');
  },
);

Managing resources with teardown function:

client.addBrewer(
  'device.motion',
  (brewer) => {
    const handler = ({ acceleration }) => {
      brewer.brewJSON(acceleration);
    };
    window.addEventListener('devicemotion', handler);
    return () => {
      window.removeEventListener('devicemotion', handler);
    };
  },
);

.removeBrewer(brewer)

Remove a brewer from this client.

Examples
const ref = client.addBrewer('topic', matchedFn);
/* ...later */
client.removeBrewer(ref);

.addPatron(topic[,[ options,] matched]) : Patron

Create a new patron of topic.

| Name | Type | Description | |-|-|-| | topic | string | The topic | | options | PatronOptions | | | matched | Function| Callback that is invoked when the patron has been matched with a brewer |

PatronOptions : object

These options can be given to the PubkeeperClient#addPatron method to customize the patrons behavior.

| Name | Type | Default | Description | |-|-|-|-| | autoRemoveListeners | boolean | false | Automatically remove all event listeners on unmatch | | brews | Array<(string\|Brew)> | - | A subset of brews to use for this patron, defaults to all registered brews. | | id | string| - | The UUID of this patron, defaults to a random UUIDv4 |

Examples

Using the matched callback:

client.addBrewer('topic', (patron) => {
  const handler = (data, meta) => { /* process data */ };
  patron.on('message', hanlder);
  return () => { patron.off('message', handler); };
});

.removePatron(brewer)

Remove a patron from this client.

Examples
const ref = client.addPatron('topic', matchedFn);
/* ...later */
client.removePatron(ref);

Brewer Class

A brewer is a publisher of information in a pubkeeper system.

Members

.id : string

the uuid of this brewer


.brews : Array<string>?

the subset of brew names that this brewer is using.


.topic : string

the topic this brewer is bound to


.count : number

current count of matched patrons


.isMatched : boolean

Is the brewer currently matched with any patrons

Methods

.brew(bytes[, options]) : Promise

Publish binary data to a brewer.

| Name | Type | Description | |-|-|-| | bytes | (Uint8Array\|Uint8ClampedArray) | Binary data to brew | | options | object | | | options.waitForBrews | number? | Override the brewers config |


.brewText(text[, options]) : Promise

Publish text as a UTF-8 encoded byte array.

| Name | Type | Description | |-|-|-| | text | string | text to brew | | options | object | | | options.waitForBrews | number? | Override the brewers config |


.brewJSON(value[, options]) : Promise

Publish value using JSON.stringify.

| Name | Type | Description | |-|-|-| | value | * | value to serialize and brew | | options | object | | | options.waitForBrews | number? | Override the brewers config |


.on(event, fn[, context]) : this
.addListener(event, fn[, context]) : this

Add a listener for a given event.


.off(event, fn[, context[, once]]) : this
.removeListener(event, fn[, context[, once]]) : this

Remove the listeners of a given event.

Events

added

Fired when a matched patron is added.

| Name | Type | Description | |-|-|-| | e | object | | | e.id | string | UUID of the added patron | | e.topic | string | topic of the brewer |


removed

Fired when a matched patron is removed.

| Name | Type | Description | |-|-|-| | e | object | | | e.id | string | UUID of the removed patron |

Patron Class

A patron is a subscriber of information in a pubkeeper system.

Members

.id : string

the uuid of this patron


.brews : Array<string>?

the subset of brew names that this patron is using.


.topic : string

the topic this patron is bound to


.count : number

current count of matched brewers


.isMatched : boolean

Is the patron currently matched with any brewers

Methods

.on(event, fn[, context]) : this
.addListener(event, fn[, context]) : this

Add a listener for a given event.


.off(event, fn[, context[, once]]) : this
.removeListener(event, fn[, context[, once]]) : this

Remove the listeners of a given event.

Events

added

Fired when a matched brewer is added.

| Name | Type | Description | |-|-|-| | e | object | | | e.id | string | UUID of the added brewer | | e.topic | string | topic of the brewer |


removed

Fired when a matched brewer is removed.

| Name | Type | Description | |-|-|-| | e | object | | | e.id | string | UUID of the removed brewer |


message

Fired when a message is recieved from a brewer.

| Name | Type | Description | |-|-|-| | data | Uint8Array | The binary data from the brewer | | meta | object | | | meta.topic | string | topic of the brewer that brewed this data | | meta.from | string | UUID of the brewer that brewed this data |