@zero-server/sdk 0.9.6 → 0.9.8

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 (94) hide show
  1. package/README.md +54 -53
  2. package/index.js +116 -4
  3. package/lib/app.js +22 -22
  4. package/lib/auth/authorize.js +11 -11
  5. package/lib/auth/enrollment.js +5 -5
  6. package/lib/auth/jwt.js +9 -9
  7. package/lib/auth/oauth.js +1 -1
  8. package/lib/auth/session.js +5 -5
  9. package/lib/auth/trustedDevice.js +2 -2
  10. package/lib/auth/twoFactor.js +11 -11
  11. package/lib/auth/webauthn.js +6 -6
  12. package/lib/body/json.js +1 -1
  13. package/lib/body/raw.js +1 -1
  14. package/lib/body/rawBuffer.js +1 -1
  15. package/lib/body/text.js +1 -1
  16. package/lib/body/urlencoded.js +3 -3
  17. package/lib/cli.js +43 -28
  18. package/lib/cluster.js +3 -3
  19. package/lib/debug.js +10 -10
  20. package/lib/env/index.js +11 -11
  21. package/lib/errors.js +131 -16
  22. package/lib/fetch/index.js +1 -1
  23. package/lib/grpc/call.js +14 -14
  24. package/lib/grpc/client.js +4 -4
  25. package/lib/grpc/codec.js +7 -7
  26. package/lib/grpc/credentials.js +2 -2
  27. package/lib/grpc/frame.js +2 -2
  28. package/lib/grpc/health.js +3 -3
  29. package/lib/grpc/index.js +3 -3
  30. package/lib/grpc/metadata.js +3 -3
  31. package/lib/grpc/proto.js +5 -5
  32. package/lib/grpc/reflection.js +2 -2
  33. package/lib/grpc/server.js +3 -3
  34. package/lib/grpc/status.js +2 -2
  35. package/lib/grpc/watch.js +1 -1
  36. package/lib/http/request.js +13 -13
  37. package/lib/http/response.js +2 -2
  38. package/lib/lifecycle.js +5 -5
  39. package/lib/middleware/compress.js +4 -4
  40. package/lib/observe/health.js +1 -1
  41. package/lib/observe/index.js +1 -1
  42. package/lib/observe/logger.js +3 -3
  43. package/lib/observe/metrics.js +4 -4
  44. package/lib/observe/tracing.js +4 -4
  45. package/lib/orm/adapters/json.js +1 -1
  46. package/lib/orm/adapters/memory.js +2 -2
  47. package/lib/orm/adapters/mongo.js +2 -2
  48. package/lib/orm/adapters/mysql.js +2 -2
  49. package/lib/orm/adapters/postgres.js +2 -2
  50. package/lib/orm/adapters/sqlite.js +3 -3
  51. package/lib/orm/audit.js +1 -1
  52. package/lib/orm/index.js +7 -7
  53. package/lib/orm/migrate.js +1 -1
  54. package/lib/orm/model.js +15 -15
  55. package/lib/orm/procedures.js +1 -1
  56. package/lib/orm/profiler.js +1 -1
  57. package/lib/orm/query.js +9 -9
  58. package/lib/orm/schema.js +1 -1
  59. package/lib/orm/seed/data/person.js +1 -1
  60. package/lib/orm/seed/fake.js +10 -10
  61. package/lib/orm/seed/index.js +4 -4
  62. package/lib/orm/seed/rng.js +1 -1
  63. package/lib/orm/snapshot.js +3 -3
  64. package/lib/orm/tenancy.js +6 -6
  65. package/lib/orm/views.js +1 -1
  66. package/lib/router/index.js +9 -9
  67. package/lib/webrtc/bot.js +405 -0
  68. package/lib/webrtc/cli.js +182 -0
  69. package/lib/webrtc/cluster.js +338 -0
  70. package/lib/webrtc/e2ee.js +274 -0
  71. package/lib/webrtc/ice.js +363 -0
  72. package/lib/webrtc/index.js +212 -0
  73. package/lib/webrtc/joinToken.js +171 -0
  74. package/lib/webrtc/observe.js +260 -0
  75. package/lib/webrtc/peer.js +143 -0
  76. package/lib/webrtc/room.js +184 -0
  77. package/lib/webrtc/sdp.js +503 -0
  78. package/lib/webrtc/sfu/index.js +251 -0
  79. package/lib/webrtc/sfu/livekit.js +304 -0
  80. package/lib/webrtc/sfu/mediasoup.js +357 -0
  81. package/lib/webrtc/sfu/memory.js +221 -0
  82. package/lib/webrtc/signaling.js +590 -0
  83. package/lib/webrtc/stun.js +484 -0
  84. package/lib/webrtc/turn/codec.js +370 -0
  85. package/lib/webrtc/turn/credentials.js +156 -0
  86. package/lib/webrtc/turn/server.js +648 -0
  87. package/package.json +2 -2
  88. package/types/body.d.ts +82 -14
  89. package/types/cli.d.ts +40 -2
  90. package/types/index.d.ts +19 -6
  91. package/types/middleware.d.ts +18 -72
  92. package/types/orm.d.ts +4 -13
  93. package/types/request.d.ts +3 -3
  94. package/types/webrtc.d.ts +501 -0
