werift 0.24.1 → 0.24.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/lib/dtls/src/context/transport.d.ts +2 -2
  2. package/lib/dtls/src/context/transport.js +2 -2
  3. package/lib/dtls/src/context/transport.js.map +1 -1
  4. package/lib/dtls/src/socket.d.ts +3 -2
  5. package/lib/dtls/src/socket.js +2 -2
  6. package/lib/dtls/src/socket.js.map +1 -1
  7. package/lib/ice/src/ice.d.ts +20 -0
  8. package/lib/ice/src/ice.js +259 -57
  9. package/lib/ice/src/ice.js.map +1 -1
  10. package/lib/ice/src/iceBase.d.ts +22 -1
  11. package/lib/ice/src/iceBase.js +30 -1
  12. package/lib/ice/src/iceBase.js.map +1 -1
  13. package/lib/ice/src/stun/protocol.d.ts +2 -2
  14. package/lib/ice/src/stun/protocol.js +17 -3
  15. package/lib/ice/src/stun/protocol.js.map +1 -1
  16. package/lib/ice/src/stun/tcpProtocol.d.ts +2 -2
  17. package/lib/ice/src/stun/tcpProtocol.js +13 -3
  18. package/lib/ice/src/stun/tcpProtocol.js.map +1 -1
  19. package/lib/ice/src/stun/transaction.d.ts +41 -4
  20. package/lib/ice/src/stun/transaction.js +193 -24
  21. package/lib/ice/src/stun/transaction.js.map +1 -1
  22. package/lib/ice/src/turn/protocol.d.ts +3 -3
  23. package/lib/ice/src/turn/protocol.js +31 -6
  24. package/lib/ice/src/turn/protocol.js.map +1 -1
  25. package/lib/ice/src/types/model.d.ts +27 -1
  26. package/lib/ice/src/types/model.js.map +1 -1
  27. package/lib/ice-server/src/stun/message.js +8 -0
  28. package/lib/ice-server/src/stun/message.js.map +1 -1
  29. package/lib/index.mjs +435 -104
  30. package/lib/webrtc/src/peerConnection.d.ts +2 -2
  31. package/lib/webrtc/src/secureTransportManager.d.ts +1 -1
  32. package/lib/webrtc/src/secureTransportManager.js +12 -0
  33. package/lib/webrtc/src/secureTransportManager.js.map +1 -1
  34. package/lib/webrtc/src/transport/dtls.d.ts +1 -1
  35. package/package.json +1 -1
