0
# Frame-Level Operations
1
2
Low-level WebSocket frame parsing and construction for advanced use cases requiring direct protocol frame manipulation and custom frame processing.
3
4
## Capabilities
5
6
### WebSocketFrame
7
8
Low-level WebSocket frame parsing and construction class.
9
10
```javascript { .api }
11
/**
12
* WebSocket frame for low-level protocol operations
13
* @param maskBytes - Optional mask bytes for frame masking
14
* @param frameHeader - Optional frame header configuration
15
* @param config - Frame processing configuration
16
*/
17
class WebSocketFrame {
18
constructor(maskBytes?: Buffer, frameHeader?: FrameHeader, config?: FrameConfig);
19
20
/**
21
* Add data for frame parsing
22
* @param bufferList - Buffer containing frame data
23
* @returns true when frame is complete, false if more data needed
24
*/
25
addData(bufferList: Buffer): boolean;
26
27
/**
28
* Discard payload data without processing
29
* @param bufferList - Buffer to discard data from
30
*/
31
throwAwayPayload(bufferList: Buffer): void;
32
33
/**
34
* Convert frame to Buffer for transmission
35
* @param nullMask - Use null mask instead of random mask
36
* @returns Buffer containing complete frame
37
*/
38
toBuffer(nullMask?: boolean): Buffer;
39
40
/** String representation for debugging */
41
toString(): string;
42
43
/** Final fragment flag (true if this is the last fragment) */
44
fin: boolean;
45
46
/** Reserved flag 1 (must be false unless extension defines it) */
47
rsv1: boolean;
48
49
/** Reserved flag 2 (must be false unless extension defines it) */
50
rsv2: boolean;
51
52
/** Reserved flag 3 (must be false unless extension defines it) */
53
rsv3: boolean;
54
55
/** Mask flag (true if payload is masked) */
56
mask: boolean;
57
58
/** Frame opcode (0x0-0xF indicating frame type) */
59
opcode: number;
60
61
/** Payload length in bytes */
62
length: number;
63
64
/** Frame payload data as Buffer */
65
binaryPayload: Buffer;
66
67
/** Close frame status code (for close frames) */
68
closeStatus: number;
69
70
/** Protocol error flag */
71
protocolError: boolean;
72
73
/** Frame too large flag */
74
frameTooLarge: boolean;
75
76
/** Invalid close frame length flag */
77
invalidCloseFrameLength: boolean;
78
}
79
```
80
81
**Frame Processing Configuration:**
82
83
```javascript { .api }
84
interface FrameConfig {
85
/** Maximum frame size in bytes */
86
maxReceivedFrameSize?: number;
87
88
/** Whether frames should be masked */
89
maskOutgoingPackets?: boolean;
90
91
/** Assemble fragmented frames */
92
assembleFragments?: boolean;
93
}
94
95
interface FrameHeader {
96
fin?: boolean;
97
rsv1?: boolean;
98
rsv2?: boolean;
99
rsv3?: boolean;
100
mask?: boolean;
101
opcode?: number;
102
length?: number;
103
}
104
```
105
106
**Frame Opcodes:**
107
108
```javascript { .api }
109
const OPCODES = {
110
CONTINUATION: 0x0, // Continuation frame
111
TEXT: 0x1, // Text frame
112
BINARY: 0x2, // Binary frame
113
// 0x3-0x7 reserved for future non-control frames
114
CLOSE: 0x8, // Close frame
115
PING: 0x9, // Ping frame
116
PONG: 0xA // Pong frame
117
// 0xB-0xF reserved for future control frames
118
};
119
```
120
121
**Usage Example:**
122
123
```javascript
124
const WebSocket = require('websocket');
125
126
// Create a text frame
127
const textFrame = new WebSocket.frame();
128
textFrame.opcode = 0x1; // Text frame
129
textFrame.fin = true; // Final fragment
130
textFrame.binaryPayload = Buffer.from('Hello, World!', 'utf8');
131
textFrame.length = textFrame.binaryPayload.length;
132
133
// Convert to buffer for transmission
134
const frameBuffer = textFrame.toBuffer();
135
console.log('Frame buffer length:', frameBuffer.length);
136
137
// Parse incoming frame data
138
const incomingFrame = new WebSocket.frame();
139
const buffer = Buffer.from(/* frame data */);
140
141
// Add data and check if complete
142
const isComplete = incomingFrame.addData(buffer);
143
if (isComplete) {
144
console.log('Frame complete');
145
console.log('Opcode:', incomingFrame.opcode);
146
console.log('Payload:', incomingFrame.binaryPayload.toString());
147
}
148
```
149
150
### Frame Parsing Process
151
152
**Frame Parsing States:**
153
154
```javascript { .api }
155
const FRAME_STATE = {
156
DECODE_HEADER: 1,
157
WAITING_FOR_16_BIT_LENGTH: 2,
158
WAITING_FOR_64_BIT_LENGTH: 3,
159
WAITING_FOR_MASK_KEY: 4,
160
WAITING_FOR_PAYLOAD: 5,
161
COMPLETE: 6
162
};
163
```
164
165
**Manual Frame Processing:**
166
167
```javascript
168
function processFrameData(frameData) {
169
const frame = new WebSocket.frame();
170
171
let offset = 0;
172
while (offset < frameData.length) {
173
const chunk = frameData.slice(offset);
174
const complete = frame.addData(chunk);
175
176
if (complete) {
177
console.log('Frame completed:');
178
console.log(' Opcode:', frame.opcode);
179
console.log(' FIN:', frame.fin);
180
console.log(' Masked:', frame.mask);
181
console.log(' Length:', frame.length);
182
183
if (frame.protocolError) {
184
console.error('Protocol error in frame');
185
break;
186
}
187
188
if (frame.frameTooLarge) {
189
console.error('Frame too large');
190
break;
191
}
192
193
// Process payload based on opcode
194
switch (frame.opcode) {
195
case 0x1: // Text
196
const text = frame.binaryPayload.toString('utf8');
197
console.log('Text payload:', text);
198
break;
199
200
case 0x2: // Binary
201
console.log('Binary payload length:', frame.binaryPayload.length);
202
break;
203
204
case 0x8: // Close
205
console.log('Close frame, status:', frame.closeStatus);
206
break;
207
208
case 0x9: // Ping
209
console.log('Ping frame');
210
// Should respond with pong
211
break;
212
213
case 0xA: // Pong
214
console.log('Pong frame');
215
break;
216
}
217
218
break;
219
}
220
221
// If not complete, we need more data
222
console.log('Frame incomplete, need more data');
223
break;
224
}
225
}
226
```
227
228
### Frame Construction
229
230
**Creating Different Frame Types:**
231
232
```javascript
233
// Text frame
234
function createTextFrame(text, isFinal = true) {
235
const frame = new WebSocket.frame();
236
frame.opcode = 0x1;
237
frame.fin = isFinal;
238
frame.binaryPayload = Buffer.from(text, 'utf8');
239
frame.length = frame.binaryPayload.length;
240
return frame;
241
}
242
243
// Binary frame
244
function createBinaryFrame(data, isFinal = true) {
245
const frame = new WebSocket.frame();
246
frame.opcode = 0x2;
247
frame.fin = isFinal;
248
frame.binaryPayload = Buffer.isBuffer(data) ? data : Buffer.from(data);
249
frame.length = frame.binaryPayload.length;
250
return frame;
251
}
252
253
// Close frame
254
function createCloseFrame(code = 1000, reason = '') {
255
const frame = new WebSocket.frame();
256
frame.opcode = 0x8;
257
frame.fin = true;
258
259
if (code || reason) {
260
const reasonBuffer = Buffer.from(reason, 'utf8');
261
const payload = Buffer.allocUnsafe(2 + reasonBuffer.length);
262
payload.writeUInt16BE(code, 0);
263
reasonBuffer.copy(payload, 2);
264
frame.binaryPayload = payload;
265
frame.length = payload.length;
266
frame.closeStatus = code;
267
} else {
268
frame.binaryPayload = Buffer.allocUnsafe(0);
269
frame.length = 0;
270
}
271
272
return frame;
273
}
274
275
// Ping frame
276
function createPingFrame(data = Buffer.allocUnsafe(0)) {
277
const frame = new WebSocket.frame();
278
frame.opcode = 0x9;
279
frame.fin = true;
280
frame.binaryPayload = Buffer.isBuffer(data) ? data : Buffer.from(data);
281
frame.length = frame.binaryPayload.length;
282
return frame;
283
}
284
285
// Pong frame
286
function createPongFrame(data = Buffer.allocUnsafe(0)) {
287
const frame = new WebSocket.frame();
288
frame.opcode = 0xA;
289
frame.fin = true;
290
frame.binaryPayload = Buffer.isBuffer(data) ? data : Buffer.from(data);
291
frame.length = frame.binaryPayload.length;
292
return frame;
293
}
294
```
295
296
### Message Fragmentation
297
298
**Creating Fragmented Messages:**
299
300
```javascript
301
function createFragmentedMessage(data, maxFrameSize = 1024) {
302
const frames = [];
303
const totalLength = Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data, 'utf8');
304
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data, 'utf8');
305
const isText = !Buffer.isBuffer(data);
306
307
let offset = 0;
308
let isFirst = true;
309
310
while (offset < totalLength) {
311
const remainingLength = totalLength - offset;
312
const frameSize = Math.min(maxFrameSize, remainingLength);
313
const isLast = offset + frameSize >= totalLength;
314
315
const frame = new WebSocket.frame();
316
317
if (isFirst) {
318
// First frame uses TEXT or BINARY opcode
319
frame.opcode = isText ? 0x1 : 0x2;
320
isFirst = false;
321
} else {
322
// Continuation frames use CONTINUATION opcode
323
frame.opcode = 0x0;
324
}
325
326
frame.fin = isLast;
327
frame.binaryPayload = buffer.slice(offset, offset + frameSize);
328
frame.length = frame.binaryPayload.length;
329
330
frames.push(frame);
331
offset += frameSize;
332
}
333
334
return frames;
335
}
336
337
// Usage
338
const largeMessage = 'A'.repeat(5000);
339
const fragments = createFragmentedMessage(largeMessage, 1024);
340
341
console.log(`Message fragmented into ${fragments.length} frames`);
342
fragments.forEach((frame, index) => {
343
console.log(`Frame ${index}: opcode=${frame.opcode}, fin=${frame.fin}, length=${frame.length}`);
344
});
345
```
346
347
### Advanced Frame Handling
348
349
**Custom Frame Processing:**
350
351
```javascript
352
class CustomFrameProcessor {
353
constructor() {
354
this.pendingFragments = [];
355
}
356
357
processFrame(frame) {
358
// Validate frame
359
if (frame.protocolError) {
360
throw new Error('Protocol error in frame');
361
}
362
363
if (frame.frameTooLarge) {
364
throw new Error('Frame too large');
365
}
366
367
switch (frame.opcode) {
368
case 0x0: // Continuation
369
return this.handleContinuation(frame);
370
371
case 0x1: // Text
372
return this.handleText(frame);
373
374
case 0x2: // Binary
375
return this.handleBinary(frame);
376
377
case 0x8: // Close
378
return this.handleClose(frame);
379
380
case 0x9: // Ping
381
return this.handlePing(frame);
382
383
case 0xA: // Pong
384
return this.handlePong(frame);
385
386
default:
387
throw new Error('Unknown opcode: ' + frame.opcode);
388
}
389
}
390
391
handleText(frame) {
392
if (frame.fin) {
393
// Complete text message
394
return {
395
type: 'text',
396
data: frame.binaryPayload.toString('utf8')
397
};
398
} else {
399
// Start of fragmented text message
400
this.pendingFragments = [frame];
401
return null; // Not complete yet
402
}
403
}
404
405
handleContinuation(frame) {
406
if (this.pendingFragments.length === 0) {
407
throw new Error('Continuation frame without initial frame');
408
}
409
410
this.pendingFragments.push(frame);
411
412
if (frame.fin) {
413
// Reassemble message
414
const firstFrame = this.pendingFragments[0];
415
const totalLength = this.pendingFragments.reduce((sum, f) => sum + f.length, 0);
416
const assembled = Buffer.allocUnsafe(totalLength);
417
418
let offset = 0;
419
this.pendingFragments.forEach(f => {
420
f.binaryPayload.copy(assembled, offset);
421
offset += f.length;
422
});
423
424
this.pendingFragments = [];
425
426
return {
427
type: firstFrame.opcode === 0x1 ? 'text' : 'binary',
428
data: firstFrame.opcode === 0x1 ? assembled.toString('utf8') : assembled
429
};
430
}
431
432
return null; // Still assembling
433
}
434
435
handleClose(frame) {
436
let code = 1005; // No status code
437
let reason = '';
438
439
if (frame.length >= 2) {
440
code = frame.binaryPayload.readUInt16BE(0);
441
if (frame.length > 2) {
442
reason = frame.binaryPayload.slice(2).toString('utf8');
443
}
444
}
445
446
return {
447
type: 'close',
448
code: code,
449
reason: reason
450
};
451
}
452
453
handlePing(frame) {
454
return {
455
type: 'ping',
456
data: frame.binaryPayload
457
};
458
}
459
460
handlePong(frame) {
461
return {
462
type: 'pong',
463
data: frame.binaryPayload
464
};
465
}
466
}
467
```
468
469
## Types
470
471
### Frame Structure
472
473
```javascript { .api }
474
interface FrameStructure {
475
fin: boolean; // Final fragment flag
476
rsv1: boolean; // Reserved bit 1
477
rsv2: boolean; // Reserved bit 2
478
rsv3: boolean; // Reserved bit 3
479
mask: boolean; // Mask flag
480
opcode: number; // Frame type (0x0-0xF)
481
length: number; // Payload length
482
maskingKey?: Buffer; // 4-byte masking key (if masked)
483
payload: Buffer; // Frame payload
484
}
485
```
486
487
### Opcode Types
488
489
```javascript { .api }
490
type FrameOpcode =
491
| 0x0 // Continuation
492
| 0x1 // Text
493
| 0x2 // Binary
494
| 0x8 // Close
495
| 0x9 // Ping
496
| 0xA; // Pong
497
```