( port: number, path: string, message: string )
| 156 | } |
| 157 | |
| 158 | export function webSocketEcho( |
| 159 | port: number, |
| 160 | path: string, |
| 161 | message: string |
| 162 | ): Promise<string> { |
| 163 | return new Promise((resolve, reject) => { |
| 164 | const socket = net.connect(port, '127.0.0.1'); |
| 165 | const key = randomBytes(16).toString('base64'); |
| 166 | let buffer: Uint8Array<ArrayBufferLike> = new Uint8Array(0); |
| 167 | let handshakeComplete = false; |
| 168 | let settled = false; |
| 169 | |
| 170 | const timeout = setTimeout(() => { |
| 171 | fail(new Error('Timed out waiting for WebSocket response')); |
| 172 | }, 10_000); |
| 173 | |
| 174 | const settle = (fn: () => void) => { |
| 175 | if (settled) return; |
| 176 | settled = true; |
| 177 | clearTimeout(timeout); |
| 178 | fn(); |
| 179 | }; |
| 180 | |
| 181 | const fail = (error: Error) => { |
| 182 | settle(() => { |
| 183 | socket.destroy(); |
| 184 | reject(error); |
| 185 | }); |
| 186 | }; |
| 187 | |
| 188 | socket.once('error', fail); |
| 189 | socket.on('data', chunk => { |
| 190 | buffer = appendBytes(buffer, toBytes(chunk)); |
| 191 | |
| 192 | if (!handshakeComplete) { |
| 193 | const headerEnd = indexOfBytes(buffer, headerSeparator); |
| 194 | if (headerEnd === -1) return; |
| 195 | |
| 196 | const headers = Buffer.from(buffer.subarray(0, headerEnd)).toString( |
| 197 | 'utf8' |
| 198 | ); |
| 199 | if (!headers.startsWith('HTTP/1.1 101 Switching Protocols')) { |
| 200 | fail(new Error(`Unexpected WebSocket handshake:\n${headers}`)); |
| 201 | return; |
| 202 | } |
| 203 | |
| 204 | const accept = createHash('sha1') |
| 205 | .update(`${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`) |
| 206 | .digest('base64'); |
| 207 | if ( |
| 208 | !headers |
| 209 | .toLowerCase() |
| 210 | .includes(`sec-websocket-accept: ${accept.toLowerCase()}`) |
| 211 | ) { |
| 212 | fail(new Error(`Unexpected Sec-WebSocket-Accept:\n${headers}`)); |
| 213 | return; |
| 214 | } |
| 215 |
no test coverage detected