@agentdance/node-webrtc 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +558 -0
  2. package/package.json +9 -8
package/README.md ADDED
@@ -0,0 +1,558 @@
1
+ # ts-rtc
2
+
3
+ **Production-grade WebRTC implementation in pure TypeScript — zero native bindings, zero C++ glue.**
4
+
5
+ Every protocol layer — ICE, DTLS 1.2, SCTP, SRTP, RTP/RTCP, STUN, and SDP — is built directly from first principles against the relevant RFCs. The public API mirrors the browser's `RTCPeerConnection` exactly, so Node.js code is portable and drop-in.
6
+
7
+ ---
8
+
9
+ ## Why ts-rtc?
10
+
11
+ Most Node.js WebRTC libraries are thin wrappers around `libwebrtc` or `libsrtp`, making them opaque, hard to audit, and brittle when native builds fail. `ts-rtc` takes the opposite approach:
12
+
13
+ | Property | ts-rtc | Native-binding libraries |
14
+ |---|---|---|
15
+ | Dependencies | Zero external crypto/TLS libs | `libwebrtc`, `libsrtp`, `openssl`, … |
16
+ | Debuggability | Step-through any protocol in plain TypeScript | Binary black box |
17
+ | Auditability | Every algorithm is readable source | Native C++ |
18
+ | RFC traceability | Inline references to RFC sections | Often undocumented |
19
+ | Build complexity | `pnpm install` — nothing to compile | Requires platform toolchain |
20
+ | Test vectors | RFC-verified test vectors in unit tests | Rarely tested at this level |
21
+
22
+ ---
23
+
24
+ ## Protocol Coverage
25
+
26
+ | Layer | Standard | Key features |
27
+ |---|---|---|
28
+ | **ICE** | RFC 8445 | Host / srflx / prflx candidates; connectivity checks with retransmit schedule (0 / 200 / 600 / 1400 / 3800 ms); aggressive & regular nomination; 15 s keepalive; BigInt pair-priority per §6.1.2.3 |
29
+ | **DTLS 1.2** | RFC 6347 | Full client+server handshake state machine; ECDHE P-256; AES-128-GCM; self-signed cert via pure ASN.1/DER builder; RFC 5763 §5 role negotiation; 60-byte SRTP key export |
30
+ | **SCTP** | RFC 4960 / RFC 8832 | Fragmentation & reassembly; SSN ordering; congestion control (cwnd / ssthresh / slow-start); fast retransmit on 3 duplicate SACKs; SACK gap blocks; FORWARD-TSN; DCEP (RFC 8832); pre-negotiated channels; TSN wrap-around |
31
+ | **SRTP** | RFC 3711 | AES-128-CM-HMAC-SHA1-80/32 and AES-128-GCM; RFC-verified key derivation; 64-bit sliding replay window; ROC rollover |
32
+ | **RTP / RTCP** | RFC 3550 | Full header codec; CSRC; one-byte & two-byte header extensions; SR / RR / SDES / BYE / NACK / PLI / FIR / REMB / compound packets |
33
+ | **STUN** | RFC 5389 | Full message codec; HMAC-SHA1 integrity; CRC-32 fingerprint; ICE attributes (PRIORITY, USE-CANDIDATE, ICE-CONTROLLING, ICE-CONTROLLED) |
34
+ | **SDP** | RFC 4566 / WebRTC | Full parse ↔ serialize round-trip; extmap; rtpmap/fmtp; ssrc/ssrc-group; BUNDLE; Chrome interop |
35
+
36
+ ---
37
+
38
+ ## Demo Web Application
39
+
40
+ A signaling server + demo client that bridges a Flutter macOS app to a Node.js peer.
41
+
42
+ ```bash
43
+ cd apps/demo-web
44
+ pnpm dev # hot-reload dev server on http://localhost:3000
45
+ pnpm start # production
46
+ ```
47
+
48
+ ### Demo scenarios
49
+
50
+ | Scenario | Description |
51
+ |---|---|
52
+ | `scenario1-multi-file` | Multi-file transfer over DataChannel |
53
+ | `scenario2-large-file` | Large file transfer with progress reporting |
54
+ | `scenario3-snake` | Snake game multiplayer over DataChannel |
55
+ | `scenario4-video` | Video streaming |
56
+
57
+ The signaling server runs WebSocket at `ws://localhost:8080/ws` with room-based peer discovery.
58
+
59
+ ---
60
+
61
+ ## Architecture
62
+
63
+ ```
64
+ packages/
65
+ ├── webrtc/ RTCPeerConnection — standard browser API (glue layer)
66
+ ├── ice/ RFC 8445 ICE agent
67
+ ├── dtls/ RFC 6347 DTLS 1.2 transport
68
+ ├── sctp/ RFC 4960 + RFC 8832 SCTP / DCEP
69
+ ├── srtp/ RFC 3711 SRTP / SRTCP
70
+ ├── rtp/ RFC 3550 RTP / RTCP codec
71
+ ├── stun/ RFC 5389 STUN message codec + client
72
+ └── sdp/ WebRTC SDP parser / serializer
73
+
74
+ apps/
75
+ ├── demo-web/ Express + WebSocket signaling server (4 demo scenarios)
76
+ ├── bench/ 500 MB DataChannel throughput benchmark
77
+ └── demo-flutter/ Flutter macOS client (flutter_webrtc)
78
+
79
+ features/ Cucumber BDD acceptance tests (living specification)
80
+ ```
81
+
82
+ Each package is independently importable. `@ts-rtc/webrtc` is the only package most consumers need.
83
+
84
+ ---
85
+
86
+ ## Quickstart
87
+
88
+ ### Prerequisites
89
+
90
+ - Node.js 18+
91
+ - pnpm
92
+
93
+ ### Install
94
+
95
+ ```bash
96
+ git clone https://github.com/your-org/ts-rtc.git
97
+ cd ts-rtc
98
+ pnpm install
99
+ ```
100
+
101
+ ### Build
102
+
103
+ ```bash
104
+ pnpm build # compile all packages to dist/
105
+ ```
106
+
107
+ ### Run tests
108
+
109
+ ```bash
110
+ pnpm test # Vitest unit tests across all packages
111
+ pnpm test:bdd # Cucumber BDD acceptance tests
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Usage
117
+
118
+ ### Minimal DataChannel (peer-to-peer in Node.js)
119
+
120
+ ```typescript
121
+ import { RTCPeerConnection } from '@ts-rtc/webrtc';
122
+
123
+ // ── Offerer ───────────────────────────────────────────────────────────────────
124
+ const pcA = new RTCPeerConnection({ iceServers: [] });
125
+ const dc = pcA.createDataChannel('chat');
126
+
127
+ dc.on('open', () => dc.send('Hello WebRTC!'));
128
+ dc.on('message', data => console.log('[A received]', data));
129
+
130
+ // ── Answerer ──────────────────────────────────────────────────────────────────
131
+ const pcB = new RTCPeerConnection({ iceServers: [] });
132
+
133
+ pcB.on('datachannel', channel => {
134
+ channel.on('message', data => {
135
+ console.log('[B received]', data);
136
+ channel.send('Hello back!');
137
+ });
138
+ });
139
+
140
+ // ── Trickle ICE ───────────────────────────────────────────────────────────────
141
+ pcA.on('icecandidate', c => c && pcB.addIceCandidate(c));
142
+ pcB.on('icecandidate', c => c && pcA.addIceCandidate(c));
143
+
144
+ // ── SDP exchange ──────────────────────────────────────────────────────────────
145
+ const offer = await pcA.createOffer();
146
+ await pcA.setLocalDescription(offer);
147
+ await pcB.setRemoteDescription(offer);
148
+
149
+ const answer = await pcB.createAnswer();
150
+ await pcB.setLocalDescription(answer);
151
+ await pcA.setRemoteDescription(answer);
152
+ ```
153
+
154
+ ### With a STUN server
155
+
156
+ ```typescript
157
+ const pc = new RTCPeerConnection({
158
+ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
159
+ });
160
+ ```
161
+
162
+ ### Binary data
163
+
164
+ ```typescript
165
+ const buf = crypto.randomBytes(65536);
166
+ dc.on('open', () => dc.send(buf));
167
+
168
+ remoteChannel.on('message', (data: Buffer) => {
169
+ console.log('received', data.byteLength, 'bytes');
170
+ });
171
+ ```
172
+
173
+ ### Multiple concurrent channels
174
+
175
+ ```typescript
176
+ const ctrl = pcA.createDataChannel('control', { ordered: true });
177
+ const bulk = pcA.createDataChannel('bulk', { ordered: false });
178
+ const log = pcA.createDataChannel('log', { maxRetransmits: 0 });
179
+ ```
180
+
181
+ ### Pre-negotiated channel (no DCEP round-trip)
182
+
183
+ ```typescript
184
+ // Both peers must call this with the same id
185
+ const chA = pcA.createDataChannel('secure', { negotiated: true, id: 5 });
186
+ const chB = pcB.createDataChannel('secure', { negotiated: true, id: 5 });
187
+ ```
188
+
189
+ ### Backpressure-aware large transfers
190
+
191
+ ```typescript
192
+ const CHUNK = 1168; // one SCTP DATA payload (fits within PMTU)
193
+ const HIGH = 4 * 1024 * 1024;
194
+ const LOW = 2 * 1024 * 1024;
195
+
196
+ dc.bufferedAmountLowThreshold = LOW;
197
+
198
+ function pump(data: Buffer, offset = 0) {
199
+ while (offset < data.length) {
200
+ if (dc.bufferedAmount > HIGH) {
201
+ dc.once('bufferedamountlow', () => pump(data, offset));
202
+ return;
203
+ }
204
+ dc.send(data.subarray(offset, offset + CHUNK));
205
+ offset += CHUNK;
206
+ }
207
+ }
208
+
209
+ dc.on('open', () => pump(largeBuffer));
210
+ ```
211
+
212
+ ### Connection state monitoring
213
+
214
+ ```typescript
215
+ pc.on('connectionstatechange', () => {
216
+ console.log('connection:', pc.connectionState);
217
+ // 'new' | 'connecting' | 'connected' | 'disconnected' | 'failed' | 'closed'
218
+ });
219
+
220
+ pc.on('iceconnectionstatechange', () => {
221
+ console.log('ICE:', pc.iceConnectionState);
222
+ });
223
+
224
+ pc.on('icegatheringstatechange', () => {
225
+ console.log('gathering:', pc.iceGatheringState);
226
+ });
227
+ ```
228
+
229
+ ### Stats
230
+
231
+ ```typescript
232
+ const stats = await pc.getStats();
233
+ for (const [, entry] of stats) {
234
+ if (entry.type === 'candidate-pair' && entry.nominated) {
235
+ console.log('RTT:', entry.currentRoundTripTime);
236
+ console.log('bytes sent:', entry.bytesSent);
237
+ }
238
+ }
239
+ ```
240
+
241
+ ### Graceful close
242
+
243
+ ```typescript
244
+ await dc.close();
245
+ pc.close();
246
+ ```
247
+
248
+ ---
249
+
250
+ ## RTCPeerConnection API Reference
251
+
252
+ ### Constructor
253
+
254
+ ```typescript
255
+ new RTCPeerConnection(config?: RTCConfiguration)
256
+ ```
257
+
258
+ | Option | Type | Default |
259
+ |---|---|---|
260
+ | `iceServers` | `RTCIceServer[]` | `[{ urls: 'stun:stun.l.google.com:19302' }]` |
261
+ | `iceTransportPolicy` | `'all' \| 'relay'` | `'all'` |
262
+ | `bundlePolicy` | `'max-bundle' \| 'balanced' \| 'max-compat'` | `'max-bundle'` |
263
+ | `rtcpMuxPolicy` | `'require'` | `'require'` |
264
+ | `iceCandidatePoolSize` | `number` | `0` |
265
+
266
+ ### Methods
267
+
268
+ | Method | Description |
269
+ |---|---|
270
+ | `createOffer()` | Generate an SDP offer |
271
+ | `createAnswer()` | Generate an SDP answer |
272
+ | `setLocalDescription(sdp)` | Apply local SDP, begin ICE gathering |
273
+ | `setRemoteDescription(sdp)` | Apply remote SDP, begin ICE connectivity checks |
274
+ | `addIceCandidate(candidate)` | Feed a trickled ICE candidate |
275
+ | `createDataChannel(label, init?)` | Create a DataChannel |
276
+ | `addTransceiver(kind, init?)` | Add an RTP transceiver |
277
+ | `getTransceivers()` | List all transceivers |
278
+ | `getSenders()` | List RTP senders |
279
+ | `getReceivers()` | List RTP receivers |
280
+ | `getStats()` | Retrieve `RTCStatsReport` |
281
+ | `restartIce()` | Trigger ICE restart |
282
+ | `close()` | Tear down the connection |
283
+
284
+ ### Events
285
+
286
+ | Event | Payload | When |
287
+ |---|---|---|
288
+ | `icecandidate` | `RTCIceCandidateInit \| null` | New local ICE candidate; `null` = gathering complete |
289
+ | `icecandidateerror` | `{ errorCode, errorText }` | STUN server unreachable |
290
+ | `iceconnectionstatechange` | — | ICE connection state changed |
291
+ | `icegatheringstatechange` | — | ICE gathering state changed |
292
+ | `connectionstatechange` | — | Overall connection state changed |
293
+ | `signalingstatechange` | — | Signaling state changed |
294
+ | `negotiationneeded` | — | Re-negotiation required |
295
+ | `datachannel` | `RTCDataChannel` | Remote opened a DataChannel |
296
+ | `track` | `RTCTrackEvent` | Remote RTP track received |
297
+
298
+ ---
299
+
300
+ ## RTCDataChannel API Reference
301
+
302
+ ### Properties
303
+
304
+ | Property | Type | Description |
305
+ |---|---|---|
306
+ | `label` | `string` | Channel name |
307
+ | `readyState` | `'connecting' \| 'open' \| 'closing' \| 'closed'` | Current state |
308
+ | `ordered` | `boolean` | Reliable ordering |
309
+ | `maxPacketLifeTime` | `number \| null` | Partial reliability (ms) |
310
+ | `maxRetransmits` | `number \| null` | Partial reliability (count) |
311
+ | `protocol` | `string` | Sub-protocol |
312
+ | `negotiated` | `boolean` | Pre-negotiated (no DCEP) |
313
+ | `id` | `number` | SCTP stream ID |
314
+ | `bufferedAmount` | `number` | Bytes queued in send buffer |
315
+ | `bufferedAmountLowThreshold` | `number` | Threshold for `bufferedamountlow` |
316
+ | `binaryType` | `'arraybuffer'` | Binary message format |
317
+
318
+ ### Methods
319
+
320
+ | Method | Description |
321
+ |---|---|
322
+ | `send(data)` | Send `string \| Buffer \| ArrayBuffer \| ArrayBufferView` |
323
+ | `close()` | Close the channel |
324
+
325
+ ### Events
326
+
327
+ | Event | Payload | When |
328
+ |---|---|---|
329
+ | `open` | — | Channel ready to send |
330
+ | `message` | `string \| Buffer` | Message received |
331
+ | `close` | — | Channel closed |
332
+ | `closing` | — | Close initiated |
333
+ | `error` | `Error` | Channel error |
334
+ | `bufferedamountlow` | — | Buffered amount crossed threshold |
335
+
336
+ ---
337
+
338
+ ## Lower-Level Package APIs
339
+
340
+ Each protocol layer is independently usable for specialized use-cases.
341
+
342
+ ### `@ts-rtc/ice` — ICE Agent
343
+
344
+ ```typescript
345
+ import { IceAgent } from '@ts-rtc/ice';
346
+
347
+ const agent = new IceAgent({ role: 'controlling', iceServers: [] });
348
+ await agent.gather();
349
+ agent.setRemoteParameters({ usernameFragment: '…', password: '…' });
350
+ agent.addRemoteCandidate(candidate);
351
+ await agent.connect();
352
+ agent.send(Buffer.from('data'));
353
+ agent.on('data', (buf) => console.log(buf));
354
+ ```
355
+
356
+ ### `@ts-rtc/dtls` — DTLS 1.2 Transport
357
+
358
+ ```typescript
359
+ import { DtlsTransport } from '@ts-rtc/dtls';
360
+
361
+ const dtls = new DtlsTransport(iceTransport, {
362
+ role: 'client', // or 'server'
363
+ remoteFingerprint: { algorithm: 'sha-256', value: '…' },
364
+ });
365
+ await dtls.start();
366
+ dtls.on('connected', () => {
367
+ const keys = dtls.getSrtpKeyingMaterial(); // { clientKey, serverKey, clientSalt, serverSalt }
368
+ });
369
+ dtls.send(Buffer.from('app data'));
370
+ ```
371
+
372
+ ### `@ts-rtc/sctp` — SCTP Association
373
+
374
+ ```typescript
375
+ import { SctpAssociation } from '@ts-rtc/sctp';
376
+
377
+ const sctp = new SctpAssociation(dtlsTransport, { role: 'client', port: 5000 });
378
+ await sctp.connect();
379
+ const channel = await sctp.createDataChannel('chat');
380
+ channel.send('hello');
381
+ sctp.on('datachannel', (ch) => ch.on('message', console.log));
382
+ ```
383
+
384
+ ### `@ts-rtc/srtp` — SRTP Protect / Unprotect
385
+
386
+ ```typescript
387
+ import { createSrtpContext, srtpProtect, srtpUnprotect } from '@ts-rtc/srtp';
388
+ import { ProtectionProfile } from '@ts-rtc/srtp';
389
+
390
+ const ctx = createSrtpContext(ProtectionProfile.AES_128_CM_HMAC_SHA1_80, keyingMaterial);
391
+ const protected_ = srtpProtect(ctx, rtpPacket);
392
+ const unprotected = srtpUnprotect(ctx, protected_);
393
+ ```
394
+
395
+ ### `@ts-rtc/stun` — STUN Codec
396
+
397
+ ```typescript
398
+ import { encodeMessage, decodeMessage, createBindingRequest } from '@ts-rtc/stun';
399
+
400
+ const req = createBindingRequest({ username: 'user:pass', priority: 12345 });
401
+ const buf = encodeMessage(req, 'password');
402
+ const msg = decodeMessage(buf);
403
+ ```
404
+
405
+ ### `@ts-rtc/sdp` — SDP Parser / Serializer
406
+
407
+ ```typescript
408
+ import { parse, serialize, parseCandidate } from '@ts-rtc/sdp';
409
+
410
+ const session = parse(sdpString);
411
+ const text = serialize(session);
412
+ const cand = parseCandidate('candidate:…');
413
+ ```
414
+
415
+ ### `@ts-rtc/rtp` — RTP / RTCP Codec
416
+
417
+ ```typescript
418
+ import { encodeRtp, decodeRtp, encodeRtcpSr, decodeRtcp } from '@ts-rtc/rtp';
419
+
420
+ const packet = encodeRtp({ payloadType: 96, sequenceNumber: 1, timestamp: 0, ssrc: 42, payload });
421
+ const { header, payload } = decodeRtp(packet);
422
+ ```
423
+
424
+ ---
425
+
426
+ ## Throughput Benchmark
427
+
428
+ Measures raw DataChannel throughput on a Node.js loopback — no network, pure protocol stack cost.
429
+
430
+ ```bash
431
+ cd apps/bench
432
+ ../../node_modules/.bin/tsx bench.ts
433
+ ```
434
+
435
+ **What it tests:**
436
+ - Two isolated Node.js processes (`sender` and `receiver`) connected via IPC-bridged signaling
437
+ - 500 MB binary transfer in 1168-byte chunks (matches SCTP DATA payload size for a 1200-byte PMTU)
438
+ - Backpressure via `bufferedAmountLowThreshold` (high-watermark 4 MB, low-watermark 2 MB)
439
+ - SHA-256 end-to-end integrity verification — the benchmark fails if a single byte is wrong
440
+
441
+ **Sample output:**
442
+ ```
443
+ ════════════════════════════════════════════════════════════
444
+ ts-rtc 500MB DataChannel Throughput Benchmark
445
+ Path: Node.js loopback (127.0.0.1)
446
+ ════════════════════════════════════════════════════════════
447
+ Benchmark complete
448
+ SHA-256 verification: ✅ passed
449
+ Transfer time: 8.3 s
450
+ Average speed: 60.24 MB/s
451
+ Total wall time: 9.1 s
452
+ ════════════════════════════════════════════════════════════
453
+ ```
454
+
455
+ ---
456
+
457
+ ## Test Suite
458
+
459
+ ### Unit tests — Vitest
460
+
461
+ ```bash
462
+ pnpm test
463
+ ```
464
+
465
+ | Package | Test file | Key coverage |
466
+ |---|---|---|
467
+ | `webrtc` | `webrtc.test.ts` (604 lines) | RTCPeerConnection lifecycle, SDP factory, DTLS role negotiation, full ICE+DTLS+SCTP loopback |
468
+ | `ice` | `ice.test.ts` (555 lines) | Candidate priority/foundation math, pair formation, loopback connectivity, restart, tier classification |
469
+ | `dtls` | `dtls.test.ts` (738 lines) | Record codec, handshake messages, PRF vectors, self-signed cert, AES-GCM, full loopback, both-client deadlock regression |
470
+ | `sctp` | `association.test.ts` (436 lines) | Handshake, DCEP, 65536B + 4 MiB transfers, cwnd growth, peerRwnd, flightSize, backpressure, pre-negotiated, ordered/unordered, 3 concurrent channels, TSN wrap-around |
471
+ | `srtp` | `srtp.test.ts` (609 lines) | RFC 3711 §B.2 keystream vectors, §B.3 key derivation vectors, HMAC-SHA1, ReplayWindow, protect+unprotect, tamper detection, ROC wrap |
472
+ | `rtp` | `rtp.test.ts` (570 lines) | RTP encode/decode, CSRC, header extensions, all RTCP types, compound packets, sequence wrap, NTP conversion |
473
+ | `sdp` | `sdp.test.ts` (827 lines) | Chrome offer/answer parsing, round-trip fidelity, all candidate types, fingerprint, directions, SSRC groups, extmap |
474
+ | `stun` | `stun.test.ts` (569 lines) | All attribute types, XOR-MAPPED-ADDRESS (IPv4 + IPv6), MESSAGE-INTEGRITY (correct/wrong/tampered), FINGERPRINT, ICE attributes |
475
+
476
+ **Total: ~4,900 lines of unit tests across 9 test files.**
477
+
478
+ ### BDD acceptance tests — Cucumber.js
479
+
480
+ ```bash
481
+ pnpm test:bdd
482
+ ```
483
+
484
+ 29 scenarios across 5 feature files:
485
+
486
+ | Feature file | Scenarios | What it covers |
487
+ |---|---|---|
488
+ | `webrtc/peer-connection.feature` | 14 | Basic negotiation, bidirectional messaging, binary data, 65 KB fragmentation, 3 concurrent channels, late channel creation, pre-negotiated, unordered, close, signaling state machine, getStats, **4 MiB end-to-end byte-integrity transfer** |
489
+ | `webrtc/dtls-role-interop.feature` | 7 | RFC 5763 §5 role negotiation (actpass / active / passive), complementary role assignment, data over negotiated connection, **both-client deadlock regression** |
490
+ | `ice/ice-connectivity.feature` | 2 | ICE gathering (valid candidates), **ICE loopback connectivity** |
491
+ | `dtls/dtls-handshake.feature` | 2 | **DTLS loopback handshake**, matching SRTP keying material, app data exchange |
492
+ | `sctp/sctp-channels.feature` | 4 | SCTP handshake, DCEP open, **65 KiB binary transfer**, **4 MiB binary transfer** |
493
+
494
+ Reports are written to `reports/cucumber-report.html`.
495
+
496
+ ---
497
+
498
+ ## Other Commands
499
+
500
+ ```bash
501
+ pnpm typecheck # TypeScript strict-mode check across all packages (no emit)
502
+ pnpm lint # ESLint 9 + @typescript-eslint
503
+ pnpm clean # Remove all dist/ directories
504
+ ```
505
+
506
+ ---
507
+
508
+ ## TypeScript Configuration
509
+
510
+ All packages share `tsconfig.base.json`:
511
+
512
+ ```json
513
+ {
514
+ "target": "ES2022",
515
+ "module": "NodeNext",
516
+ "strict": true,
517
+ "exactOptionalPropertyTypes": true,
518
+ "noUncheckedIndexedAccess": true,
519
+ "noImplicitOverride": true
520
+ }
521
+ ```
522
+
523
+ `exactOptionalPropertyTypes` and `noUncheckedIndexedAccess` are enabled intentionally — they catch protocol-level bugs at compile time that strict mode alone misses.
524
+
525
+ ---
526
+
527
+ ## Monorepo Layout
528
+
529
+ ```
530
+ ts-rtc/
531
+ ├── packages/ # Protocol stack (each independently publishable)
532
+ ├── apps/ # Demo and benchmark applications
533
+ ├── features/ # Cucumber BDD specs + step definitions
534
+ ├── package.json # pnpm workspace root
535
+ ├── pnpm-workspace.yaml
536
+ ├── tsconfig.base.json # Shared compiler options
537
+ └── cucumber.yaml # BDD runner config
538
+ ```
539
+
540
+ ---
541
+
542
+ ## Design Principles
543
+
544
+ 1. **No native dependencies.** Everything is implemented in TypeScript using only `node:crypto`, `node:dgram`, and `node:net`. No OpenSSL bindings, no `node-gyp`, no pre-built binaries.
545
+
546
+ 2. **RFC first.** Every algorithm includes inline RFC section references. If behavior diverges from the spec, it is a bug.
547
+
548
+ 3. **Layered, independently testable.** ICE, DTLS, SCTP, and SRTP are separate packages that can be tested in isolation. The full WebRTC stack is integration-tested at the `@ts-rtc/webrtc` layer.
549
+
550
+ 4. **Backpressure everywhere.** `bufferedAmount` and `bufferedAmountLowThreshold` are plumbed from SCTP congestion control all the way through DCEP to `RTCDataChannel`, enabling safe high-throughput transfers without unbounded memory growth.
551
+
552
+ 5. **Test vectors over trust.** Cryptographic primitives (AES-CM keystream, HMAC-SHA1 key derivation, CRC-32 fingerprint) are verified against the exact vectors published in their respective RFCs.
553
+
554
+ ---
555
+
556
+ ## License
557
+
558
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentdance/node-webrtc",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Production-grade WebRTC in pure TypeScript — RTCPeerConnection, DataChannel, ICE, DTLS, SCTP, SRTP. Zero native bindings.",
5
5
  "keywords": [
6
6
  "webrtc",
@@ -37,6 +37,7 @@
37
37
  }
38
38
  },
39
39
  "files": [
40
+ "README.md",
40
41
  "dist",
41
42
  "src"
42
43
  ],
@@ -46,13 +47,13 @@
46
47
  "registry": "https://registry.npmjs.org/"
47
48
  },
48
49
  "dependencies": {
49
- "@agentdance/node-webrtc-sdp": "1.0.0",
50
- "@agentdance/node-webrtc-ice": "1.0.0",
51
- "@agentdance/node-webrtc-dtls": "1.0.0",
52
- "@agentdance/node-webrtc-srtp": "1.0.0",
53
- "@agentdance/node-webrtc-rtp": "1.0.0",
54
- "@agentdance/node-webrtc-sctp": "1.0.0",
55
- "@agentdance/node-webrtc-stun": "1.0.0"
50
+ "@agentdance/node-webrtc-sdp": "1.0.2",
51
+ "@agentdance/node-webrtc-ice": "1.0.2",
52
+ "@agentdance/node-webrtc-dtls": "1.0.2",
53
+ "@agentdance/node-webrtc-srtp": "1.0.2",
54
+ "@agentdance/node-webrtc-rtp": "1.0.2",
55
+ "@agentdance/node-webrtc-sctp": "1.0.2",
56
+ "@agentdance/node-webrtc-stun": "1.0.2"
56
57
  },
57
58
  "devDependencies": {
58
59
  "typescript": "*",