Webstrates A research prototype enabling collaborative editing of websites through DOM manipulations.

The God API allows services to listen to real-time activity taking place on a Webstrates server through a websocket connection.

To enable the God API, define a God API key in the Server Config and then restart the Webstrates server. Afterwards, the God API will be available at ws://localhost:7007/ws?noop. (The ?noop is added to prevent the server from handling this connection as a regular Webstrates websocket connection. )

All commands sent to the server must include the ga (God Action) key and a command for the server.

To authenticate with the server, send a JSON object key command along with the key defined in the server config.

{ "ga": "key", "key": "<key>" }

Afterwards, you may subscribe to subjects. A subject is either a webstrateId, a userId, or a list of either. For each subject, you may subscribe to one (or all) of three types of events:

  • dom – DOM changes. If subscribing to a user, this is all DOM changes caused by the user. If subscribing to a webstrate, this is all DOM changes happening in the webstrate.
  • userUser actions. If subscribing to a user, this is any joins and parts the user performs. If subscribing to a webstrate, this is joins and parts occuring in the seecific webstrate.
  • signalSignals. If subscribing to a user, this is any signals sent by the user. If subscribing to a webstrate, this is signals sent within a webstrate. (Keep in mind that – as an optimization – if a signal is sent in a webstrate with no receivers, it will not be transmitted to the server, and thus not be registered by the God API either.)

A few examples

To subscribe to webstrates, the subscribeWebstrate command must be used, along with a webstrateId (or array of webstrateIds). Additionally, a filter must be provided, containing any of the three events (dom, user, signal). As with the webstrates, this can be a single string value or an array.

Additionally, all subscriptions must include a subscriptionId, which can be any string. A randomly generated string is suitable.

A subscription to all DOM changes in the webstrate honest-kangaroo-9 may therefore look something like:

{
  "ga": "subscribeWebstrate",
  "subscriptionId": "45czu6mbbqo",
  "webstrates": "honest-kangaroo-9",
  "filter": "dom"
}

And to subscribe to multiple webstrates (e.g. frontpage and other) and events (e.g. user and signal) at once:

{
  "ga": "subscribeWebstrate",
  "subscriptionId": "45czu6mbbqo",
  "webstrates": ["frontpage", "other"],
  "filter": ["user", "signal"]
}

To subscribe to a user (or multiple), the users value must be defined as either a single userId (or an array of userIds). A subscription to all signals from userId kbadk:github could look like this:

{
  "ga": "subscribeUser",
  "subscriptionId": "45czu6mbbqo",
  "users": "kbadk:github",
  "filter": "signal"
}

A real-life NodeJS example

For a real-life example, check out Webstrates Monitor. This service is built on NodeJS and uses the data received from the God API to create an activity database which applications may then query for historical data.

And another NodeJS example

To use the below example NodeJS must be installed on the machine. To get started, create a new folder with a file index.js containing the code below. Modify the file with correct server host (line 80) and key (line 35), then run:

npm install --prefix . websocket

To install the websocket package in the current folder, followed by running:

node index.js

The script will create the three subscriptions used in the above examples, i.e. subscriptions to:

  • All DOM changes to webstrate honest-kangaroo-9.
  • All user joins/parts as well as signals in the webstrates frontpage and other.
  • All signals sent by userId kbadk:github.

The events matching any of the three subscriptions will be logged to the console when the script is running.

const WebSocketClient = require('websocket').client;

const client = new WebSocketClient();

const timestamp = () => (new Date()).toLocaleTimeString();
const log = (...args) => console.log(`[${timestamp()}]`, ...args);
const error = (...args) => console.error(`[${timestamp()}]`, ...args);

client.on('connectFailed', error => {
  log(error.toString());
});

client.on('connect', conn => {

  log('Connected.');

  conn.on('error', error => {
    error(error.toString());
  });

  conn.on('message', message => {
    if (message.type === 'utf8') {
      log('', message.utf8Data);
    }
  });

  const send = (msg, silent) => {
    msg = JSON.stringify(msg);
    !silent && log( '', msg);
    conn.sendUTF(msg);
  };

  // Authenticate ourselves with the God API key defined in config.json.
  send({ ga: 'key', key: '<key goes here>' });

  // Each connection requires a unique subscrptionId. We just generate a pseudo-random string.
  const subscriptionId = Math.random().toString(36).substr(2)

  // You can subscribe to DOM changes, signals or user actions (join/part) for either a specific
  // user or a specific webstrate.

  // Subscribe all all DOM changes in the webstrate honest-kangaroo-9 by any user.
  send({
    ga: 'subscribeWebstrate',
    subscriptionId,
    webstrates: 'honest-kangaroo-9',
    filter: 'dom'
  });

  // Subscribe to any join/part or signals by any user in two webstrates: frontpage and other.
  send({
    ga: 'subscribeWebstrate',
    subscriptionId,
    webstrates: ['frontpage', 'other'],
    filter: ['user', 'signal']
  });

  // Subscribe to any signals sent by user kbadk:github regardless of webstrate.
  send({
    ga: 'subscribeUser',
    subscriptionId,
    users: 'kbadk:github',
    filter: 'signal'
  });

  // Websocket will automatically close after 30 seconds of inactivity. Send keep alive message
  // every 25 seconds.
  setInterval(() => {
    send({ type: 'alive' }, true);
  }, 25 * 1000)

  conn.on('close', (code, reason) => {
    log(`Closed: ${reason} (code ${code})`);
  });

});

// `?noop` to establishing a connection with ShareDB.
client.connect('wss://<host of server>/ws/?noop');