100 lines
3.7 KiB
Markdown
100 lines
3.7 KiB
Markdown
|
# 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](https://api.lightning.community/#v2-chainnotifier-register-blocks).
|
||
|
|
||
|
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":
|
||
|
|
||
|
```javascript
|
||
|
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:
|
||
|
|
||
|
```javascript
|
||
|
// --------------------------
|
||
|
// 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>,
|
||
|
// }
|
||
|
```
|