or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

client.mdconnection.mdframe.mdindex.mdrouter-request.mdserver.mdutilities.md

frame.mddocs/

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

```