decred.org/dcrdex@v1.0.3/client/webserver/site/src/js/ws.ts (about)

     1  // MessageSocket is a WebSocket manager that uses the Decred DEX Message format
     2  // for communications.
     3  //
     4  // Message request format:
     5  // {
     6  //   route: 'name',
     7  //   id: int,
     8  //   payload: anything or nothing
     9  // }
    10  //
    11  // Message response payload will be a result object with either a valid 'result'
    12  // field or an 'error' field
    13  //
    14  // Functions for external use:
    15  // registerRoute (route, handler) -- register a function to handle events
    16  // of the given type
    17  // request (route, payload) -- create a JSON message in the above format and
    18  // send it
    19  //
    20  // Based on messagesocket_service.js by Jonathan Chappelow @ dcrdata, which is
    21  // based on ws_events_dispatcher.js by Ismael Celis
    22  const typeRequest = 1
    23  
    24  function forward (route: string, payload: any, handlers: Record<string, ((payload: any) => void)[]>) {
    25    if (!route && payload.error) {
    26      const err = payload.error
    27      console.error(`websocket error (code ${err.code}): ${err.message}`)
    28      return
    29    }
    30    if (typeof handlers[route] === 'undefined') {
    31      // console.log(`unhandled message for ${route}: ${payload}`)
    32      return
    33    }
    34    // call each handler
    35    for (let i = 0; i < handlers[route].length; i++) {
    36      handlers[route][i](payload)
    37    }
    38  }
    39  
    40  let id = 0
    41  
    42  type NoteReceiver = (payload: any) => void
    43  
    44  class MessageSocket {
    45    uri: string
    46    connection: WebSocket | null
    47    handlers: Record<string, NoteReceiver[]>
    48    queue: [string, any][]
    49    maxQlength: number
    50    reloader: () => void // appears unused
    51  
    52    constructor () {
    53      this.handlers = {}
    54      this.queue = []
    55      this.maxQlength = 5
    56    }
    57  
    58    registerRoute (route: string, handler: NoteReceiver) {
    59      this.handlers[route] = this.handlers[route] || []
    60      this.handlers[route].push(handler)
    61    }
    62  
    63    deregisterRoute (route: string) {
    64      this.handlers[route] = []
    65    }
    66  
    67    // request sends a request-type message to the server
    68    request (route: string, payload: any) {
    69      if (!this.connection || this.connection.readyState !== window.WebSocket.OPEN) {
    70        while (this.queue.length > this.maxQlength - 1) this.queue.shift()
    71        this.queue.push([route, payload])
    72        return
    73      }
    74      id++
    75      const message = JSON.stringify({
    76        route: route,
    77        type: typeRequest,
    78        id: id,
    79        payload: payload
    80      })
    81  
    82      window.log('ws', 'sending', message)
    83      this.connection.send(message)
    84    }
    85  
    86    close (reason: string) {
    87      window.log('ws', 'close, reason:', reason, this.handlers)
    88      this.handlers = {}
    89      if (this.connection) this.connection.close()
    90    }
    91  
    92    connect (uri: string, reloader: () => void) {
    93      this.uri = uri
    94      this.reloader = reloader
    95      let retrys = 0
    96      const go = () => {
    97        window.log('ws', `connecting to ${uri}`)
    98        let conn: WebSocket | null = this.connection = new window.WebSocket(uri)
    99        if (!conn) return
   100        const timeout = setTimeout(() => {
   101          // readyState is still WebSocket.CONNECTING. Cancel and trigger onclose.
   102          if (conn) conn.close()
   103        }, 500)
   104  
   105        // unmarshal message, and forward the message to registered handlers
   106        conn.onmessage = (evt: MessageEvent) => {
   107          const message = JSON.parse(evt.data)
   108          forward(message.route, message.payload, this.handlers)
   109        }
   110  
   111        // Stub out standard functions
   112        conn.onclose = (evt: CloseEvent) => {
   113          window.log('ws', 'onclose')
   114          clearTimeout(timeout)
   115          conn = this.connection = null
   116          forward('close', null, this.handlers)
   117          retrys++
   118          // 1.2, 1.6, 2.0, 2.4, 3.1, 3.8, 4.8, 6.0, 7.5, 9.3, ...
   119          const delay = Math.min(Math.pow(1.25, retrys), 10)
   120          console.error(`websocket disconnected (${evt.code}), trying again in ${delay.toFixed(1)} seconds`)
   121          setTimeout(() => {
   122            go()
   123          }, delay * 1000)
   124        }
   125  
   126        conn.onopen = () => {
   127          window.log('ws', 'onopen')
   128          clearTimeout(timeout)
   129          if (retrys > 0) {
   130            retrys = 0
   131            reloader()
   132          }
   133          forward('open', null, this.handlers)
   134          const queue = this.queue
   135          this.queue = []
   136          for (const [route, message] of queue) {
   137            this.request(route, message)
   138          }
   139        }
   140  
   141        conn.onerror = (evt: Event) => {
   142          window.log('ws', 'onerror:', evt)
   143          forward('error', evt, this.handlers)
   144        }
   145      }
   146      go()
   147    }
   148  }
   149  
   150  const ws = new MessageSocket()
   151  export default ws