lnd.xprv/docs/rest/websockets.md
Oliver Gugger c7cb2c0a78
docs: describe how to use WebSockets with the REST API
We add a new document that shows two examples of how to use the
WebSocket REST API with JavaScript.
2020-08-06 12:09:15 +02:00

3.7 KiB

WebSockets with lnd's REST API

This document describes how streaming response REST calls can be used correctly by making use of the WebSocket API.

As an example, we are going to write a simple JavaScript program that subscribes to lnd's block notification RPC.

The WebSocket will be kept open as long as lnd runs and JavaScript program isn't stopped.

Browser environment

When using WebSockets in a browser, there are certain security limitations of what header fields are allowed to be sent. Therefore, the macaroon cannot just be added as a Grpc-Metadata-Macaroon header field as it would work with normal REST calls. The browser will just ignore that header field and not send it.

Instead we have added a workaround in lnd's WebSocket proxy that allows sending the macaroon as a WebSocket "protocol":

const host = 'localhost:8080'; // The default REST port of lnd, can be overwritten with --restlisten=ip:port
const macaroon = '0201036c6e6402eb01030a10625e7e60fd00f5a6f9cd53f33fc82a...'; // The hex encoded macaroon to send
const initialRequest = { // The initial request to send (see API docs for each RPC).
    hash: "xlkMdV382uNPskw6eEjDGFMQHxHNnZZgL47aVDSwiRQ=", // Just some example to show that all `byte` fields always have to be base64 encoded in the REST API.
    height: 144,
}

// The protocol is our workaround for sending the macaroon because custom header
// fields aren't allowed to be sent by the browser when opening a WebSocket.
const protocolString = 'Grpc-Metadata-Macaroon+' + macaroon;

// Let's now connect the web socket. Notice that all WebSocket open calls are
// always GET requests. If the RPC expects a call to be POST or DELETE (see API
// docs to find out), the query parameter "method" can be set to overwrite.
const wsUrl = 'wss://' + host + '/v2/chainnotifier/register/blocks?method=POST';
let ws = new WebSocket(wsUrl, protocolString);
ws.onopen = function (event) {
    // After the WS connection is establishes, lnd expects the client to send the
    // initial message. If an RPC doesn't have any request parameters, an empty
    // JSON object has to be sent as a string, for example: ws.send('{}')
    ws.send(JSON.stringify(initialRequest));
}
ws.onmessage = function (event) {
    // We received a new message.
    console.log(event);

    // The data we're really interested in is in data and is always a string
    // that needs to be parsed as JSON and always contains a "result" field:
    console.log("Payload: ");
    console.log(JSON.parse(event.data).result);
}
ws.onerror = function (event) {
    // An error occured, let's log it to the console.
    console.log(event);
}

Node.js environment

With Node.js it is a bit easier to use the streaming response APIs because we can set the macaroon header field directly. This is the example from the API docs:

// --------------------------
// Example with websockets:
// --------------------------
const WebSocket = require('ws');
const fs = require('fs');
const macaroon = fs.readFileSync('LND_DIR/data/chain/bitcoin/simnet/admin.macaroon').toString('hex');
let ws = new WebSocket('wss://localhost:8080/v2/chainnotifier/register/blocks?method=POST', {
  // Work-around for self-signed certificates.
  rejectUnauthorized: false,
  headers: {
    'Grpc-Metadata-Macaroon': macaroon,
  },
});
let requestBody = { 
  hash: "<byte>",
  height: "<int64>",
}
ws.on('open', function() {
    ws.send(JSON.stringify(requestBody));
});
ws.on('error', function(err) {
    console.log('Error: ' + err);
});
ws.on('message', function(body) {
    console.log(body);
});
// Console output (repeated for every message in the stream):
//  { 
//      "hash": <byte>, 
//      "height": <int64>, 
//  }