@@ -0,0 +1,484 @@
1
+ /**
2
+ * @module webrtc/stun
3
+ * @description Zero-dependency RFC 8489 STUN client. Sends a Binding Request
4
+ * over UDP, parses the Binding Response, and returns the server-reflexive
5
+ * address from XOR-MAPPED-ADDRESS (or legacy MAPPED-ADDRESS). NAT-discovery
6
+ * subset only; TURN allocations live in `lib/webrtc/turn/`.
7
+ *
8
+ * @see https://datatracker.ietf.org/doc/html/rfc8489
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ const dgram = require('node:dgram');
14
+ const crypto = require('node:crypto');
15
+ const net = require('node:net');
16
+
17
+ const { TurnError } = require('../errors');
18
+
19
+ // -- Protocol constants -------------------------------------------
20
+
21
+ /** STUN magic cookie (RFC 8489 §5). */
22
+ const STUN_MAGIC_COOKIE = 0x2112A442;
23
+
24
+ /** Length of the fixed STUN header. */
25
+ const HEADER_LEN = 20;
26
+
27
+ /** Length of the transaction ID. */
28
+ const TXID_LEN = 12;
29
+
30
+ /**
31
+ * STUN methods.
32
+ * @enum {number}
33
+ */
34
+ const STUN_METHOD = Object.freeze({ BINDING: 0x001 });
35
+
36
+ /**
37
+ * STUN classes (message type low bits 0x10 and 0x100).
38
+ * @enum {number}
39
+ */
40
+ const STUN_CLASS = Object.freeze({
41
+ REQUEST: 0x0,
42
+ INDICATION: 0x1,
43
+ SUCCESS: 0x2,
44
+ ERROR: 0x3,
45
+ });
46
+
47
+ /**
48
+ * STUN attribute type registry (just the ones we touch).
49
+ * @enum {number}
50
+ */
51
+ const STUN_ATTR = Object.freeze({
52
+ MAPPED_ADDRESS: 0x0001,
53
+ XOR_MAPPED_ADDRESS: 0x0020,
54
+ ERROR_CODE: 0x0009,
55
+ SOFTWARE: 0x8022,
56
+ });
57
+
58
+ // =================================================================
59
+ // Header / attribute codec
60
+ // =================================================================
61
+
62
+ /**
63
+ * Build a STUN Binding Request packet.
64
+ *
65
+ * @param {Buffer} [transactionId] - 12-byte ID; one is generated if omitted.
66
+ * @returns {{buffer:Buffer, transactionId:Buffer}}
67
+ * @throws {TurnError} If supplied transaction ID is the wrong length.
68
+ *
69
+ * @section ICE & TURN
70
+ */
71
+ function encodeBindingRequest(transactionId)
72
+ {
73
+ const txid = transactionId || crypto.randomBytes(TXID_LEN);
74
+ if (!Buffer.isBuffer(txid) || txid.length !== TXID_LEN)
75
+ throw new TurnError('encodeBindingRequest: transactionId must be a 12-byte Buffer');
76
+
77
+ const buf = Buffer.alloc(HEADER_LEN);
78
+ buf.writeUInt16BE(_makeType(STUN_METHOD.BINDING, STUN_CLASS.REQUEST), 0);
79
+ buf.writeUInt16BE(0, 2);
80
+ buf.writeUInt32BE(STUN_MAGIC_COOKIE, 4);
81
+ txid.copy(buf, 8);
82
+ return { buffer: buf, transactionId: txid };
83
+ }
84
+
85
+ /**
86
+ * Parse a STUN message (header + attributes).
87
+ *
88
+ * @param {Buffer} buf - Raw datagram.
89
+ * @returns {{method:number, class:number, transactionId:Buffer, attributes:Array<{type:number,value:Buffer}>}}
90
+ * @throws {TurnError} On any structural problem.
91
+ *
92
+ * @section ICE & TURN
93
+ */
94
+ function decodeMessage(buf)
95
+ {
96
+ if (!Buffer.isBuffer(buf) || buf.length < HEADER_LEN)
97
+ throw new TurnError('decodeMessage: buffer shorter than STUN header');
98
+
99
+ const type = buf.readUInt16BE(0);
100
+ if ((type & 0xC000) !== 0)
101
+ throw new TurnError('decodeMessage: high two bits of message type must be zero');
102
+
103
+ const length = buf.readUInt16BE(2);
104
+ if (buf.readUInt32BE(4) !== STUN_MAGIC_COOKIE)
105
+ throw new TurnError('decodeMessage: bad magic cookie');
106
+ if (HEADER_LEN + length > buf.length)
107
+ throw new TurnError('decodeMessage: message length exceeds buffer');
108
+
109
+ const txid = Buffer.from(buf.subarray(8, 20));
110
+ const { method, cls } = _splitType(type);
111
+
112
+ /** @type {Array<{type:number,value:Buffer}>} */
113
+ const attributes = [];
114
+ let off = HEADER_LEN;
115
+ const end = HEADER_LEN + length;
116
+ while (off + 4 <= end)
117
+ {
118
+ const aType = buf.readUInt16BE(off);
119
+ const aLen = buf.readUInt16BE(off + 2);
120
+ const vEnd = off + 4 + aLen;
121
+ if (vEnd > end)
122
+ throw new TurnError('decodeMessage: attribute length overruns message');
123
+ attributes.push({ type: aType, value: Buffer.from(buf.subarray(off + 4, vEnd)) });
124
+ // Attributes are padded to a 4-byte boundary.
125
+ const pad = (4 - (aLen % 4)) % 4;
126
+ off = vEnd + pad;
127
+ }
128
+
129
+ return { method, class: cls, transactionId: txid, attributes };
130
+ }
131
+
132
+ /** @private */
133
+ function _makeType(method, cls)
134
+ {
135
+ // RFC 8489 §5: M11..M7 C1 M6..M4 C0 M3..M0
136
+ const m = method & 0xfff;
137
+ const c = cls & 0x3;
138
+ return ((m & 0xf80) << 2)
139
+ | ((c & 0x2) << 7)
140
+ | ((m & 0x70) << 1)
141
+ | ((c & 0x1) << 4)
142
+ | (m & 0xf);
143
+ }
144
+
145
+ /** @private */
146
+ function _splitType(type)
147
+ {
148
+ const method = ((type & 0x3e00) >> 2)
149
+ | ((type & 0xe0) >> 1)
150
+ | (type & 0xf);
151
+ const cls = ((type & 0x100) >> 7) | ((type & 0x10) >> 4);
152
+ return { method, cls };
153
+ }
154
+
155
+ // =================================================================
156
+ // XOR-MAPPED-ADDRESS / MAPPED-ADDRESS
157
+ // =================================================================
158
+
159
+ /**
160
+ * Encode an XOR-MAPPED-ADDRESS attribute body (no TLV header).
161
+ *
162
+ * @param {string} address - IPv4 or IPv6 string.
163
+ * @param {number} port - 0-65535.
164
+ * @param {Buffer} transactionId - 12-byte transaction ID (needed for IPv6).
165
+ * @returns {Buffer}
166
+ *
167
+ * @section ICE & TURN
168
+ */
169
+ function encodeXorMappedAddress(address, port, transactionId)
170
+ {
171
+ const family = net.isIPv4(address) ? 4 : net.isIPv6(address) ? 6 : 0;
172
+ if (!family) throw new TurnError(`encodeXorMappedAddress: not an IP address: ${address}`);
173
+
174
+ const xport = port ^ (STUN_MAGIC_COOKIE >>> 16);
175
+ if (family === 4)
176
+ {
177
+ const out = Buffer.alloc(8);
178
+ out.writeUInt8(0, 0);
179
+ out.writeUInt8(0x01, 1);
180
+ out.writeUInt16BE(xport & 0xffff, 2);
181
+ const addr = _ipv4ToBuffer(address);
182
+ for (let i = 0; i < 4; i++)
183
+ out.writeUInt8(addr[i] ^ _cookieByte(i), 4 + i);
184
+ return out;
185
+ }
186
+
187
+ // IPv6: xaddr = addr XOR (cookie || transactionId)
188
+ const out = Buffer.alloc(20);
189
+ out.writeUInt8(0, 0);
190
+ out.writeUInt8(0x02, 1);
191
+ out.writeUInt16BE(xport & 0xffff, 2);
192
+ const addr = _ipv6ToBuffer(address);
193
+ for (let i = 0; i < 16; i++)
194
+ {
195
+ const mask = i < 4 ? _cookieByte(i) : transactionId[i - 4];
196
+ out.writeUInt8(addr[i] ^ mask, 4 + i);
197
+ }
198
+ return out;
199
+ }
200
+
201
+ /**
202
+ * Decode an XOR-MAPPED-ADDRESS attribute body to `{family, address, port}`.
203
+ *
204
+ * @param {Buffer} buf - Attribute value bytes.
205
+ * @param {Buffer} transactionId - 12-byte ID from the response header.
206
+ * @returns {{family:number, address:string, port:number}}
207
+ *
208
+ * @section ICE & TURN
209
+ */
210
+ function decodeXorMappedAddress(buf, transactionId)
211
+ {
212
+ if (!Buffer.isBuffer(buf) || buf.length < 8)
213
+ throw new TurnError('decodeXorMappedAddress: attribute too short');
214
+ const family = buf.readUInt8(1);
215
+ const xport = buf.readUInt16BE(2);
216
+ const port = xport ^ (STUN_MAGIC_COOKIE >>> 16);
217
+
218
+ if (family === 0x01)
219
+ {
220
+ if (buf.length < 8) throw new TurnError('decodeXorMappedAddress: IPv4 too short');
221
+ const addr = Buffer.alloc(4);
222
+ for (let i = 0; i < 4; i++) addr[i] = buf[4 + i] ^ _cookieByte(i);
223
+ return { family: 4, address: _bufferToIPv4(addr), port: port & 0xffff };
224
+ }
225
+ if (family === 0x02)
226
+ {
227
+ if (buf.length < 20) throw new TurnError('decodeXorMappedAddress: IPv6 too short');
228
+ const addr = Buffer.alloc(16);
229
+ for (let i = 0; i < 16; i++)
230
+ {
231
+ const mask = i < 4 ? _cookieByte(i) : transactionId[i - 4];
232
+ addr[i] = buf[4 + i] ^ mask;
233
+ }
234
+ return { family: 6, address: _bufferToIPv6(addr), port: port & 0xffff };
235
+ }
236
+ throw new TurnError(`decodeXorMappedAddress: unknown family 0x${family.toString(16)}`);
237
+ }
238
+
239
+ /**
240
+ * Decode the unauthenticated, pre-RFC-5389 MAPPED-ADDRESS attribute. Some
241
+ * very old STUN servers still emit this instead of XOR-MAPPED-ADDRESS; we
242
+ * accept it as a fallback.
243
+ * @private
244
+ */
245
+ function _decodeMappedAddress(buf)
246
+ {
247
+ if (!Buffer.isBuffer(buf) || buf.length < 8)
248
+ throw new TurnError('MAPPED-ADDRESS too short');
249
+ const family = buf.readUInt8(1);
250
+ const port = buf.readUInt16BE(2);
251
+ if (family === 0x01)
252
+ return { family: 4, address: _bufferToIPv4(buf.subarray(4, 8)), port };
253
+ if (family === 0x02 && buf.length >= 20)
254
+ return { family: 6, address: _bufferToIPv6(buf.subarray(4, 20)), port };
255
+ throw new TurnError(`MAPPED-ADDRESS: unknown family 0x${family.toString(16)}`);
256
+ }
257
+
258
+ /** @private */
259
+ function _cookieByte(i)
260
+ {
261
+ return (STUN_MAGIC_COOKIE >>> (24 - i * 8)) & 0xff;
262
+ }
263
+
264
+ // =================================================================
265
+ // IP <-> Buffer
266
+ // =================================================================
267
+
268
+ /** @private */
269
+ function _ipv4ToBuffer(addr)
270
+ {
271
+ const parts = addr.split('.');
272
+ const out = Buffer.alloc(4);
273
+ for (let i = 0; i < 4; i++) out[i] = Number(parts[i]) & 0xff;
274
+ return out;
275
+ }
276
+
277
+ /** @private */
278
+ function _bufferToIPv4(buf)
279
+ {
280
+ return `${buf[0]}.${buf[1]}.${buf[2]}.${buf[3]}`;
281
+ }
282
+
283
+ /** @private */
284
+ function _ipv6ToBuffer(addr)
285
+ {
286
+ const dbl = addr.indexOf('::');
287
+ let head, tail;
288
+ if (dbl === -1)
289
+ {
290
+ head = addr.split(':'); tail = [];
291
+ }
292
+ else
293
+ {
294
+ head = addr.slice(0, dbl).split(':').filter(Boolean);
295
+ tail = addr.slice(dbl + 2).split(':').filter(Boolean);
296
+ }
297
+ const fill = 8 - head.length - tail.length;
298
+ const groups = [...head, ...Array(fill).fill('0'), ...tail];
299
+ if (groups.length !== 8)
300
+ throw new TurnError(`_ipv6ToBuffer: cannot parse "${addr}"`);
301
+ const out = Buffer.alloc(16);
302
+ for (let i = 0; i < 8; i++)
303
+ {
304
+ const v = parseInt(groups[i], 16) & 0xffff;
305
+ out.writeUInt16BE(v, i * 2);
306
+ }
307
+ return out;
308
+ }
309
+
310
+ /** @private */
311
+ function _bufferToIPv6(buf)
312
+ {
313
+ const groups = [];
314
+ for (let i = 0; i < 8; i++) groups.push(buf.readUInt16BE(i * 2).toString(16));
315
+
316
+ // Compress the longest run of zero groups (length >= 2).
317
+ let bestStart = -1, bestLen = 0;
318
+ let curStart = -1, curLen = 0;
319
+ for (let i = 0; i < 8; i++)
320
+ {
321
+ if (groups[i] === '0')
322
+ {
323
+ if (curStart === -1) { curStart = i; curLen = 1; }
324
+ else curLen++;
325
+ if (curLen > bestLen) { bestLen = curLen; bestStart = curStart; }
326
+ }
327
+ else { curStart = -1; curLen = 0; }
328
+ }
329
+ if (bestLen < 2) return groups.join(':');
330
+ const left = groups.slice(0, bestStart).join(':');
331
+ const right = groups.slice(bestStart + bestLen).join(':');
332
+ return `${left}::${right}`;
333
+ }
334
+
335
+ // =================================================================
336
+ // Client
337
+ // =================================================================
338
+
339
+ /**
340
+ * @typedef {object} StunBindingOptions
341
+ * @property {string} host - STUN server hostname or IP.
342
+ * @property {number} [port=3478] - STUN server port.
343
+ * @property {number} [timeoutMs=500] - Per-attempt timeout.
344
+ * @property {number} [retries=7] - Max attempts (RFC 5389 default is 7).
345
+ * @property {'udp4'|'udp6'} [socketType='udp4']
346
+ */
347
+
348
+ /**
349
+ * @typedef {object} StunBindingResult
350
+ * @property {number} family - 4 or 6.
351
+ * @property {string} address - Server-reflexive address.
352
+ * @property {number} port - Server-reflexive port.
353
+ */
354
+
355
+ /**
356
+ * Discover the public (server-reflexive) address by sending a STUN Binding
357
+ * Request to `opts.host:opts.port`.
358
+ *
359
+ * Retries with the same transaction ID until a matching Binding Success is
360
+ * received or `retries` attempts elapse. Closes the socket on resolve/reject.
361
+ *
362
+ * @param {StunBindingOptions} opts
363
+ * @returns {Promise<StunBindingResult>}
364
+ * @throws {TurnError} On timeout, malformed response, or transaction-ID mismatch.
365
+ *
366
+ * @example
367
+ * const { address, port } = await stunBinding({ host: 'stun.l.google.com', port: 19302 });
368
+ *
369
+ * @section ICE & TURN
370
+ */
371
+ function stunBinding(opts)
372
+ {
373
+ return new Promise((resolve, reject) =>
374
+ {
375
+ const o = opts || {};
376
+ if (typeof o.host !== 'string' || o.host.length === 0)
377
+ return reject(new TurnError('stunBinding: opts.host is required'));
378
+ const port = o.port ?? 3478;
379
+ const timeoutMs = o.timeoutMs ?? 500;
380
+ const retries = o.retries ?? 7;
381
+ const socketType = o.socketType ?? 'udp4';
382
+ if (!Number.isInteger(port) || port <= 0 || port > 65535)
383
+ return reject(new TurnError('stunBinding: invalid port'));
384
+
385
+ const { buffer, transactionId } = encodeBindingRequest();
386
+ const sock = dgram.createSocket(socketType);
387
+ let attempt = 0;
388
+ let timer = null;
389
+ let done = false;
390
+
391
+ const cleanup = () =>
392
+ {
393
+ done = true;
394
+ if (timer) { clearTimeout(timer); timer = null; }
395
+ try { sock.close(); } catch { /* ignore */ }
396
+ };
397
+
398
+ sock.on('error', (err) =>
399
+ {
400
+ if (done) return;
401
+ cleanup();
402
+ reject(new TurnError(`stunBinding: socket error: ${err.message}`));
403
+ });
404
+
405
+ sock.on('message', (msg) =>
406
+ {
407
+ if (done) return;
408
+ let parsed;
409
+ try { parsed = decodeMessage(msg); }
410
+ catch (err) { return reject(_failWith(cleanup, err)); }
411
+
412
+ if (!parsed.transactionId.equals(transactionId))
413
+ return reject(_failWith(cleanup, new TurnError('stunBinding: transaction ID mismatch')));
414
+ if (parsed.method !== STUN_METHOD.BINDING)
415
+ return; // ignore unrelated method
416
+ if (parsed.class === STUN_CLASS.ERROR)
417
+ return reject(_failWith(cleanup, new TurnError('stunBinding: server returned ERROR class')));
418
+ if (parsed.class !== STUN_CLASS.SUCCESS) return;
419
+
420
+ const xor = parsed.attributes.find(a => a.type === STUN_ATTR.XOR_MAPPED_ADDRESS);
421
+ const mapped = parsed.attributes.find(a => a.type === STUN_ATTR.MAPPED_ADDRESS);
422
+
423
+ try
424
+ {
425
+ let result;
426
+ if (xor) result = decodeXorMappedAddress(xor.value, parsed.transactionId);
427
+ else if (mapped) result = _decodeMappedAddress(mapped.value);
428
+ else throw new TurnError('stunBinding: response missing XOR-MAPPED-ADDRESS / MAPPED-ADDRESS');
429
+ cleanup();
430
+ resolve(result);
431
+ }
432
+ catch (err)
433
+ {
434
+ reject(_failWith(cleanup, err));
435
+ }
436
+ });
437
+
438
+ const send = () =>
439
+ {
440
+ if (done) return;
441
+ attempt++;
442
+ sock.send(buffer, 0, buffer.length, port, o.host, (err) =>
443
+ {
444
+ if (err && !done)
445
+ {
446
+ cleanup();
447
+ return reject(new TurnError(`stunBinding: send failed: ${err.message}`));
448
+ }
449
+ });
450
+ timer = setTimeout(() =>
451
+ {
452
+ if (done) return;
453
+ if (attempt >= retries)
454
+ {
455
+ cleanup();
456
+ return reject(new TurnError(`stunBinding: timed out after ${attempt} attempts`));
457
+ }
458
+ send();
459
+ }, timeoutMs);
460
+ };
461
+
462
+ sock.bind(0, () => send());
463
+ });
464
+ }
465
+
466
+ /** @private */
467
+ function _failWith(cleanup, err)
468
+ {
469
+ cleanup();
470
+ return err instanceof TurnError ? err : new TurnError(err.message || String(err));
471
+ }
472
+
473
+ module.exports = {
474
+ stunBinding,
475
+ encodeBindingRequest,
476
+ decodeMessage,
477
+ encodeXorMappedAddress,
478
+ decodeXorMappedAddress,
479
+ STUN_MAGIC_COOKIE,
480
+ STUN_METHOD,
481
+ STUN_CLASS,
482
+ STUN_ATTR,
483
+ TurnError,
484
+ };