github.com/decred/dcrlnd@v0.7.6/docs/rest/websockets.md (about) 1 # WebSockets with `lnd`'s REST API 2 3 This document describes how streaming response REST calls can be used correctly 4 by making use of the WebSocket API. 5 6 As an example, we are going to write a simple JavaScript program that subscribes 7 to `lnd`'s 8 [block notification RPC](https://api.lightning.community/#v2-chainnotifier-register-blocks). 9 10 The WebSocket will be kept open as long as `lnd` runs and JavaScript program 11 isn't stopped. 12 13 ## Browser environment 14 15 When using WebSockets in a browser, there are certain security limitations of 16 what header fields are allowed to be sent. Therefore, the macaroon cannot just 17 be added as a `Grpc-Metadata-Macaroon` header field as it would work with normal 18 REST calls. The browser will just ignore that header field and not send it. 19 20 Instead we have added a workaround in `lnd`'s WebSocket proxy that allows 21 sending the macaroon as a WebSocket "protocol": 22 23 ```javascript 24 const host = 'localhost:8080'; // The default REST port of lnd, can be overwritten with --restlisten=ip:port 25 const macaroon = '0201036c6e6402eb01030a10625e7e60fd00f5a6f9cd53f33fc82a...'; // The hex encoded macaroon to send 26 const initialRequest = { // The initial request to send (see API docs for each RPC). 27 hash: "xlkMdV382uNPskw6eEjDGFMQHxHNnZZgL47aVDSwiRQ=", // Just some example to show that all `byte` fields always have to be base64 encoded in the REST API. 28 height: 144, 29 } 30 31 // The protocol is our workaround for sending the macaroon because custom header 32 // fields aren't allowed to be sent by the browser when opening a WebSocket. 33 const protocolString = 'Grpc-Metadata-Macaroon+' + macaroon; 34 35 // Let's now connect the web socket. Notice that all WebSocket open calls are 36 // always GET requests. If the RPC expects a call to be POST or DELETE (see API 37 // docs to find out), the query parameter "method" can be set to overwrite. 38 const wsUrl = 'wss://' + host + '/v2/chainnotifier/register/blocks?method=POST'; 39 let ws = new WebSocket(wsUrl, protocolString); 40 ws.onopen = function (event) { 41 // After the WS connection is establishes, lnd expects the client to send the 42 // initial message. If an RPC doesn't have any request parameters, an empty 43 // JSON object has to be sent as a string, for example: ws.send('{}') 44 ws.send(JSON.stringify(initialRequest)); 45 } 46 ws.onmessage = function (event) { 47 // We received a new message. 48 console.log(event); 49 50 // The data we're really interested in is in data and is always a string 51 // that needs to be parsed as JSON and always contains a "result" field: 52 console.log("Payload: "); 53 console.log(JSON.parse(event.data).result); 54 } 55 ws.onerror = function (event) { 56 // An error occured, let's log it to the console. 57 console.log(event); 58 } 59 ``` 60 61 ## Node.js environment 62 63 With Node.js it is a bit easier to use the streaming response APIs because we 64 can set the macaroon header field directly. This is the example from the API 65 docs: 66 67 ```javascript 68 // -------------------------- 69 // Example with websockets: 70 // -------------------------- 71 const WebSocket = require('ws'); 72 const fs = require('fs'); 73 const macaroon = fs.readFileSync('LND_DIR/data/chain/bitcoin/simnet/admin.macaroon').toString('hex'); 74 let ws = new WebSocket('wss://localhost:8080/v2/chainnotifier/register/blocks?method=POST', { 75 // Work-around for self-signed certificates. 76 rejectUnauthorized: false, 77 headers: { 78 'Grpc-Metadata-Macaroon': macaroon, 79 }, 80 }); 81 let requestBody = { 82 hash: "<byte>", 83 height: "<int64>", 84 } 85 ws.on('open', function() { 86 ws.send(JSON.stringify(requestBody)); 87 }); 88 ws.on('error', function(err) { 89 console.log('Error: ' + err); 90 }); 91 ws.on('message', function(body) { 92 console.log(body); 93 }); 94 // Console output (repeated for every message in the stream): 95 // { 96 // "hash": <byte>, 97 // "height": <int64>, 98 // } 99 ``` 100 101 ## Request-streaming RPCs 102 103 Starting with `lnd v0.13.0-beta` all RPCs can be used through REST, even those 104 that are fully bi-directional (e.g. the client can also send multiple request 105 messages to the stream). 106 107 **Example**: 108 109 As an example we show how one can use the bi-directional channel acceptor RPC. 110 Through that RPC each incoming channel open request (another peer opening a 111 channel to our node) will be passed in for inspection. We can decide 112 programmatically whether to accept or reject the channel. 113 114 ```javascript 115 // -------------------------- 116 // Example with websockets: 117 // -------------------------- 118 const WebSocket = require('ws'); 119 const fs = require('fs'); 120 const macaroon = fs.readFileSync('LND_DIR/data/chain/bitcoin/simnet/admin.macaroon').toString('hex'); 121 let ws = new WebSocket('wss://localhost:8080/v1/channels/acceptor?method=POST', { 122 // Work-around for self-signed certificates. 123 rejectUnauthorized: false, 124 headers: { 125 'Grpc-Metadata-Macaroon': macaroon, 126 }, 127 }); 128 ws.on('open', function() { 129 // We always _need_ to send an initial message to kickstart the request. 130 // This empty message will be ignored by the channel acceptor though, this 131 // is just for telling the grpc-gateway library that it can forward the 132 // request to the gRPC interface now. If this were an RPC where the client 133 // always sends the first message (for example the streaming payment RPC 134 // /v1/channels/transaction-stream), we'd simply send the first "real" 135 // message here when needed. 136 ws.send('{}'); 137 }); 138 ws.on('error', function(err) { 139 console.log('Error: ' + err); 140 }); 141 ws.on('ping', function ping(event) { 142 console.log('Received ping from server: ' + JSON.stringify(event)); 143 }); 144 ws.on('message', function incoming(event) { 145 console.log('New channel accept message: ' + event); 146 const result = JSON.parse(event).result; 147 148 // Accept the channel after inspecting it. 149 ws.send(JSON.stringify({accept: true, pending_chan_id: result.pending_chan_id})); 150 }); 151 ```