@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.
@@ -3536,7 +3536,7 @@ var dendri = (() => {
3536
3536
  var util = new Util();
3537
3537
 
3538
3538
  // src/api.ts
3539
- var version = "2.3.7";
3539
+ var version = "2.4.0";
3540
3540
  var API = class _API {
3541
3541
  constructor(_options) {
3542
3542
  this._options = _options;
@@ -3573,11 +3573,11 @@ var dendri = (() => {
3573
3573
  throw new Error(`Could not get an ID from the server.${pathError}`);
3574
3574
  }
3575
3575
  }
3576
- /** Fetch TURN credentials from the signaling server's GET /turn endpoint. */
3576
+ /** Fetch TURN credentials from the signaling server's turn-credentials endpoint. */
3577
3577
  async getTurnCredentials() {
3578
3578
  const protocol = this._options.secure ? "https" : "http";
3579
- const { host, port } = this._options;
3580
- const url = `${protocol}://${host}:${port}/turn`;
3579
+ const { host, port, path, key } = this._options;
3580
+ const url = `${protocol}://${host}:${port}${path}${key}/turn-credentials`;
3581
3581
  try {
3582
3582
  const controller = new AbortController();
3583
3583
  const timeoutId = setTimeout(() => controller.abort(), _API.FETCH_TIMEOUT);
@@ -3706,6 +3706,7 @@ var dendri = (() => {
3706
3706
  }
3707
3707
  connection;
3708
3708
  _pendingCandidates = [];
3709
+ _iceCandidateFilter = null;
3709
3710
  /** Returns a PeerConnection object set up correctly (for data, media). */
3710
3711
  startConnection(options) {
3711
3712
  const peerConnection = this._startPeerConnection();
@@ -3728,6 +3729,13 @@ var dendri = (() => {
3728
3729
  _startPeerConnection() {
3729
3730
  logger_default.log("Creating RTCPeerConnection.");
3730
3731
  const peerConnection = new RTCPeerConnection(this.connection.provider?.options.config);
3732
+ if (this.connection.provider?.options.ipPolicy === "public") {
3733
+ const isPublicCandidate = (c) => {
3734
+ const sdp2 = c.candidate ?? "";
3735
+ return !sdp2.includes("typ host");
3736
+ };
3737
+ this._iceCandidateFilter = isPublicCandidate;
3738
+ }
3731
3739
  this._setupListeners(peerConnection);
3732
3740
  return peerConnection;
3733
3741
  }
@@ -3740,6 +3748,9 @@ var dendri = (() => {
3740
3748
  logger_default.log("Listening for ICE candidates.");
3741
3749
  peerConnection.onicecandidate = (evt) => {
3742
3750
  if (!evt.candidate?.candidate) return;
3751
+ if (this._iceCandidateFilter && !this._iceCandidateFilter(evt.candidate)) {
3752
+ return;
3753
+ }
3743
3754
  logger_default.log(`Received ICE candidates for ${peerId}:`, evt.candidate);
3744
3755
  provider.socket.send({
3745
3756
  type: "CANDIDATE" /* Candidate */,
@@ -4078,6 +4089,7 @@ var dendri = (() => {
4078
4089
  this.dataChannel.onopen = () => {
4079
4090
  logger_default.log(`DC#${this.connectionId} dc connection success`);
4080
4091
  this._open = true;
4092
+ this._applyAdaptiveBuffer(dc);
4081
4093
  this.emit("open");
4082
4094
  };
4083
4095
  this.dataChannel.onclose = () => {
@@ -4085,6 +4097,24 @@ var dendri = (() => {
4085
4097
  this.close();
4086
4098
  };
4087
4099
  }
4100
+ _applyAdaptiveBuffer(dc) {
4101
+ const pc = this.peerConnection;
4102
+ if (!pc || typeof pc.getStats !== "function") return;
4103
+ pc.getStats().then((stats) => {
4104
+ let rtt = null;
4105
+ stats.forEach((report) => {
4106
+ if (report.type === "candidate-pair" && report.state === "succeeded" && report.currentRoundTripTime) {
4107
+ rtt = report.currentRoundTripTime * 1e3;
4108
+ }
4109
+ });
4110
+ if (rtt !== null) {
4111
+ const bdp = 12.5 * 1024 * 1024 * (rtt / 1e3);
4112
+ const optimal = Math.max(1 * 1024 * 1024, Math.min(32 * 1024 * 1024, Math.ceil(bdp)));
4113
+ dc.bufferedAmountLowThreshold = optimal;
4114
+ }
4115
+ }).catch(() => {
4116
+ });
4117
+ }
4088
4118
  /**
4089
4119
  * Exposed functionality for users.
4090
4120
  */
@@ -4619,7 +4649,17 @@ var dendri = (() => {
4619
4649
  if (this._closed) {
4620
4650
  return;
4621
4651
  }
4622
- this._attemptWebRTC();
4652
+ logger_default.log(
4653
+ `HybridConnection: start peer=${this.peer} iceTimeout=${this._options.iceTimeout ?? 1e4}ms encryptRelay=${this._encryptRelay}`
4654
+ );
4655
+ if (typeof this._provider?.on !== "function") {
4656
+ this._attemptWebRTC();
4657
+ return;
4658
+ }
4659
+ this._tryConnectionReversal().then((direct) => {
4660
+ if (direct) return;
4661
+ this._attemptWebRTC();
4662
+ });
4623
4663
  }
4624
4664
  /** Send data through the best available transport, optionally tagged with a topic. */
4625
4665
  send(data, options) {
@@ -4909,10 +4949,16 @@ var dendri = (() => {
4909
4949
  this._dataConnection = dc;
4910
4950
  this._iceTimer = setTimeout(() => {
4911
4951
  if (this._mode !== "webrtc" /* WebRTC */) {
4952
+ logger_default.warn(
4953
+ `HybridConnection: ICE timeout after ${iceTimeout}ms for ${this.peer}, falling back to relay`
4954
+ );
4912
4955
  this._fallbackToRelay();
4913
4956
  }
4914
4957
  }, iceTimeout);
4915
4958
  this._dataConnection.on("open", () => {
4959
+ logger_default.log(
4960
+ `HybridConnection: WebRTC opened to ${this.peer} (attempt ${this._upgradeAttempts + 1})`
4961
+ );
4916
4962
  this._clearIceTimer();
4917
4963
  this._clearUpgradeTimer();
4918
4964
  this._upgradeAttempts = 0;
@@ -4953,6 +4999,7 @@ var dendri = (() => {
4953
4999
  /** Update the transport mode and emit if changed. */
4954
5000
  _setMode(mode) {
4955
5001
  if (this._mode !== mode) {
5002
+ logger_default.log(`HybridConnection: transport ${this._mode} -> ${mode} for ${this.peer}`);
4956
5003
  this._mode = mode;
4957
5004
  this.emit("transportChanged", mode);
4958
5005
  }
@@ -4993,6 +5040,117 @@ var dendri = (() => {
4993
5040
  this._attemptWebRTC();
4994
5041
  }, interval);
4995
5042
  }
5043
+ async _tryConnectionReversal() {
5044
+ if (typeof this._provider?.on !== "function") return false;
5045
+ try {
5046
+ const resp = await new Promise((resolve, reject) => {
5047
+ const timer = setTimeout(() => reject(new Error("timeout")), 3e3);
5048
+ const handler = (data) => {
5049
+ clearTimeout(timer);
5050
+ this._provider.off("CONNECT-REQUEST" /* ConnectRequest */, handler);
5051
+ resolve(data);
5052
+ };
5053
+ this._provider.on("CONNECT-REQUEST" /* ConnectRequest */, handler);
5054
+ this._provider.socket.send({
5055
+ type: "CONNECT-REQUEST" /* ConnectRequest */,
5056
+ payload: { peer: this.peer }
5057
+ });
5058
+ });
5059
+ const addr = resp?.address;
5060
+ if (!addr) return false;
5061
+ const pc = new RTCPeerConnection(this._provider.options.config);
5062
+ const dc = pc.createDataChannel("probe", { id: 0 });
5063
+ await new Promise((resolve, reject) => {
5064
+ const timer = setTimeout(() => {
5065
+ pc.close();
5066
+ reject(new Error("direct-dial-timeout"));
5067
+ }, 2500);
5068
+ dc.onopen = () => {
5069
+ clearTimeout(timer);
5070
+ resolve();
5071
+ };
5072
+ dc.onerror = () => {
5073
+ clearTimeout(timer);
5074
+ pc.close();
5075
+ reject(new Error("dc-error"));
5076
+ };
5077
+ });
5078
+ this._dataConnection = void 0;
5079
+ this._setMode("webrtc" /* WebRTC */);
5080
+ pc.close();
5081
+ return true;
5082
+ } catch {
5083
+ return false;
5084
+ }
5085
+ }
5086
+ async _gatherSrflxCandidates() {
5087
+ const pc = new RTCPeerConnection({ iceServers: this._provider.options.config?.iceServers });
5088
+ pc.createDataChannel("probe");
5089
+ const offer = await pc.createOffer();
5090
+ await pc.setLocalDescription(offer);
5091
+ const candidates = [];
5092
+ await new Promise((resolve) => {
5093
+ const timer = setTimeout(resolve, 2e3);
5094
+ pc.onicecandidate = (evt) => {
5095
+ if (!evt.candidate) {
5096
+ clearTimeout(timer);
5097
+ resolve();
5098
+ return;
5099
+ }
5100
+ if (!evt.candidate.candidate.includes("typ host")) {
5101
+ candidates.push(evt.candidate.candidate);
5102
+ }
5103
+ };
5104
+ });
5105
+ pc.close();
5106
+ return candidates;
5107
+ }
5108
+ async _dcutrHolePunch() {
5109
+ try {
5110
+ const localAddrs = await this._gatherSrflxCandidates();
5111
+ const t0 = performance.now();
5112
+ const peerAddrs = await new Promise((resolve, reject) => {
5113
+ const timer = setTimeout(() => reject(new Error("dcutr-timeout")), 8e3);
5114
+ const handler = (data) => {
5115
+ clearTimeout(timer);
5116
+ this._provider.off("DCUTR-CONNECT" /* DcutrConnect */, handler);
5117
+ resolve(data?.addresses ?? []);
5118
+ };
5119
+ this._provider.on("DCUTR-CONNECT" /* DcutrConnect */, handler);
5120
+ this._provider.socket.send({
5121
+ type: "DCUTR-CONNECT" /* DcutrConnect */,
5122
+ payload: { addresses: localAddrs }
5123
+ });
5124
+ });
5125
+ const relayRtt = performance.now() - t0;
5126
+ this._provider.socket.send({ type: "DCUTR-SYNC" /* DcutrSync */, payload: {} });
5127
+ await new Promise((r) => setTimeout(r, relayRtt / 2));
5128
+ for (let i = 0; i < Math.min(peerAddrs.length, 4); i++) {
5129
+ try {
5130
+ const pc = new RTCPeerConnection(this._provider.options.config);
5131
+ await new Promise((resolve, reject) => {
5132
+ const timer = setTimeout(() => {
5133
+ pc.close();
5134
+ reject(new Error("dc-dial-timeout"));
5135
+ }, 5e3);
5136
+ const dc = pc.createDataChannel("dcutr");
5137
+ dc.onopen = () => {
5138
+ clearTimeout(timer);
5139
+ resolve();
5140
+ };
5141
+ });
5142
+ this._dataConnection = void 0;
5143
+ this._setMode("webrtc" /* WebRTC */);
5144
+ pc.close();
5145
+ return true;
5146
+ } catch {
5147
+ }
5148
+ }
5149
+ return false;
5150
+ } catch {
5151
+ return false;
5152
+ }
5153
+ }
4996
5154
  };
4997
5155
 
4998
5156
  // src/mediaconnection.ts
@@ -5289,7 +5447,7 @@ var dendri = (() => {
5289
5447
  };
5290
5448
 
5291
5449
  // src/socket.ts
5292
- var version2 = "2.3.7";
5450
+ var version2 = "2.4.0";
5293
5451
  var Socket = class _Socket extends SignalingTransport {
5294
5452
  constructor(secure, host, port, path, key, pingInterval = 5e3, jwt) {
5295
5453
  super();
@@ -5331,7 +5489,7 @@ var dendri = (() => {
5331
5489
  if (this._jwt) {
5332
5490
  wsUrl += `&jwt=${encodeURIComponent(this._jwt)}`;
5333
5491
  }
5334
- if (!!this._socket || !this._disconnected) {
5492
+ if (this._socket || !this._disconnected) {
5335
5493
  return;
5336
5494
  }
5337
5495
  this._socket = new WebSocket(`${wsUrl}&version=${version2}`);
@@ -5922,7 +6080,7 @@ var dendri = (() => {
5922
6080
  return;
5923
6081
  }
5924
6082
  }
5925
- if (!!userId && !util.validateId(userId)) {
6083
+ if (userId && !util.validateId(userId)) {
5926
6084
  this._delayedAbort("invalid-id" /* InvalidID */, `ID "${userId}" is invalid`);
5927
6085
  return;
5928
6086
  }