@mertushka/webrtc-node 0.1.0-alpha.0 → 0.1.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/lib/index.js CHANGED
@@ -53,17 +53,23 @@ class SimpleEventTarget {
53
53
  map.set(key, listeners);
54
54
  }
55
55
  const once = typeof options === "object" && options !== null && Boolean(options.once);
56
+ let added = false;
56
57
  if (!listeners.some((listener) => listener.callback === callback)) {
57
58
  listeners.push({ callback, once });
59
+ added = true;
58
60
  }
59
- if (typeof this._eventListenerAdded === "function") this._eventListenerAdded(key);
61
+ if (added && typeof this._eventListenerAdded === "function") this._eventListenerAdded(key);
60
62
  }
61
63
 
62
64
  removeEventListener(type, callback) {
63
- const listeners = kHandlers.get(this)?.get(String(type));
65
+ const key = String(type);
66
+ const listeners = kHandlers.get(this)?.get(key);
64
67
  if (!listeners) return;
65
68
  const index = listeners.findIndex((listener) => listener.callback === callback);
66
- if (index !== -1) listeners.splice(index, 1);
69
+ if (index !== -1) {
70
+ listeners.splice(index, 1);
71
+ if (typeof this._eventListenerRemoved === "function") this._eventListenerRemoved(key);
72
+ }
67
73
  }
68
74
 
