decred.org/dcrdex@v1.0.5/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