werift 0.24.3 → 0.25.0-alpha.0

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 (168) hide show
  1. package/README.md +116 -113
  2. package/lib/common/src/event.js +2 -1
  3. package/lib/common/src/event.js.map +1 -1
  4. package/lib/common/src/transport.d.ts +22 -2
  5. package/lib/common/src/transport.js +84 -26
  6. package/lib/common/src/transport.js.map +1 -1
  7. package/lib/ice/src/exceptions.d.ts +9 -0
  8. package/lib/ice/src/exceptions.js +23 -1
  9. package/lib/ice/src/exceptions.js.map +1 -1
  10. package/lib/ice/src/ice.d.ts +40 -0
  11. package/lib/ice/src/ice.js +194 -28
  12. package/lib/ice/src/ice.js.map +1 -1
  13. package/lib/ice/src/iceBase.d.ts +26 -0
  14. package/lib/ice/src/iceBase.js.map +1 -1
  15. package/lib/ice/src/stun/const.d.ts +7 -0
  16. package/lib/ice/src/stun/const.js +8 -0
  17. package/lib/ice/src/stun/const.js.map +1 -1
  18. package/lib/ice/src/stun/protocol.js +1 -4
  19. package/lib/ice/src/stun/protocol.js.map +1 -1
  20. package/lib/ice/src/stun/tcpProtocol.d.ts +7 -1
  21. package/lib/ice/src/stun/tcpProtocol.js +65 -14
  22. package/lib/ice/src/stun/tcpProtocol.js.map +1 -1
  23. package/lib/ice/src/stun/transaction.d.ts +12 -1
  24. package/lib/ice/src/stun/transaction.js +37 -4
  25. package/lib/ice/src/stun/transaction.js.map +1 -1
  26. package/lib/ice/src/turn/protocol.d.ts +79 -22
  27. package/lib/ice/src/turn/protocol.js +282 -78
  28. package/lib/ice/src/turn/protocol.js.map +1 -1
  29. package/lib/ice/src/types/model.d.ts +8 -0
  30. package/lib/ice/src/types/model.js.map +1 -1
  31. package/lib/index.mjs +5646 -3451
  32. package/lib/nonstandard/index.mjs +287 -1004
  33. package/lib/polyfill/dom.mjs +23794 -0
  34. package/lib/polyfill/index.mjs +23794 -0
  35. package/lib/rtp/src/extra/container/mp4/container.js +12 -4
  36. package/lib/rtp/src/extra/container/mp4/container.js.map +1 -1
  37. package/lib/rtp/src/extra/processor/mp4.js +8 -3
  38. package/lib/rtp/src/extra/processor/mp4.js.map +1 -1
  39. package/lib/rtp/src/extra/processor/mp4Callback.js +3 -1
  40. package/lib/rtp/src/extra/processor/mp4Callback.js.map +1 -1
  41. package/lib/sctp/src/chunk.d.ts +5 -0
  42. package/lib/sctp/src/chunk.js +16 -11
  43. package/lib/sctp/src/chunk.js.map +1 -1
  44. package/lib/sctp/src/index.d.ts +3 -1
  45. package/lib/sctp/src/index.js +10 -1
  46. package/lib/sctp/src/index.js.map +1 -1
  47. package/lib/sctp/src/sctp.d.ts +12 -3
  48. package/lib/sctp/src/sctp.js +51 -18
  49. package/lib/sctp/src/sctp.js.map +1 -1
  50. package/lib/webrtc/src/const.js +2 -0
  51. package/lib/webrtc/src/const.js.map +1 -1
  52. package/lib/webrtc/src/errors.d.ts +9 -1
  53. package/lib/webrtc/src/errors.js +19 -0
  54. package/lib/webrtc/src/errors.js.map +1 -1
  55. package/lib/webrtc/src/media/codec.d.ts +5 -0
  56. package/lib/webrtc/src/media/codec.js +21 -0
  57. package/lib/webrtc/src/media/codec.js.map +1 -1
  58. package/lib/webrtc/src/media/codecCompatibility.d.ts +23 -0
  59. package/lib/webrtc/src/media/codecCompatibility.js +138 -0
  60. package/lib/webrtc/src/media/codecCompatibility.js.map +1 -0
  61. package/lib/webrtc/src/media/index.d.ts +1 -0
  62. package/lib/webrtc/src/media/index.js +1 -0
  63. package/lib/webrtc/src/media/index.js.map +1 -1
  64. package/lib/webrtc/src/media/router.d.ts +2 -0
  65. package/lib/webrtc/src/media/router.js +23 -3
  66. package/lib/webrtc/src/media/router.js.map +1 -1
  67. package/lib/webrtc/src/media/rtpReceiver.d.ts +12 -0
  68. package/lib/webrtc/src/media/rtpReceiver.js +35 -0
  69. package/lib/webrtc/src/media/rtpReceiver.js.map +1 -1
  70. package/lib/webrtc/src/media/rtpSender.d.ts +65 -4
  71. package/lib/webrtc/src/media/rtpSender.js +264 -35
  72. package/lib/webrtc/src/media/rtpSender.js.map +1 -1
  73. package/lib/webrtc/src/media/rtpTransceiver.d.ts +42 -0
  74. package/lib/webrtc/src/media/rtpTransceiver.js +157 -4
  75. package/lib/webrtc/src/media/rtpTransceiver.js.map +1 -1
  76. package/lib/webrtc/src/media/track.d.ts +29 -1
  77. package/lib/webrtc/src/media/track.js +164 -5
  78. package/lib/webrtc/src/media/track.js.map +1 -1
  79. package/lib/webrtc/src/nonstandard/index.d.ts +0 -1
  80. package/lib/webrtc/src/nonstandard/index.js +0 -1
  81. package/lib/webrtc/src/nonstandard/index.js.map +1 -1
  82. package/lib/webrtc/src/nonstandard/recorder/writer/webm.js +2 -1
  83. package/lib/webrtc/src/nonstandard/recorder/writer/webm.js.map +1 -1
  84. package/lib/webrtc/src/nonstandard/userMedia.d.ts +1 -2
  85. package/lib/webrtc/src/nonstandard/userMedia.js +30 -14
  86. package/lib/webrtc/src/nonstandard/userMedia.js.map +1 -1
  87. package/lib/webrtc/src/peerConnection.d.ts +127 -5
  88. package/lib/webrtc/src/peerConnection.js +629 -143
  89. package/lib/webrtc/src/peerConnection.js.map +1 -1
  90. package/lib/webrtc/src/polyfill/api.d.ts +14 -0
  91. package/lib/webrtc/src/polyfill/api.js +23 -0
  92. package/lib/webrtc/src/polyfill/api.js.map +1 -0
  93. package/lib/webrtc/src/polyfill/browserIdentity.d.ts +11 -0
  94. package/lib/webrtc/src/polyfill/browserIdentity.js +59 -0
  95. package/lib/webrtc/src/polyfill/browserIdentity.js.map +1 -0
  96. package/lib/webrtc/src/polyfill/dom.d.ts +11 -0
  97. package/lib/webrtc/src/polyfill/dom.js +18 -0
  98. package/lib/webrtc/src/polyfill/dom.js.map +1 -0
  99. package/lib/webrtc/src/polyfill/existingMediaDevices.d.ts +2 -0
  100. package/lib/webrtc/src/polyfill/existingMediaDevices.js +22 -0
  101. package/lib/webrtc/src/polyfill/existingMediaDevices.js.map +1 -0
  102. package/lib/webrtc/src/polyfill/globals.d.ts +28 -0
  103. package/lib/webrtc/src/polyfill/globals.js +3 -0
  104. package/lib/webrtc/src/polyfill/globals.js.map +1 -0
  105. package/lib/webrtc/src/polyfill/index.d.ts +2 -0
  106. package/lib/webrtc/src/polyfill/index.js +19 -0
  107. package/lib/webrtc/src/polyfill/index.js.map +1 -0
  108. package/lib/webrtc/src/polyfill/install.d.ts +13 -0
  109. package/lib/webrtc/src/polyfill/install.js +266 -0
  110. package/lib/webrtc/src/polyfill/install.js.map +1 -0
  111. package/lib/webrtc/src/polyfill/mediaDevices.d.ts +38 -0
  112. package/lib/webrtc/src/polyfill/mediaDevices.js +228 -0
  113. package/lib/webrtc/src/polyfill/mediaDevices.js.map +1 -0
  114. package/lib/webrtc/src/polyfill/mediaRegister.d.ts +51 -0
  115. package/lib/webrtc/src/polyfill/mediaRegister.js +14 -0
  116. package/lib/webrtc/src/polyfill/mediaRegister.js.map +1 -0
  117. package/lib/webrtc/src/polyfill/peerConnectionConfig.d.ts +3 -0
  118. package/lib/webrtc/src/polyfill/peerConnectionConfig.js +52 -0
  119. package/lib/webrtc/src/polyfill/peerConnectionConfig.js.map +1 -0
  120. package/lib/webrtc/src/polyfill/registers/callback.d.ts +16 -0
  121. package/lib/webrtc/src/polyfill/registers/callback.js +57 -0
  122. package/lib/webrtc/src/polyfill/registers/callback.js.map +1 -0
  123. package/lib/webrtc/src/polyfill/registers/empty.d.ts +2 -0
  124. package/lib/webrtc/src/polyfill/registers/empty.js +28 -0
  125. package/lib/webrtc/src/polyfill/registers/empty.js.map +1 -0
  126. package/lib/webrtc/src/polyfill/registers/mp4Webm.d.ts +36 -0
  127. package/lib/webrtc/src/polyfill/registers/mp4Webm.js +242 -0
  128. package/lib/webrtc/src/polyfill/registers/mp4Webm.js.map +1 -0
  129. package/lib/webrtc/src/polyfill/registers/rtpRtcp.d.ts +17 -0
  130. package/lib/webrtc/src/polyfill/registers/rtpRtcp.js +143 -0
  131. package/lib/webrtc/src/polyfill/registers/rtpRtcp.js.map +1 -0
  132. package/lib/webrtc/src/polyfill/rtcSessionDescription.d.ts +18 -0
  133. package/lib/webrtc/src/polyfill/rtcSessionDescription.js +36 -0
  134. package/lib/webrtc/src/polyfill/rtcSessionDescription.js.map +1 -0
  135. package/lib/webrtc/src/polyfill/rtpCodec.d.ts +17 -0
  136. package/lib/webrtc/src/polyfill/rtpCodec.js +93 -0
  137. package/lib/webrtc/src/polyfill/rtpCodec.js.map +1 -0
  138. package/lib/webrtc/src/polyfill/selectSettings.d.ts +31 -0
  139. package/lib/webrtc/src/polyfill/selectSettings.js +156 -0
  140. package/lib/webrtc/src/polyfill/selectSettings.js.map +1 -0
  141. package/lib/webrtc/src/polyfill/sourceIo.d.ts +21 -0
  142. package/lib/webrtc/src/polyfill/sourceIo.js +298 -0
  143. package/lib/webrtc/src/polyfill/sourceIo.js.map +1 -0
  144. package/lib/webrtc/src/polyfill/trackStop.d.ts +4 -0
  145. package/lib/webrtc/src/polyfill/trackStop.js +16 -0
  146. package/lib/webrtc/src/polyfill/trackStop.js.map +1 -0
  147. package/lib/webrtc/src/sctpManager.d.ts +2 -1
  148. package/lib/webrtc/src/sctpManager.js +5 -2
  149. package/lib/webrtc/src/sctpManager.js.map +1 -1
  150. package/lib/webrtc/src/sdp.d.ts +1 -1
  151. package/lib/webrtc/src/sdp.js +5 -6
  152. package/lib/webrtc/src/sdp.js.map +1 -1
  153. package/lib/webrtc/src/sdpManager.d.ts +38 -1
  154. package/lib/webrtc/src/sdpManager.js +236 -30
  155. package/lib/webrtc/src/sdpManager.js.map +1 -1
  156. package/lib/webrtc/src/secureTransportManager.d.ts +43 -12
  157. package/lib/webrtc/src/secureTransportManager.js +123 -47
  158. package/lib/webrtc/src/secureTransportManager.js.map +1 -1
  159. package/lib/webrtc/src/transceiverManager.d.ts +91 -6
  160. package/lib/webrtc/src/transceiverManager.js +477 -62
  161. package/lib/webrtc/src/transceiverManager.js.map +1 -1
  162. package/lib/webrtc/src/transport/ice.d.ts +8 -0
  163. package/lib/webrtc/src/transport/ice.js +17 -1
  164. package/lib/webrtc/src/transport/ice.js.map +1 -1
  165. package/lib/webrtc/src/transport/sctp.d.ts +3 -2
  166. package/lib/webrtc/src/transport/sctp.js +8 -2
  167. package/lib/webrtc/src/transport/sctp.js.map +1 -1
  168. package/package.json +14 -3
