sender.test.js (11363B)
1 'use strict'; 2 3 const assert = require('assert'); 4 5 const extension = require('../lib/extension'); 6 const PerMessageDeflate = require('../lib/permessage-deflate'); 7 const Sender = require('../lib/sender'); 8 const { EMPTY_BUFFER } = require('../lib/constants'); 9 10 class MockSocket { 11 constructor({ write } = {}) { 12 this.readable = true; 13 this.writable = true; 14 15 if (write) this.write = write; 16 } 17 18 cork() {} 19 write() {} 20 uncork() {} 21 } 22 23 describe('Sender', () => { 24 describe('.frame', () => { 25 it('does not mutate the input buffer if data is `readOnly`', () => { 26 const buf = Buffer.from([1, 2, 3, 4, 5]); 27 28 Sender.frame(buf, { 29 readOnly: true, 30 rsv1: false, 31 mask: true, 32 opcode: 2, 33 fin: true 34 }); 35 36 assert.ok(buf.equals(Buffer.from([1, 2, 3, 4, 5]))); 37 }); 38 39 it('honors the `rsv1` option', () => { 40 const list = Sender.frame(EMPTY_BUFFER, { 41 readOnly: false, 42 mask: false, 43 rsv1: true, 44 opcode: 1, 45 fin: true 46 }); 47 48 assert.strictEqual(list[0][0] & 0x40, 0x40); 49 }); 50 51 it('accepts a string as first argument', () => { 52 const list = Sender.frame('€', { 53 readOnly: false, 54 rsv1: false, 55 mask: false, 56 opcode: 1, 57 fin: true 58 }); 59 60 assert.deepStrictEqual(list[0], Buffer.from('8103', 'hex')); 61 assert.deepStrictEqual(list[1], Buffer.from('e282ac', 'hex')); 62 }); 63 }); 64 65 describe('#send', () => { 66 it('compresses data if compress option is enabled', (done) => { 67 const chunks = []; 68 const perMessageDeflate = new PerMessageDeflate(); 69 const mockSocket = new MockSocket({ 70 write: (chunk) => { 71 chunks.push(chunk); 72 if (chunks.length !== 6) return; 73 74 assert.strictEqual(chunks[0].length, 2); 75 assert.strictEqual(chunks[0][0] & 0x40, 0x40); 76 77 assert.strictEqual(chunks[2].length, 2); 78 assert.strictEqual(chunks[2][0] & 0x40, 0x40); 79 80 assert.strictEqual(chunks[4].length, 2); 81 assert.strictEqual(chunks[4][0] & 0x40, 0x40); 82 done(); 83 } 84 }); 85 const sender = new Sender(mockSocket, { 86 'permessage-deflate': perMessageDeflate 87 }); 88 89 perMessageDeflate.accept([{}]); 90 91 const options = { compress: true, fin: true }; 92 const array = new Uint8Array([0x68, 0x69]); 93 94 sender.send(array.buffer, options); 95 sender.send(array, options); 96 sender.send('hi', options); 97 }); 98 99 describe('when context takeover is disabled', () => { 100 it('honors the compression threshold', (done) => { 101 const chunks = []; 102 const perMessageDeflate = new PerMessageDeflate(); 103 const mockSocket = new MockSocket({ 104 write: (chunk) => { 105 chunks.push(chunk); 106 if (chunks.length !== 2) return; 107 108 assert.strictEqual(chunks[0].length, 2); 109 assert.notStrictEqual(chunk[0][0] & 0x40, 0x40); 110 assert.strictEqual(chunks[1], 'hi'); 111 done(); 112 } 113 }); 114 const sender = new Sender(mockSocket, { 115 'permessage-deflate': perMessageDeflate 116 }); 117 const extensions = extension.parse( 118 'permessage-deflate; client_no_context_takeover' 119 ); 120 121 perMessageDeflate.accept(extensions['permessage-deflate']); 122 123 sender.send('hi', { compress: true, fin: true }); 124 }); 125 126 it('compresses all fragments of a fragmented message', (done) => { 127 const chunks = []; 128 const perMessageDeflate = new PerMessageDeflate({ threshold: 3 }); 129 const mockSocket = new MockSocket({ 130 write: (chunk) => { 131 chunks.push(chunk); 132 if (chunks.length !== 4) return; 133 134 assert.strictEqual(chunks[0].length, 2); 135 assert.strictEqual(chunks[0][0] & 0x40, 0x40); 136 assert.strictEqual(chunks[1].length, 9); 137 138 assert.strictEqual(chunks[2].length, 2); 139 assert.strictEqual(chunks[2][0] & 0x40, 0x00); 140 assert.strictEqual(chunks[3].length, 4); 141 done(); 142 } 143 }); 144 const sender = new Sender(mockSocket, { 145 'permessage-deflate': perMessageDeflate 146 }); 147 const extensions = extension.parse( 148 'permessage-deflate; client_no_context_takeover' 149 ); 150 151 perMessageDeflate.accept(extensions['permessage-deflate']); 152 153 sender.send('123', { compress: true, fin: false }); 154 sender.send('12', { compress: true, fin: true }); 155 }); 156 157 it('does not compress any fragments of a fragmented message', (done) => { 158 const chunks = []; 159 const perMessageDeflate = new PerMessageDeflate({ threshold: 3 }); 160 const mockSocket = new MockSocket({ 161 write: (chunk) => { 162 chunks.push(chunk); 163 if (chunks.length !== 4) return; 164 165 assert.strictEqual(chunks[0].length, 2); 166 assert.strictEqual(chunks[0][0] & 0x40, 0x00); 167 assert.strictEqual(chunks[1].length, 2); 168 169 assert.strictEqual(chunks[2].length, 2); 170 assert.strictEqual(chunks[2][0] & 0x40, 0x00); 171 assert.strictEqual(chunks[3].length, 3); 172 done(); 173 } 174 }); 175 const sender = new Sender(mockSocket, { 176 'permessage-deflate': perMessageDeflate 177 }); 178 const extensions = extension.parse( 179 'permessage-deflate; client_no_context_takeover' 180 ); 181 182 perMessageDeflate.accept(extensions['permessage-deflate']); 183 184 sender.send('12', { compress: true, fin: false }); 185 sender.send('123', { compress: true, fin: true }); 186 }); 187 188 it('compresses empty buffer as first fragment', (done) => { 189 const chunks = []; 190 const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); 191 const mockSocket = new MockSocket({ 192 write: (chunk) => { 193 chunks.push(chunk); 194 if (chunks.length !== 4) return; 195 196 assert.strictEqual(chunks[0].length, 2); 197 assert.strictEqual(chunks[0][0] & 0x40, 0x40); 198 assert.strictEqual(chunks[1].length, 5); 199 200 assert.strictEqual(chunks[2].length, 2); 201 assert.strictEqual(chunks[2][0] & 0x40, 0x00); 202 assert.strictEqual(chunks[3].length, 6); 203 done(); 204 } 205 }); 206 const sender = new Sender(mockSocket, { 207 'permessage-deflate': perMessageDeflate 208 }); 209 const extensions = extension.parse( 210 'permessage-deflate; client_no_context_takeover' 211 ); 212 213 perMessageDeflate.accept(extensions['permessage-deflate']); 214 215 sender.send(Buffer.alloc(0), { compress: true, fin: false }); 216 sender.send('data', { compress: true, fin: true }); 217 }); 218 219 it('compresses empty buffer as last fragment', (done) => { 220 const chunks = []; 221 const perMessageDeflate = new PerMessageDeflate({ threshold: 0 }); 222 const mockSocket = new MockSocket({ 223 write: (chunk) => { 224 chunks.push(chunk); 225 if (chunks.length !== 4) return; 226 227 assert.strictEqual(chunks[0].length, 2); 228 assert.strictEqual(chunks[0][0] & 0x40, 0x40); 229 assert.strictEqual(chunks[1].length, 10); 230 231 assert.strictEqual(chunks[2].length, 2); 232 assert.strictEqual(chunks[2][0] & 0x40, 0x00); 233 assert.strictEqual(chunks[3].length, 1); 234 done(); 235 } 236 }); 237 const sender = new Sender(mockSocket, { 238 'permessage-deflate': perMessageDeflate 239 }); 240 const extensions = extension.parse( 241 'permessage-deflate; client_no_context_takeover' 242 ); 243 244 perMessageDeflate.accept(extensions['permessage-deflate']); 245 246 sender.send('data', { compress: true, fin: false }); 247 sender.send(Buffer.alloc(0), { compress: true, fin: true }); 248 }); 249 }); 250 }); 251 252 describe('#ping', () => { 253 it('works with multiple types of data', (done) => { 254 const perMessageDeflate = new PerMessageDeflate(); 255 let count = 0; 256 const mockSocket = new MockSocket({ 257 write: (data) => { 258 if (++count < 3) return; 259 260 if (count % 2) { 261 assert.ok(data.equals(Buffer.from([0x89, 0x02]))); 262 } else if (count < 8) { 263 assert.ok(data.equals(Buffer.from([0x68, 0x69]))); 264 } else { 265 assert.strictEqual(data, 'hi'); 266 done(); 267 } 268 } 269 }); 270 const sender = new Sender(mockSocket, { 271 'permessage-deflate': perMessageDeflate 272 }); 273 274 perMessageDeflate.accept([{}]); 275 276 const array = new Uint8Array([0x68, 0x69]); 277 278 sender.send('foo', { compress: true, fin: true }); 279 sender.ping(array.buffer, false); 280 sender.ping(array, false); 281 sender.ping('hi', false); 282 }); 283 }); 284 285 describe('#pong', () => { 286 it('works with multiple types of data', (done) => { 287 const perMessageDeflate = new PerMessageDeflate(); 288 let count = 0; 289 const mockSocket = new MockSocket({ 290 write: (data) => { 291 if (++count < 3) return; 292 293 if (count % 2) { 294 assert.ok(data.equals(Buffer.from([0x8a, 0x02]))); 295 } else if (count < 8) { 296 assert.ok(data.equals(Buffer.from([0x68, 0x69]))); 297 } else { 298 assert.strictEqual(data, 'hi'); 299 done(); 300 } 301 } 302 }); 303 const sender = new Sender(mockSocket, { 304 'permessage-deflate': perMessageDeflate 305 }); 306 307 perMessageDeflate.accept([{}]); 308 309 const array = new Uint8Array([0x68, 0x69]); 310 311 sender.send('foo', { compress: true, fin: true }); 312 sender.pong(array.buffer, false); 313 sender.pong(array, false); 314 sender.pong('hi', false); 315 }); 316 }); 317 318 describe('#close', () => { 319 it('throws an error if the first argument is invalid', () => { 320 const mockSocket = new MockSocket(); 321 const sender = new Sender(mockSocket); 322 323 assert.throws( 324 () => sender.close('error'), 325 /^TypeError: First argument must be a valid error code number$/ 326 ); 327 328 assert.throws( 329 () => sender.close(1004), 330 /^TypeError: First argument must be a valid error code number$/ 331 ); 332 }); 333 334 it('throws an error if the message is greater than 123 bytes', () => { 335 const mockSocket = new MockSocket(); 336 const sender = new Sender(mockSocket); 337 338 assert.throws( 339 () => sender.close(1000, 'a'.repeat(124)), 340 /^RangeError: The message must not be greater than 123 bytes$/ 341 ); 342 }); 343 344 it('should consume all data before closing', (done) => { 345 const perMessageDeflate = new PerMessageDeflate(); 346 347 let count = 0; 348 const mockSocket = new MockSocket({ 349 write: (data, cb) => { 350 count++; 351 if (cb) cb(); 352 } 353 }); 354 const sender = new Sender(mockSocket, { 355 'permessage-deflate': perMessageDeflate 356 }); 357 358 perMessageDeflate.accept([{}]); 359 360 sender.send('foo', { compress: true, fin: true }); 361 sender.send('bar', { compress: true, fin: true }); 362 sender.send('baz', { compress: true, fin: true }); 363 364 sender.close(1000, undefined, false, () => { 365 assert.strictEqual(count, 8); 366 done(); 367 }); 368 }); 369 }); 370 });