69
75
  dispatchEvent(event) {
@@ -72,11 +78,14 @@ class SimpleEventTarget {
72
78
  }
73
79
  event.target = event.target || this;
74
80
  event.currentTarget = this;
75
- const listeners = Array.from(kHandlers.get(this)?.get(event.type) || []);
81
+ const registeredListeners = kHandlers.get(this)?.get(event.type);
82
+ const listeners = registeredListeners?.length ? Array.from(registeredListeners) : null;
76
83
  const handler = this[`on${event.type}`];
77
- for (const listener of listeners) {
78
- callListener(listener.callback, this, event);
79
- if (listener.once) this.removeEventListener(event.type, listener.callback);
84
+ if (listeners) {
85
+ for (const listener of listeners) {
86
+ callListener(listener.callback, this, event);
87
+ if (listener.once) this.removeEventListener(event.type, listener.callback);
88
+ }
80
89
  }
81
90
  if (typeof handler === "function") callListener(handler, this, event);
82
91
  return !event.defaultPrevented;
@@ -477,7 +486,7 @@ function maxMessageSizeFromSdp(description) {
477
486
  }
478
487
 
479
488
  function nextTask() {
480
- return new Promise((resolve) => setTimeout(resolve, 0));
489
+ return new Promise((resolve) => setImmediate(resolve));
481
490
  }
482
491
 
483
492
  function delay(ms) {
@@ -1283,6 +1292,9 @@ class RTCDataChannel extends SimpleEventTarget {
1283
1292
  );
1284
1293
  peerConnection._channels.set(channel._native.bindingId, channel);
1285
1294
  peerConnection._registerDataChannelId(channel);
1295
+ if (channel._registeredDataChannelId == null && channel._effectiveId() == null) {
1296
+ peerConnection._dataChannelIdRefreshNeeded = true;
1297
+ }
1286
1298
  return channel;
1287
1299
  }
1288
1300
 
@@ -1305,9 +1317,12 @@ class RTCDataChannel extends SimpleEventTarget {
1305
1317
  this._nativeEventDrainActive = false;
1306
1318
  this._queuedNativeEvents = [];
1307
1319
  this._queuedMessageEvents = [];
1320
+ this._queuedMessageEventIndex = 0;
1308
1321
  this._messageEventFlushScheduled = false;
1309
1322
  this._messageEventGateActive = false;
1310
1323
  this._messageConsumerGateActive = false;
1324
+ this._messageEventListenerCount = 0;
1325
+ this._messageHandlerNeedsTaskYield = false;
1311
1326
  this._pendingPairedDeliveryBytes = 0;
1312
1327
  this._nativeCloseEventQueued = false;
1313
1328
  this._openEventDeferredForIce = false;
@@ -1391,6 +1406,7 @@ class RTCDataChannel extends SimpleEventTarget {
1391
1406
 
1392
1407
  set onmessage(callback) {
1393
1408
  this._onmessage = typeof callback === "function" ? callback : null;
1409
+ this._messageHandlerNeedsTaskYield = this._messageContinuationNeedsTaskYield(this._onmessage);
1394
1410
  if (this._onmessage) this._releaseMessageConsumerGate();
1395
1411
  }
1396
1412
 
@@ -1428,17 +1444,17 @@ class RTCDataChannel extends SimpleEventTarget {
1428
1444
  return;
1429
1445
  }
1430
1446
 
1431
- const view = toUint8Array(data);
1447
+ const view = data instanceof Uint8Array ? data : toUint8Array(data);
1432
1448
  const size = view.byteLength;
1433
1449
  this._assertWithinMaxMessageSize(size);
1434
1450
  if (this._hasPendingSends() || !this._nativeReadyForSend()) {
1435
1451
  const payload = new Uint8Array(view);
1436
1452
  this._enqueueSend(() => this._sendNativeBinary(payload), size);
1437
1453
  } else {
1438
- const payload = new Uint8Array(view);
1439
- if (this._sendNativeBinary(payload)) {
1454
+ if (this._sendNativeBinary(view)) {
1440
1455
  this._increaseBufferedAmount(size);
1441
1456
  } else {
1457
+ const payload = new Uint8Array(view);
1442
1458
  this._enqueueSend(() => this._sendNativeBinary(payload), size);
1443
1459
  }
1444
1460
  }
@@ -1457,11 +1473,7 @@ class RTCDataChannel extends SimpleEventTarget {
1457
1473
  this._nativeCloseScheduled = true;
1458
1474
  const closeNative = () => {
1459
1475
  const close = () => {
1460
- const synthesizePairedClose =
1461
- pairedChannel &&
1462
- pairedChannel.readyState !== "closed" &&
1463
- (pairedChannel._syntheticIncoming ||
1464
- (this._bufferedAmount === 0 && !this._hasPendingSends()));
1476
+ const synthesizePairedClose = pairedChannel && pairedChannel.readyState !== "closed";
1465
1477
  this._native.close();
1466
1478
  if (synthesizePairedClose) {
1467
1479
  setTimeout(() => pairedChannel._handleRemoteChannelClose(), 0);
@@ -1473,7 +1485,7 @@ class RTCDataChannel extends SimpleEventTarget {
1473
1485
  if (shouldDrainBeforeClose) {
1474
1486
  this._closeNativeAfterBufferedSends(close);
1475
1487
  } else {
1476
- close();
1488
+ setImmediate(close);
1477
1489
  }
1478
1490
  };
1479
1491
  if (this._hasPendingSends()) {
@@ -1487,7 +1499,7 @@ class RTCDataChannel extends SimpleEventTarget {
1487
1499
  _increaseBufferedAmount(size) {
1488
1500
  if (!size) return;
1489
1501
  this._bufferedAmount += size;
1490
- setTimeout(() => this._decreaseBufferedAmount(size), 0);
1502
+ setImmediate(() => this._decreaseBufferedAmount(size));
1491
1503
  }
1492
1504
 
1493
1505
  _hasPendingSends() {
@@ -1527,8 +1539,12 @@ class RTCDataChannel extends SimpleEventTarget {
1527
1539
  }
1528
1540
 
1529
1541
  _assertWithinMaxMessageSize(size) {
1530
- const maxMessageSize = this._pc.sctp?.maxMessageSize;
1531
- if (Number.isFinite(maxMessageSize) && size > maxMessageSize) {
1542
+ const maxMessageSize = this._pc._sctpTransport?._maxMessageSize;
1543
+ if (
1544
+ maxMessageSize !== null &&
1545
+ maxMessageSize !== Number.POSITIVE_INFINITY &&
1546
+ size > maxMessageSize
1547
+ ) {
1532
1548
  throw new TypeError("RTCDataChannel message exceeds RTCSctpTransport.maxMessageSize");
1533
1549
  }
1534
1550
  }
@@ -1589,8 +1605,13 @@ class RTCDataChannel extends SimpleEventTarget {
1589
1605
  return true;
1590
1606
  }
1591
1607
  try {
1592
- const sent = this._tryNativeSend(() => this._native.sendString(data));
1593
- if (sent) this._trackPendingPairedDelivery(byteLength(data));
1608
+ let sent = this._native.sendString(data);
1609
+ if (sent !== false) {
1610
+ sent = true;
1611
+ } else {
1612
+ sent = Boolean(this._native?.isOpen);
1613
+ }
1614
+ if (sent && this._pairedChannel) this._pendingPairedDeliveryBytes += byteLength(data);
1594
1615
  return sent;
1595
1616
  } catch (error) {
1596
1617
  throw mapNativeError(error, "OperationError");
@@ -1606,21 +1627,22 @@ class RTCDataChannel extends SimpleEventTarget {
1606
1627
  return true;
1607
1628
  }
1608
1629
  try {
1609
- const view = toUint8Array(data);
1610
- const sent = this._tryNativeSend(() => this._native.sendBinary(view));
1611
- if (sent) this._trackPendingPairedDelivery(view.byteLength);
1630
+ const view = data instanceof Uint8Array ? data : toUint8Array(data);
1631
+ let sent = this._native.sendBinary(view);
1632
+ if (sent !== false) {
1633
+ sent = true;
1634
+ } else {
1635
+ sent = Boolean(this._native?.isOpen);
1636
+ }
1637
+ if (sent && this._pairedChannel && view.byteLength > 0) {
1638
+ this._pendingPairedDeliveryBytes += view.byteLength;
1639
+ }
1612
1640
  return sent;
1613
1641
  } catch (error) {
1614
1642
  throw mapNativeError(error, "OperationError");
1615
1643
  }
1616
1644
  }
1617
1645
 
1618
- _tryNativeSend(send) {
1619
- const sent = send();
1620
- if (sent !== false) return true;
1621
- return Boolean(this._native?.isOpen);
1622
- }
1623
-
1624
1646
  _usesSyntheticPairDelivery() {
1625
1647
  return this._syntheticIncoming || this._pairedChannel?._syntheticIncoming;
1626
1648
  }
@@ -1628,10 +1650,10 @@ class RTCDataChannel extends SimpleEventTarget {
1628
1650
  _deliverSyntheticMessage(event) {
1629
1651
  const target = this._pairedChannel;
1630
1652
  if (!target || target.readyState === "closed") return;
1631
- setTimeout(() => {
1653
+ setImmediate(() => {
1632
1654
  if (target.readyState === "closed") return;
1633
1655
  target._handleNativeEvent({ type: "message", ...event });
1634
- }, 0);
1656
+ });
1635
1657
  }
1636
1658
 
1637
1659
  _trackPendingPairedDelivery(size) {
@@ -1645,6 +1667,7 @@ class RTCDataChannel extends SimpleEventTarget {
1645
1667
  const size = event.binary ? event.data?.byteLength || 0 : byteLength(event.data || "");
1646
1668
  if (size <= 0) return;
1647
1669
  sender._pendingPairedDeliveryBytes = Math.max(0, sender._pendingPairedDeliveryBytes - size);
1670
+ sender._decreaseBufferedAmount(size);
1648
1671
  }
1649
1672
 
1650
1673
  _decreaseBufferedAmount(size) {
@@ -1839,6 +1862,9 @@ class RTCDataChannel extends SimpleEventTarget {
1839
1862
  if (this._pairedChannel?._pairedChannel === this) this._pairedChannel._pairedChannel = null;
1840
1863
  this._pairedChannel = null;
1841
1864
  this._pc._unregisterDataChannelId(this);
1865
+ if (this._pc._channels.get(this._native.bindingId) === this) {
1866
+ this._pc._channels.delete(this._native.bindingId);
1867
+ }
1842
1868
  if (!this._createdLocally && id != null) {
1843
1869
  this._pc._remoteAnnouncedDataChannelIds.delete(id);
1844
1870
  this._pc._pendingSyntheticDataChannelAnnouncements.delete(id);
@@ -1921,10 +1947,10 @@ class RTCDataChannel extends SimpleEventTarget {
1921
1947
  return;
1922
1948
  }
1923
1949
  this._handleNativeEvent(event, true);
1924
- if (this._queuedNativeEvents.length) setTimeout(dispatchNext, 0);
1950
+ if (this._queuedNativeEvents.length) setImmediate(dispatchNext);
1925
1951
  else this._nativeEventDrainActive = false;
1926
1952
  };
1927
- setTimeout(dispatchNext, 0);
1953
+ setImmediate(dispatchNext);
1928
1954
  }
1929
1955
 
1930
1956
  _queueMessageEvent(event) {
@@ -1932,39 +1958,175 @@ class RTCDataChannel extends SimpleEventTarget {
1932
1958
  this._scheduleMessageEventFlush();
1933
1959
  }
1934
1960
 
1961
+ _queueMessageEvents(events) {
1962
+ for (const event of events) this._queuedMessageEvents.push(event);
1963
+ this._scheduleMessageEventFlush();
1964
+ }
1965
+
1935
1966
  _scheduleMessageEventFlush() {
1936
1967
  if (this._messageEventFlushScheduled) return;
1937
1968
  this._messageEventFlushScheduled = true;
1938
- setTimeout(() => {
1969
+ setImmediate(() => {
1939
1970
  this._messageEventFlushScheduled = false;
1940
1971
  this._flushQueuedMessageEvents();
1941
- }, 0);
1972
+ });
1973
+ }
1974
+
1975
+ _scheduleMessageEventContinuation({ yieldToTask = false } = {}) {
1976
+ if (this._messageEventFlushScheduled) return;
1977
+ this._messageEventFlushScheduled = true;
1978
+ const schedule = yieldToTask ? setTimeout : setImmediate;
1979
+ schedule(
1980
+ () => {
1981
+ this._messageEventFlushScheduled = false;
1982
+ this._flushQueuedMessageEvents();
1983
+ },
1984
+ yieldToTask ? 1 : 0,
1985
+ );
1986
+ }
1987
+
1988
+ _messageContinuationNeedsTaskYield(handler = this._onmessage) {
1989
+ if (typeof handler !== "function") return false;
1990
+ try {
1991
+ return /\[native code\]/.test(Function.prototype.toString.call(handler));
1992
+ } catch {
1993
+ return false;
1994
+ }
1995
+ }
1996
+
1997
+ _hasMessageEventListeners() {
1998
+ return this._messageEventListenerCount > 0;
1999
+ }
2000
+
2001
+ _canContinueMessageBatchInCurrentTask(handler, yieldToTask) {
2002
+ return (
2003
+ typeof handler === "function" &&
2004
+ handler instanceof Function &&
2005
+ this._onmessage === handler &&
2006
+ !yieldToTask &&
2007
+ !this._hasMessageEventListeners()
2008
+ );
2009
+ }
2010
+
2011
+ _canUseDirectMessageHandler(handler, yieldToTask, hasMessageListeners) {
2012
+ return !hasMessageListeners && this._canContinueMessageBatchInCurrentTask(handler, yieldToTask);
2013
+ }
2014
+
2015
+ _dispatchMessageEvent(event, directHandler) {
2016
+ const messageEvent = makeMessageEvent("message", { data: this._convertMessage(event) });
2017
+ if (directHandler) {
2018
+ messageEvent.target = this;
2019
+ messageEvent.currentTarget = this;
2020
+ callListener(directHandler, this, messageEvent);
2021
+ } else {
2022
+ this.dispatchEvent(messageEvent);
2023
+ }
1942
2024
  }
1943
2025
 
1944
2026
  _flushQueuedMessageEvents() {
1945
- if (this._readyState === "closed") {
1946
- this._queuedMessageEvents.length = 0;
1947
- return;
2027
+ while (true) {
2028
+ if (this._readyState === "closed") {
2029
+ this._queuedMessageEvents.length = 0;
2030
+ this._queuedMessageEventIndex = 0;
2031
+ return;
2032
+ }
2033
+
2034
+ const pendingPeer =
2035
+ this._pc._operationsPending > 0
2036
+ ? this._pc
2037
+ : this._pc._pairedPeer?._operationsPending > 0
2038
+ ? this._pc._pairedPeer
2039
+ : null;
2040
+ if (pendingPeer) {
2041
+ pendingPeer._afterOperationsIdle(() => this._scheduleMessageEventFlush());
2042
+ return;
2043
+ }
2044
+
2045
+ if (this._messageEventGateActive) return;
2046
+ if (this._messageConsumerGateActive && !this._hasEventConsumer("message")) return;
2047
+ if (this._messageConsumerGateActive) this._messageConsumerGateActive = false;
2048
+
2049
+ const event = this._queuedMessageEvents[this._queuedMessageEventIndex++];
2050
+ if (!event) {
2051
+ this._queuedMessageEventIndex = 0;
2052
+ return;
2053
+ }
2054
+ const handler = this._onmessage;
2055
+ const hasMessageListeners = this._hasMessageEventListeners();
2056
+ const yieldToTask = this._messageHandlerNeedsTaskYield;
2057
+ const directHandler = this._canUseDirectMessageHandler(
2058
+ handler,
2059
+ yieldToTask,
2060
+ hasMessageListeners,
2061
+ )
2062
+ ? handler
2063
+ : null;
2064
+ this._dispatchMessageEvent(event, directHandler);
2065
+ this._markPairedDeliveryReceived(event);
2066
+
2067
+ if (this._queuedMessageEventIndex >= this._queuedMessageEvents.length) {
2068
+ this._queuedMessageEvents.length = 0;
2069
+ this._queuedMessageEventIndex = 0;
2070
+ return;
2071
+ }
2072
+ if (this._queuedMessageEventIndex > 1024) {
2073
+ this._queuedMessageEvents.splice(0, this._queuedMessageEventIndex);
2074
+ this._queuedMessageEventIndex = 0;
2075
+ }
2076
+ if (!this._canContinueMessageBatchInCurrentTask(handler, yieldToTask)) {
2077
+ this._scheduleMessageEventContinuation({ yieldToTask });
2078
+ return;
2079
+ }
1948
2080
  }
1949
- if (!this._queuedMessageEvents.length) return;
2081
+ }
2082
+
2083
+ _dispatchNativeMessageBatch(events) {
2084
+ if (this._readyState === "closed") return true;
1950
2085
  const pendingPeer =
1951
2086
  this._pc._operationsPending > 0
1952
2087
  ? this._pc
1953
2088
  : this._pc._pairedPeer?._operationsPending > 0
1954
2089
  ? this._pc._pairedPeer
1955
2090
  : null;
1956
- if (pendingPeer) {
1957
- pendingPeer._afterOperationsIdle(() => this._scheduleMessageEventFlush());
1958
- return;
1959
- }
1960
- if (this._messageEventGateActive) return;
1961
- if (this._messageConsumerGateActive && !this._hasEventConsumer("message")) return;
2091
+ if (pendingPeer || this._messageEventGateActive) return false;
2092
+ if (this._messageConsumerGateActive && !this._hasEventConsumer("message")) return false;
1962
2093
  if (this._messageConsumerGateActive) this._messageConsumerGateActive = false;
1963
2094
 
1964
- const event = this._queuedMessageEvents.shift();
1965
- this.dispatchEvent(makeMessageEvent("message", { data: this._convertMessage(event) }));
1966
- this._markPairedDeliveryReceived(event);
1967
- if (this._queuedMessageEvents.length) this._scheduleMessageEventFlush();
2095
+ const handler = this._onmessage;
2096
+ const hasMessageListeners = this._hasMessageEventListeners();
2097
+ const yieldToTask = this._messageHandlerNeedsTaskYield;
2098
+ const directHandler = this._canUseDirectMessageHandler(
2099
+ handler,
2100
+ yieldToTask,
2101
+ hasMessageListeners,
2102
+ )
2103
+ ? handler
2104
+ : null;
2105
+ if (!directHandler) return false;
2106
+
2107
+ const shouldCreateBlob = this._binaryType === "blob" && typeof Blob !== "undefined";
2108
+ for (let index = 0; index < events.length; index += 1) {
2109
+ const event = events[index];
2110
+ const messageEvent = makeMessageEvent("message", {
2111
+ data: event.binary && shouldCreateBlob ? new Blob([event.data]) : event.data,
2112
+ });
2113
+ messageEvent.target = this;
2114
+ messageEvent.currentTarget = this;
2115
+ directHandler.call(this, messageEvent);
2116
+ this._markPairedDeliveryReceived(event);
2117
+ if (this._readyState === "closed") return true;
2118
+ if (
2119
+ index + 1 < events.length &&
2120
+ !this._canContinueMessageBatchInCurrentTask(handler, yieldToTask)
2121
+ ) {
2122
+ for (let remaining = index + 1; remaining < events.length; remaining += 1) {
2123
+ this._queuedMessageEvents.push(events[remaining]);
2124
+ }
2125
+ this._scheduleMessageEventContinuation({ yieldToTask: this._messageHandlerNeedsTaskYield });
2126
+ return true;
2127
+ }
2128
+ }
2129
+ return true;
1968
2130
  }
1969
2131
 
1970
2132
  _gateMessageEventsAfterAnnouncement() {
@@ -1991,7 +2153,15 @@ class RTCDataChannel extends SimpleEventTarget {
1991
2153
  }
1992
2154
 
1993
2155
  _eventListenerAdded(type) {
1994
- if (type === "message") this._releaseMessageConsumerGate();
2156
+ if (type !== "message") return;
2157
+ this._messageEventListenerCount += 1;
2158
+ this._releaseMessageConsumerGate();
2159
+ }
2160
+
2161
+ _eventListenerRemoved(type) {
2162
+ if (type === "message" && this._messageEventListenerCount > 0) {
2163
+ this._messageEventListenerCount -= 1;
2164
+ }
1995
2165
  }
1996
2166
 
1997
2167
  _convertMessage(event) {
@@ -2004,6 +2174,7 @@ class RTCDataChannel extends SimpleEventTarget {
2004
2174
  }
2005
2175
 
2006
2176
  function toUint8Array(data) {
2177
+ if (data instanceof Uint8Array) return data;
2007
2178
  if (data instanceof ArrayBuffer) return new Uint8Array(data);
2008
2179
  if (ArrayBuffer.isView(data)) {
2009
2180
  return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
@@ -2024,6 +2195,7 @@ class RTCPeerConnection extends SimpleEventTarget {
2024
2195
  this._configuration = normalizedConfiguration;
2025
2196
  this._channels = new Map();
2026
2197
  this._usedDataChannelIds = new Map();
2198
+ this._dataChannelIdRefreshNeeded = false;
2027
2199
  this._closed = false;
2028
2200
  this._localDescription = null;
2029
2201
  this._remoteDescription = null;
@@ -2049,6 +2221,8 @@ class RTCPeerConnection extends SimpleEventTarget {
2049
2221
  this._lastCreatedOffer = null;
2050
2222
  this._lastCreatedAnswer = null;
2051
2223
  this._localDescriptionSetByApi = false;
2224
+ this._localDescriptionRefreshScheduled = false;
2225
+ this._nativeCandidateGatheringScheduled = false;
2052
2226
  this._iceRestartPending = false;
2053
2227
  this._pendingIceRestartCredentials = null;
2054
2228
  this._jsOnlyIceRestartOfferPending = false;
@@ -2105,9 +2279,13 @@ class RTCPeerConnection extends SimpleEventTarget {
2105
2279
  nativeCertificates = [createDefaultNativeCertificate()];
2106
2280
  nativeConfiguration.certificates = nativeCertificates;
2107
2281
  }
2108
- const nativePeerConnection = new native.NativePeerConnection(nativeConfiguration, (event) =>
2109
- this._handleNativeEvent(event),
2110
- );
2282
+ const nativePeerConnection = new native.NativePeerConnection(nativeConfiguration, (event) => {
2283
+ if (Array.isArray(event)) {
2284
+ this._handleNativeEventBatch(event);
2285
+ } else {
2286
+ this._handleNativeEvent(event);
2287
+ }
2288
+ });
2111
2289
  this._nativeCertificates = nativeCertificates;
2112
2290
  this._native = nativePeerConnection;
2113
2291
  return this._native;
@@ -2516,6 +2694,7 @@ class RTCPeerConnection extends SimpleEventTarget {
2516
2694
  ? new RTCSessionDescription(nativeDescription)
2517
2695
  : null;
2518
2696
  this._syncStatesFromNative();
2697
+ this._scheduleNativeCandidateGathering();
2519
2698
  }
2520
2699
  }
2521
2700
  if (type === "offer") {
@@ -2557,6 +2736,7 @@ class RTCPeerConnection extends SimpleEventTarget {
2557
2736
  normalized.type === "answer" &&
2558
2737
  this._signalingState !== "have-local-offer" &&
2559
2738
  this._signalingState !== "have-remote-pranswer" &&
2739
+ !this._jsOnlyIceRestartOfferPending &&
2560
2740
  this._operationsPending === 0
2561
2741
  ) {
2562
2742
  throw makeDOMException(
@@ -2592,7 +2772,8 @@ class RTCPeerConnection extends SimpleEventTarget {
2592
2772
  if (
2593
2773
  normalized.type === "answer" &&
2594
2774
  this._signalingState !== "have-local-offer" &&
2595
- this._signalingState !== "have-remote-pranswer"
2775
+ this._signalingState !== "have-remote-pranswer" &&
2776
+ !this._jsOnlyIceRestartOfferPending
2596
2777
  ) {
2597
2778
  throw makeDOMException(
2598
2779
  "Cannot set a remote answer in current signaling state",
@@ -2879,9 +3060,9 @@ class RTCPeerConnection extends SimpleEventTarget {
2879
3060
  }
2880
3061
  }, PEER_CONNECTION_NATIVE_CLOSE_DELAY_MS);
2881
3062
  }
2882
- setTimeout(() => {
3063
+ setImmediate(() => {
2883
3064
  if (pairedPeer && !pairedPeer._closed) pairedPeer._handleRemotePeerClosed();
2884
- }, 0);
3065
+ });
2885
3066
  this._connectionState = "closed";
2886
3067
  this._iceConnectionState = "closed";
2887
3068
  this._deferredIceEvents = [];
@@ -3088,12 +3269,12 @@ class RTCPeerConnection extends SimpleEventTarget {
3088
3269
  _scheduleSctpConnectedTransition() {
3089
3270
  if (this._sctpConnectedTransitionScheduled) return;
3090
3271
  this._sctpConnectedTransitionScheduled = true;
3091
- setTimeout(() => {
3272
+ setImmediate(() => {
3092
3273
  this._sctpConnectedTransitionScheduled = false;
3093
3274
  if (this._closed || !this._sctpTransport) return;
3094
3275
  this._sctpConnectedTransitionReady = true;
3095
3276
  this._updateSctpTransport();
3096
- }, 0);
3277
+ });
3097
3278
  }
3098
3279
 
3099
3280
  _shouldRepairConnectedState() {
@@ -3205,13 +3386,13 @@ class RTCPeerConnection extends SimpleEventTarget {
3205
3386
  this._pairedPeer?._flushPendingRemoteCandidatesForNative();
3206
3387
  this._scheduleDeferredIceEventFlush();
3207
3388
  const callbacks = this._operationIdleCallbacks.splice(0);
3208
- for (const callback of callbacks) setTimeout(callback, 0);
3389
+ for (const callback of callbacks) setImmediate(callback);
3209
3390
  }
3210
3391
  }
3211
3392
 
3212
3393
  _afterOperationsIdle(callback) {
3213
3394
  if (this._closed || this._operationsPending === 0) {
3214
- setTimeout(callback, 0);
3395
+ setImmediate(callback);
3215
3396
  return;
3216
3397
  }
3217
3398
  this._operationIdleCallbacks.push(callback);
@@ -3225,7 +3406,7 @@ class RTCPeerConnection extends SimpleEventTarget {
3225
3406
  _scheduleDeferredIceEventFlush() {
3226
3407
  if (this._iceEventFlushScheduled || this._deferredIceEvents.length === 0) return;
3227
3408
  this._iceEventFlushScheduled = true;
3228
- setTimeout(() => {
3409
+ setImmediate(() => {
3229
3410
  this._iceEventFlushScheduled = false;
3230
3411
  if (this._closed) {
3231
3412
  this._deferredIceEvents = [];
@@ -3245,7 +3426,7 @@ class RTCPeerConnection extends SimpleEventTarget {
3245
3426
  }
3246
3427
  if (this._deferredIceEvents.length) this._scheduleDeferredIceEventFlush();
3247
3428
  else this._flushIceEventIdleCallbacks();
3248
- }, 0);
3429
+ });
3249
3430
  }
3250
3431
 
3251
3432
  _hasPendingDeferredIceEvents() {
@@ -3258,7 +3439,7 @@ class RTCPeerConnection extends SimpleEventTarget {
3258
3439
 
3259
3440
  _afterDeferredIceEventsFlushed(callback) {
3260
3441
  if (this._closed || !this._hasPendingDeferredIceEvents()) {
3261
- setTimeout(callback, 0);
3442
+ setImmediate(callback);
3262
3443
  return;
3263
3444
  }
3264
3445
  this._iceEventIdleCallbacks.push(callback);
@@ -3268,7 +3449,7 @@ class RTCPeerConnection extends SimpleEventTarget {
3268
3449
  _flushIceEventIdleCallbacks() {
3269
3450
  if (this._hasPendingDeferredIceEvents()) return;
3270
3451
  const callbacks = this._iceEventIdleCallbacks.splice(0);
3271
- for (const callback of callbacks) setTimeout(callback, 0);
3452
+ for (const callback of callbacks) setImmediate(callback);
3272
3453
  }
3273
3454
 
3274
3455
  _queueSyntheticIceRestartGathering() {
@@ -3285,7 +3466,7 @@ class RTCPeerConnection extends SimpleEventTarget {
3285
3466
  _scheduleSctpTransportUpdate() {
3286
3467
  if (this._sctpTransportUpdateScheduled) return;
3287
3468
  this._sctpTransportUpdateScheduled = true;
3288
- setTimeout(() => {
3469
+ setImmediate(() => {
3289
3470
  this._sctpTransportUpdateScheduled = false;
3290
3471
  if (
3291
3472
  !this._closed &&
@@ -3297,7 +3478,7 @@ class RTCPeerConnection extends SimpleEventTarget {
3297
3478
  this._iceConnectionState = this._native.iceConnectionState;
3298
3479
  }
3299
3480
  this._updateSctpTransport();
3300
- }, 0);
3481
+ });
3301
3482
  }
3302
3483
 
3303
3484
  _scheduleSctpConnectPollIfNeeded() {
@@ -3771,6 +3952,7 @@ class RTCPeerConnection extends SimpleEventTarget {
3771
3952
  ? new RTCSessionDescription(nativeDescription)
3772
3953
  : null;
3773
3954
  this._syncStatesFromNative();
3955
+ this._scheduleNativeCandidateGathering();
3774
3956
  for (const candidate of this._pendingIce.splice(0)) await this.addIceCandidate(candidate);
3775
3957
  this._flushPendingRemoteCandidatesForNative();
3776
3958
  await nextTask();
@@ -3814,11 +3996,23 @@ class RTCPeerConnection extends SimpleEventTarget {
3814
3996
  await nextTask();
3815
3997
  }
3816
3998
 
3817
- async _refreshLocalDescriptionAfterGatheringWindow() {
3999
+ _scheduleNativeCandidateGathering() {
4000
+ if (this._closed || !this._native || !hasDataMediaSection(this._localDescription)) return;
4001
+ if (this._nativeCandidateGatheringScheduled) return;
4002
+ this._nativeCandidateGatheringScheduled = true;
4003
+ setImmediate(() => {
4004
+ this._nativeCandidateGatheringScheduled = false;
4005
+ if (this._closed || !this._native || !hasDataMediaSection(this._localDescription)) return;
4006
+ try {
4007
+ this._native.gatherLocalCandidates();
4008
+ } catch {
4009
+ // Gathering may already be complete or the native transport may be closing.
4010
+ }
4011
+ });
4012
+ }
4013
+
4014
+ _refreshLocalDescriptionFromNative() {
3818
4015
  if (this._closed || !this._localDescription) return;
3819
- if (this._iceGatheringState !== "complete") {
3820
- await delay(50);
3821
- }
3822
4016
  if (!this._native) return;
3823
4017
  const nativeDescription = this._native.localDescription();
3824
4018
  if (nativeDescription) {
@@ -3828,12 +4022,27 @@ class RTCPeerConnection extends SimpleEventTarget {
3828
4022
  this._syncStatesFromNative();
3829
4023
  }
3830
4024
 
4025
+ _refreshLocalDescriptionAfterGatheringWindow() {
4026
+ this._refreshLocalDescriptionFromNative();
4027
+ if (this._closed || !this._localDescription || this._iceGatheringState === "complete") return;
4028
+ if (this._localDescriptionRefreshScheduled) return;
4029
+ this._localDescriptionRefreshScheduled = true;
4030
+ setTimeout(() => {
4031
+ this._localDescriptionRefreshScheduled = false;
4032
+ this._refreshLocalDescriptionFromNative();
4033
+ }, 50);
4034
+ }
4035
+
3831
4036
  _syncStatesFromNative() {
3832
4037
  if (this._closed) return;
3833
4038
  if (!this._native) return;
3834
4039
  this._connectionState = this._native.connectionState;
3835
4040
  this._iceConnectionState = this._native.iceConnectionState;
3836
- this._signalingState = this._native.signalingState;
4041
+ if (this._jsOnlyIceRestartOfferPending) {
4042
+ this._signalingState = "have-local-offer";
4043
+ } else {
4044
+ this._signalingState = this._native.signalingState;
4045
+ }
3837
4046
  this._refreshIceRole();
3838
4047
  this._updateSctpTransport();
3839
4048
  }
@@ -3936,6 +4145,10 @@ class RTCPeerConnection extends SimpleEventTarget {
3936
4145
  this._closeChannelsOnPeerFailure();
3937
4146
  break;
3938
4147
  case "signalingstatechange":
4148
+ if (this._jsOnlyIceRestartOfferPending) {
4149
+ this._signalingState = "have-local-offer";
4150
+ break;
4151
+ }
3939
4152
  this._signalingState = event.state;
3940
4153
  if (this._suppressNextNativeSignalingState === event.state) {
3941
4154
  this._suppressNextNativeSignalingState = null;
@@ -3946,6 +4159,43 @@ class RTCPeerConnection extends SimpleEventTarget {
3946
4159
  }
3947
4160
  }
3948
4161
 
4162
+ _handleNativeEventBatch(events) {
4163
+ if (!events.length) return;
4164
+ const first = events[0];
4165
+ if (first?.target !== "datachannel" || first.type !== "message" || first.channelId == null) {
4166
+ for (const event of events) this._handleNativeEvent(event);
4167
+ return;
4168
+ }
4169
+
4170
+ const channelId = first.channelId;
4171
+ for (let index = 1; index < events.length; index += 1) {
4172
+ const event = events[index];
4173
+ if (
4174
+ event?.target !== "datachannel" ||
4175
+ event.type !== "message" ||
4176
+ event.channelId !== channelId
4177
+ ) {
4178
+ for (const item of events) this._handleNativeEvent(item);
4179
+ return;
4180
+ }
4181
+ }
4182
+
4183
+ const channel = this._channels.get(channelId);
4184
+ if (channel) {
4185
+ if (channel._announcementPending || channel._nativeEventDrainActive) {
4186
+ for (const event of events) channel._queuedNativeEvents.push(event);
4187
+ return;
4188
+ }
4189
+ if (channel._dispatchNativeMessageBatch(events)) return;
4190
+ channel._queueMessageEvents(events);
4191
+ return;
4192
+ }
4193
+
4194
+ const pending = this._pendingNativeDataChannelEvents.get(channelId) || [];
4195
+ for (const event of events) pending.push(event);
4196
+ this._pendingNativeDataChannelEvents.set(channelId, pending);
4197
+ }
4198
+
3949
4199
  _registerDataChannelId(channel, id = channel._effectiveId()) {
3950
4200
  if (id == null) return;
3951
4201
  if (channel._registeredDataChannelId === id) return;
@@ -3980,10 +4230,22 @@ class RTCPeerConnection extends SimpleEventTarget {
3980
4230
  }
3981
4231
 
3982
4232
  _isDataChannelIdInUse(id) {
3983
- for (const channel of this._channels.values()) {
3984
- this._registerDataChannelId(channel, channel._effectiveId());
4233
+ let channel = this._usedDataChannelIds.get(id);
4234
+ if (channel && channel.readyState !== "closed") return true;
4235
+ if (!this._dataChannelIdRefreshNeeded) return false;
4236
+
4237
+ let refreshStillNeeded = false;
4238
+ for (const currentChannel of this._channels.values()) {
4239
+ if (currentChannel.readyState === "closed") continue;
4240
+ const effectiveId = currentChannel._effectiveId();
4241
+ if (effectiveId == null) {
4242
+ refreshStillNeeded = true;
4243
+ continue;
4244
+ }
4245
+ this._registerDataChannelId(currentChannel, effectiveId);
3985
4246
  }
3986
- const channel = this._usedDataChannelIds.get(id);
4247
+ this._dataChannelIdRefreshNeeded = refreshStillNeeded;
4248
+ channel = this._usedDataChannelIds.get(id);
3987
4249
  return Boolean(channel && channel.readyState !== "closed");
3988
4250
  }
3989
4251
 
@@ -4069,6 +4331,7 @@ class RTCPeerConnection extends SimpleEventTarget {
4069
4331
  const events = this._pendingDataChannelEvents.splice(0);
4070
4332
  for (const event of events) {
4071
4333
  if (this._closed) return;
4334
+ event.channel._gateMessageEventsUntilConsumer();
4072
4335
  this.dispatchEvent(event);
4073
4336
  event.channel._announcementPending = false;
4074
4337
  event.channel._announceOpenAfterDataChannelEvent();