@afterrealism/dendri-client 2.3.7 → 2.4.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.
@@ -3529,7 +3529,7 @@ var Util = class extends BinaryPackChunker {
3529
3529
  var util = new Util();
3530
3530
 
3531
3531
  // src/api.ts
3532
- var version = "2.3.7";
3532
+ var version = "2.4.0";
3533
3533
  var API = class _API {
3534
3534
  constructor(_options) {
3535
3535
  this._options = _options;
@@ -3566,11 +3566,11 @@ var API = class _API {
3566
3566
  throw new Error(`Could not get an ID from the server.${pathError}`);
3567
3567
  }
3568
3568
  }
3569
- /** Fetch TURN credentials from the signaling server's GET /turn endpoint. */
3569
+ /** Fetch TURN credentials from the signaling server's turn-credentials endpoint. */
3570
3570
  async getTurnCredentials() {
3571
3571
  const protocol = this._options.secure ? "https" : "http";
3572
- const { host, port } = this._options;
3573
- const url = `${protocol}://${host}:${port}/turn`;
3572
+ const { host, port, path, key } = this._options;
3573
+ const url = `${protocol}://${host}:${port}${path}${key}/turn-credentials`;
3574
3574
  try {
3575
3575
  const controller = new AbortController();
3576
3576
  const timeoutId = setTimeout(() => controller.abort(), _API.FETCH_TIMEOUT);
@@ -3679,6 +3679,9 @@ var ServerMessageType = /* @__PURE__ */ ((ServerMessageType2) => {
3679
3679
  ServerMessageType2["HostMigrate"] = "HOST-MIGRATE";
3680
3680
  ServerMessageType2["PresenceUpdate"] = "PRESENCE-UPDATE";
3681
3681
  ServerMessageType2["KeyExchange"] = "KEY-EXCHANGE";
3682
+ ServerMessageType2["ConnectRequest"] = "CONNECT-REQUEST";
3683
+ ServerMessageType2["DcutrConnect"] = "DCUTR-CONNECT";
3684
+ ServerMessageType2["DcutrSync"] = "DCUTR-SYNC";
3682
3685
  return ServerMessageType2;
3683
3686
  })(ServerMessageType || {});
3684
3687
  var TransportMode = /* @__PURE__ */ ((TransportMode2) => {
@@ -3704,6 +3707,11 @@ var ConnectionQuality = /* @__PURE__ */ ((ConnectionQuality2) => {
3704
3707
  ConnectionQuality2["Unknown"] = "unknown";
3705
3708
  return ConnectionQuality2;
3706
3709
  })(ConnectionQuality || {});
3710
+ var TopicClass = /* @__PURE__ */ ((TopicClass2) => {
3711
+ TopicClass2["Persistent"] = "persistent";
3712
+ TopicClass2["Ephemeral"] = "ephemeral";
3713
+ return TopicClass2;
3714
+ })(TopicClass || {});
3707
3715
 
3708
3716
  // src/dendriError.ts
3709
3717
  var import_eventemitter3 = __toESM(require_eventemitter3(), 1);
@@ -3792,6 +3800,7 @@ var Negotiator = class {
3792
3800
  }
3793
3801
  connection;
3794
3802
  _pendingCandidates = [];
3803
+ _iceCandidateFilter = null;
3795
3804
  /** Returns a PeerConnection object set up correctly (for data, media). */
3796
3805
  startConnection(options) {
3797
3806
  const peerConnection = this._startPeerConnection();
@@ -3814,6 +3823,13 @@ var Negotiator = class {
3814
3823
  _startPeerConnection() {
3815
3824
  logger_default.log("Creating RTCPeerConnection.");
3816
3825
  const peerConnection = new RTCPeerConnection(this.connection.provider?.options.config);
3826
+ if (this.connection.provider?.options.ipPolicy === "public") {
3827
+ const isPublicCandidate = (c) => {
3828
+ const sdp2 = c.candidate ?? "";
3829
+ return !sdp2.includes("typ host");
3830
+ };
3831
+ this._iceCandidateFilter = isPublicCandidate;
3832
+ }
3817
3833
  this._setupListeners(peerConnection);
3818
3834
  return peerConnection;
3819
3835
  }
@@ -3826,6 +3842,9 @@ var Negotiator = class {
3826
3842
  logger_default.log("Listening for ICE candidates.");
3827
3843
  peerConnection.onicecandidate = (evt) => {
3828
3844
  if (!evt.candidate?.candidate) return;
3845
+ if (this._iceCandidateFilter && !this._iceCandidateFilter(evt.candidate)) {
3846
+ return;
3847
+ }
3829
3848
  logger_default.log(`Received ICE candidates for ${peerId}:`, evt.candidate);
3830
3849
  provider.socket.send({
3831
3850
  type: "CANDIDATE" /* Candidate */,
@@ -4164,6 +4183,7 @@ var DataConnection = class _DataConnection extends BaseConnection {
4164
4183
  this.dataChannel.onopen = () => {
4165
4184
  logger_default.log(`DC#${this.connectionId} dc connection success`);
4166
4185
  this._open = true;
4186
+ this._applyAdaptiveBuffer(dc);
4167
4187
  this.emit("open");
4168
4188
  };
4169
4189
  this.dataChannel.onclose = () => {
@@ -4171,6 +4191,24 @@ var DataConnection = class _DataConnection extends BaseConnection {
4171
4191
  this.close();
4172
4192
  };
4173
4193
  }
4194
+ _applyAdaptiveBuffer(dc) {
4195
+ const pc = this.peerConnection;
4196
+ if (!pc || typeof pc.getStats !== "function") return;
4197
+ pc.getStats().then((stats) => {
4198
+ let rtt = null;
4199
+ stats.forEach((report) => {
4200
+ if (report.type === "candidate-pair" && report.state === "succeeded" && report.currentRoundTripTime) {
4201
+ rtt = report.currentRoundTripTime * 1e3;
4202
+ }
4203
+ });
4204
+ if (rtt !== null) {
4205
+ const bdp = 12.5 * 1024 * 1024 * (rtt / 1e3);
4206
+ const optimal = Math.max(1 * 1024 * 1024, Math.min(32 * 1024 * 1024, Math.ceil(bdp)));
4207
+ dc.bufferedAmountLowThreshold = optimal;
4208
+ }
4209
+ }).catch(() => {
4210
+ });
4211
+ }
4174
4212
  /**
4175
4213
  * Exposed functionality for users.
4176
4214
  */
@@ -4705,7 +4743,17 @@ var HybridConnection = class extends import_eventemitter32.EventEmitter {
4705
4743
  if (this._closed) {
4706
4744
  return;
4707
4745
  }
4708
- this._attemptWebRTC();
4746
+ logger_default.log(
4747
+ `HybridConnection: start peer=${this.peer} iceTimeout=${this._options.iceTimeout ?? 1e4}ms encryptRelay=${this._encryptRelay}`
4748
+ );
4749
+ if (typeof this._provider?.on !== "function") {
4750
+ this._attemptWebRTC();
4751
+ return;
4752
+ }
4753
+ this._tryConnectionReversal().then((direct) => {
4754
+ if (direct) return;
4755
+ this._attemptWebRTC();
4756
+ });
4709
4757
  }
4710
4758
  /** Send data through the best available transport, optionally tagged with a topic. */
4711
4759
  send(data, options) {
@@ -4995,10 +5043,16 @@ var HybridConnection = class extends import_eventemitter32.EventEmitter {
4995
5043
  this._dataConnection = dc;
4996
5044
  this._iceTimer = setTimeout(() => {
4997
5045
  if (this._mode !== "webrtc" /* WebRTC */) {
5046
+ logger_default.warn(
5047
+ `HybridConnection: ICE timeout after ${iceTimeout}ms for ${this.peer}, falling back to relay`
5048
+ );
4998
5049
  this._fallbackToRelay();
4999
5050
  }
5000
5051
  }, iceTimeout);
5001
5052
  this._dataConnection.on("open", () => {
5053
+ logger_default.log(
5054
+ `HybridConnection: WebRTC opened to ${this.peer} (attempt ${this._upgradeAttempts + 1})`
5055
+ );
5002
5056
  this._clearIceTimer();
5003
5057
  this._clearUpgradeTimer();
5004
5058
  this._upgradeAttempts = 0;
@@ -5039,6 +5093,7 @@ var HybridConnection = class extends import_eventemitter32.EventEmitter {
5039
5093
  /** Update the transport mode and emit if changed. */
5040
5094
  _setMode(mode) {
5041
5095
  if (this._mode !== mode) {
5096
+ logger_default.log(`HybridConnection: transport ${this._mode} -> ${mode} for ${this.peer}`);
5042
5097
  this._mode = mode;
5043
5098
  this.emit("transportChanged", mode);
5044
5099
  }
@@ -5079,6 +5134,117 @@ var HybridConnection = class extends import_eventemitter32.EventEmitter {
5079
5134
  this._attemptWebRTC();
5080
5135
  }, interval);
5081
5136
  }
5137
+ async _tryConnectionReversal() {
5138
+ if (typeof this._provider?.on !== "function") return false;
5139
+ try {
5140
+ const resp = await new Promise((resolve, reject) => {
5141
+ const timer = setTimeout(() => reject(new Error("timeout")), 3e3);
5142
+ const handler = (data) => {
5143
+ clearTimeout(timer);
5144
+ this._provider.off("CONNECT-REQUEST" /* ConnectRequest */, handler);
5145
+ resolve(data);
5146
+ };
5147
+ this._provider.on("CONNECT-REQUEST" /* ConnectRequest */, handler);
5148
+ this._provider.socket.send({
5149
+ type: "CONNECT-REQUEST" /* ConnectRequest */,
5150
+ payload: { peer: this.peer }
5151
+ });
5152
+ });
5153
+ const addr = resp?.address;
5154
+ if (!addr) return false;
5155
+ const pc = new RTCPeerConnection(this._provider.options.config);
5156
+ const dc = pc.createDataChannel("probe", { id: 0 });
5157
+ await new Promise((resolve, reject) => {
5158
+ const timer = setTimeout(() => {
5159
+ pc.close();
5160
+ reject(new Error("direct-dial-timeout"));
5161
+ }, 2500);
5162
+ dc.onopen = () => {
5163
+ clearTimeout(timer);
5164
+ resolve();
5165
+ };
5166
+ dc.onerror = () => {
5167
+ clearTimeout(timer);
5168
+ pc.close();
5169
+ reject(new Error("dc-error"));
5170
+ };
5171
+ });
5172
+ this._dataConnection = void 0;
5173
+ this._setMode("webrtc" /* WebRTC */);
5174
+ pc.close();
5175
+ return true;
5176
+ } catch {
5177
+ return false;
5178
+ }
5179
+ }
5180
+ async _gatherSrflxCandidates() {
5181
+ const pc = new RTCPeerConnection({ iceServers: this._provider.options.config?.iceServers });
5182
+ pc.createDataChannel("probe");
5183
+ const offer = await pc.createOffer();
5184
+ await pc.setLocalDescription(offer);
5185
+ const candidates = [];
5186
+ await new Promise((resolve) => {
5187
+ const timer = setTimeout(resolve, 2e3);
5188
+ pc.onicecandidate = (evt) => {
5189
+ if (!evt.candidate) {
5190
+ clearTimeout(timer);
5191
+ resolve();
5192
+ return;
5193
+ }
5194
+ if (!evt.candidate.candidate.includes("typ host")) {
5195
+ candidates.push(evt.candidate.candidate);
5196
+ }
5197
+ };
5198
+ });
5199
+ pc.close();
5200
+ return candidates;
5201
+ }
5202
+ async _dcutrHolePunch() {
5203
+ try {
5204
+ const localAddrs = await this._gatherSrflxCandidates();
5205
+ const t0 = performance.now();
5206
+ const peerAddrs = await new Promise((resolve, reject) => {
5207
+ const timer = setTimeout(() => reject(new Error("dcutr-timeout")), 8e3);
5208
+ const handler = (data) => {
5209
+ clearTimeout(timer);
5210
+ this._provider.off("DCUTR-CONNECT" /* DcutrConnect */, handler);
5211
+ resolve(data?.addresses ?? []);
5212
+ };
5213
+ this._provider.on("DCUTR-CONNECT" /* DcutrConnect */, handler);
5214
+ this._provider.socket.send({
5215
+ type: "DCUTR-CONNECT" /* DcutrConnect */,
5216
+ payload: { addresses: localAddrs }
5217
+ });
5218
+ });
5219
+ const relayRtt = performance.now() - t0;
5220
+ this._provider.socket.send({ type: "DCUTR-SYNC" /* DcutrSync */, payload: {} });
5221
+ await new Promise((r) => setTimeout(r, relayRtt / 2));
5222
+ for (let i = 0; i < Math.min(peerAddrs.length, 4); i++) {
5223
+ try {
5224
+ const pc = new RTCPeerConnection(this._provider.options.config);
5225
+ await new Promise((resolve, reject) => {
5226
+ const timer = setTimeout(() => {
5227
+ pc.close();
5228
+ reject(new Error("dc-dial-timeout"));
5229
+ }, 5e3);
5230
+ const dc = pc.createDataChannel("dcutr");
5231
+ dc.onopen = () => {
5232
+ clearTimeout(timer);
5233
+ resolve();
5234
+ };
5235
+ });
5236
+ this._dataConnection = void 0;
5237
+ this._setMode("webrtc" /* WebRTC */);
5238
+ pc.close();
5239
+ return true;
5240
+ } catch {
5241
+ }
5242
+ }
5243
+ return false;
5244
+ } catch {
5245
+ return false;
5246
+ }
5247
+ }
5082
5248
  };
5083
5249
 
5084
5250
  // src/mediaconnection.ts
@@ -5375,7 +5541,7 @@ var PollingTransport = class _PollingTransport extends SignalingTransport {
5375
5541
  };
5376
5542
 
5377
5543
  // src/socket.ts
5378
- var version2 = "2.3.7";
5544
+ var version2 = "2.4.0";
5379
5545
  var Socket = class _Socket extends SignalingTransport {
5380
5546
  constructor(secure, host, port, path, key, pingInterval = 5e3, jwt) {
5381
5547
  super();
@@ -5417,7 +5583,7 @@ var Socket = class _Socket extends SignalingTransport {
5417
5583
  if (this._jwt) {
5418
5584
  wsUrl += `&jwt=${encodeURIComponent(this._jwt)}`;
5419
5585
  }
5420
- if (!!this._socket || !this._disconnected) {
5586
+ if (this._socket || !this._disconnected) {
5421
5587
  return;
5422
5588
  }
5423
5589
  this._socket = new WebSocket(`${wsUrl}&version=${version2}`);
@@ -6008,7 +6174,7 @@ var Dendri = class _Dendri extends EventEmitterWithError {
6008
6174
  return;
6009
6175
  }
6010
6176
  }
6011
- if (!!userId && !util.validateId(userId)) {
6177
+ if (userId && !util.validateId(userId)) {
6012
6178
  this._delayedAbort("invalid-id" /* InvalidID */, `ID "${userId}" is invalid`);
6013
6179
  return;
6014
6180
  }
@@ -6930,7 +7096,13 @@ var Room = class extends import_eventemitter35.EventEmitter {
6930
7096
  sentViaWebRTC = true;
6931
7097
  }
6932
7098
  }
6933
- if (!sentViaWebRTC && this._peer?.socket) {
7099
+ if (this._dendriOptions?.enableRelay && this._peer?.socket) {
7100
+ this._peer.socket.send({
7101
+ type: "DATA" /* Data */,
7102
+ room: this._roomId,
7103
+ payload: wire
7104
+ });
7105
+ } else if (!sentViaWebRTC && this._peer?.socket) {
6934
7106
  this._peer.socket.send({
6935
7107
  type: "DATA" /* Data */,
6936
7108
  room: this._roomId,
@@ -6964,7 +7136,13 @@ var Room = class extends import_eventemitter35.EventEmitter {
6964
7136
  sentViaWebRTC = true;
6965
7137
  }
6966
7138
  }
6967
- if (!sentViaWebRTC && this._peer?.socket) {
7139
+ if (this._dendriOptions?.enableRelay && this._peer?.socket) {
7140
+ this._peer.socket.send({
7141
+ type: "DATA" /* Data */,
7142
+ room: this._roomId,
7143
+ payload: wire
7144
+ });
7145
+ } else if (!sentViaWebRTC && this._peer?.socket) {
6968
7146
  this._peer.socket.send({
6969
7147
  type: "DATA" /* Data */,
6970
7148
  room: this._roomId,
@@ -7155,8 +7333,8 @@ var Room = class extends import_eventemitter35.EventEmitter {
7155
7333
  const remotePeerId = conn.peer;
7156
7334
  this._connections.set(remotePeerId, conn);
7157
7335
  this._knownPeers.add(remotePeerId);
7336
+ this.emit("peerJoined", remotePeerId);
7158
7337
  conn.on("open", () => {
7159
- this.emit("peerJoined", remotePeerId);
7160
7338
  for (const [peerId, c] of this._connections) {
7161
7339
  if (peerId !== remotePeerId && c.open) {
7162
7340
  c.send({ __room: { type: "peer-joined", peerId: remotePeerId } });
@@ -7478,6 +7656,14 @@ var Room = class extends import_eventemitter35.EventEmitter {
7478
7656
  const conn = this._connections.get(peerId);
7479
7657
  if (conn?.open) {
7480
7658
  conn.send(data);
7659
+ if (this._dendriOptions?.enableRelay && this._peer?.socket) {
7660
+ this._peer.socket.send({
7661
+ type: "DATA" /* Data */,
7662
+ dst: peerId,
7663
+ room: this._roomId,
7664
+ payload: data
7665
+ });
7666
+ }
7481
7667
  return;
7482
7668
  }
7483
7669
  if (!this._isHost && this._hostId) {
@@ -7780,6 +7966,6 @@ function createDendriStore(input) {
7780
7966
  };
7781
7967
  }
7782
7968
 
7783
- export { AckManager, BaseConnectionErrorType, BufferedConnection, ConnectionQuality, ConnectionState, ConnectionType, DataConnection, DataConnectionErrorType, Dendri, DendriError, DendriErrorType, HybridConnection, PollingTransport, PresenceManager, RelayEncryption, Room, RpcError, RpcErrorCode, RpcManager, SSETransport, SerializationType, ServerMessageType, SignalingTransport, SocketEventType, TopicManager, TransportMode, createDendriStore, electNewHost, isRpcRequest, isRpcResponse, isTopicEnvelope, logger_default, util };
7784
- //# sourceMappingURL=chunk-MJW5M75V.js.map
7785
- //# sourceMappingURL=chunk-MJW5M75V.js.map
7969
+ export { AckManager, BaseConnectionErrorType, BufferedConnection, ConnectionQuality, ConnectionState, ConnectionType, DataConnection, DataConnectionErrorType, Dendri, DendriError, DendriErrorType, HybridConnection, PollingTransport, PresenceManager, RelayEncryption, Room, RpcError, RpcErrorCode, RpcManager, SSETransport, SerializationType, ServerMessageType, SignalingTransport, SocketEventType, TopicClass, TopicManager, TransportMode, createDendriStore, electNewHost, isRpcRequest, isRpcResponse, isTopicEnvelope, logger_default, util };
7970
+ //# sourceMappingURL=chunk-MBMSG4EC.js.map
7971
+ //# sourceMappingURL=chunk-MBMSG4EC.js.map