@@ -3,6 +3,13 @@ import { type Address, Event, type InterfaceAddresses, type TlsConnectionOptions
3
3
  import { Message } from "../stun/message";
4
4
  import { Transaction } from "../stun/transaction";
5
5
  import type { Protocol, TransactionRequestOptions } from "../types/model";
6
+ /** Channel binding state for a single peer transport address. */
7
+ export interface TurnChannel {
8
+ number: number;
9
+ address: Address;
10
+ /** Unix seconds when this channel should be refreshed. */
11
+ refreshAt: number;
12
+ }
6
13
  export declare class StunOverTurnProtocol implements Protocol {
7
14
  turn: TurnProtocol;
8
15
  static type: string;
@@ -20,6 +27,7 @@ export declare class StunOverTurnProtocol implements Protocol {
20
27
  close(): Promise<void>;
21
28
  }
22
29
  export declare class TurnProtocol implements Protocol {
30
+ /** TURN server as configured; the host may be a hostname. */
23
31
  server: Address;
24
32
  username: string;
25
33
  password: string;
@@ -49,12 +57,30 @@ export declare class TurnProtocol implements Protocol {
49
57
  private addrByChannel;
50
58
  /**sec */
51
59
  private channelRefreshTime;
52
- private channelBinding?;
53
- private channelRefreshAt;
60
+ /**
61
+ * Serializes ChannelBind requests so allocation-wide auth state
62
+ * (nonce/realm/integrityKey) is not updated concurrently. Rejections
63
+ * must not poison this tail — see channelBindQueue assignment sites.
64
+ */
65
+ private channelBindQueue;
66
+ /** In-flight ChannelBind per peer transport address (dedupe concurrent). */
67
+ private channelBindingByAddr;
54
68
  private tcpBuffer;
69
+ /** Permission cache keyed by peer IP only (RFC 8656). */
55
70
  private permissionByAddr;
56
- private creatingPermission;
57
- constructor(server: Address, username: string, password: string, lifetime: number, transport: Transport, options?: {
71
+ /**
72
+ * Serializes CreatePermission requests (auth state race avoidance).
73
+ * Rejections must not poison this tail.
74
+ */
75
+ private permissionQueue;
76
+ /** In-flight CreatePermission per peer IP (dedupe concurrent). */
77
+ private creatingPermissionByAddr;
78
+ /** Concrete TURN server address the allocation lives on, once selected. */
79
+ private pinnedServerEndpoint?;
80
+ private serverEndpointSelection?;
81
+ constructor(
82
+ /** TURN server as configured; the host may be a hostname. */
83
+ server: Address, username: string, password: string, lifetime: number, transport: Transport, options?: {
58
84
  /**sec */
59
85
  channelRefreshTime?: number;
60
86
  });
@@ -66,13 +92,47 @@ export declare class TurnProtocol implements Protocol {
66
92
  private createPermission;
67
93
  private refresh;
68
94
  request(request: Message, addr: Address, _integrityKey?: Buffer, retransmissionsOrOptions?: number | TransactionRequestOptions, onRequestSent?: (attempt: number) => void): Promise<[Message, Address]>;
69
- requestWithRetry(request: Message, addr: Address): Promise<[Message, Address]>;
95
+ /**
96
+ * The concrete TURN server address, selected on the first request and
97
+ * pinned for the rest of the allocation, so a later DNS answer can never
98
+ * move REFRESH, CREATE_PERMISSION, CHANNEL_BIND or data to another server.
99
+ */
100
+ get serverEndpoint(): Address | undefined;
101
+ /**
102
+ * Address a request to `addr` is sent to, and the address its response
103
+ * must come from. A stream carries every request to the peer it is
104
+ * connected to, so it always uses the server endpoint.
105
+ */
106
+ private resolveRequestTarget;
107
+ private serverDestination;
108
+ private selectServerEndpoint;
109
+ /**
110
+ * UDP: resolve the configured server in the bound socket's family; a udp4
111
+ * socket cannot send to an IPv6 answer. TCP/TLS: use the peer the socket
112
+ * actually connected to. Looking the hostname up again could return a
113
+ * different A/AAAA record, and responses from the real peer would then be
114
+ * dropped as coming from an unexpected address.
115
+ */
116
+ private resolveServerEndpoint;
117
+ requestWithRetry(request: Message, addr: Address, signal?: AbortSignal): Promise<[Message, Address]>;
70
118
  sendData(data: Buffer, addr: Address): Promise<void>;
119
+ /**
120
+ * Ensure a CreatePermission exists for the peer IP.
121
+ * Peer failures are isolated: a rejection for peer A does not poison peer B.
122
+ */
71
123
  getPermission(addr: Address): Promise<void>;
72
- getChannel(addr: Address): Promise<{
73
- number: number;
74
- address: Address;
75
- }>;
124
+ /**
125
+ * Ensure a ChannelBind exists for the peer transport address.
126
+ * Peer failures are isolated; concurrent same-peer calls share one Promise.
127
+ */
128
+ getChannel(addr: Address): Promise<TurnChannel>;
129
+ /**
130
+ * Create or refresh a channel for addr. Provisional mapping is installed
131
+ * before the request so early ChannelData can be decoded; only a failed
132
+ * *initial* bind rolls the mapping back. Failed channel numbers are never
133
+ * reused.
134
+ */
135
+ private ensureChannel;
76
136
  private channelBind;
77
137
  sendStun(message: Message, addr: Address): Promise<void>;
78
138
  close(): Promise<void>;
@@ -89,18 +149,15 @@ export interface TurnClientOptions {
89
149
  tlsOptions?: TlsConnectionOptions;
90
150
  portRange?: [number, number];
91
151
  interfaceAddresses?: InterfaceAddresses;
152
+ /** Maximum time to wait for TCP/TLS connection establishment, in milliseconds. */
153
+ connectTimeoutMs?: number;
154
+ /**
155
+ * Preferred IP family of the UDP socket, applied only when the server
156
+ * address is a hostname. Ignored for an IP literal server address, whose
157
+ * own family always selects the socket. Defaults to 4.
158
+ */
159
+ udpFamily?: 4 | 6;
92
160
  }
93
- export declare function createTurnClient({ address, username, password }: TurnClientConfig, { lifetime, portRange, interfaceAddresses, ssl, tlsOptions, transport: transportType, }?: TurnClientOptions): Promise<TurnProtocol>;
94
- export declare function createStunOverTurnClient({ address, username, password, }: {
95
- address: Address;
96
- username: string;
97
- password: string;
98
- }, { lifetime, portRange, interfaceAddresses, ssl, tlsOptions, transport: transportType, }?: {
99
- lifetime?: number;
100
- ssl?: boolean;
101
- transport?: "udp" | "tcp" | "tls";
102
- tlsOptions?: TlsConnectionOptions;
103
- portRange?: [number, number];
104
- interfaceAddresses?: InterfaceAddresses;
105
- }): Promise<StunOverTurnProtocol>;
161
+ export declare function createTurnClient({ address, username, password }: TurnClientConfig, { lifetime, portRange, interfaceAddresses, ssl, tlsOptions, transport: transportType, connectTimeoutMs, udpFamily, }?: TurnClientOptions): Promise<TurnProtocol>;
162
+ export declare function createStunOverTurnClient(config: TurnClientConfig, options?: TurnClientOptions): Promise<StunOverTurnProtocol>;
106
163
  export declare function makeIntegrityKey(username: string, realm: string, password: string): Buffer<ArrayBufferLike>;
@@ -4,6 +4,7 @@ exports.TurnProtocol = exports.StunOverTurnProtocol = void 0;
4
4
  exports.createTurnClient = createTurnClient;
5
5
  exports.createStunOverTurnClient = createStunOverTurnClient;
6
6
  exports.makeIntegrityKey = makeIntegrityKey;
7
+ const node_net_1 = require("node:net");
7
8
  const promises_1 = require("timers/promises");
8
9
  const auth_1 = require("../../../ice-server/src/turn/auth");
9
10
  const exceptions_1 = require("../exceptions");
@@ -20,6 +21,14 @@ const UDP_TRANSPORT = 0x11000000;
20
21
  function isStreamTransport(transport) {
21
22
  return transport.type === "tcp" || transport.type === "tls";
22
23
  }
24
+ /** Permission is peer-IP scoped (RFC 8656). */
25
+ function permissionKey(addr) {
26
+ return addr[0];
27
+ }
28
+ /** ChannelBind is peer transport-address scoped (IP + port). */
29
+ function channelKey(addr) {
30
+ return JSON.stringify(addr);
31
+ }
23
32
  class StunOverTurnProtocol {
24
33
  constructor(turn) {
25
34
  Object.defineProperty(this, "turn", {
@@ -144,7 +153,9 @@ Object.defineProperty(StunOverTurnProtocol, "type", {
144
153
  value: "turn"
145
154
  });
146
155
  class TurnProtocol {
147
- constructor(server, username, password, lifetime, transport, options = {}) {
156
+ constructor(
157
+ /** TURN server as configured; the host may be a hostname. */
158
+ server, username, password, lifetime, transport, options = {}) {
148
159
  Object.defineProperty(this, "server", {
149
160
  enumerable: true,
150
161
  configurable: true,
@@ -278,17 +289,23 @@ class TurnProtocol {
278
289
  writable: true,
279
290
  value: void 0
280
291
  });
281
- Object.defineProperty(this, "channelBinding", {
292
+ /**
293
+ * Serializes ChannelBind requests so allocation-wide auth state
294
+ * (nonce/realm/integrityKey) is not updated concurrently. Rejections
295
+ * must not poison this tail — see channelBindQueue assignment sites.
296
+ */
297
+ Object.defineProperty(this, "channelBindQueue", {
282
298
  enumerable: true,
283
299
  configurable: true,
284
300
  writable: true,
285
- value: void 0
301
+ value: Promise.resolve()
286
302
  });
287
- Object.defineProperty(this, "channelRefreshAt", {
303
+ /** In-flight ChannelBind per peer transport address (dedupe concurrent). */
304
+ Object.defineProperty(this, "channelBindingByAddr", {
288
305
  enumerable: true,
289
306
  configurable: true,
290
307
  writable: true,
291
- value: 0
308
+ value: new Map()
292
309
  });
293
310
  Object.defineProperty(this, "tcpBuffer", {
294
311
  enumerable: true,
@@ -296,41 +313,80 @@ class TurnProtocol {
296
313
  writable: true,
297
314
  value: Buffer.alloc(0)
298
315
  });
316
+ /** Permission cache keyed by peer IP only (RFC 8656). */
299
317
  Object.defineProperty(this, "permissionByAddr", {
300
318
  enumerable: true,
301
319
  configurable: true,
302
320
  writable: true,
303
321
  value: {}
304
322
  });
305
- Object.defineProperty(this, "creatingPermission", {
323
+ /**
324
+ * Serializes CreatePermission requests (auth state race avoidance).
325
+ * Rejections must not poison this tail.
326
+ */
327
+ Object.defineProperty(this, "permissionQueue", {
306
328
  enumerable: true,
307
329
  configurable: true,
308
330
  writable: true,
309
331
  value: Promise.resolve()
310
332
  });
333
+ /** In-flight CreatePermission per peer IP (dedupe concurrent). */
334
+ Object.defineProperty(this, "creatingPermissionByAddr", {
335
+ enumerable: true,
336
+ configurable: true,
337
+ writable: true,
338
+ value: new Map()
339
+ });
340
+ /** Concrete TURN server address the allocation lives on, once selected. */
341
+ Object.defineProperty(this, "pinnedServerEndpoint", {
342
+ enumerable: true,
343
+ configurable: true,
344
+ writable: true,
345
+ value: void 0
346
+ });
347
+ Object.defineProperty(this, "serverEndpointSelection", {
348
+ enumerable: true,
349
+ configurable: true,
350
+ writable: true,
351
+ value: void 0
352
+ });
311
353
  Object.defineProperty(this, "refresh", {
312
354
  enumerable: true,
313
355
  configurable: true,
314
356
  writable: true,
315
357
  value: (exp) => {
316
358
  this.refreshHandle = (0, helper_1.cancelable)(async (_, __, onCancel) => {
317
- let run = true;
359
+ const abort = new AbortController();
318
360
  onCancel.once(() => {
319
- run = false;
361
+ abort.abort();
320
362
  });
321
- while (run) {
363
+ while (!abort.signal.aborted) {
322
364
  // refresh before expire
323
365
  const delay = (5 / 6) * exp * 1000;
324
366
  log("refresh delay", delay, { exp });
325
- await (0, promises_1.setTimeout)(delay);
367
+ try {
368
+ await (0, promises_1.setTimeout)(delay, undefined, { signal: abort.signal });
369
+ }
370
+ catch (error) {
371
+ if (abort.signal.aborted) {
372
+ return;
373
+ }
374
+ throw error;
375
+ }
376
+ if (abort.signal.aborted) {
377
+ return;
378
+ }
326
379
  const request = new message_1.Message(const_1.methods.REFRESH, const_1.classes.REQUEST);
327
380
  request.setAttribute("LIFETIME", exp);
328
381
  try {
329
- const [message] = await this.requestWithRetry(request, this.server);
382
+ const [message] = await this.requestWithRetry(request, this.server, abort.signal);
330
383
  exp = message.getAttributeValue("LIFETIME");
331
384
  log("refresh", { exp });
332
385
  }
333
386
  catch (error) {
387
+ if (abort.signal.aborted) {
388
+ return;
389
+ }
334
390
  log("refresh error", error);
335
391
  }
336
392
  }
@@ -432,10 +488,8 @@ class TurnProtocol {
432
488
  .setAttribute("USERNAME", this.username)
433
489
  .setAttribute("REALM", this.realm)
434
490
  .setAttribute("NONCE", this.nonce);
435
- await this.request(request, this.server).catch((e) => {
436
- request;
437
- throw e;
438
- });
491
+ // Use requestWithRetry for stale-nonce (438) consistency with ChannelBind.
492
+ await this.requestWithRetry(request, this.server);
439
493
  }
440
494
  async request(request, addr, _integrityKey, retransmissionsOrOptions, onRequestSent) {
441
495
  if (this.transactions[request.transactionIdHex]) {
@@ -452,7 +506,7 @@ class TurnProtocol {
452
506
  // TURN server allocation/refresh uses default STUN retry policy unless
453
507
  // callers pass explicit options. Peer consent goes through StunOverTurnProtocol.
454
508
  // Prefer the TURN session integrity key for response verification.
455
- const resolvedAddr = await (0, transaction_1.resolveRequestAddress)(addr);
509
+ const resolvedAddr = await this.resolveRequestTarget(addr);
456
510
  const options = (0, transaction_1.buildTransactionOptions)(this.integrityKey, retransmissionsOrOptions, onRequestSent);
457
511
  const transaction = new transaction_1.Transaction(request, resolvedAddr, this, options);
458
512
  this.transactions[request.transactionIdHex] = transaction;
@@ -466,18 +520,74 @@ class TurnProtocol {
466
520
  delete this.transactions[request.transactionIdHex];
467
521
  }
468
522
  }
469
- async requestWithRetry(request, addr) {
523
+ /**
524
+ * The concrete TURN server address, selected on the first request and
525
+ * pinned for the rest of the allocation, so a later DNS answer can never
526
+ * move REFRESH, CREATE_PERMISSION, CHANNEL_BIND or data to another server.
527
+ */
528
+ get serverEndpoint() {
529
+ return this.pinnedServerEndpoint;
530
+ }
531
+ /**
532
+ * Address a request to `addr` is sent to, and the address its response
533
+ * must come from. A stream carries every request to the peer it is
534
+ * connected to, so it always uses the server endpoint.
535
+ */
536
+ async resolveRequestTarget(addr) {
537
+ if (isStreamTransport(this.transport) || (0, transaction_1.addressEquals)(addr, this.server)) {
538
+ return this.selectServerEndpoint();
539
+ }
540
+ return (0, transaction_1.resolveRequestAddress)(addr, this.transport.addressFamily ?? 0);
541
+ }
542
+ async serverDestination() {
543
+ return this.pinnedServerEndpoint ?? this.selectServerEndpoint();
544
+ }
545
+ selectServerEndpoint() {
546
+ this.serverEndpointSelection ?? (this.serverEndpointSelection = this.resolveServerEndpoint().then((endpoint) => {
547
+ this.pinnedServerEndpoint = endpoint;
548
+ return endpoint;
549
+ }, (error) => {
550
+ // Let a later request try again, e.g. after a transient DNS failure.
551
+ this.serverEndpointSelection = undefined;
552
+ throw error;
553
+ }));
554
+ return this.serverEndpointSelection;
555
+ }
556
+ /**
557
+ * UDP: resolve the configured server in the bound socket's family; a udp4
558
+ * socket cannot send to an IPv6 answer. TCP/TLS: use the peer the socket
559
+ * actually connected to. Looking the hostname up again could return a
560
+ * different A/AAAA record, and responses from the real peer would then be
561
+ * dropped as coming from an unexpected address.
562
+ */
563
+ async resolveServerEndpoint() {
564
+ const transport = this.transport;
565
+ if (!isStreamTransport(transport)) {
566
+ return (0, transaction_1.resolveRequestAddress)(this.server, transport.addressFamily ?? 0);
567
+ }
568
+ if (transport.remoteAddress) {
569
+ return transport.remoteAddress;
570
+ }
571
+ if (transport instanceof common_1.TcpTransport ||
572
+ transport instanceof common_1.TlsTransport) {
573
+ throw new Error(`${transport.type} transport has no remote address`);
574
+ }
575
+ // A custom stream transport that does not report its peer: fall back to
576
+ // a lookup with no family preference.
577
+ return (0, transaction_1.resolveRequestAddress)(this.server);
578
+ }
579
+ async requestWithRetry(request, addr, signal) {
470
580
  let message, address;
471
581
  try {
472
- [message, address] = await this.request(request, addr);
582
+ [message, address] = await this.request(request, addr, undefined, {
583
+ signal,
584
+ });
473
585
  }
474
586
  catch (error) {
475
587
  if (error instanceof exceptions_1.TransactionFailed == false) {
476
588
  log("requestWithRetry error", error);
477
589
  throw error;
478
590
  }
479
- // resolve dns address
480
- this.server = error.addr;
481
591
  const [errorCode] = error.response.getAttributeValue("ERROR-CODE");
482
592
  const nonce = error.response.getAttributeValue("NONCE");
483
593
  const realm = error.response.getAttributeValue("REALM");
@@ -490,7 +600,7 @@ class TurnProtocol {
490
600
  }
491
601
  this.integrityKey = makeIntegrityKey(this.username, this.realm, this.password);
492
602
  request.transactionId = (0, helper_1.randomTransactionId)();
493
- [message, address] = await this.request(request, this.server);
603
+ [message, address] = await this.request(request, this.server, undefined, { signal });
494
604
  }
495
605
  else {
496
606
  throw error;
@@ -499,63 +609,134 @@ class TurnProtocol {
499
609
  return [message, address];
500
610
  }
501
611
  async sendData(data, addr) {
502
- const channel = await this.getChannel(addr).catch((e) => {
503
- return new Error("channelBind error");
504
- });
505
- if (channel instanceof Error) {
612
+ let channel;
613
+ try {
614
+ channel = await this.getChannel(addr);
615
+ }
616
+ catch (e) {
617
+ // Keep original ChannelBind error for diagnostics (403/438/timeout).
618
+ log("channelBind error; falling back to Send Indication", e);
619
+ }
620
+ if (!channel) {
506
621
  await this.getPermission(addr);
507
622
  const indicate = new message_1.Message(const_1.methods.SEND, const_1.classes.INDICATION)
508
623
  .setAttribute("DATA", data)
509
624
  .setAttribute("XOR-PEER-ADDRESS", addr);
510
- await this.sendStun(indicate, this.server);
625
+ await this.sendStun(indicate, await this.serverDestination());
511
626
  return;
512
627
  }
513
- await this.send((0, frame_1.encodeChannelData)(channel.number, data), this.server);
628
+ await this.send((0, frame_1.encodeChannelData)(channel.number, data), await this.serverDestination());
514
629
  }
630
+ /**
631
+ * Ensure a CreatePermission exists for the peer IP.
632
+ * Peer failures are isolated: a rejection for peer A does not poison peer B.
633
+ */
515
634
  async getPermission(addr) {
516
- await this.creatingPermission;
517
- const permitted = this.permissionByAddr[addr.join(":")];
518
- if (!permitted) {
519
- this.creatingPermission = this.createPermission(addr);
520
- this.permissionByAddr[addr.join(":")] = true;
521
- await this.creatingPermission.catch((e) => {
522
- log("createPermission error", e);
523
- throw e;
524
- });
635
+ const key = permissionKey(addr);
636
+ if (this.permissionByAddr[key]) {
637
+ return;
638
+ }
639
+ const existing = this.creatingPermissionByAddr.get(key);
640
+ if (existing) {
641
+ return existing;
642
+ }
643
+ const operation = this.permissionQueue.then(async () => {
644
+ // Another caller may have succeeded while we waited on the queue.
645
+ if (this.permissionByAddr[key]) {
646
+ return;
647
+ }
648
+ await this.createPermission(addr);
649
+ // Cache only after successful CreatePermission.
650
+ this.permissionByAddr[key] = true;
651
+ });
652
+ // Do not let rejection poison subsequent peers on the shared queue.
653
+ this.permissionQueue = operation.then(() => undefined, () => undefined);
654
+ this.creatingPermissionByAddr.set(key, operation);
655
+ try {
656
+ await operation;
657
+ }
658
+ catch (error) {
659
+ log("createPermission error", error);
660
+ throw error;
661
+ }
662
+ finally {
663
+ if (this.creatingPermissionByAddr.get(key) === operation) {
664
+ this.creatingPermissionByAddr.delete(key);
665
+ }
525
666
  }
526
667
  }
668
+ /**
669
+ * Ensure a ChannelBind exists for the peer transport address.
670
+ * Peer failures are isolated; concurrent same-peer calls share one Promise.
671
+ */
527
672
  async getChannel(addr) {
528
- if (this.channelBinding) {
529
- await this.channelBinding;
673
+ const key = channelKey(addr);
674
+ const existing = this.channelBindingByAddr.get(key);
675
+ if (existing) {
676
+ return existing;
677
+ }
678
+ // Fast path: bound and not due for refresh.
679
+ const cached = this.channelByAddr[key];
680
+ const now = (0, common_1.int)(Date.now() / 1000);
681
+ if (cached && cached.refreshAt > now) {
682
+ return cached;
683
+ }
684
+ const operation = this.channelBindQueue.then(() => this.ensureChannel(addr));
685
+ // Do not let rejection poison subsequent peers on the shared queue.
686
+ this.channelBindQueue = operation.then(() => undefined, () => undefined);
687
+ this.channelBindingByAddr.set(key, operation);
688
+ try {
689
+ return await operation;
690
+ }
691
+ catch (error) {
692
+ log("channelBind error", error);
693
+ throw error;
694
+ }
695
+ finally {
696
+ if (this.channelBindingByAddr.get(key) === operation) {
697
+ this.channelBindingByAddr.delete(key);
698
+ }
699
+ }
700
+ }
701
+ /**
702
+ * Create or refresh a channel for addr. Provisional mapping is installed
703
+ * before the request so early ChannelData can be decoded; only a failed
704
+ * *initial* bind rolls the mapping back. Failed channel numbers are never
705
+ * reused.
706
+ */
707
+ async ensureChannel(addr) {
708
+ const key = channelKey(addr);
709
+ const now = (0, common_1.int)(Date.now() / 1000);
710
+ let channel = this.channelByAddr[key];
711
+ if (channel && channel.refreshAt > now) {
712
+ return channel;
530
713
  }
531
- let channel = this.channelByAddr[addr.join(":")];
714
+ const isNew = !channel;
532
715
  if (!channel) {
533
- this.channelByAddr[addr.join(":")] = {
716
+ channel = {
534
717
  number: this.channelNumber++,
535
718
  address: addr,
719
+ refreshAt: 0,
536
720
  };
537
- channel = this.channelByAddr[addr.join(":")];
721
+ // Provisional mapping for ChannelData that may arrive before success.
722
+ this.channelByAddr[key] = channel;
538
723
  this.addrByChannel[channel.number] = addr;
539
- this.channelBinding = this.channelBind(channel.number, addr);
540
- await this.channelBinding.catch((e) => {
541
- log("channelBind error", e);
542
- throw e;
543
- });
544
- this.channelRefreshAt = (0, common_1.int)(Date.now() / 1000) + this.channelRefreshTime;
545
- this.channelBinding = undefined;
546
- log("channelBind", channel);
547
- }
548
- else if (this.channelRefreshAt < (0, common_1.int)(Date.now() / 1000)) {
549
- this.channelBinding = this.channelBind(channel.number, addr);
550
- this.channelRefreshAt = (0, common_1.int)(Date.now() / 1000) + this.channelRefreshTime;
551
- await this.channelBinding.catch((e) => {
552
- log("channelBind error", e);
553
- throw e;
554
- });
555
- this.channelBinding = undefined;
556
- log("channelBind refresh", channel);
557
724
  }
558
- return channel;
725
+ try {
726
+ await this.channelBind(channel.number, addr);
727
+ channel.refreshAt = (0, common_1.int)(Date.now() / 1000) + this.channelRefreshTime;
728
+ log(isNew ? "channelBind" : "channelBind refresh", channel);
729
+ return channel;
730
+ }
731
+ catch (error) {
732
+ if (isNew) {
733
+ // Roll back provisional mapping only for a failed initial bind.
734
+ delete this.channelByAddr[key];
735
+ delete this.addrByChannel[channel.number];
736
+ // Do not reuse the channel number (no channelNumber--).
737
+ }
738
+ throw error;
739
+ }
559
740
  }
560
741
  async channelBind(channelNumber, addr) {
561
742
  const request = new message_1.Message(const_1.methods.CHANNEL_BIND, const_1.classes.REQUEST);
@@ -582,31 +763,54 @@ Object.defineProperty(TurnProtocol, "type", {
582
763
  writable: true,
583
764
  value: "turn"
584
765
  });
585
- async function createTurnClient({ address, username, password }, { lifetime, portRange, interfaceAddresses, ssl, tlsOptions, transport: transportType, } = {}) {
766
+ /**
767
+ * Select the UDP socket type for a TURN server.
768
+ * Precedence: IP literal family > udpFamily > 4.
769
+ * An IP literal fixes the destination family, so a conflicting udpFamily is
770
+ * ignored (and logged) rather than making the server unreachable.
771
+ */
772
+ function selectTurnUdpSocketType(address, udpFamily) {
773
+ const literalFamily = (0, node_net_1.isIP)(address[0]);
774
+ if (literalFamily === 4 || literalFamily === 6) {
775
+ if (udpFamily && udpFamily !== literalFamily) {
776
+ log("udpFamily ignored for IP literal TURN server", {
777
+ address,
778
+ udpFamily,
779
+ literalFamily,
780
+ });
781
+ }
782
+ return literalFamily === 6 ? "udp6" : "udp4";
783
+ }
784
+ return (udpFamily ?? 4) === 6 ? "udp6" : "udp4";
785
+ }
786
+ async function createTurnClient({ address, username, password }, { lifetime, portRange, interfaceAddresses, ssl, tlsOptions, transport: transportType, connectTimeoutMs, udpFamily, } = {}) {
586
787
  lifetime ?? (lifetime = DEFAULT_ALLOCATION_LIFETIME);
587
788
  transportType ?? (transportType = ssl ? "tls" : "udp");
588
789
  const transport = transportType === "udp"
589
- ? await common_1.UdpTransport.init("udp4", { portRange, interfaceAddresses })
790
+ ? await common_1.UdpTransport.init(selectTurnUdpSocketType(address, udpFamily), {
791
+ portRange,
792
+ interfaceAddresses,
793
+ })
590
794
  : transportType === "tcp"
591
- ? await common_1.TcpTransport.init(address)
592
- : await common_1.TlsTransport.init(address, tlsOptions);
795
+ ? await common_1.TcpTransport.init(address, { connectTimeoutMs })
796
+ : await common_1.TlsTransport.init(address, tlsOptions, { connectTimeoutMs });
593
797
  const turn = new TurnProtocol(address, username, password, lifetime, transport);
594
- await turn.connectionMade();
798
+ try {
799
+ await turn.connectionMade();
800
+ }
801
+ catch (error) {
802
+ try {
803
+ await turn.close();
804
+ }
805
+ catch (closeError) {
806
+ log("failed to close TURN client after allocation failure", closeError);
807
+ }
808
+ throw error;
809
+ }
595
810
  return turn;
596
811
  }
597
- async function createStunOverTurnClient({ address, username, password, }, { lifetime, portRange, interfaceAddresses, ssl, tlsOptions, transport: transportType, } = {}) {
598
- const turn = await createTurnClient({
599
- address,
600
- username,
601
- password,
602
- }, {
603
- lifetime,
604
- portRange,
605
- interfaceAddresses,
606
- ssl,
607
- tlsOptions,
608
- transport: transportType,
609
- });
812
+ async function createStunOverTurnClient(config, options = {}) {
813
+ const turn = await createTurnClient(config, options);
610
814
  const turnTransport = new StunOverTurnProtocol(turn);
611
815
  return turnTransport;
612
816
  }