@afterrealism/dendri-client 2.3.7 → 2.5.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/dist/dendri.cjs CHANGED
@@ -3533,7 +3533,7 @@ var Logger = class {
3533
3533
  var logger_default = new Logger();
3534
3534
 
3535
3535
  // src/api.ts
3536
- var version = "2.3.7";
3536
+ var version = "2.5.0";
3537
3537
  var API = class _API {
3538
3538
  constructor(_options) {
3539
3539
  this._options = _options;
@@ -3546,6 +3546,7 @@ var API = class _API {
3546
3546
  const url = new URL(`${protocol}://${host}:${port}${path}${key}/${method}`);
3547
3547
  url.searchParams.set("ts", `${Date.now()}${Math.random()}`);
3548
3548
  url.searchParams.set("version", version);
3549
+ if (this._options.apiKey) url.searchParams.set("api_key", this._options.apiKey);
3549
3550
  const controller = new AbortController();
3550
3551
  const timeoutId = setTimeout(() => controller.abort(), _API.FETCH_TIMEOUT);
3551
3552
  return fetch(url.href, {
@@ -3570,15 +3571,16 @@ var API = class _API {
3570
3571
  throw new Error(`Could not get an ID from the server.${pathError}`);
3571
3572
  }
3572
3573
  }
3573
- /** Fetch TURN credentials from the signaling server's GET /turn endpoint. */
3574
+ /** Fetch TURN credentials from the signaling server's turn-credentials endpoint. */
3574
3575
  async getTurnCredentials() {
3575
3576
  const protocol = this._options.secure ? "https" : "http";
3576
- const { host, port } = this._options;
3577
- const url = `${protocol}://${host}:${port}/turn`;
3577
+ const { host, port, path, key } = this._options;
3578
+ const url = new URL(`${protocol}://${host}:${port}${path}${key}/turn-credentials`);
3579
+ if (this._options.apiKey) url.searchParams.set("api_key", this._options.apiKey);
3578
3580
  try {
3579
3581
  const controller = new AbortController();
3580
3582
  const timeoutId = setTimeout(() => controller.abort(), _API.FETCH_TIMEOUT);
3581
- const response = await fetch(url, {
3583
+ const response = await fetch(url.href, {
3582
3584
  referrerPolicy: this._options.referrerPolicy,
3583
3585
  signal: controller.signal
3584
3586
  }).finally(() => clearTimeout(timeoutId));
@@ -3683,6 +3685,9 @@ var ServerMessageType = /* @__PURE__ */ ((ServerMessageType2) => {
3683
3685
  ServerMessageType2["HostMigrate"] = "HOST-MIGRATE";
3684
3686
  ServerMessageType2["PresenceUpdate"] = "PRESENCE-UPDATE";
3685
3687
  ServerMessageType2["KeyExchange"] = "KEY-EXCHANGE";
3688
+ ServerMessageType2["ConnectRequest"] = "CONNECT-REQUEST";
3689
+ ServerMessageType2["DcutrConnect"] = "DCUTR-CONNECT";
3690
+ ServerMessageType2["DcutrSync"] = "DCUTR-SYNC";
3686
3691
  return ServerMessageType2;
3687
3692
  })(ServerMessageType || {});
3688
3693
  var TransportMode = /* @__PURE__ */ ((TransportMode2) => {
@@ -3708,6 +3713,11 @@ var ConnectionQuality = /* @__PURE__ */ ((ConnectionQuality2) => {
3708
3713
  ConnectionQuality2["Unknown"] = "unknown";
3709
3714
  return ConnectionQuality2;
3710
3715
  })(ConnectionQuality || {});
3716
+ var TopicClass = /* @__PURE__ */ ((TopicClass2) => {
3717
+ TopicClass2["Persistent"] = "persistent";
3718
+ TopicClass2["Ephemeral"] = "ephemeral";
3719
+ return TopicClass2;
3720
+ })(TopicClass || {});
3711
3721
 
3712
3722
  // src/dendriError.ts
3713
3723
  var import_eventemitter3 = __toESM(require_eventemitter3(), 1);
@@ -3796,6 +3806,7 @@ var Negotiator = class {
3796
3806
  }
3797
3807
  connection;
3798
3808
  _pendingCandidates = [];
3809
+ _iceCandidateFilter = null;
3799
3810
  /** Returns a PeerConnection object set up correctly (for data, media). */
3800
3811
  startConnection(options) {
3801
3812
  const peerConnection = this._startPeerConnection();
@@ -3818,6 +3829,13 @@ var Negotiator = class {
3818
3829
  _startPeerConnection() {
3819
3830
  logger_default.log("Creating RTCPeerConnection.");
3820
3831
  const peerConnection = new RTCPeerConnection(this.connection.provider?.options.config);
3832
+ if (this.connection.provider?.options.ipPolicy === "public") {
3833
+ const isPublicCandidate = (c) => {
3834
+ const sdp2 = c.candidate ?? "";
3835
+ return !sdp2.includes("typ host");
3836
+ };
3837
+ this._iceCandidateFilter = isPublicCandidate;
3838
+ }
3821
3839
  this._setupListeners(peerConnection);
3822
3840
  return peerConnection;
3823
3841
  }
@@ -3830,6 +3848,9 @@ var Negotiator = class {
3830
3848
  logger_default.log("Listening for ICE candidates.");
3831
3849
  peerConnection.onicecandidate = (evt) => {
3832
3850
  if (!evt.candidate?.candidate) return;
3851
+ if (this._iceCandidateFilter && !this._iceCandidateFilter(evt.candidate)) {
3852
+ return;
3853
+ }
3833
3854
  logger_default.log(`Received ICE candidates for ${peerId}:`, evt.candidate);
3834
3855
  provider.socket.send({
3835
3856
  type: "CANDIDATE" /* Candidate */,
@@ -4168,6 +4189,7 @@ var DataConnection = class _DataConnection extends BaseConnection {
4168
4189
  this.dataChannel.onopen = () => {
4169
4190
  logger_default.log(`DC#${this.connectionId} dc connection success`);
4170
4191
  this._open = true;
4192
+ this._applyAdaptiveBuffer(dc);
4171
4193
  this.emit("open");
4172
4194
  };
4173
4195
  this.dataChannel.onclose = () => {
@@ -4175,6 +4197,24 @@ var DataConnection = class _DataConnection extends BaseConnection {
4175
4197
  this.close();
4176
4198
  };
4177
4199
  }
4200
+ _applyAdaptiveBuffer(dc) {
4201
+ const pc = this.peerConnection;
4202
+ if (!pc || typeof pc.getStats !== "function") return;
4203
+ pc.getStats().then((stats) => {
4204
+ let rtt = null;
4205
+ stats.forEach((report) => {
4206
+ if (report.type === "candidate-pair" && report.state === "succeeded" && report.currentRoundTripTime) {
4207
+ rtt = report.currentRoundTripTime * 1e3;
4208
+ }
4209
+ });
4210
+ if (rtt !== null) {
4211
+ const bdp = 12.5 * 1024 * 1024 * (rtt / 1e3);
4212
+ const optimal = Math.max(1 * 1024 * 1024, Math.min(32 * 1024 * 1024, Math.ceil(bdp)));
4213
+ dc.bufferedAmountLowThreshold = optimal;
4214
+ }
4215
+ }).catch(() => {
4216
+ });
4217
+ }
4178
4218
  /**
4179
4219
  * Exposed functionality for users.
4180
4220
  */
@@ -4709,7 +4749,17 @@ var HybridConnection = class extends import_eventemitter32.EventEmitter {
4709
4749
  if (this._closed) {
4710
4750
  return;
4711
4751
  }
4712
- this._attemptWebRTC();
4752
+ logger_default.log(
4753
+ `HybridConnection: start peer=${this.peer} iceTimeout=${this._options.iceTimeout ?? 1e4}ms encryptRelay=${this._encryptRelay}`
4754
+ );
4755
+ if (typeof this._provider?.on !== "function") {
4756
+ this._attemptWebRTC();
4757
+ return;
4758
+ }
4759
+ this._tryConnectionReversal().then((direct) => {
4760
+ if (direct) return;
4761
+ this._attemptWebRTC();
4762
+ });
4713
4763
  }
4714
4764
  /** Send data through the best available transport, optionally tagged with a topic. */
4715
4765
  send(data, options) {
@@ -4999,10 +5049,16 @@ var HybridConnection = class extends import_eventemitter32.EventEmitter {
4999
5049
  this._dataConnection = dc;
5000
5050
  this._iceTimer = setTimeout(() => {
5001
5051
  if (this._mode !== "webrtc" /* WebRTC */) {
5052
+ logger_default.warn(
5053
+ `HybridConnection: ICE timeout after ${iceTimeout}ms for ${this.peer}, falling back to relay`
5054
+ );
5002
5055
  this._fallbackToRelay();
5003
5056
  }
5004
5057
  }, iceTimeout);
5005
5058
  this._dataConnection.on("open", () => {
5059
+ logger_default.log(
5060
+ `HybridConnection: WebRTC opened to ${this.peer} (attempt ${this._upgradeAttempts + 1})`
5061
+ );
5006
5062
  this._clearIceTimer();
5007
5063
  this._clearUpgradeTimer();
5008
5064
  this._upgradeAttempts = 0;
@@ -5043,6 +5099,7 @@ var HybridConnection = class extends import_eventemitter32.EventEmitter {
5043
5099
  /** Update the transport mode and emit if changed. */
5044
5100
  _setMode(mode) {
5045
5101
  if (this._mode !== mode) {
5102
+ logger_default.log(`HybridConnection: transport ${this._mode} -> ${mode} for ${this.peer}`);
5046
5103
  this._mode = mode;
5047
5104
  this.emit("transportChanged", mode);
5048
5105
  }
@@ -5083,6 +5140,117 @@ var HybridConnection = class extends import_eventemitter32.EventEmitter {
5083
5140
  this._attemptWebRTC();
5084
5141
  }, interval);
5085
5142
  }
5143
+ async _tryConnectionReversal() {
5144
+ if (typeof this._provider?.on !== "function") return false;
5145
+ try {
5146
+ const resp = await new Promise((resolve, reject) => {
5147
+ const timer = setTimeout(() => reject(new Error("timeout")), 3e3);
5148
+ const handler = (data) => {
5149
+ clearTimeout(timer);
5150
+ this._provider.off("CONNECT-REQUEST" /* ConnectRequest */, handler);
5151
+ resolve(data);
5152
+ };
5153
+ this._provider.on("CONNECT-REQUEST" /* ConnectRequest */, handler);
5154
+ this._provider.socket.send({
5155
+ type: "CONNECT-REQUEST" /* ConnectRequest */,
5156
+ payload: { peer: this.peer }
5157
+ });
5158
+ });
5159
+ const addr = resp?.address;
5160
+ if (!addr) return false;
5161
+ const pc = new RTCPeerConnection(this._provider.options.config);
5162
+ const dc = pc.createDataChannel("probe", { id: 0 });
5163
+ await new Promise((resolve, reject) => {
5164
+ const timer = setTimeout(() => {
5165
+ pc.close();
5166
+ reject(new Error("direct-dial-timeout"));
5167
+ }, 2500);
5168
+ dc.onopen = () => {
5169
+ clearTimeout(timer);
5170
+ resolve();
5171
+ };
5172
+ dc.onerror = () => {
5173
+ clearTimeout(timer);
5174
+ pc.close();
5175
+ reject(new Error("dc-error"));
5176
+ };
5177
+ });
5178
+ this._dataConnection = void 0;
5179
+ this._setMode("webrtc" /* WebRTC */);
5180
+ pc.close();
5181
+ return true;
5182
+ } catch {
5183
+ return false;
5184
+ }
5185
+ }
5186
+ async _gatherSrflxCandidates() {
5187
+ const pc = new RTCPeerConnection({ iceServers: this._provider.options.config?.iceServers });
5188
+ pc.createDataChannel("probe");
5189
+ const offer = await pc.createOffer();
5190
+ await pc.setLocalDescription(offer);
5191
+ const candidates = [];
5192
+ await new Promise((resolve) => {
5193
+ const timer = setTimeout(resolve, 2e3);
5194
+ pc.onicecandidate = (evt) => {
5195
+ if (!evt.candidate) {
5196
+ clearTimeout(timer);
5197
+ resolve();
5198
+ return;
5199
+ }
5200
+ if (!evt.candidate.candidate.includes("typ host")) {
5201
+ candidates.push(evt.candidate.candidate);
5202
+ }
5203
+ };
5204
+ });
5205
+ pc.close();
5206
+ return candidates;
5207
+ }
5208
+ async _dcutrHolePunch() {
5209
+ try {
5210
+ const localAddrs = await this._gatherSrflxCandidates();
5211
+ const t0 = performance.now();
5212
+ const peerAddrs = await new Promise((resolve, reject) => {
5213
+ const timer = setTimeout(() => reject(new Error("dcutr-timeout")), 8e3);
5214
+ const handler = (data) => {
5215
+ clearTimeout(timer);
5216
+ this._provider.off("DCUTR-CONNECT" /* DcutrConnect */, handler);
5217
+ resolve(data?.addresses ?? []);
5218
+ };
5219
+ this._provider.on("DCUTR-CONNECT" /* DcutrConnect */, handler);
5220
+ this._provider.socket.send({
5221
+ type: "DCUTR-CONNECT" /* DcutrConnect */,
5222
+ payload: { addresses: localAddrs }
5223
+ });
5224
+ });
5225
+ const relayRtt = performance.now() - t0;
5226
+ this._provider.socket.send({ type: "DCUTR-SYNC" /* DcutrSync */, payload: {} });
5227
+ await new Promise((r) => setTimeout(r, relayRtt / 2));
5228
+ for (let i = 0; i < Math.min(peerAddrs.length, 4); i++) {
5229
+ try {
5230
+ const pc = new RTCPeerConnection(this._provider.options.config);
5231
+ await new Promise((resolve, reject) => {
5232
+ const timer = setTimeout(() => {
5233
+ pc.close();
5234
+ reject(new Error("dc-dial-timeout"));
5235
+ }, 5e3);
5236
+ const dc = pc.createDataChannel("dcutr");
5237
+ dc.onopen = () => {
5238
+ clearTimeout(timer);
5239
+ resolve();
5240
+ };
5241
+ });
5242
+ this._dataConnection = void 0;
5243
+ this._setMode("webrtc" /* WebRTC */);
5244
+ pc.close();
5245
+ return true;
5246
+ } catch {
5247
+ }
5248
+ }
5249
+ return false;
5250
+ } catch {
5251
+ return false;
5252
+ }
5253
+ }
5086
5254
  };
5087
5255
 
5088
5256
  // src/mediaconnection.ts
@@ -5239,17 +5407,29 @@ var PollingTransport = class _PollingTransport extends SignalingTransport {
5239
5407
  _heartbeatTimer;
5240
5408
  _lastSeq = 0;
5241
5409
  _baseUrl;
5410
+ _key;
5411
+ _jwt;
5412
+ _apiKey;
5242
5413
  _pingInterval;
5243
5414
  _abortController;
5244
5415
  /** Backoff schedule base delays in milliseconds. */
5245
5416
  static BACKOFF_SCHEDULE = [0, 1e3, 2e3, 4e3, 8e3, 16e3, 3e4];
5246
5417
  /** Random jitter range in milliseconds (applied as +/-). */
5247
5418
  static BACKOFF_JITTER = 500;
5248
- constructor(secure, host, port, path, _key, pingInterval = 5e3, _jwt) {
5419
+ constructor(secure, host, port, path, key, pingInterval = 5e3, jwt, apiKey) {
5249
5420
  super();
5250
5421
  const protocol = secure ? "https://" : "http://";
5251
5422
  this._baseUrl = `${protocol + host}:${port}${path}`;
5252
5423
  this._pingInterval = pingInterval;
5424
+ this._key = key;
5425
+ this._jwt = jwt;
5426
+ this._apiKey = apiKey;
5427
+ }
5428
+ /** Append the shared auth params (key, jwt, api_key) so every HTTP call authenticates like the WS transport. */
5429
+ _applyAuthParams(params) {
5430
+ params.set("key", this._key);
5431
+ if (this._jwt) params.set("jwt", this._jwt);
5432
+ if (this._apiKey) params.set("api_key", this._apiKey);
5253
5433
  }
5254
5434
  get reconnectAttempt() {
5255
5435
  return this._reconnectAttempt;
@@ -5277,6 +5457,7 @@ var PollingTransport = class _PollingTransport extends SignalingTransport {
5277
5457
  id: this._id,
5278
5458
  token: this._token
5279
5459
  });
5460
+ this._applyAuthParams(params);
5280
5461
  if (this._lastSeq > 0) params.set("last_seq", String(this._lastSeq));
5281
5462
  const response = await fetch(`${this._baseUrl}http/poll?${params}`, {
5282
5463
  signal: this._abortController.signal
@@ -5315,6 +5496,7 @@ var PollingTransport = class _PollingTransport extends SignalingTransport {
5315
5496
  id: this._id,
5316
5497
  token: this._token
5317
5498
  });
5499
+ this._applyAuthParams(params);
5318
5500
  try {
5319
5501
  await fetch(`${this._baseUrl}http/send?${params}`, {
5320
5502
  method: "POST",
@@ -5379,13 +5561,16 @@ var PollingTransport = class _PollingTransport extends SignalingTransport {
5379
5561
  };
5380
5562
 
5381
5563
  // src/socket.ts
5382
- var version2 = "2.3.7";
5564
+ var version2 = "2.5.0";
5383
5565
  var Socket = class _Socket extends SignalingTransport {
5384
- constructor(secure, host, port, path, key, pingInterval = 5e3, jwt) {
5566
+ constructor(secure, host, port, path, key, pingInterval = 5e3, jwt, apiKey) {
5385
5567
  super();
5386
5568
  this.pingInterval = pingInterval;
5387
5569
  const wsProtocol = secure ? "wss://" : "ws://";
5388
5570
  this._baseUrl = `${wsProtocol + host}:${port}${path}dendri?key=${key}`;
5571
+ if (apiKey) {
5572
+ this._baseUrl += `&api_key=${encodeURIComponent(apiKey)}`;
5573
+ }
5389
5574
  this._jwt = jwt;
5390
5575
  }
5391
5576
  pingInterval;
@@ -5421,7 +5606,7 @@ var Socket = class _Socket extends SignalingTransport {
5421
5606
  if (this._jwt) {
5422
5607
  wsUrl += `&jwt=${encodeURIComponent(this._jwt)}`;
5423
5608
  }
5424
- if (!!this._socket || !this._disconnected) {
5609
+ if (this._socket || !this._disconnected) {
5425
5610
  return;
5426
5611
  }
5427
5612
  this._socket = new WebSocket(`${wsUrl}&version=${version2}`);
@@ -5691,18 +5876,28 @@ var SSETransport = class _SSETransport extends SignalingTransport {
5691
5876
  _heartbeatTimer;
5692
5877
  _lastSeq = 0;
5693
5878
  _baseUrl;
5879
+ _key;
5694
5880
  _jwt;
5881
+ _apiKey;
5695
5882
  _pingInterval;
5696
5883
  /** Backoff schedule base delays in milliseconds. */
5697
5884
  static BACKOFF_SCHEDULE = [0, 1e3, 2e3, 4e3, 8e3, 16e3, 3e4];
5698
5885
  /** Random jitter range in milliseconds (applied as +/-). */
5699
5886
  static BACKOFF_JITTER = 500;
5700
- constructor(secure, host, port, path, _key, pingInterval = 5e3, jwt) {
5887
+ constructor(secure, host, port, path, key, pingInterval = 5e3, jwt, apiKey) {
5701
5888
  super();
5702
5889
  const protocol = secure ? "https://" : "http://";
5703
5890
  this._baseUrl = `${protocol + host}:${port}${path}`;
5704
5891
  this._pingInterval = pingInterval;
5892
+ this._key = key;
5705
5893
  this._jwt = jwt;
5894
+ this._apiKey = apiKey;
5895
+ }
5896
+ /** Append the shared auth params (key, jwt, api_key) so every HTTP call authenticates like the WS transport. */
5897
+ _applyAuthParams(params) {
5898
+ params.set("key", this._key);
5899
+ if (this._jwt) params.set("jwt", this._jwt);
5900
+ if (this._apiKey) params.set("api_key", this._apiKey);
5706
5901
  }
5707
5902
  get reconnectAttempt() {
5708
5903
  return this._reconnectAttempt;
@@ -5720,10 +5915,9 @@ var SSETransport = class _SSETransport extends SignalingTransport {
5720
5915
  if (this._disconnected) return;
5721
5916
  const params = new URLSearchParams({
5722
5917
  id: this._id,
5723
- token: this._token,
5724
- key: "dendri"
5918
+ token: this._token
5725
5919
  });
5726
- if (this._jwt) params.set("jwt", this._jwt);
5920
+ this._applyAuthParams(params);
5727
5921
  if (this._lastSeq > 0) params.set("last_seq", String(this._lastSeq));
5728
5922
  const url = `${this._baseUrl}http/sse?${params}`;
5729
5923
  try {
@@ -5797,6 +5991,7 @@ var SSETransport = class _SSETransport extends SignalingTransport {
5797
5991
  id: this._id,
5798
5992
  token: this._token
5799
5993
  });
5994
+ this._applyAuthParams(params);
5800
5995
  try {
5801
5996
  await fetch(`${this._baseUrl}http/send?${params}`, {
5802
5997
  method: "POST",
@@ -5869,6 +6064,28 @@ var SSETransport = class _SSETransport extends SignalingTransport {
5869
6064
  };
5870
6065
 
5871
6066
  // src/dendri.ts
6067
+ function parseServerUrl(url) {
6068
+ let parsed;
6069
+ try {
6070
+ parsed = new URL(url);
6071
+ } catch {
6072
+ throw new Error(
6073
+ `Invalid Dendri "url" option: "${url}". Expected a full URL like "wss://signal.example.com".`
6074
+ );
6075
+ }
6076
+ const secure = parsed.protocol === "wss:" || parsed.protocol === "https:";
6077
+ if (!secure && parsed.protocol !== "ws:" && parsed.protocol !== "http:") {
6078
+ throw new Error(
6079
+ `Invalid Dendri "url" protocol: "${parsed.protocol}". Use wss://, ws://, https://, or http://.`
6080
+ );
6081
+ }
6082
+ return {
6083
+ host: parsed.hostname,
6084
+ port: parsed.port ? Number(parsed.port) : secure ? 443 : 80,
6085
+ secure,
6086
+ path: parsed.pathname || "/"
6087
+ };
6088
+ }
5872
6089
  var Dendri = class _Dendri extends EventEmitterWithError {
5873
6090
  static DEFAULT_KEY = "dendri";
5874
6091
  _serializers = {
@@ -5959,6 +6176,9 @@ var Dendri = class _Dendri extends EventEmitterWithError {
5959
6176
  } else if (id) {
5960
6177
  userId = id.toString();
5961
6178
  }
6179
+ if (providedOptions?.url) {
6180
+ providedOptions = { ...parseServerUrl(providedOptions.url), ...providedOptions };
6181
+ }
5962
6182
  const normalizedOptions = {
5963
6183
  debug: 0,
5964
6184
  // 1: Errors, 2: Warnings, 3: All logs
@@ -6012,7 +6232,7 @@ var Dendri = class _Dendri extends EventEmitterWithError {
6012
6232
  return;
6013
6233
  }
6014
6234
  }
6015
- if (!!userId && !util.validateId(userId)) {
6235
+ if (userId && !util.validateId(userId)) {
6016
6236
  this._delayedAbort("invalid-id" /* InvalidID */, `ID "${userId}" is invalid`);
6017
6237
  return;
6018
6238
  }
@@ -6077,7 +6297,8 @@ var Dendri = class _Dendri extends EventEmitterWithError {
6077
6297
  this._options.path,
6078
6298
  this._options.key,
6079
6299
  this._options.pingInterval,
6080
- this._options.jwt
6300
+ this._options.jwt,
6301
+ this._options.apiKey
6081
6302
  ) : transport === "polling" ? new PollingTransport(
6082
6303
  this._options.secure ?? false,
6083
6304
  this._options.host,
@@ -6085,7 +6306,8 @@ var Dendri = class _Dendri extends EventEmitterWithError {
6085
6306
  this._options.path,
6086
6307
  this._options.key,
6087
6308
  this._options.pingInterval,
6088
- this._options.jwt
6309
+ this._options.jwt,
6310
+ this._options.apiKey
6089
6311
  ) : new Socket(
6090
6312
  this._options.secure ?? false,
6091
6313
  this._options.host,
@@ -6093,7 +6315,8 @@ var Dendri = class _Dendri extends EventEmitterWithError {
6093
6315
  this._options.path,
6094
6316
  this._options.key,
6095
6317
  this._options.pingInterval,
6096
- this._options.jwt
6318
+ this._options.jwt,
6319
+ this._options.apiKey
6097
6320
  );
6098
6321
  socket.on("message" /* Message */, (data) => {
6099
6322
  this._handleMessage(data);
@@ -8791,7 +9014,13 @@ var Room = class extends import_eventemitter35.EventEmitter {
8791
9014
  sentViaWebRTC = true;
8792
9015
  }
8793
9016
  }
8794
- if (!sentViaWebRTC && this._peer?.socket) {
9017
+ if (this._dendriOptions?.enableRelay && this._peer?.socket) {
9018
+ this._peer.socket.send({
9019
+ type: "DATA" /* Data */,
9020
+ room: this._roomId,
9021
+ payload: wire
9022
+ });
9023
+ } else if (!sentViaWebRTC && this._peer?.socket) {
8795
9024
  this._peer.socket.send({
8796
9025
  type: "DATA" /* Data */,
8797
9026
  room: this._roomId,
@@ -8825,7 +9054,13 @@ var Room = class extends import_eventemitter35.EventEmitter {
8825
9054
  sentViaWebRTC = true;
8826
9055
  }
8827
9056
  }
8828
- if (!sentViaWebRTC && this._peer?.socket) {
9057
+ if (this._dendriOptions?.enableRelay && this._peer?.socket) {
9058
+ this._peer.socket.send({
9059
+ type: "DATA" /* Data */,
9060
+ room: this._roomId,
9061
+ payload: wire
9062
+ });
9063
+ } else if (!sentViaWebRTC && this._peer?.socket) {
8829
9064
  this._peer.socket.send({
8830
9065
  type: "DATA" /* Data */,
8831
9066
  room: this._roomId,
@@ -9016,8 +9251,8 @@ var Room = class extends import_eventemitter35.EventEmitter {
9016
9251
  const remotePeerId = conn.peer;
9017
9252
  this._connections.set(remotePeerId, conn);
9018
9253
  this._knownPeers.add(remotePeerId);
9254
+ this.emit("peerJoined", remotePeerId);
9019
9255
  conn.on("open", () => {
9020
- this.emit("peerJoined", remotePeerId);
9021
9256
  for (const [peerId, c] of this._connections) {
9022
9257
  if (peerId !== remotePeerId && c.open) {
9023
9258
  c.send({ __room: { type: "peer-joined", peerId: remotePeerId } });
@@ -9339,6 +9574,14 @@ var Room = class extends import_eventemitter35.EventEmitter {
9339
9574
  const conn = this._connections.get(peerId);
9340
9575
  if (conn?.open) {
9341
9576
  conn.send(data);
9577
+ if (this._dendriOptions?.enableRelay && this._peer?.socket) {
9578
+ this._peer.socket.send({
9579
+ type: "DATA" /* Data */,
9580
+ dst: peerId,
9581
+ room: this._roomId,
9582
+ payload: data
9583
+ });
9584
+ }
9342
9585
  return;
9343
9586
  }
9344
9587
  if (!this._isHost && this._hostId) {
@@ -9403,7 +9646,7 @@ var DendriServerAPI = class {
9403
9646
  }
9404
9647
  /** Get TURN credentials */
9405
9648
  async getTurnCredentials() {
9406
- const res = await fetch(`${this._baseUrl}/turn`);
9649
+ const res = await fetch(`${this._baseUrl}/${this._key}/turn-credentials`);
9407
9650
  if (!res.ok) throw new Error(`TURN credentials failed: ${res.status}`);
9408
9651
  return res.json();
9409
9652
  }
@@ -9712,6 +9955,7 @@ exports.ServerMessageType = ServerMessageType;
9712
9955
  exports.SignalingTransport = SignalingTransport;
9713
9956
  exports.SocketEventType = SocketEventType;
9714
9957
  exports.StreamConnection = StreamConnection;
9958
+ exports.TopicClass = TopicClass;
9715
9959
  exports.TopicManager = TopicManager;
9716
9960
  exports.TransportMode = TransportMode;
9717
9961
  exports.createDendriStore = createDendriStore;