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  ```