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.
- package/README.md +116 -113
- package/lib/common/src/event.js +2 -1
- package/lib/common/src/event.js.map +1 -1
- package/lib/common/src/transport.d.ts +22 -2
- package/lib/common/src/transport.js +84 -26
- package/lib/common/src/transport.js.map +1 -1
- package/lib/ice/src/exceptions.d.ts +9 -0
- package/lib/ice/src/exceptions.js +23 -1
- package/lib/ice/src/exceptions.js.map +1 -1
- package/lib/ice/src/ice.d.ts +40 -0
- package/lib/ice/src/ice.js +194 -28
- package/lib/ice/src/ice.js.map +1 -1
- package/lib/ice/src/iceBase.d.ts +26 -0
- package/lib/ice/src/iceBase.js.map +1 -1
- package/lib/ice/src/stun/const.d.ts +7 -0
- package/lib/ice/src/stun/const.js +8 -0
- package/lib/ice/src/stun/const.js.map +1 -1
- package/lib/ice/src/stun/protocol.js +1 -4
- package/lib/ice/src/stun/protocol.js.map +1 -1
- package/lib/ice/src/stun/tcpProtocol.d.ts +7 -1
- package/lib/ice/src/stun/tcpProtocol.js +65 -14
- package/lib/ice/src/stun/tcpProtocol.js.map +1 -1
- package/lib/ice/src/stun/transaction.d.ts +12 -1
- package/lib/ice/src/stun/transaction.js +37 -4
- package/lib/ice/src/stun/transaction.js.map +1 -1
- package/lib/ice/src/turn/protocol.d.ts +79 -22
- package/lib/ice/src/turn/protocol.js +282 -78
- package/lib/ice/src/turn/protocol.js.map +1 -1
- package/lib/ice/src/types/model.d.ts +8 -0
- package/lib/ice/src/types/model.js.map +1 -1
- package/lib/index.mjs +5646 -3451
- package/lib/nonstandard/index.mjs +287 -1004
- package/lib/polyfill/dom.mjs +23794 -0
- package/lib/polyfill/index.mjs +23794 -0
- package/lib/rtp/src/extra/container/mp4/container.js +12 -4
- package/lib/rtp/src/extra/container/mp4/container.js.map +1 -1
- package/lib/rtp/src/extra/processor/mp4.js +8 -3
- package/lib/rtp/src/extra/processor/mp4.js.map +1 -1
- package/lib/rtp/src/extra/processor/mp4Callback.js +3 -1
- package/lib/rtp/src/extra/processor/mp4Callback.js.map +1 -1
- package/lib/sctp/src/chunk.d.ts +5 -0
- package/lib/sctp/src/chunk.js +16 -11
- package/lib/sctp/src/chunk.js.map +1 -1
- package/lib/sctp/src/index.d.ts +3 -1
- package/lib/sctp/src/index.js +10 -1
- package/lib/sctp/src/index.js.map +1 -1
- package/lib/sctp/src/sctp.d.ts +12 -3
- package/lib/sctp/src/sctp.js +51 -18
- package/lib/sctp/src/sctp.js.map +1 -1
- package/lib/webrtc/src/const.js +2 -0
- package/lib/webrtc/src/const.js.map +1 -1
- package/lib/webrtc/src/errors.d.ts +9 -1
- package/lib/webrtc/src/errors.js +19 -0
- package/lib/webrtc/src/errors.js.map +1 -1
- package/lib/webrtc/src/media/codec.d.ts +5 -0
- package/lib/webrtc/src/media/codec.js +21 -0
- package/lib/webrtc/src/media/codec.js.map +1 -1
- package/lib/webrtc/src/media/codecCompatibility.d.ts +23 -0
- package/lib/webrtc/src/media/codecCompatibility.js +138 -0
- package/lib/webrtc/src/media/codecCompatibility.js.map +1 -0
- package/lib/webrtc/src/media/index.d.ts +1 -0
- package/lib/webrtc/src/media/index.js +1 -0
- package/lib/webrtc/src/media/index.js.map +1 -1
- package/lib/webrtc/src/media/router.d.ts +2 -0
- package/lib/webrtc/src/media/router.js +23 -3
- package/lib/webrtc/src/media/router.js.map +1 -1
- package/lib/webrtc/src/media/rtpReceiver.d.ts +12 -0
- package/lib/webrtc/src/media/rtpReceiver.js +35 -0
- package/lib/webrtc/src/media/rtpReceiver.js.map +1 -1
- package/lib/webrtc/src/media/rtpSender.d.ts +65 -4
- package/lib/webrtc/src/media/rtpSender.js +264 -35
- package/lib/webrtc/src/media/rtpSender.js.map +1 -1
- package/lib/webrtc/src/media/rtpTransceiver.d.ts +42 -0
- package/lib/webrtc/src/media/rtpTransceiver.js +157 -4
- package/lib/webrtc/src/media/rtpTransceiver.js.map +1 -1
- package/lib/webrtc/src/media/track.d.ts +29 -1
- package/lib/webrtc/src/media/track.js +164 -5
- package/lib/webrtc/src/media/track.js.map +1 -1
- package/lib/webrtc/src/nonstandard/index.d.ts +0 -1
- package/lib/webrtc/src/nonstandard/index.js +0 -1
- package/lib/webrtc/src/nonstandard/index.js.map +1 -1
- package/lib/webrtc/src/nonstandard/recorder/writer/webm.js +2 -1
- package/lib/webrtc/src/nonstandard/recorder/writer/webm.js.map +1 -1
- package/lib/webrtc/src/nonstandard/userMedia.d.ts +1 -2
- package/lib/webrtc/src/nonstandard/userMedia.js +30 -14
- package/lib/webrtc/src/nonstandard/userMedia.js.map +1 -1
- package/lib/webrtc/src/peerConnection.d.ts +127 -5
- package/lib/webrtc/src/peerConnection.js +629 -143
- package/lib/webrtc/src/peerConnection.js.map +1 -1
- package/lib/webrtc/src/polyfill/api.d.ts +14 -0
- package/lib/webrtc/src/polyfill/api.js +23 -0
- package/lib/webrtc/src/polyfill/api.js.map +1 -0
- package/lib/webrtc/src/polyfill/browserIdentity.d.ts +11 -0
- package/lib/webrtc/src/polyfill/browserIdentity.js +59 -0
- package/lib/webrtc/src/polyfill/browserIdentity.js.map +1 -0
- package/lib/webrtc/src/polyfill/dom.d.ts +11 -0
- package/lib/webrtc/src/polyfill/dom.js +18 -0
- package/lib/webrtc/src/polyfill/dom.js.map +1 -0
- package/lib/webrtc/src/polyfill/existingMediaDevices.d.ts +2 -0
- package/lib/webrtc/src/polyfill/existingMediaDevices.js +22 -0
- package/lib/webrtc/src/polyfill/existingMediaDevices.js.map +1 -0
- package/lib/webrtc/src/polyfill/globals.d.ts +28 -0
- package/lib/webrtc/src/polyfill/globals.js +3 -0
- package/lib/webrtc/src/polyfill/globals.js.map +1 -0
- package/lib/webrtc/src/polyfill/index.d.ts +2 -0
- package/lib/webrtc/src/polyfill/index.js +19 -0
- package/lib/webrtc/src/polyfill/index.js.map +1 -0
- package/lib/webrtc/src/polyfill/install.d.ts +13 -0
- package/lib/webrtc/src/polyfill/install.js +266 -0
- package/lib/webrtc/src/polyfill/install.js.map +1 -0
- package/lib/webrtc/src/polyfill/mediaDevices.d.ts +38 -0
- package/lib/webrtc/src/polyfill/mediaDevices.js +228 -0
- package/lib/webrtc/src/polyfill/mediaDevices.js.map +1 -0
- package/lib/webrtc/src/polyfill/mediaRegister.d.ts +51 -0
- package/lib/webrtc/src/polyfill/mediaRegister.js +14 -0
- package/lib/webrtc/src/polyfill/mediaRegister.js.map +1 -0
- package/lib/webrtc/src/polyfill/peerConnectionConfig.d.ts +3 -0
- package/lib/webrtc/src/polyfill/peerConnectionConfig.js +52 -0
- package/lib/webrtc/src/polyfill/peerConnectionConfig.js.map +1 -0
- package/lib/webrtc/src/polyfill/registers/callback.d.ts +16 -0
- package/lib/webrtc/src/polyfill/registers/callback.js +57 -0
- package/lib/webrtc/src/polyfill/registers/callback.js.map +1 -0
- package/lib/webrtc/src/polyfill/registers/empty.d.ts +2 -0
- package/lib/webrtc/src/polyfill/registers/empty.js +28 -0
- package/lib/webrtc/src/polyfill/registers/empty.js.map +1 -0
- package/lib/webrtc/src/polyfill/registers/mp4Webm.d.ts +36 -0
- package/lib/webrtc/src/polyfill/registers/mp4Webm.js +242 -0
- package/lib/webrtc/src/polyfill/registers/mp4Webm.js.map +1 -0
- package/lib/webrtc/src/polyfill/registers/rtpRtcp.d.ts +17 -0
- package/lib/webrtc/src/polyfill/registers/rtpRtcp.js +143 -0
- package/lib/webrtc/src/polyfill/registers/rtpRtcp.js.map +1 -0
- package/lib/webrtc/src/polyfill/rtcSessionDescription.d.ts +18 -0
- package/lib/webrtc/src/polyfill/rtcSessionDescription.js +36 -0
- package/lib/webrtc/src/polyfill/rtcSessionDescription.js.map +1 -0
- package/lib/webrtc/src/polyfill/rtpCodec.d.ts +17 -0
- package/lib/webrtc/src/polyfill/rtpCodec.js +93 -0
- package/lib/webrtc/src/polyfill/rtpCodec.js.map +1 -0
- package/lib/webrtc/src/polyfill/selectSettings.d.ts +31 -0
- package/lib/webrtc/src/polyfill/selectSettings.js +156 -0
- package/lib/webrtc/src/polyfill/selectSettings.js.map +1 -0
- package/lib/webrtc/src/polyfill/sourceIo.d.ts +21 -0
- package/lib/webrtc/src/polyfill/sourceIo.js +298 -0
- package/lib/webrtc/src/polyfill/sourceIo.js.map +1 -0
- package/lib/webrtc/src/polyfill/trackStop.d.ts +4 -0
- package/lib/webrtc/src/polyfill/trackStop.js +16 -0
- package/lib/webrtc/src/polyfill/trackStop.js.map +1 -0
- package/lib/webrtc/src/sctpManager.d.ts +2 -1
- package/lib/webrtc/src/sctpManager.js +5 -2
- package/lib/webrtc/src/sctpManager.js.map +1 -1
- package/lib/webrtc/src/sdp.d.ts +1 -1
- package/lib/webrtc/src/sdp.js +5 -6
- package/lib/webrtc/src/sdp.js.map +1 -1
- package/lib/webrtc/src/sdpManager.d.ts +38 -1
- package/lib/webrtc/src/sdpManager.js +236 -30
- package/lib/webrtc/src/sdpManager.js.map +1 -1
- package/lib/webrtc/src/secureTransportManager.d.ts +43 -12
- package/lib/webrtc/src/secureTransportManager.js +123 -47
- package/lib/webrtc/src/secureTransportManager.js.map +1 -1
- package/lib/webrtc/src/transceiverManager.d.ts +91 -6
- package/lib/webrtc/src/transceiverManager.js +477 -62
- package/lib/webrtc/src/transceiverManager.js.map +1 -1
- package/lib/webrtc/src/transport/ice.d.ts +8 -0
- package/lib/webrtc/src/transport/ice.js +17 -1
- package/lib/webrtc/src/transport/ice.js.map +1 -1
- package/lib/webrtc/src/transport/sctp.d.ts +3 -2
- package/lib/webrtc/src/transport/sctp.js +8 -2
- package/lib/webrtc/src/transport/sctp.js.map +1 -1
- 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
|
-
|
|
53
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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:
|
|
301
|
+
value: Promise.resolve()
|
|
286
302
|
});
|
|
287
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
359
|
+
const abort = new AbortController();
|
|
318
360
|
onCancel.once(() => {
|
|
319
|
-
|
|
361
|
+
abort.abort();
|
|
320
362
|
});
|
|
321
|
-
while (
|
|
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
|
-
|
|
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
|
-
|
|
436
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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
|
-
|
|
529
|
-
|
|
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
|
-
|
|
714
|
+
const isNew = !channel;
|
|
532
715
|
if (!channel) {
|
|
533
|
-
|
|
716
|
+
channel = {
|
|
534
717
|
number: this.channelNumber++,
|
|
535
718
|
address: addr,
|
|
719
|
+
refreshAt: 0,
|
|
536
720
|
};
|
|
537
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
}
|