@mebius-io/web 0.6.2 → 0.7.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/index.js CHANGED
@@ -222,10 +222,21 @@ function resetVideoElement(video) {
222
222
  }
223
223
 
224
224
  // src/internal/ll-view-transport.ts
225
+ var DEFAULT_REALTIME_TARGET_MS = 300;
226
+ function holdBuffer(receiver, targetMs) {
227
+ const r = receiver;
228
+ try {
229
+ if ("jitterBufferTarget" in r) r.jitterBufferTarget = targetMs;
230
+ if ("playoutDelayHint" in r) r.playoutDelayHint = targetMs / 1e3;
231
+ } catch {
232
+ }
233
+ }
225
234
  var WhepViewTransport = class {
226
- constructor(signaling) {
235
+ constructor(signaling, targetLatencyMs = DEFAULT_REALTIME_TARGET_MS) {
227
236
  this.signaling = signaling;
237
+ this.targetLatencyMs = targetLatencyMs;
228
238
  this.kind = "whep";
239
+ this.cursor = null;
229
240
  this.pc = null;
230
241
  this.resourceUrl = null;
231
242
  this.endedCb = null;
@@ -249,6 +260,7 @@ var WhepViewTransport = class {
249
260
  pc.addTransceiver("video", { direction: "recvonly" });
250
261
  pc.addTransceiver("audio", { direction: "recvonly" });
251
262
  pc.ontrack = (ev) => {
263
+ holdBuffer(ev.receiver, this.targetLatencyMs);
252
264
  remote.addTrack(ev.track);
253
265
  video.srcObject = remote;
254
266
  void playWithAutoplayFallback(video).then((o) => {
@@ -276,6 +288,7 @@ var WhepViewTransport = class {
276
288
  await pc.setRemoteDescription({ type: "answer", sdp: answer });
277
289
  }
278
290
  async stop() {
291
+ this.cursor = null;
279
292
  await this.signaling.deleteResource(this.resourceUrl);
280
293
  this.resourceUrl = null;
281
294
  this.pc?.close();
@@ -283,24 +296,59 @@ var WhepViewTransport = class {
283
296
  if (this.video) resetVideoElement(this.video);
284
297
  this.video = null;
285
298
  }
299
+ /**
300
+ * Read what the connection actually did since the last reading.
301
+ *
302
+ * This route needs to measure its own freezes, because nothing else can. A
303
+ * frozen real-time picture still reports a healthy connection and raises no
304
+ * event on the video element — so from the outside the session looks flawless
305
+ * while the viewer stares at a still frame. Every freeze on this route used to
306
+ * be recorded as zero, which is why the problem could be felt and never seen.
307
+ */
286
308
  async getStats() {
287
309
  if (!this.pc) return null;
288
310
  const report = await this.pc.getStats();
289
- let bitrateKbps = 0;
290
- let framesPerSecond = 0;
291
- let latencyMs;
311
+ let framesPerSecond;
312
+ let freezeS = 0;
313
+ let packetsLost = 0;
314
+ let packetsReceived = 0;
315
+ let bytesReceived = 0;
316
+ let bufferDelayS = 0;
317
+ let bufferEmitted = 0;
318
+ let rttMs;
292
319
  report.forEach((stat) => {
293
- if (stat.type === "inbound-rtp") {
294
- if (typeof stat.framesPerSecond === "number") framesPerSecond = stat.framesPerSecond;
295
- if (typeof stat.jitter === "number") latencyMs = Math.round(stat.jitter * 1e3);
320
+ if (stat.type === "inbound-rtp" && stat.kind === "video") {
321
+ const v = stat;
322
+ if (typeof v.framesPerSecond === "number") framesPerSecond = v.framesPerSecond;
323
+ if (typeof v.totalFreezesDuration === "number") freezeS = v.totalFreezesDuration;
324
+ if (typeof v.packetsLost === "number") packetsLost = v.packetsLost;
325
+ if (typeof v.packetsReceived === "number") packetsReceived = v.packetsReceived;
326
+ if (typeof v.bytesReceived === "number") bytesReceived = v.bytesReceived;
327
+ if (typeof v.jitterBufferDelay === "number") bufferDelayS = v.jitterBufferDelay;
328
+ if (typeof v.jitterBufferEmittedCount === "number") bufferEmitted = v.jitterBufferEmittedCount;
296
329
  }
297
330
  if (stat.type === "candidate-pair" && stat.state === "succeeded") {
298
- if (typeof stat.availableIncomingBitrate === "number") {
299
- bitrateKbps = Math.round(stat.availableIncomingBitrate / 1e3);
300
- }
331
+ const p = stat;
332
+ if (typeof p.currentRoundTripTime === "number") rttMs = Math.round(p.currentRoundTripTime * 1e3);
301
333
  }
302
334
  });
303
- return { bitrateKbps, framesPerSecond, latencyMs };
335
+ const atMs = Date.now();
336
+ const previous = this.cursor;
337
+ this.cursor = { atMs, freezeS, packetsLost, packetsReceived, bytesReceived, bufferDelayS, bufferEmitted };
338
+ if (!previous) return { framesPerSecond, rttMs };
339
+ const elapsedS = Math.max(1e-3, (atMs - previous.atMs) / 1e3);
340
+ const deltaLost = Math.max(0, packetsLost - previous.packetsLost);
341
+ const deltaReceived = Math.max(0, packetsReceived - previous.packetsReceived);
342
+ const deltaEmitted = bufferEmitted - previous.bufferEmitted;
343
+ const heldMs = deltaEmitted > 0 ? (bufferDelayS - previous.bufferDelayS) / deltaEmitted * 1e3 : void 0;
344
+ return {
345
+ bitrateKbps: Math.round((bytesReceived - previous.bytesReceived) * 8 / elapsedS / 1e3),
346
+ framesPerSecond,
347
+ latencyMs: heldMs === void 0 ? void 0 : Math.round(heldMs + (rttMs !== void 0 ? rttMs / 2 : 0)),
348
+ rttMs,
349
+ packetLossPct: deltaLost + deltaReceived > 0 ? Number((deltaLost / (deltaLost + deltaReceived) * 100).toFixed(2)) : 0,
350
+ freezeMs: Math.max(0, Math.round((freezeS - previous.freezeS) * 1e3))
351
+ };
304
352
  }
305
353
  };
306
354
 
@@ -308,6 +356,19 @@ var WhepViewTransport = class {
308
356
  function retryWarmupNotFound(cfg, retryCount, res, retry) {
309
357
  return retry || retryCount < (cfg?.maxNumRetry ?? 0) && res?.code === 404;
310
358
  }
359
+ var TOKEN_PARAM = /([?&]token=)[^&#]*/;
360
+ function withCurrentToken(url, token) {
361
+ return url.replace(TOKEN_PARAM, (_match, prefix) => prefix + encodeURIComponent(token));
362
+ }
363
+ function tokenRestampingLoader(Hls, currentToken) {
364
+ const Base = Hls.DefaultConfig.loader;
365
+ return class TokenRestampingLoader extends Base {
366
+ load(context, config2, callbacks) {
367
+ context.url = withCurrentToken(context.url, currentToken());
368
+ super.load(context, config2, callbacks);
369
+ }
370
+ };
371
+ }
311
372
  var HlsViewTransport = class {
312
373
  /**
313
374
  * deliveryPath, when given, is a gateway-relative path from the gateway's own
@@ -315,9 +376,10 @@ var HlsViewTransport = class {
315
376
  * origin one. Without it this falls back to the origin playlist, which is
316
377
  * still correct, just served from our own bandwidth.
317
378
  */
318
- constructor(signaling, deliveryPath) {
379
+ constructor(signaling, deliveryPath, targetS) {
319
380
  this.signaling = signaling;
320
381
  this.deliveryPath = deliveryPath;
382
+ this.targetS = targetS;
321
383
  this.kind = "hls";
322
384
  this.hls = null;
323
385
  this.video = null;
@@ -357,6 +419,11 @@ var HlsViewTransport = class {
357
419
  }
358
420
  const hls = new Hls({
359
421
  maxLiveSyncPlaybackRate: 1.1,
422
+ loader: tokenRestampingLoader(Hls, () => this.signaling.accessToken()),
423
+ // Only when the app actually asked. Left alone, the library follows the
424
+ // playlist's own HOLD-BACK, which the server measured from the segments it
425
+ // is producing — a number guessed here would only override a real one.
426
+ ...this.targetS === void 0 ? {} : { liveSyncDuration: this.targetS },
360
427
  manifestLoadPolicy: {
361
428
  default: {
362
429
  maxTimeToFirstByteMs: 1e4,
@@ -426,8 +493,12 @@ var LIVE_FLV_CONFIG = {
426
493
  autoCleanupMinBackwardDuration: 10,
427
494
  reuseRedirectedURL: true
428
495
  };
429
- var MAX_DRIFT_S = 2;
430
- var EDGE_MARGIN_S = 0.4;
496
+ var DEFAULT_BALANCED_TARGET_S = 2;
497
+ var CATCH_UP_RATE = 1.05;
498
+ var BUILD_UP_RATE = 0.98;
499
+ var SEEK_AT = 4;
500
+ var CATCH_UP_ABOVE = 1.5;
501
+ var BUILD_UP_BELOW = 0.9;
431
502
  var AUDIO_RETRY_MS = 2500;
432
503
  var UPSTREAM_LATENCY_MS = 800;
433
504
  function stalledWithData(video, ms) {
@@ -445,17 +516,31 @@ function stalledWithData(video, ms) {
445
516
  video.addEventListener("timeupdate", onTime);
446
517
  });
447
518
  }
448
- function chaseLiveEdge(video) {
519
+ function syncLiveEdge(video, targetS = DEFAULT_BALANCED_TARGET_S) {
449
520
  const ranges = video.buffered;
450
521
  if (ranges.length === 0) return;
451
522
  const edge = ranges.end(ranges.length - 1);
452
- if (edge - video.currentTime <= MAX_DRIFT_S) return;
453
- video.currentTime = edge - EDGE_MARGIN_S;
523
+ const drift = edge - video.currentTime;
524
+ if (drift > targetS * SEEK_AT) {
525
+ video.currentTime = edge - targetS;
526
+ video.playbackRate = 1;
527
+ return;
528
+ }
529
+ if (drift > targetS * CATCH_UP_ABOVE) {
530
+ video.playbackRate = CATCH_UP_RATE;
531
+ return;
532
+ }
533
+ if (drift < targetS * BUILD_UP_BELOW) {
534
+ video.playbackRate = BUILD_UP_RATE;
535
+ return;
536
+ }
537
+ video.playbackRate = 1;
454
538
  }
455
539
  var FlvViewTransport = class {
456
- constructor(signaling, deliveryPath) {
540
+ constructor(signaling, deliveryPath, targetS = DEFAULT_BALANCED_TARGET_S) {
457
541
  this.signaling = signaling;
458
542
  this.deliveryPath = deliveryPath;
543
+ this.targetS = targetS;
459
544
  this.kind = "flv_js";
460
545
  this.player = null;
461
546
  this.video = null;
@@ -481,7 +566,7 @@ var FlvViewTransport = class {
481
566
  const { signal } = this.listeners;
482
567
  video.addEventListener("ended", () => this.endedCb?.(), { signal });
483
568
  video.addEventListener("waiting", () => this.bufferingCb?.(), { signal });
484
- video.addEventListener("timeupdate", () => chaseLiveEdge(video), { signal });
569
+ video.addEventListener("timeupdate", () => syncLiveEdge(video, this.targetS), { signal });
485
570
  let mod;
486
571
  try {
487
572
  mod = await import("flv.js");
@@ -525,6 +610,7 @@ var FlvViewTransport = class {
525
610
  this.listeners = null;
526
611
  this.teardownPlayer();
527
612
  if (this.video) {
613
+ this.video.playbackRate = 1;
528
614
  this.video.removeAttribute("src");
529
615
  this.video.load();
530
616
  }
@@ -590,18 +676,29 @@ var KIND_LOCAL = "local";
590
676
  function canPlayBuffered() {
591
677
  return typeof MediaSource !== "undefined";
592
678
  }
593
- function transportFor(kind, path, signaling) {
594
- if (kind === KIND_FAST) return canPlayBuffered() ? new FlvViewTransport(signaling, path) : null;
595
- if (kind === KIND_WIDE || kind === KIND_LOCAL) return new HlsViewTransport(signaling, path);
679
+ function transportFor(kind, path, signaling, targetLatencyMs) {
680
+ const targetS = targetLatencyMs === void 0 ? void 0 : targetLatencyMs / 1e3;
681
+ if (kind === KIND_FAST)
682
+ return canPlayBuffered() ? new FlvViewTransport(signaling, path, targetS) : null;
683
+ if (kind === KIND_WIDE || kind === KIND_LOCAL)
684
+ return new HlsViewTransport(signaling, path, targetS);
596
685
  return null;
597
686
  }
598
- function createViewCandidates(mode, signaling, deliveries = []) {
599
- const fromGateway = (kinds) => deliveries.filter((d) => kinds.includes(d.kind)).map((d) => transportFor(d.kind, d.path, signaling)).filter((t) => t !== null);
600
- const originFallback = new HlsViewTransport(signaling);
687
+ function createViewCandidates(mode, signaling, deliveries = [], targetLatencyMs) {
688
+ const fromGateway = (kinds) => deliveries.filter((d) => kinds.includes(d.kind)).map((d) => transportFor(d.kind, d.path, signaling, targetLatencyMs)).filter((t) => t !== null);
689
+ const originFallback = new HlsViewTransport(
690
+ signaling,
691
+ void 0,
692
+ targetLatencyMs === void 0 ? void 0 : targetLatencyMs / 1e3
693
+ );
601
694
  const allKinds = [KIND_FAST, KIND_WIDE, KIND_LOCAL];
602
695
  switch (mode) {
603
696
  case "low-latency":
604
- return [new WhepViewTransport(signaling), ...fromGateway(allKinds), originFallback];
697
+ return [
698
+ new WhepViewTransport(signaling, targetLatencyMs ?? DEFAULT_REALTIME_TARGET_MS),
699
+ ...fromGateway(allKinds),
700
+ originFallback
701
+ ];
605
702
  case "balanced":
606
703
  return [...fromGateway(allKinds), originFallback];
607
704
  case "scale":
@@ -612,7 +709,7 @@ function createViewCandidates(mode, signaling, deliveries = []) {
612
709
  }
613
710
 
614
711
  // src/internal/telemetry.ts
615
- var SDK_VERSION = "web/0.4.8";
712
+ var SDK_VERSION = true ? `web/${"0.7.0"}` : "web/dev";
616
713
  var FLUSH_INTERVAL_MS = 15e3;
617
714
  var MAX_BATCH = 64;
618
715
  function describeDevice() {
@@ -1020,7 +1117,12 @@ var MebiusPlayer = class extends TypedEmitter {
1020
1117
  this.freeze = new FreezeClock();
1021
1118
  /** Cancels element listeners bound for the lifetime of one play(). */
1022
1119
  this.elementListeners = null;
1023
- this.candidates = createViewCandidates(options.mode ?? "auto", signaling, deliveries);
1120
+ this.candidates = createViewCandidates(
1121
+ options.mode ?? "auto",
1122
+ signaling,
1123
+ deliveries,
1124
+ options.targetLatencyMs
1125
+ );
1024
1126
  }
1025
1127
  /** Start playing `streamId` into the given video element or selector. */
1026
1128
  async play(streamId, viewTarget) {
@@ -1117,13 +1219,15 @@ var MebiusPlayer = class extends TypedEmitter {
1117
1219
  }
1118
1220
  /**
1119
1221
  * Wall-clock time (Unix ms) currently on screen, or `null` when the active
1120
- * route cannot produce one (HTTP-FLV, WHEP see {@link ViewTransport}).
1222
+ * route cannot produce one. A real-time route carries no wall clock at all,
1223
+ * and a segmented route has none until its first timestamped segment arrives
1224
+ * (see {@link ViewTransport}).
1121
1225
  *
1122
1226
  * This is what {@link MebiusClient.createCaptions} compares against a
1123
1227
  * segment's `epochMs` to know when it is due. Delegating to the transport
1124
1228
  * rather than reading the element directly is what keeps this correct across
1125
- * a route failover: the player may switch from HLS to FLV mid-session, and
1126
- * the clock source has to follow.
1229
+ * a route failover: the player may change route mid-session, and the clock
1230
+ * source has to follow.
1127
1231
  */
1128
1232
  currentEpochMs() {
1129
1233
  return this.transport?.playheadEpochMs?.() ?? null;
@@ -1147,9 +1251,11 @@ var MebiusPlayer = class extends TypedEmitter {
1147
1251
  startStats() {
1148
1252
  this.statsTimer = setInterval(async () => {
1149
1253
  const stats = await this.transport?.getStats();
1150
- const freezeMs = this.freeze.take();
1254
+ const elementFreezeMs = this.freeze.take();
1151
1255
  if (!stats) {
1152
- if (freezeMs > 0) this.reporter?.add({ ts: Math.floor(Date.now() / 1e3), freezeMs });
1256
+ if (elementFreezeMs > 0) {
1257
+ this.reporter?.add({ ts: Math.floor(Date.now() / 1e3), freezeMs: elementFreezeMs });
1258
+ }
1153
1259
  return;
1154
1260
  }
1155
1261
  this.emit("stats", stats);
@@ -1157,7 +1263,9 @@ var MebiusPlayer = class extends TypedEmitter {
1157
1263
  ts: Math.floor(Date.now() / 1e3),
1158
1264
  bitrateKbps: stats.bitrateKbps,
1159
1265
  fps: stats.framesPerSecond,
1160
- freezeMs
1266
+ rttMs: stats.rttMs,
1267
+ packetLossPct: stats.packetLossPct,
1268
+ freezeMs: elementFreezeMs + (stats.freezeMs ?? 0)
1161
1269
  });
1162
1270
  }, STATS_INTERVAL_MS2);
1163
1271
  }
@@ -1188,6 +1296,21 @@ var SignalingClient = class {
1188
1296
  this.gateway = gateway;
1189
1297
  this.token = token;
1190
1298
  }
1299
+ /**
1300
+ * Swap in a freshly-minted access token.
1301
+ *
1302
+ * Every URL this class builds is built at call time, so a session that starts
1303
+ * a new request after this point uses the new token with no further wiring.
1304
+ * What it does NOT reach is a request already in flight or a media URL another
1305
+ * library has memorised — see the scale route's loader for that half.
1306
+ */
1307
+ setToken(token) {
1308
+ this.token = token;
1309
+ }
1310
+ /** The current access token, for transports that must re-stamp their own URLs. */
1311
+ accessToken() {
1312
+ return this.token;
1313
+ }
1191
1314
  base() {
1192
1315
  return this.gateway.replace(/\/+$/, "");
1193
1316
  }
@@ -1308,16 +1431,21 @@ function readToken(token) {
1308
1431
  }
1309
1432
 
1310
1433
  // src/client.ts
1434
+ var REFRESH_MARGIN_MS = 6e4;
1435
+ var REFRESH_RETRY_BASE_MS = 2e3;
1436
+ var REFRESH_RETRY_MAX_MS = 3e4;
1311
1437
  var MebiusClient = class extends TypedEmitter {
1312
1438
  /** @internal */
1313
- constructor(config2, token, deliveries = [], telemetry = null, userId) {
1439
+ constructor(config2, token, deliveries = [], telemetry = null, userId, getToken) {
1314
1440
  super();
1315
1441
  this.token = token;
1316
1442
  this.deliveries = deliveries;
1317
1443
  this.telemetry = telemetry;
1318
1444
  this.userId = userId;
1445
+ this.getToken = getToken;
1319
1446
  this.expiryTimer = null;
1320
1447
  this.connected = false;
1448
+ this.refreshFailures = 0;
1321
1449
  this.signaling = new SignalingClient(config2.gateway, token);
1322
1450
  }
1323
1451
  /** @internal Called by {@link Mebius.connect}. */
@@ -1329,13 +1457,82 @@ var MebiusClient = class extends TypedEmitter {
1329
1457
  return;
1330
1458
  }
1331
1459
  this.connected = true;
1332
- if (expiresAtMs !== null) {
1460
+ this.scheduleTokenWork(expiresAtMs);
1461
+ queueMicrotask(() => this.emit("connected", void 0));
1462
+ }
1463
+ /**
1464
+ * Arm whatever has to happen as this token approaches its expiry: renew it if
1465
+ * the app gave us a way to, otherwise report that the session is over.
1466
+ *
1467
+ * Renewing is what makes an unattended session possible at all. The gateway
1468
+ * checks the token on every media request, so without a fresh one playback
1469
+ * stops the moment it expires — no matter how healthy the stream is.
1470
+ */
1471
+ scheduleTokenWork(expiresAtMs) {
1472
+ this.clearTimer();
1473
+ if (expiresAtMs === null) return;
1474
+ const remaining = expiresAtMs - Date.now();
1475
+ if (!this.getToken) {
1333
1476
  this.expiryTimer = setTimeout(
1334
1477
  () => this.emit("error", mebiusError("TOKEN_EXPIRED")),
1335
- Math.max(0, expiresAtMs - now)
1478
+ Math.max(0, remaining)
1336
1479
  );
1480
+ return;
1337
1481
  }
1338
- queueMicrotask(() => this.emit("connected", void 0));
1482
+ this.expiryTimer = setTimeout(
1483
+ () => void this.refreshToken(expiresAtMs),
1484
+ Math.max(0, remaining - REFRESH_MARGIN_MS)
1485
+ );
1486
+ }
1487
+ async refreshToken(previousExpiryMs) {
1488
+ if (!this.connected || !this.getToken) return;
1489
+ let next;
1490
+ try {
1491
+ next = await this.getToken();
1492
+ } catch (cause) {
1493
+ this.onRefreshFailed(previousExpiryMs, cause);
1494
+ return;
1495
+ }
1496
+ if (!this.connected) return;
1497
+ const { expiresAtMs } = readToken(next);
1498
+ if (expiresAtMs !== null && expiresAtMs <= previousExpiryMs) {
1499
+ this.emit(
1500
+ "error",
1501
+ mebiusError("TOKEN_EXPIRED", "Mebius token refresh returned a token that is not newer.")
1502
+ );
1503
+ return;
1504
+ }
1505
+ this.refreshFailures = 0;
1506
+ this.token = next;
1507
+ this.signaling.setToken(next);
1508
+ this.emit("token-refreshed", void 0);
1509
+ this.scheduleTokenWork(expiresAtMs);
1510
+ }
1511
+ /**
1512
+ * A failed refresh is not a dead session: the current token is still valid
1513
+ * until `expiryMs`, and the viewer is still watching. Retry inside that window
1514
+ * and only report expiry once it has actually run out.
1515
+ */
1516
+ onRefreshFailed(expiryMs, cause) {
1517
+ const remaining = expiryMs - Date.now();
1518
+ if (remaining <= 0) {
1519
+ this.emit("error", mebiusError("TOKEN_EXPIRED", void 0, cause));
1520
+ return;
1521
+ }
1522
+ this.refreshFailures += 1;
1523
+ const backoff = Math.min(
1524
+ REFRESH_RETRY_MAX_MS,
1525
+ REFRESH_RETRY_BASE_MS * 2 ** (this.refreshFailures - 1)
1526
+ );
1527
+ this.clearTimer();
1528
+ this.expiryTimer = setTimeout(
1529
+ () => void this.refreshToken(expiryMs),
1530
+ Math.min(backoff, remaining)
1531
+ );
1532
+ }
1533
+ clearTimer() {
1534
+ if (this.expiryTimer) clearTimeout(this.expiryTimer);
1535
+ this.expiryTimer = null;
1339
1536
  }
1340
1537
  /** Create a broadcaster bound to this connection. */
1341
1538
  createBroadcaster(options = {}) {
@@ -1380,8 +1577,7 @@ var MebiusClient = class extends TypedEmitter {
1380
1577
  }
1381
1578
  /** Close the connection and release resources. */
1382
1579
  disconnect(reason) {
1383
- if (this.expiryTimer) clearTimeout(this.expiryTimer);
1384
- this.expiryTimer = null;
1580
+ this.clearTimer();
1385
1581
  this.connected = false;
1386
1582
  this.emit("disconnected", { reason });
1387
1583
  this.removeAllListeners();
@@ -1410,7 +1606,14 @@ var Mebius = {
1410
1606
  }
1411
1607
  if (!options.token) throw mebiusError("UNKNOWN", "Mebius.connect requires a token.");
1412
1608
  const telemetry = options.beaconToken && options.beaconUrl ? { token: options.beaconToken, url: options.beaconUrl } : null;
1413
- const client = new MebiusClient(config, options.token, options.deliveries ?? [], telemetry, options.userId);
1609
+ const client = new MebiusClient(
1610
+ config,
1611
+ options.token,
1612
+ options.deliveries ?? [],
1613
+ telemetry,
1614
+ options.userId,
1615
+ options.getToken
1616
+ );
1414
1617
  client.open();
1415
1618
  return client;
1416
1619
  },