We add a new document that shows two examples of how to use the WebSocket REST API with JavaScript.
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>,
// }