package/lib/index.mjs CHANGED
@@ -5767,8 +5767,8 @@ var TransportContext = class {
5767
5767
  constructor(socket) {
5768
5768
  this.socket = socket;
5769
5769
  }
5770
- send = (buf) => {
5771
- return this.socket.send(buf);
5770
+ send = (buf, addr) => {
5771
+ return this.socket.send(buf, addr);
5772
5772
  };
5773
5773
  };
5774
5774
 
@@ -6107,12 +6107,12 @@ var DtlsSocket = class {
6107
6107
  return handshakes;
6108
6108
  }
6109
6109
  /**send application data */
6110
- send = async (buf) => {
6110
+ send = async (buf, addr) => {
6111
6111
  const pkt = createPlaintext(this.dtls)(
6112
6112
  [{ type: 23 /* applicationData */, fragment: buf }],
6113
6113
  ++this.dtls.recordSequenceNumber
6114
6114
  )[0];
6115
- await this.transport.send(this.cipher.encryptPacket(pkt).serialize());
6115
+ await this.transport.send(this.cipher.encryptPacket(pkt).serialize(), addr);
6116
6116
  };
6117
6117
  close() {
6118
6118
  this.transport.socket.close();
@@ -7033,6 +7033,7 @@ function parseMessage(data, integrityKey) {
7033
7033
  );
7034
7034
  const attributeRepository = new AttributeRepository();
7035
7035
  const rawAttributes = [];
7036
+ let messageIntegrityVerified = false;
7036
7037
  for (let pos = HEADER_LENGTH; pos < data.length; ) {
7037
7038
  if (pos + 4 > data.length) {
7038
7039
  return void 0;
@@ -7070,6 +7071,7 @@ function parseMessage(data, integrityKey) {
7070
7071
  if (!integrity.equals(expected)) {
7071
7072
  return void 0;
7072
7073
  }
7074
+ messageIntegrityVerified = true;
7073
7075
  }
7074
7076
  } else {
7075
7077
  rawAttributes.push({
@@ -7080,6 +7082,9 @@ function parseMessage(data, integrityKey) {
7080
7082
  }
7081
7083
  pos = valueEnd + padLen;
7082
7084
  }
7085
+ if (integrityKey && !messageIntegrityVerified) {
7086
+ return void 0;
7087
+ }
7083
7088
  return new Message(
7084
7089
  messageType & 16111,
7085
7090
  messageType & 272,
@@ -7204,6 +7209,10 @@ function paddingLength(length) {
7204
7209
  return rest === 0 ? 0 : 4 - rest;
7205
7210
  }
7206
7211
 
7212
+ // ../ice/src/stun/transaction.ts
7213
+ import { promises as dns } from "node:dns";
7214
+ import { isIP as isIP2 } from "node:net";
7215
+
7207
7216
  // ../ice/src/exceptions.ts
7208
7217
  var TransactionError = class extends Error {
7209
7218
  response;
@@ -7233,32 +7242,100 @@ var TransactionTimeout = class extends TransactionError {
7233
7242
 
7234
7243
  // ../ice/src/stun/transaction.ts
7235
7244
  var log18 = debug("werift-ice:packages/ice/src/stun/transaction.ts");
7245
+ async function resolveRequestAddress(addr, family = 0) {
7246
+ if (isIP2(addr[0])) {
7247
+ return addr;
7248
+ }
7249
+ const looked = await dns.lookup(addr[0], { family });
7250
+ return [looked.address, addr[1]];
7251
+ }
7252
+ function normalizeTransactionOptions(retransmissionsOrOptions, onRequestSent) {
7253
+ if (retransmissionsOrOptions !== null && typeof retransmissionsOrOptions === "object") {
7254
+ return retransmissionsOrOptions;
7255
+ }
7256
+ const retransmissions = typeof retransmissionsOrOptions === "number" ? retransmissionsOrOptions : void 0;
7257
+ return {
7258
+ retransmissions,
7259
+ onRequestSent
7260
+ };
7261
+ }
7262
+ function addressEquals(a, b) {
7263
+ return a[0] === b[0] && a[1] === b[1];
7264
+ }
7236
7265
  var Transaction = class {
7237
- constructor(request, addr, protocol, retransmissions, onRequestSent) {
7266
+ constructor(request, addr, protocol, retransmissionsOrOptions, onRequestSent) {
7238
7267
  this.request = request;
7239
7268
  this.addr = addr;
7240
7269
  this.protocol = protocol;
7241
- this.retransmissions = retransmissions;
7242
- this.onRequestSent = onRequestSent;
7243
- this.triesMax = 1 + (this.retransmissions ?? RETRY_MAX);
7244
- }
7245
- timeoutDelay = RETRY_RTO;
7270
+ const options = normalizeTransactionOptions(
7271
+ retransmissionsOrOptions,
7272
+ onRequestSent
7273
+ );
7274
+ this.triesMax = 1 + (options.retransmissions ?? RETRY_MAX);
7275
+ this.timeoutDelay = options.responseTimeout ?? RETRY_RTO;
7276
+ this.onRequestSent = options.onRequestSent;
7277
+ this.signal = options.signal;
7278
+ this.expectedAddr = addr;
7279
+ this.integrityKey = options.integrityKey;
7280
+ }
7281
+ timeoutDelay;
7246
7282
  ended = false;
7247
7283
  tries = 0;
7248
7284
  triesMax;
7249
7285
  onResponse = new Event2();
7286
+ onRequestSent;
7287
+ signal;
7288
+ /** Remote address this transaction was sent to; responses must match. */
7289
+ expectedAddr;
7290
+ /**
7291
+ * When set, protocol layers re-parse the wire response with this key so
7292
+ * MESSAGE-INTEGRITY failures are rejected before responseReceived.
7293
+ */
7294
+ integrityKey;
7295
+ waitTimer;
7296
+ waitResolve;
7297
+ onAbort;
7298
+ /**
7299
+ * Accept a matching authenticated non-error response from the expected
7300
+ * remote address. Wrong address, missing MESSAGE-INTEGRITY (when required),
7301
+ * or non-success class is rejected without completing the transaction
7302
+ * (wrong address / unauthenticated responses are ignored so we keep waiting).
7303
+ */
7250
7304
  responseReceived = (message, addr) => {
7251
- if (this.onResponse.length > 0) {
7252
- if (message.messageClass === 256 /* RESPONSE */) {
7253
- this.onResponse.execute(message, addr);
7254
- this.onResponse.complete();
7255
- } else {
7256
- this.onResponse.error(new TransactionFailed(message, addr));
7305
+ if (this.ended || this.onResponse.length === 0) {
7306
+ return;
7307
+ }
7308
+ if (!addressEquals(this.expectedAddr, addr)) {
7309
+ log18(
7310
+ "ignore STUN response from unexpected address",
7311
+ addr,
7312
+ "expected",
7313
+ this.expectedAddr
7314
+ );
7315
+ return;
7316
+ }
7317
+ if (this.integrityKey) {
7318
+ const hasIntegrity = message.attributesKeys.includes("MESSAGE-INTEGRITY") || message.attributesKeys.includes("MESSAGE-INTEGRITY-SHA256");
7319
+ if (!hasIntegrity) {
7320
+ log18(
7321
+ "ignore unauthenticated STUN response (MESSAGE-INTEGRITY required)"
7322
+ );
7323
+ return;
7257
7324
  }
7258
7325
  }
7326
+ if (message.messageClass === 256 /* RESPONSE */) {
7327
+ this.onResponse.execute(message, addr);
7328
+ this.onResponse.complete();
7329
+ } else {
7330
+ this.onResponse.error(new TransactionFailed(message, addr));
7331
+ }
7259
7332
  };
7260
7333
  run = async () => {
7261
7334
  try {
7335
+ if (this.signal?.aborted) {
7336
+ throw new TransactionTimeout();
7337
+ }
7338
+ this.attachAbortListener();
7262
7339
  this.retry().catch((e) => {
7263
7340
  log18("retry failed", e);
7264
7341
  });
@@ -7270,28 +7347,85 @@ var Transaction = class {
7270
7347
  this.cancel();
7271
7348
  }
7272
7349
  };
7350
+ attachAbortListener() {
7351
+ if (!this.signal) {
7352
+ return;
7353
+ }
7354
+ this.onAbort = () => {
7355
+ this.failWithTimeout();
7356
+ };
7357
+ this.signal.addEventListener("abort", this.onAbort, { once: true });
7358
+ }
7359
+ failWithTimeout() {
7360
+ if (this.ended) {
7361
+ return;
7362
+ }
7363
+ this.ended = true;
7364
+ this.clearWait();
7365
+ if (this.onResponse.length > 0) {
7366
+ this.onResponse.error(new TransactionTimeout());
7367
+ }
7368
+ }
7369
+ clearWait() {
7370
+ if (this.waitTimer !== void 0) {
7371
+ clearTimeout(this.waitTimer);
7372
+ this.waitTimer = void 0;
7373
+ }
7374
+ const resolve = this.waitResolve;
7375
+ this.waitResolve = void 0;
7376
+ resolve?.();
7377
+ }
7378
+ wait(ms) {
7379
+ return new Promise((resolve) => {
7380
+ if (this.ended || this.signal?.aborted) {
7381
+ resolve();
7382
+ return;
7383
+ }
7384
+ this.waitResolve = resolve;
7385
+ this.waitTimer = setTimeout(() => {
7386
+ this.waitTimer = void 0;
7387
+ this.waitResolve = void 0;
7388
+ resolve();
7389
+ }, ms);
7390
+ });
7391
+ }
7273
7392
  retry = async () => {
7274
7393
  while (this.tries < this.triesMax && !this.ended) {
7275
7394
  this.onRequestSent?.(this.tries);
7276
7395
  this.protocol.sendStun(this.request, this.addr).catch((e) => {
7277
7396
  log18("send stun failed", e);
7278
7397
  });
7279
- await new Promise((r) => setTimeout(r, this.timeoutDelay));
7398
+ await this.wait(this.timeoutDelay);
7280
7399
  if (this.ended) {
7281
7400
  break;
7282
7401
  }
7283
7402
  this.timeoutDelay *= 2;
7284
7403
  this.tries++;
7285
7404
  }
7286
- if (this.tries >= this.triesMax) {
7405
+ if (this.tries >= this.triesMax && !this.ended) {
7287
7406
  log18(`retry failed times:${this.tries} maxLimit:${this.triesMax}`);
7288
- this.onResponse.error(new TransactionTimeout());
7407
+ this.failWithTimeout();
7289
7408
  }
7290
7409
  };
7291
7410
  cancel() {
7292
7411
  this.ended = true;
7412
+ this.clearWait();
7413
+ if (this.signal && this.onAbort) {
7414
+ this.signal.removeEventListener("abort", this.onAbort);
7415
+ this.onAbort = void 0;
7416
+ }
7293
7417
  }
7294
7418
  };
7419
+ function buildTransactionOptions(integrityKey, retransmissionsOrOptions, onRequestSent) {
7420
+ const options = normalizeTransactionOptions(
7421
+ retransmissionsOrOptions,
7422
+ onRequestSent
7423
+ );
7424
+ if (integrityKey && !options.integrityKey) {
7425
+ options.integrityKey = integrityKey;
7426
+ }
7427
+ return options;
7428
+ }
7295
7429
 
7296
7430
  // ../ice/src/stun/tcpProtocol.ts
7297
7431
  var log19 = debug("werift-ice:packages/ice/src/stun/tcpProtocol.ts");
@@ -7390,10 +7524,13 @@ var BaseTcpProtocol = class _BaseTcpProtocol {
7390
7524
  return;
7391
7525
  }
7392
7526
  if ((message.messageClass === 256 /* RESPONSE */ || message.messageClass === 272 /* ERROR */) && this.transactions[message.transactionIdHex]) {
7393
- this.transactions[message.transactionIdHex].responseReceived(
7394
- message,
7395
- addr
7396
- );
7527
+ const transaction = this.transactions[message.transactionIdHex];
7528
+ const verified = transaction.integrityKey ? parseMessage(data, transaction.integrityKey) : message;
7529
+ if (!verified) {
7530
+ log19("STUN response failed MESSAGE-INTEGRITY check");
7531
+ return;
7532
+ }
7533
+ transaction.responseReceived(verified, addr);
7397
7534
  } else if (message.messageClass === 0 /* REQUEST */) {
7398
7535
  this.onRequestReceived.execute(message, addr, data);
7399
7536
  }
@@ -7419,7 +7556,7 @@ var BaseTcpProtocol = class _BaseTcpProtocol {
7419
7556
  async sendData(data, addr) {
7420
7557
  await this.sendFrame(data, addr);
7421
7558
  }
7422
- async request(request, addr, integrityKey, retransmissions, onRequestSent) {
7559
+ async request(request, addr, integrityKey, retransmissionsOrOptions, onRequestSent) {
7423
7560
  if (this.transactions[request.transactionIdHex]) {
7424
7561
  throw new Error("already requested");
7425
7562
  }
@@ -7427,13 +7564,13 @@ var BaseTcpProtocol = class _BaseTcpProtocol {
7427
7564
  request.addMessageIntegrity(integrityKey);
7428
7565
  request.addFingerprint();
7429
7566
  }
7430
- const transaction = new Transaction(
7431
- request,
7432
- addr,
7433
- this,
7434
- retransmissions,
7567
+ const resolvedAddr = await resolveRequestAddress(addr);
7568
+ const options = buildTransactionOptions(
7569
+ integrityKey,
7570
+ retransmissionsOrOptions,
7435
7571
  onRequestSent
7436
7572
  );
7573
+ const transaction = new Transaction(request, resolvedAddr, this, options);
7437
7574
  this.transactions[request.transactionIdHex] = transaction;
7438
7575
  try {
7439
7576
  return await transaction.run();
@@ -7592,7 +7729,12 @@ var StunProtocol = class _StunProtocol {
7592
7729
  }
7593
7730
  if ((message.messageClass === 256 /* RESPONSE */ || message.messageClass === 272 /* ERROR */) && this.transactionsKeys.includes(message.transactionIdHex)) {
7594
7731
  const transaction = this.transactions[message.transactionIdHex];
7595
- transaction.responseReceived(message, addr);
7732
+ const verified = transaction.integrityKey ? parseMessage(data, transaction.integrityKey) : message;
7733
+ if (!verified) {
7734
+ log20("STUN response failed MESSAGE-INTEGRITY check");
7735
+ return;
7736
+ }
7737
+ transaction.responseReceived(verified, addr);
7596
7738
  } else if (message.messageClass === 0 /* REQUEST */) {
7597
7739
  this.onRequestReceived.execute(message, addr, data);
7598
7740
  }
@@ -7619,19 +7761,26 @@ var StunProtocol = class _StunProtocol {
7619
7761
  }
7620
7762
  await this.transport.send(data, addr);
7621
7763
  }
7622
- async request(request, addr, integrityKey, retransmissions, onRequestSent) {
7764
+ async request(request, addr, integrityKey, retransmissionsOrOptions, onRequestSent) {
7623
7765
  if (this.transactionsKeys.includes(request.transactionIdHex))
7624
7766
  throw new Error("already request ed");
7625
7767
  if (integrityKey) {
7626
7768
  request.addMessageIntegrity(integrityKey);
7627
7769
  request.addFingerprint();
7628
7770
  }
7771
+ const socketType = this.transport.socketType;
7772
+ const family = socketType === "udp6" ? 6 : 4;
7773
+ const resolvedAddr = await resolveRequestAddress(addr, family);
7774
+ const options = buildTransactionOptions(
7775
+ integrityKey,
7776
+ retransmissionsOrOptions,
7777
+ onRequestSent
7778
+ );
7629
7779
  const transaction = new Transaction(
7630
7780
  request,
7631
- addr,
7781
+ resolvedAddr,
7632
7782
  this,
7633
- retransmissions,
7634
- onRequestSent
7783
+ options
7635
7784
  );
7636
7785
  this.transactions[request.transactionIdHex] = transaction;
7637
7786
  try {
@@ -7802,7 +7951,12 @@ var StunOverTurnProtocol = class _StunOverTurnProtocol {
7802
7951
  if (message.messageClass === 256 /* RESPONSE */ || message.messageClass === 272 /* ERROR */) {
7803
7952
  const transaction = this.turn.transactions[message.transactionIdHex];
7804
7953
  if (transaction) {
7805
- transaction.responseReceived(message, addr);
7954
+ const verified = transaction.integrityKey ? parseMessage(data, transaction.integrityKey) : message;
7955
+ if (!verified) {
7956
+ log21("STUN over TURN response failed MESSAGE-INTEGRITY check");
7957
+ return;
7958
+ }
7959
+ transaction.responseReceived(verified, addr);
7806
7960
  }
7807
7961
  } else if (message.messageClass === 0 /* REQUEST */) {
7808
7962
  this.onRequestReceived.execute(message, addr, data);
@@ -7811,7 +7965,7 @@ var StunOverTurnProtocol = class _StunOverTurnProtocol {
7811
7965
  log21("datagramReceived error", error);
7812
7966
  }
7813
7967
  };
7814
- async request(request, addr, integrityKey, _retransmissions, onRequestSent) {
7968
+ async request(request, addr, integrityKey, retransmissionsOrOptions, onRequestSent) {
7815
7969
  if (this.turn.transactions[request.transactionIdHex]) {
7816
7970
  throw new Error("exist");
7817
7971
  }
@@ -7819,13 +7973,13 @@ var StunOverTurnProtocol = class _StunOverTurnProtocol {
7819
7973
  request.addMessageIntegrity(integrityKey);
7820
7974
  request.addFingerprint();
7821
7975
  }
7822
- const transaction = new Transaction(
7823
- request,
7824
- addr,
7825
- this,
7826
- void 0,
7976
+ const resolvedAddr = await resolveRequestAddress(addr);
7977
+ const options = buildTransactionOptions(
7978
+ integrityKey,
7979
+ retransmissionsOrOptions,
7827
7980
  onRequestSent
7828
7981
  );
7982
+ const transaction = new Transaction(request, resolvedAddr, this, options);
7829
7983
  this.turn.transactions[request.transactionIdHex] = transaction;
7830
7984
  try {
7831
7985
  return await transaction.run();
@@ -7915,7 +8069,12 @@ var TurnProtocol = class _TurnProtocol {
7915
8069
  if (message.messageClass === 256 /* RESPONSE */ || message.messageClass === 272 /* ERROR */) {
7916
8070
  const transaction = this.transactions[message.transactionIdHex];
7917
8071
  if (transaction) {
7918
- transaction.responseReceived(message, addr);
8072
+ const verified = transaction.integrityKey ? parseMessage(data, transaction.integrityKey) : message;
8073
+ if (!verified) {
8074
+ log21("TURN STUN response failed MESSAGE-INTEGRITY check");
8075
+ return;
8076
+ }
8077
+ transaction.responseReceived(verified, addr);
7919
8078
  }
7920
8079
  } else if (message.messageClass === 0 /* REQUEST */) {
7921
8080
  this.onData.execute(data, addr);
@@ -7987,20 +8146,20 @@ var TurnProtocol = class _TurnProtocol {
7987
8146
  }
7988
8147
  });
7989
8148
  };
7990
- async request(request, addr, _integrityKey, _retransmissions, onRequestSent) {
8149
+ async request(request, addr, _integrityKey, retransmissionsOrOptions, onRequestSent) {
7991
8150
  if (this.transactions[request.transactionIdHex]) {
7992
8151
  throw new Error("exist");
7993
8152
  }
7994
8153
  if (this.integrityKey) {
7995
8154
  request.setAttribute("USERNAME", this.username).setAttribute("REALM", this.realm).setAttribute("NONCE", this.nonce).addMessageIntegrity(this.integrityKey).addFingerprint();
7996
8155
  }
7997
- const transaction = new Transaction(
7998
- request,
7999
- addr,
8000
- this,
8001
- void 0,
8156
+ const resolvedAddr = await resolveRequestAddress(addr);
8157
+ const options = buildTransactionOptions(
8158
+ this.integrityKey,
8159
+ retransmissionsOrOptions,
8002
8160
  onRequestSent
8003
8161
  );
8162
+ const transaction = new Transaction(request, resolvedAddr, this, options);
8004
8163
  this.transactions[request.transactionIdHex] = transaction;
8005
8164
  try {
8006
8165
  return await transaction.run();
@@ -8466,6 +8625,18 @@ var ICE_COMPLETED = 1;
8466
8625
  var ICE_FAILED = 2;
8467
8626
  var CONSENT_INTERVAL = 5;
8468
8627
  var CONSENT_FAILURES = 6;
8628
+ var CONSENT_TIMEOUT = 30;
8629
+ var CONSENT_RESPONSE_TIMEOUT = 1e3;
8630
+ var CONSENT_RESPONSE_TIMEOUT_MIN = 500;
8631
+ function consentResponseTimeoutMs(rttSeconds) {
8632
+ if (rttSeconds === void 0 || !Number.isFinite(rttSeconds) || rttSeconds <= 0) {
8633
+ return CONSENT_RESPONSE_TIMEOUT;
8634
+ }
8635
+ return Math.max(
8636
+ CONSENT_RESPONSE_TIMEOUT_MIN,
8637
+ Math.round(rttSeconds * 1e3 * 2 + 200)
8638
+ );
8639
+ }
8469
8640
  var CandidatePairState = /* @__PURE__ */ ((CandidatePairState2) => {
8470
8641
  CandidatePairState2[CandidatePairState2["FROZEN"] = 0] = "FROZEN";
8471
8642
  CandidatePairState2[CandidatePairState2["WAITING"] = 1] = "WAITING";
@@ -8665,6 +8836,12 @@ var Connection = class {
8665
8836
  localCandidatesStart = false;
8666
8837
  protocols = [];
8667
8838
  queryConsentHandle;
8839
+ /** RFC 7675 consent-to-send: application data may use the selected pair. */
8840
+ consentFresh = false;
8841
+ /** Invalidates in-flight consent callbacks on restart / close / replace / expire. */
8842
+ consentSessionId = 0;
8843
+ consentExpiryTimer;
8844
+ consentRequestAbort;
8668
8845
  onData = new Event2();
8669
8846
  stateChanged = new Event2();
8670
8847
  onIceCandidate = new Event2();
@@ -8675,13 +8852,10 @@ var Connection = class {
8675
8852
  if (this.iceLite) {
8676
8853
  value = false;
8677
8854
  }
8678
- if (this.generation > 0 || this.nominated) {
8855
+ if (this.nominated) {
8679
8856
  return;
8680
8857
  }
8681
- this._iceControlling = value;
8682
- for (const pair of this.checkList) {
8683
- pair.iceControlling = value;
8684
- }
8858
+ this.applyIceControlling(value);
8685
8859
  }
8686
8860
  get iceLite() {
8687
8861
  return this.options.iceLite;
@@ -8718,13 +8892,13 @@ var Connection = class {
8718
8892
  protocol.localCandidate.ufrag = this.localUsername;
8719
8893
  }
8720
8894
  }
8721
- this.queryConsentHandle?.resolve?.();
8722
- this.queryConsentHandle = void 0;
8895
+ this.stopConsentLifecycle();
8723
8896
  }
8724
8897
  resetNominatedPair() {
8725
8898
  log23("resetNominatedPair");
8726
8899
  this.nominated = void 0;
8727
8900
  this.nominating = false;
8901
+ this.stopConsentLifecycle();
8728
8902
  }
8729
8903
  setRemoteParams({
8730
8904
  iceLite,
@@ -8740,6 +8914,13 @@ var Connection = class {
8740
8914
  async gatherCandidates() {
8741
8915
  if (!this.localCandidatesStart) {
8742
8916
  this.localCandidatesStart = true;
8917
+ for (const protocol of this.protocols) {
8918
+ if (protocol.localCandidate) {
8919
+ protocol.localCandidate.generation = this.generation;
8920
+ protocol.localCandidate.ufrag = this.localUsername;
8921
+ this.appendLocalCandidate(protocol.localCandidate);
8922
+ }
8923
+ }
8743
8924
  let address = getHostAddresses(
8744
8925
  this.options.useIpv4,
8745
8926
  this.options.useIpv6,
@@ -9132,84 +9313,211 @@ var Connection = class {
9132
9313
  }
9133
9314
  return false;
9134
9315
  }
9135
- // 4.1.1.4 ? 生存確認 life check
9316
+ /**
9317
+ * Stop consent request cadence, expiry timer, and outstanding transactions.
9318
+ * Does not change ICE state by itself.
9319
+ */
9320
+ stopConsentLifecycle() {
9321
+ this.consentSessionId++;
9322
+ this.consentFresh = false;
9323
+ if (this.consentExpiryTimer !== void 0) {
9324
+ clearTimeout(this.consentExpiryTimer);
9325
+ this.consentExpiryTimer = void 0;
9326
+ }
9327
+ this.consentRequestAbort?.abort();
9328
+ this.consentRequestAbort = void 0;
9329
+ const handle = this.queryConsentHandle;
9330
+ this.queryConsentHandle = void 0;
9331
+ handle?.resolve?.();
9332
+ }
9333
+ /**
9334
+ * ICE-lite interop only (not required by RFC 7675): mirror libwebrtc
9335
+ * semi-aggressive nomination — attach USE-CANDIDATE when we are controlling,
9336
+ * the remote is ICE-lite, and the target is the current selected pair.
9337
+ */
9338
+ shouldNominateConsentRequest(pair) {
9339
+ return this.iceControlling && this.remoteIsLite && this.nominated?.id === pair.id;
9340
+ }
9341
+ canSendApplicationData() {
9342
+ if (!this.nominated) {
9343
+ return false;
9344
+ }
9345
+ if (this.state === "closed" || this.state === "failed") {
9346
+ return false;
9347
+ }
9348
+ if (this.iceLite) {
9349
+ return true;
9350
+ }
9351
+ return this.consentFresh;
9352
+ }
9353
+ abortableDelay(ms, signal) {
9354
+ return new Promise((resolve, reject) => {
9355
+ if (signal.aborted) {
9356
+ reject(new DOMException("The operation was aborted", "AbortError"));
9357
+ return;
9358
+ }
9359
+ const timer2 = setTimeout(() => {
9360
+ signal.removeEventListener("abort", onAbort);
9361
+ resolve();
9362
+ }, ms);
9363
+ const onAbort = () => {
9364
+ clearTimeout(timer2);
9365
+ reject(new DOMException("The operation was aborted", "AbortError"));
9366
+ };
9367
+ signal.addEventListener("abort", onAbort, { once: true });
9368
+ });
9369
+ }
9370
+ // RFC 7675 consent freshness
9136
9371
  queryConsent = () => {
9137
9372
  if (this.iceLite) {
9138
9373
  return;
9139
9374
  }
9140
- if (this.queryConsentHandle) {
9141
- this.queryConsentHandle.resolve();
9142
- }
9143
- this.queryConsentHandle = cancelable(async (_, __, onCancel) => {
9144
- let failures = 0;
9375
+ this.stopConsentLifecycle();
9376
+ const sessionId = this.consentSessionId;
9377
+ this.consentFresh = true;
9378
+ const handle = cancelable(async (_, __, onCancel) => {
9145
9379
  let canceled = false;
9146
9380
  const cancelEvent = new AbortController();
9381
+ const clearConsentExpiry = () => {
9382
+ if (this.consentExpiryTimer === void 0) {
9383
+ return;
9384
+ }
9385
+ clearTimeout(this.consentExpiryTimer);
9386
+ this.consentExpiryTimer = void 0;
9387
+ };
9388
+ const refreshConsentExpiry = () => {
9389
+ if (canceled || sessionId !== this.consentSessionId) {
9390
+ return;
9391
+ }
9392
+ clearConsentExpiry();
9393
+ this.consentExpiryTimer = setTimeout(() => {
9394
+ this.consentExpiryTimer = void 0;
9395
+ if (canceled || sessionId !== this.consentSessionId) {
9396
+ return;
9397
+ }
9398
+ if (this.state === "closed" || this.state === "failed") {
9399
+ return;
9400
+ }
9401
+ log23("Consent to send expired");
9402
+ this.consentFresh = false;
9403
+ this.consentSessionId++;
9404
+ this.consentRequestAbort?.abort();
9405
+ this.consentRequestAbort = void 0;
9406
+ if (this.queryConsentHandle === handle) {
9407
+ this.queryConsentHandle = void 0;
9408
+ }
9409
+ canceled = true;
9410
+ cancelEvent.abort();
9411
+ this.setState("failed");
9412
+ }, CONSENT_TIMEOUT * 1e3);
9413
+ };
9147
9414
  onCancel.once(() => {
9148
9415
  canceled = true;
9149
- failures += CONSENT_FAILURES;
9416
+ if (sessionId === this.consentSessionId) {
9417
+ clearConsentExpiry();
9418
+ this.consentRequestAbort?.abort();
9419
+ this.consentRequestAbort = void 0;
9420
+ }
9150
9421
  cancelEvent.abort();
9151
- this.queryConsentHandle = void 0;
9422
+ if (this.queryConsentHandle === handle) {
9423
+ this.queryConsentHandle = void 0;
9424
+ }
9152
9425
  });
9153
- const { localUsername, remoteUsername, iceControlling } = this;
9426
+ refreshConsentExpiry();
9427
+ const randomizedConsentInterval = () => CONSENT_INTERVAL * (0.8 + 0.4 * Math.random()) * 1e3;
9428
+ let nextConsentAt = Date.now() + randomizedConsentInterval();
9429
+ const isTerminalState = () => this.state === "closed" || this.state === "failed";
9154
9430
  try {
9155
- while (this.state !== "closed" && !canceled) {
9156
- await timers.setTimeout(
9157
- CONSENT_INTERVAL * (0.8 + 0.4 * Math.random()) * 1e3,
9158
- void 0,
9159
- { signal: cancelEvent.signal }
9431
+ while (!isTerminalState() && !canceled && sessionId === this.consentSessionId) {
9432
+ await this.abortableDelay(
9433
+ Math.max(0, nextConsentAt - Date.now()),
9434
+ cancelEvent.signal
9160
9435
  );
9436
+ if (canceled || isTerminalState() || sessionId !== this.consentSessionId) {
9437
+ break;
9438
+ }
9439
+ nextConsentAt = Date.now() + randomizedConsentInterval();
9161
9440
  const nominated = this.nominated;
9162
- if (!nominated || canceled) {
9441
+ if (!nominated) {
9163
9442
  break;
9164
9443
  }
9444
+ const pairId = nominated.id;
9445
+ const generation = this.generation;
9446
+ const remotePassword = this.remotePassword;
9447
+ const { localUsername, remoteUsername, iceControlling } = this;
9165
9448
  const request = this.buildRequest({
9166
- nominate: false,
9449
+ nominate: this.shouldNominateConsentRequest(nominated),
9167
9450
  localUsername,
9168
9451
  remoteUsername,
9169
9452
  iceControlling,
9170
9453
  localCandidate: nominated.localCandidate
9171
9454
  });
9172
- try {
9173
- nominated.consentRequestsSent++;
9174
- nominated.requestsSent++;
9175
- await nominated.protocol.request(
9176
- request,
9177
- nominated.remoteAddr,
9178
- Buffer.from(this.remotePassword, "utf8"),
9179
- 0,
9180
- (attempt) => {
9455
+ this.consentRequestAbort?.abort();
9456
+ const requestAbort = new AbortController();
9457
+ this.consentRequestAbort = requestAbort;
9458
+ nominated.consentRequestsSent++;
9459
+ nominated.requestsSent++;
9460
+ const responseTimeout = consentResponseTimeoutMs(nominated.rtt);
9461
+ const requestStartedAt = performance.now();
9462
+ nominated.protocol.request(
9463
+ request,
9464
+ nominated.remoteAddr,
9465
+ Buffer.from(remotePassword, "utf8"),
9466
+ {
9467
+ retransmissions: 0,
9468
+ responseTimeout,
9469
+ signal: requestAbort.signal,
9470
+ onRequestSent: (attempt) => {
9181
9471
  if (attempt > 0) {
9182
9472
  nominated.retransmissionsSent++;
9183
9473
  }
9184
9474
  }
9185
- );
9475
+ }
9476
+ ).then(() => {
9477
+ if (sessionId !== this.consentSessionId || canceled) {
9478
+ return;
9479
+ }
9480
+ const state = this.state;
9481
+ if (state === "closed" || state === "failed") {
9482
+ return;
9483
+ }
9484
+ if (this.nominated?.id !== pairId) {
9485
+ return;
9486
+ }
9487
+ if (this.generation !== generation) {
9488
+ return;
9489
+ }
9490
+ if (this.remotePassword !== remotePassword) {
9491
+ return;
9492
+ }
9493
+ const rtt = (performance.now() - requestStartedAt) / 1e3;
9494
+ nominated.rtt = rtt;
9495
+ nominated.totalRoundTripTime += rtt;
9496
+ nominated.roundTripTimeMeasurements++;
9186
9497
  nominated.responsesReceived++;
9187
- failures = 0;
9188
- if (this.state === "disconnected") {
9498
+ this.consentFresh = true;
9499
+ refreshConsentExpiry();
9500
+ if (state === "disconnected") {
9189
9501
  this.setState("connected");
9190
9502
  }
9191
- } catch (error) {
9192
- if (nominated.id === this.nominated?.id) {
9193
- log23("no stun response");
9194
- failures++;
9195
- this.setState("disconnected");
9196
- break;
9503
+ }).catch((error) => {
9504
+ if (sessionId === this.consentSessionId && this.nominated?.id === pairId) {
9505
+ log23("no stun response", error);
9197
9506
  }
9198
- }
9199
- if (failures >= CONSENT_FAILURES) {
9200
- log23("Consent to send expired");
9201
- this.queryConsentHandle = void 0;
9202
- this.setState("closed");
9203
- break;
9204
- }
9507
+ });
9205
9508
  }
9206
9509
  } catch (error) {
9510
+ } finally {
9511
+ if (sessionId === this.consentSessionId) {
9512
+ clearConsentExpiry();
9513
+ }
9207
9514
  }
9208
9515
  });
9516
+ this.queryConsentHandle = handle;
9209
9517
  };
9210
9518
  async close() {
9211
9519
  this.setState("closed");
9212
- this.queryConsentHandle?.resolve?.();
9520
+ this.stopConsentLifecycle();
9213
9521
  if (this.checkList && !this.checkListDone) {
9214
9522
  this.checkListState.put(
9215
9523
  new Promise((r) => {
@@ -9259,14 +9567,13 @@ var Connection = class {
9259
9567
  this.sortCheckList();
9260
9568
  }
9261
9569
  send = async (data) => {
9262
- const activePair = this.nominated;
9263
- if (activePair) {
9264
- await activePair.protocol.sendData(data, activePair.remoteAddr);
9265
- activePair.packetsSent++;
9266
- activePair.bytesSent += data.length;
9267
- } else {
9570
+ if (!this.canSendApplicationData()) {
9268
9571
  return;
9269
9572
  }
9573
+ const activePair = this.nominated;
9574
+ await activePair.protocol.sendData(data, activePair.remoteAddr);
9575
+ activePair.packetsSent++;
9576
+ activePair.bytesSent += data.length;
9270
9577
  };
9271
9578
  getDefaultCandidate() {
9272
9579
  const candidates = this.localCandidates.sort(
@@ -9305,9 +9612,18 @@ var Connection = class {
9305
9612
  );
9306
9613
  return pair;
9307
9614
  }
9615
+ applyIceControlling(iceControlling) {
9616
+ this._iceControlling = iceControlling;
9617
+ for (const pair of this.checkList) {
9618
+ pair.iceControlling = iceControlling;
9619
+ }
9620
+ }
9308
9621
  switchRole(iceControlling) {
9309
9622
  log23("switch role", iceControlling);
9310
- this.iceControlling = iceControlling;
9623
+ if (this.iceLite) {
9624
+ iceControlling = false;
9625
+ }
9626
+ this.applyIceControlling(iceControlling);
9311
9627
  this.sortCheckList();
9312
9628
  }
9313
9629
  checkComplete(pair) {
@@ -9319,6 +9635,9 @@ var Connection = class {
9319
9635
  this.nominated = pair;
9320
9636
  this.nominating = false;
9321
9637
  this.pruneTcpConnections(pair);
9638
+ if (!this.iceLite && (this.state === "connected" || this.state === "completed")) {
9639
+ this.queryConsent();
9640
+ }
9322
9641
  for (const p of this.checkList) {
9323
9642
  if (p.component === pair.component && [1 /* WAITING */, 0 /* FROZEN */].includes(
9324
9643
  p.state
@@ -16648,6 +16967,11 @@ var SecureTransportManager = class {
16648
16967
  log36("iceConnectionStateChange", newState);
16649
16968
  this.iceConnectionState = newState;
16650
16969
  this.iceConnectionStateChange.execute(newState);
16970
+ if (newState === "failed" && this.connectionState !== "closed") {
16971
+ this.setConnectionState("failed");
16972
+ } else if (newState === "disconnected" && this.connectionState === "connected") {
16973
+ this.setConnectionState("disconnected");
16974
+ }
16651
16975
  }
16652
16976
  async gatherCandidates(remoteIsBundled) {
16653
16977
  const connected = this.iceTransports.find(
@@ -16664,6 +16988,9 @@ var SecureTransportManager = class {
16664
16988
  }
16665
16989
  }
16666
16990
  setConnectionState(state) {
16991
+ if (this.connectionState === state) {
16992
+ return;
16993
+ }
16667
16994
  log36("connectionStateChange", state);
16668
16995
  this.connectionState = state;
16669
16996
  this.connectionStateChange.execute(state);
@@ -18023,6 +18350,9 @@ export {
18023
18350
  BufferChain,
18024
18351
  CONSENT_FAILURES,
18025
18352
  CONSENT_INTERVAL,
18353
+ CONSENT_RESPONSE_TIMEOUT,
18354
+ CONSENT_RESPONSE_TIMEOUT_MIN,
18355
+ CONSENT_TIMEOUT,
18026
18356
  COOKIE,
18027
18357
  Candidate,
18028
18358
  CandidatePair,
@@ -18178,6 +18508,7 @@ export {
18178
18508
  codecParametersFromString,
18179
18509
  codecParametersToString,
18180
18510
  compactNtp,
18511
+ consentResponseTimeoutMs,
18181
18512
  crc32,
18182
18513
  crc32c,
18183
18514
  createBufferWriter,