stream-chat 10.0.0-rc.16 → 10.0.0-rc.17

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.
Files changed (33) hide show
  1. package/dist/cjs/index.browser.js +371 -87
  2. package/dist/cjs/index.browser.js.map +4 -4
  3. package/dist/cjs/index.node.js +371 -87
  4. package/dist/cjs/index.node.js.map +4 -4
  5. package/dist/esm/index.mjs +369 -85
  6. package/dist/esm/index.mjs.map +4 -4
  7. package/dist/types/api-client.d.ts +6 -1
  8. package/dist/types/client.d.ts +46 -12
  9. package/dist/types/connection/ConnectionIdManager.d.ts +9 -5
  10. package/dist/types/connection/ConnectionRecoveryManager.d.ts +6 -2
  11. package/dist/types/connection/networkConnection/NetworkConnectionObserver.d.ts +8 -0
  12. package/dist/types/connection/networkConnection/reporters.d.ts +4 -4
  13. package/dist/types/connection/wsConnection/WSConnection.d.ts +34 -9
  14. package/dist/types/connection/wsConnection/WSConnectionFallback.d.ts +55 -0
  15. package/dist/types/connection/wsConnection/types.d.ts +17 -4
  16. package/dist/types/errors.d.ts +1 -0
  17. package/dist/types/gen/chat/ChatApi.d.ts +1 -0
  18. package/dist/types/types.d.ts +8 -0
  19. package/package.json +1 -1
  20. package/src/api-client.ts +25 -24
  21. package/src/client.ts +79 -33
  22. package/src/configuration/shape.ts +7 -1
  23. package/src/connection/ConnectionIdManager.ts +9 -5
  24. package/src/connection/ConnectionRecoveryManager.ts +11 -6
  25. package/src/connection/networkConnection/NetworkConnectionObserver.ts +19 -1
  26. package/src/connection/networkConnection/reporters.ts +4 -4
  27. package/src/connection/wsConnection/WSConnection.ts +86 -14
  28. package/src/connection/wsConnection/WSConnectionFallback.ts +259 -0
  29. package/src/connection/wsConnection/config.ts +1 -0
  30. package/src/connection/wsConnection/types.ts +17 -4
  31. package/src/errors.ts +4 -0
  32. package/src/gen/chat/ChatApi.ts +2 -1
  33. package/src/types.ts +6 -0
@@ -266,7 +266,7 @@ __export(src_exports, {
266
266
  module.exports = __toCommonJS(src_exports);
267
267
 
268
268
  // src/client.ts
269
- var import_axios4 = __toESM(require("axios"));
269
+ var import_axios5 = __toESM(require("axios"));
270
270
 
271
271
  // src/gen/models/index.ts
272
272
  var ChannelOwnCapability = {
@@ -7744,6 +7744,9 @@ function isErrorRetryable(error) {
7744
7744
  if (!err) return false;
7745
7745
  return err.retryable;
7746
7746
  }
7747
+ function isConnectionIDError(error) {
7748
+ return error.code === 46;
7749
+ }
7747
7750
  function isEphemeral(error) {
7748
7751
  if ((0, import_axios2.isCancel)(error)) return false;
7749
7752
  if (!error.response) return true;
@@ -17847,6 +17850,7 @@ var runWithRetry = (f, {
17847
17850
  // src/api-client.ts
17848
17851
  var logger9 = chatLoggerSystem.getLogger("api-client");
17849
17852
  var MULTIPART_CONTENT_TYPE = "multipart/form-data";
17853
+ var LONG_POLL_PATH = "/api/v2/longpoll";
17850
17854
  var UPLOAD_REQUEST_DEFAULTS = {
17851
17855
  timeout: 0,
17852
17856
  maxContentLength: Infinity,
@@ -17867,8 +17871,8 @@ var ApiClient = class {
17867
17871
  params: queryParams,
17868
17872
  headers: { "Content-Type": requestContentType },
17869
17873
  ...isMultipart ? UPLOAD_REQUEST_DEFAULTS : {},
17870
- // Keep this last so a caller-supplied signal wins - and keep it returning only the keys
17871
- // it owns, so it can never clobber the upload defaults above.
17874
+ // Keep this last so a caller-supplied signal and timeout win - and keep it returning only
17875
+ // the keys it owns, so it can never clobber the upload defaults above.
17872
17876
  ...toAxiosRequestConfig(options)
17873
17877
  });
17874
17878
  }
@@ -17899,7 +17903,11 @@ var ApiClient = class {
17899
17903
  }
17900
17904
  }
17901
17905
  if (resolved.startsWith("/")) {
17902
- resolved = this.client.baseURL + resolved;
17906
+ const baseURL = resolved === LONG_POLL_PATH ? (
17907
+ // replace port if present for testing with local API
17908
+ this.client.baseURL?.replace(":3030", ":8900")
17909
+ ) : this.client.baseURL;
17910
+ resolved = baseURL + resolved;
17903
17911
  }
17904
17912
  return resolved;
17905
17913
  }
@@ -17953,6 +17961,7 @@ var ApiClient = class {
17953
17961
  params: { ...additionalConfig.params, connection_id: connectionId }
17954
17962
  };
17955
17963
  }
17964
+ await this.client.tokenManager.tokenReady();
17956
17965
  const initialRequestConfig = this.populateRequestConfigWithDefaults(additionalConfig);
17957
17966
  const clientRequestId = initialRequestConfig.headers?.["x-client-request-id"];
17958
17967
  try {
@@ -18014,25 +18023,23 @@ var ApiClient = class {
18014
18023
  var requiresConnectionId = (params, body) => {
18015
18024
  const payload = params?.payload;
18016
18025
  const requestBody = typeof body === "object" && body !== null ? body : void 0;
18017
- if (params?.watch || params?.presence || payload?.watch || payload?.presence || requestBody?.watch || requestBody?.presence) {
18018
- return true;
18019
- }
18020
- if ([params, payload, requestBody].some(
18021
- (source) => source && ("watch" in source || "presence" in source)
18022
- )) {
18023
- return false;
18024
- }
18025
- return Boolean(params && "connection_id" in params);
18026
+ return Boolean(
18027
+ params?.watch || params?.presence || payload?.watch || payload?.presence || requestBody?.watch || requestBody?.presence
18028
+ );
18026
18029
  };
18027
18030
  var isUsableAbortSignal = (signal) => typeof signal?.addEventListener === "function";
18028
18031
  var toAxiosRequestConfig = ({
18029
18032
  signal,
18030
- onUploadProgress
18033
+ onUploadProgress,
18034
+ timeout
18031
18035
  } = {}) => ({
18032
18036
  signal: isUsableAbortSignal(signal) ? signal : void 0,
18033
18037
  // Same reasoning as `isUsableAbortSignal`: an options object revived from a persisted
18034
18038
  // offline-db task payload has lost its functions.
18035
- onUploadProgress: typeof onUploadProgress === "function" ? onUploadProgress : void 0
18039
+ onUploadProgress: typeof onUploadProgress === "function" ? onUploadProgress : void 0,
18040
+ // Only when set: an explicit `undefined` would clobber the upload defaults' `timeout: 0`, and
18041
+ // axios would fall back to the instance timeout.
18042
+ ...typeof timeout === "number" ? { timeout } : {}
18036
18043
  });
18037
18044
  var errorIsApiError = (error) => {
18038
18045
  if (!(error instanceof import_axios3.AxiosError)) return false;
@@ -20580,6 +20587,16 @@ var NetworkConnectionObserver = class extends WithSubscriptions {
20580
20587
  get isOnline() {
20581
20588
  return this.state.getLatestValue().isOnline;
20582
20589
  }
20590
+ /**
20591
+ * Whether the installed reporter is the host default that mirrors this client's WebSocket
20592
+ * (`createWSConnectionNetworkStatusReporter`). Under it `isOnline === false` means "the socket is
20593
+ * down", not "the device is offline".
20594
+ *
20595
+ * @internal
20596
+ */
20597
+ get usesDefaultWSConnectionNetworkStatusReporter() {
20598
+ return this.installedReporter !== void 0 && this.installedReporter === this.defaultReporter && this.defaultReporter !== browserNetworkStatusReporter;
20599
+ }
20583
20600
  /**
20584
20601
  * Installs the platform listener that reports device network status, unsubscribing whatever was
20585
20602
  * there. Pass `null` to clear it, which leaves the last known status alone rather than reverting to
@@ -20631,6 +20648,7 @@ var NetworkConnectionObserver = class extends WithSubscriptions {
20631
20648
  // src/connection/wsConnection/config.ts
20632
20649
  var DEFAULT_WS_CONNECTION_CONFIG = deepFreezeConfig({
20633
20650
  connectTimeoutMs: 15 * 1e3,
20651
+ enableWSFallback: false,
20634
20652
  pingIntervalMs: 25 * 1e3,
20635
20653
  healthCheckGracePeriodMs: 10 * 1e3,
20636
20654
  offlineNotificationDisplayDelayMs: 5 * 1e3,
@@ -21218,7 +21236,190 @@ var StableWSConnection = class {
21218
21236
 
21219
21237
  // src/connection/wsConnection/WSConnection.ts
21220
21238
  var import_state_store25 = require("@stream-io/state-store");
21221
- var logger15 = chatLoggerSystem.getLogger("client");
21239
+
21240
+ // src/connection/wsConnection/WSConnectionFallback.ts
21241
+ var import_axios4 = __toESM(require("axios"));
21242
+ var logger15 = chatLoggerSystem.getLogger("connection");
21243
+ var WSConnectionFallback = class {
21244
+ constructor({ client }) {
21245
+ /**
21246
+ * Applies a change in the device's network status. Stands in for v9's `window` online/offline
21247
+ * listeners: `WSConnection` routes the network-status store here once it has switched to this
21248
+ * long-poll.
21249
+ *
21250
+ * @internal
21251
+ */
21252
+ this._applyNetworkStatus = (online) => {
21253
+ if (this.state === "DISCONNECTED" /* Disconnected */) return;
21254
+ this._log(`_applyNetworkStatus() - ${online ? "online" : "offline"}`);
21255
+ if (!online) {
21256
+ this._setState("CLOSED" /* Closed */);
21257
+ this.abortController?.abort();
21258
+ this.abortController = void 0;
21259
+ return;
21260
+ }
21261
+ if (this.state === "CLOSED" /* Closed */) {
21262
+ this.connect(true);
21263
+ }
21264
+ };
21265
+ /**
21266
+ * connect try to open a longpoll request
21267
+ *
21268
+ * @param reconnect - should be false for first call and true for subsequent calls to keep the connection alive and settle the connect promises
21269
+ *
21270
+ * @internal
21271
+ */
21272
+ this.connect = async (reconnect = false) => {
21273
+ if (this.state === "CONNECTING" /* Connecting */) {
21274
+ this._log("connect() - connecting already in progress", { reconnect }, "warn");
21275
+ return;
21276
+ }
21277
+ if (this.state === "CONNECTED" /* Connected */) {
21278
+ this._log("connect() - already connected and polling", { reconnect }, "warn");
21279
+ return;
21280
+ }
21281
+ this._setState("CONNECTING" /* Connecting */);
21282
+ this.client.connectionIdManager.arm();
21283
+ try {
21284
+ const { event } = await this._req(
21285
+ // Authenticated by the request's `Authorization` header, so the message carries only a
21286
+ // placeholder token.
21287
+ { json: this.client._buildWSAuthPayload() },
21288
+ { timeout: 8e3 },
21289
+ // 8s
21290
+ reconnect
21291
+ );
21292
+ this.client.connectionIdManager.resolveConnectionId(event.connection_id);
21293
+ this._setState("CONNECTED" /* Connected */);
21294
+ this.client.dispatchEvent(event);
21295
+ this._poll();
21296
+ if (reconnect) {
21297
+ this.client._settleConnectPromises();
21298
+ }
21299
+ return event;
21300
+ } catch (err) {
21301
+ if (this.state !== "DISCONNECTED" /* Disconnected */)
21302
+ this._setState("CLOSED" /* Closed */);
21303
+ if (!import_axios4.default.isCancel(err)) this.client.connectionIdManager.rejectConnectionId(err);
21304
+ throw err;
21305
+ }
21306
+ };
21307
+ /**
21308
+ * Stops polling and tells the server to close `connectionId`.
21309
+ *
21310
+ * @param timeout - The close request's timeout, in milliseconds.
21311
+ * @param connectionId - The id to close. Passed in rather than read from
21312
+ * `client.connectionIdManager`, because `WSConnection.disconnect()` disconnects the old socket
21313
+ * first, and that drops the manager's id.
21314
+ *
21315
+ * @internal
21316
+ */
21317
+ this.disconnect = async (timeout = 2e3, connectionId) => {
21318
+ this._setState("DISCONNECTED" /* Disconnected */);
21319
+ this.abortController?.abort();
21320
+ this.abortController = void 0;
21321
+ try {
21322
+ await this._req({ close: true, connection_id: connectionId }, { timeout }, false);
21323
+ this._log(`disconnect() - Closed connectionID`);
21324
+ } catch (err) {
21325
+ this._log(`disconnect() - Failed`, { err }, "error");
21326
+ }
21327
+ };
21328
+ this._req = async (params, config, retry) => {
21329
+ if (!this.abortController && !params.close) {
21330
+ this.abortController = new AbortController();
21331
+ }
21332
+ try {
21333
+ const res = await this.client.longPoll(params, {
21334
+ ...config,
21335
+ signal: this.abortController?.signal
21336
+ });
21337
+ this.consecutiveFailures = 0;
21338
+ return res;
21339
+ } catch (error) {
21340
+ this.consecutiveFailures += 1;
21341
+ if (retry && isErrorRetryable(error)) {
21342
+ this._log(`_req() - Retryable error, retrying request`);
21343
+ await sleep(retryInterval(this.consecutiveFailures));
21344
+ if (params.connection_id && params.connection_id !== this.client.connectionIdManager.connectionId) {
21345
+ this._log(`_req() - Connection id changed, dropping the retry`);
21346
+ throw new import_axios4.CanceledError(
21347
+ "The connection id changed while the retry was waiting"
21348
+ );
21349
+ }
21350
+ return this._req(params, config, retry);
21351
+ }
21352
+ throw error;
21353
+ }
21354
+ };
21355
+ this._poll = async () => {
21356
+ while (this.state === "CONNECTED" /* Connected */) {
21357
+ try {
21358
+ const data = await this._req(
21359
+ { connection_id: this.client.connectionIdManager.connectionId },
21360
+ { timeout: 3e4 },
21361
+ // 30s => API responds in 20s if there is no event
21362
+ true
21363
+ );
21364
+ if (data.events?.length) {
21365
+ for (let i = 0; i < data.events.length; i++) {
21366
+ this.client.dispatchEvent(data.events[i]);
21367
+ }
21368
+ }
21369
+ } catch (error) {
21370
+ if (import_axios4.default.isCancel(error)) {
21371
+ this._log(`_poll() - axios canceled request`);
21372
+ return;
21373
+ }
21374
+ if (isConnectionIDError(error)) {
21375
+ this._log(`_poll() - ConnectionID error, connecting without ID...`);
21376
+ this._setState("DISCONNECTED" /* Disconnected */);
21377
+ this.connect(true);
21378
+ return;
21379
+ }
21380
+ if (isAPIError(error) && !isErrorRetryable(error)) {
21381
+ this._setState("CLOSED" /* Closed */);
21382
+ this.client.connectionIdManager.rejectConnectionId(error);
21383
+ return;
21384
+ }
21385
+ await sleep(retryInterval(this.consecutiveFailures));
21386
+ }
21387
+ }
21388
+ };
21389
+ this.client = client;
21390
+ this.state = "INIT" /* Init */;
21391
+ this.consecutiveFailures = 0;
21392
+ }
21393
+ /**
21394
+ * Whether a connect request is in flight.
21395
+ *
21396
+ * @internal
21397
+ */
21398
+ get isConnecting() {
21399
+ return this.state === "CONNECTING" /* Connecting */;
21400
+ }
21401
+ _log(msg, extra = {}, level = "info") {
21402
+ const log = logger15.withExtraTags("connection_fallback");
21403
+ log[level]("WSConnectionFallback:" + msg, extra);
21404
+ }
21405
+ _setState(state) {
21406
+ this._log(`_setState() - ${state}`);
21407
+ const previous = this.state;
21408
+ this.state = state;
21409
+ if (previous === "CONNECTING" /* Connecting */ && this.state === "CONNECTED" /* Connected */) {
21410
+ this.client.wsConnection._setStatus({ isHealthy: true });
21411
+ }
21412
+ if (this.state === "CLOSED" /* Closed */ || this.state === "DISCONNECTED" /* Disconnected */) {
21413
+ this.client.connectionIdManager.invalidate();
21414
+ if (this.client.wsConnection._setStatus({ isHealthy: false })) {
21415
+ this.client._markActiveChannelsWatchInterrupted();
21416
+ }
21417
+ }
21418
+ }
21419
+ };
21420
+
21421
+ // src/connection/wsConnection/WSConnection.ts
21422
+ var logger16 = chatLoggerSystem.getLogger("client");
21222
21423
  var WSConnection = class extends WithSubscriptions {
21223
21424
  constructor({ client }) {
21224
21425
  super();
@@ -21244,6 +21445,9 @@ var WSConnection = class extends WithSubscriptions {
21244
21445
  *
21245
21446
  * Read from the store rather than from an event, so there is one description of the device's
21246
21447
  * network rather than two that can disagree.
21448
+ *
21449
+ * After an `enableWSFallback` switch it routes to {@link fallback} instead, leaving the
21450
+ * disconnected socket out of it.
21247
21451
  */
21248
21452
  this.registerSubscriptions = () => {
21249
21453
  if (!this.hasSubscriptions) {
@@ -21252,7 +21456,7 @@ var WSConnection = class extends WithSubscriptions {
21252
21456
  ({ isOnline }) => ({ isOnline }),
21253
21457
  ({ isOnline }) => {
21254
21458
  if (typeof isOnline !== "boolean") return;
21255
- this.connection?._applyNetworkStatus(isOnline);
21459
+ (this.fallback ?? this.connection)?._applyNetworkStatus(isOnline);
21256
21460
  }
21257
21461
  )
21258
21462
  );
@@ -21305,7 +21509,7 @@ var WSConnection = class extends WithSubscriptions {
21305
21509
  for (const correction of corrections) {
21306
21510
  if (this.warnedClamps.get(correction.field) === correction.requested) continue;
21307
21511
  this.warnedClamps.set(correction.field, correction.requested);
21308
- logger15.withExtraTags("wsConnection").warn(
21512
+ logger16.withExtraTags("wsConnection").warn(
21309
21513
  `The WebSocket setting '${correction.field}' is outside its supported range; using ${correction.used}ms instead of ${correction.requested}ms.`,
21310
21514
  { bounds: WS_CONNECTION_CONFIG_BOUNDS[correction.field] }
21311
21515
  );
@@ -21324,22 +21528,24 @@ var WSConnection = class extends WithSubscriptions {
21324
21528
  this.configController.initialize(config);
21325
21529
  }
21326
21530
  /**
21327
- * Is this WebSocket up. Read from {@link state} rather than from the current socket, so it
21328
- * survives the socket being replaced and answers `false` rather than throwing before the first
21329
- * connect.
21531
+ * Is this WebSocket up — or, after an `enableWSFallback` switch, the long-poll. Read from
21532
+ * {@link state} rather than from the current socket, so it survives the socket being replaced and
21533
+ * answers `false` rather than throwing before the first connect.
21330
21534
  */
21331
21535
  get isHealthy() {
21332
21536
  return this.state.getLatestValue().isHealthy;
21333
21537
  }
21334
- /** Whether a connection attempt is in flight. */
21538
+ /** Whether a connection attempt is in flight — the long-poll's after an `enableWSFallback` switch. */
21335
21539
  get isConnecting() {
21336
- return this.connection?.isConnecting ?? false;
21540
+ return (this.fallback ?? this.connection)?.isConnecting ?? false;
21337
21541
  }
21338
21542
  /**
21339
- * Records this WebSocket's status. Returns whether it changed.
21543
+ * Records this connection's status. Returns whether it changed.
21340
21544
  *
21341
- * {@link StableWSConnection} is the only caller, and it routes **every** status transition through
21342
- * here — including `disconnect()`, which `closeConnection()` uses, and the two error paths.
21545
+ * {@link StableWSConnection} routes **every** status transition through here — including
21546
+ * `disconnect()`, which `closeConnection()` uses, and the two error paths. The one other
21547
+ * caller is {@link fallback}, which takes over once `enableWSFallback` has switched to
21548
+ * long-polling.
21343
21549
  *
21344
21550
  * @internal
21345
21551
  */
@@ -21362,19 +21568,45 @@ var WSConnection = class extends WithSubscriptions {
21362
21568
  * Creation lives here rather than in the constructor because a client that never calls
21363
21569
  * `connectUser` should never build a socket, and here rather than in `client.connect()` because a
21364
21570
  * field belongs to the object that owns it.
21571
+ *
21572
+ * With `enableWSFallback`, a socket that fails with a network error is replaced by the long-poll
21573
+ * ({@link fallback}), which every later call connects instead of building a socket.
21365
21574
  */
21366
- connect(timeout) {
21575
+ async connect(timeout) {
21576
+ if (this.fallback) {
21577
+ return await this.fallback.connect();
21578
+ }
21367
21579
  const next = this.buildConnection();
21368
21580
  const previous = this.connection;
21369
21581
  if (previous && previous !== next) {
21370
21582
  void Promise.resolve(previous.disconnect()).catch((error) => {
21371
- logger15.withExtraTags("wsConnection").debug("Closing the replaced WebSocket failed; discarding it anyway.", {
21583
+ logger16.withExtraTags("wsConnection").debug("Closing the replaced WebSocket failed; discarding it anyway.", {
21372
21584
  error
21373
21585
  });
21374
21586
  });
21375
21587
  }
21376
21588
  this.connection = next;
21377
- return next.connect(timeout);
21589
+ try {
21590
+ return await next.connect(timeout);
21591
+ } catch (error) {
21592
+ const { networkConnection } = this.client;
21593
+ const isDeviceOffline = networkConnection.isOnline === false && !networkConnection.usesDefaultWSConnectionNetworkStatusReporter;
21594
+ if (this.config.enableWSFallback && isWSFailure(error) && !isDeviceOffline) {
21595
+ logger16.withExtraTags("connect").info("WS failed, fallback to longpoll");
21596
+ this.client.dispatchEvent({
21597
+ type: "connection.fallback_activated",
21598
+ mode: "longpoll"
21599
+ });
21600
+ next._destroyCurrentWSConnection();
21601
+ void next.disconnect();
21602
+ this.fallback = new WSConnectionFallback({ client: this.client });
21603
+ return await this.fallback.connect();
21604
+ }
21605
+ if (!isWSFailure(error)) {
21606
+ this.client.connectionIdManager.rejectConnectionId(error);
21607
+ }
21608
+ throw error;
21609
+ }
21378
21610
  }
21379
21611
  /**
21380
21612
  * `config.connection` lets a caller supply a pre-built socket — used by unit tests, and the reason
@@ -21388,8 +21620,17 @@ var WSConnection = class extends WithSubscriptions {
21388
21620
  }
21389
21621
  return new StableWSConnection({ wsConnection: this });
21390
21622
  }
21391
- disconnect(timeout) {
21392
- return this.connection?.disconnect(timeout);
21623
+ /**
21624
+ * Closes the socket and, after an `enableWSFallback` switch, the long-poll too, telling the
21625
+ * server to close its connection id. The long-poll's close request uses `timeout` as its timeout
21626
+ * (2s when omitted).
21627
+ */
21628
+ async disconnect(timeout) {
21629
+ const connectionId = this.client.connectionIdManager.connectionId;
21630
+ await Promise.all([
21631
+ this.connection?.disconnect(timeout),
21632
+ this.fallback?.disconnect(timeout, connectionId)
21633
+ ]);
21393
21634
  }
21394
21635
  /**
21395
21636
  * @deprecated Report network status through `client.networkConnection.setStatus(isOnline)` instead,
@@ -21891,7 +22132,7 @@ var CONSTRUCTION_ONLY_CONFIG_PATHS = {
21891
22132
  };
21892
22133
 
21893
22134
  // src/configuration/InstanceConfigurationRegistry.ts
21894
- var logger16 = chatLoggerSystem.getLogger("instance-configuration");
22135
+ var logger17 = chatLoggerSystem.getLogger("instance-configuration");
21895
22136
  var InstanceConfigurationRegistry = class {
21896
22137
  constructor() {
21897
22138
  /**
@@ -22089,7 +22330,7 @@ var InstanceConfigurationRegistry = class {
22089
22330
  try {
22090
22331
  instance.reinitializeConfig?.();
22091
22332
  } catch (error) {
22092
- logger16.error("reinitializeConfig threw during reset", error);
22333
+ logger17.error("reinitializeConfig threw during reset", error);
22093
22334
  }
22094
22335
  }
22095
22336
  }
@@ -22147,7 +22388,7 @@ var InstanceConfigurationRegistry = class {
22147
22388
  warnIfKeyLooksUnknown(key) {
22148
22389
  if (INSTANCE_CONFIG_TREE_KEYS.includes(key)) return;
22149
22390
  if (this.hasLiveInstances(key)) return;
22150
- logger16.withExtraTags(key).warn(
22391
+ logger17.withExtraTags(key).warn(
22151
22392
  "Configuration registered for a key this package does not define, with nothing subscribed to it. Check the spelling; a key declared by a downstream SDK is expected here only if its owner subscribes later."
22152
22393
  );
22153
22394
  }
@@ -22174,7 +22415,7 @@ var InstanceConfigurationRegistry = class {
22174
22415
  return !isEqual(getPath(registered, path), getPath(config, path));
22175
22416
  });
22176
22417
  if (late.length === 0) return;
22177
- logger16.withExtraTags(key).warn(
22418
+ logger17.withExtraTags(key).warn(
22178
22419
  `These paths are read once during construction, so they will not affect the ${key} instance(s) that already exist: ${late.join(", ")}. Register configuration before the instances are created \u2014 typically alongside StreamChat.getInstance().`
22179
22420
  );
22180
22421
  }
@@ -23525,6 +23766,7 @@ var ChatApi = class {
23525
23766
  }
23526
23767
  async longPoll(request, requestOptions) {
23527
23768
  const queryParams = {
23769
+ close: request?.close,
23528
23770
  connection_id: request?.connection_id,
23529
23771
  json: request?.json
23530
23772
  };
@@ -24137,7 +24379,7 @@ var ChatApi = class {
24137
24379
  function isString(value) {
24138
24380
  return typeof value === "string" || value instanceof String;
24139
24381
  }
24140
- var logger17 = chatLoggerSystem.getLogger("client");
24382
+ var logger18 = chatLoggerSystem.getLogger("client");
24141
24383
  var offlineDbLogger2 = chatLoggerSystem.getLogger("offline-db");
24142
24384
  var markThreadChannelWatched = ({ channel }) => {
24143
24385
  channel.watchStatus = ChannelWatchStatus.Watching;
@@ -24196,7 +24438,7 @@ var StreamChat = class _StreamChat extends ChatApi {
24196
24438
  throw new Error('The "id" field on the user is missing');
24197
24439
  }
24198
24440
  if (this.userId === user.id && this.setUserPromise) {
24199
- logger17.withExtraTags("connectUser").warn(
24441
+ logger18.withExtraTags("connectUser").warn(
24200
24442
  "Detected consecutive calls to connectUser. Ideally, this function should only be called once."
24201
24443
  );
24202
24444
  return this.setUserPromise;
@@ -24207,7 +24449,7 @@ var StreamChat = class _StreamChat extends ChatApi {
24207
24449
  );
24208
24450
  }
24209
24451
  if (this.node && !this.options.allowServerSideConnect) {
24210
- logger17.withExtraTags("connectUser").warn(
24452
+ logger18.withExtraTags("connectUser").warn(
24211
24453
  'Do not use connectUser server-side. connectUser impacts MAU and concurrent connection usage, and therefore your bill. If you have a valid use case, set "allowServerSideConnect: true" in the client options to disable this warning.'
24212
24454
  );
24213
24455
  }
@@ -24253,8 +24495,12 @@ var StreamChat = class _StreamChat extends ChatApi {
24253
24495
  * So when your app goes to background, you can call `client.closeConnection`.
24254
24496
  * And when app comes back to foreground, call `client.openConnection`.
24255
24497
  *
24498
+ * After an `enableWSFallback` switch it closes the long-poll as well, telling the server to
24499
+ * close its connection id.
24500
+ *
24256
24501
  * @param timeout - Max number of milliseconds to wait for the WebSocket close event before forcefully assuming
24257
24502
  * successful disconnection. See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent (optional).
24503
+ * The long-poll's close request uses it as its timeout (2s when omitted).
24258
24504
  */
24259
24505
  this.closeConnection = async (timeout) => {
24260
24506
  this._resetAIStateOnActiveChannels();
@@ -24278,7 +24524,8 @@ var StreamChat = class _StreamChat extends ChatApi {
24278
24524
  return Promise.resolve();
24279
24525
  };
24280
24526
  /**
24281
- * Creates a new WebSocket connection with the current user.
24527
+ * Creates a new WebSocket connection with the current user. After an `enableWSFallback` switch it
24528
+ * reconnects the long-poll instead: the client never goes back to the WebSocket.
24282
24529
  *
24283
24530
  * @returns The WebSocket connect promise, or an empty resolved promise if a connection is already active.
24284
24531
  */
@@ -24289,11 +24536,11 @@ var StreamChat = class _StreamChat extends ChatApi {
24289
24536
  );
24290
24537
  }
24291
24538
  if (this.wsConnection?.isConnecting && this.wsPromise) {
24292
- logger17.withExtraTags("openConnection").debug("A connection attempt is already in progress.");
24539
+ logger18.withExtraTags("openConnection").debug("A connection attempt is already in progress.");
24293
24540
  return this.wsPromise;
24294
24541
  }
24295
24542
  if (this.wsConnection?.isHealthy && this.connectionIdManager.connectionId) {
24296
- logger17.withExtraTags("openConnection").debug("openConnection was called twice; a healthy connection already exists.");
24543
+ logger18.withExtraTags("openConnection").debug("openConnection was called twice; a healthy connection already exists.");
24297
24544
  return;
24298
24545
  }
24299
24546
  this.clientId = `${this.userId}--${randomId()}`;
@@ -24350,7 +24597,7 @@ var StreamChat = class _StreamChat extends ChatApi {
24350
24597
  * @returns The close-connection promise.
24351
24598
  */
24352
24599
  this.disconnectUser = (timeout) => {
24353
- logger17.withExtraTags("disconnectUser").info("Disconnecting the client.");
24600
+ logger18.withExtraTags("disconnectUser").info("Disconnecting the client.");
24354
24601
  delete this.user;
24355
24602
  delete this._user;
24356
24603
  const teardownReason = new Error(
@@ -24358,8 +24605,8 @@ var StreamChat = class _StreamChat extends ChatApi {
24358
24605
  );
24359
24606
  this._rejectPendingWsPromise(teardownReason);
24360
24607
  this.wsPromise = null;
24361
- this.connectionIdManager.rejectConnectionId(teardownReason);
24362
24608
  const closePromise = this.closeConnection(timeout);
24609
+ this.connectionIdManager.rejectConnectionId(teardownReason);
24363
24610
  for (const channel of Object.values(this.activeChannels)) {
24364
24611
  channel._disconnect();
24365
24612
  }
@@ -24373,9 +24620,9 @@ var StreamChat = class _StreamChat extends ChatApi {
24373
24620
  this.unsubscribeClientConfiguration?.();
24374
24621
  this.unsubscribeClientConfiguration = void 0;
24375
24622
  closePromise.finally(() => {
24376
- this.tokenManager.reset();
24623
+ if (!this.userId) this.tokenManager.reset();
24377
24624
  }).catch(
24378
- (err) => logger17.withExtraTags("disconnectUser").error("The close promise rejected during disconnect.", { error: err })
24625
+ (err) => logger18.withExtraTags("disconnectUser").error("The close promise rejected during disconnect.", { error: err })
24379
24626
  );
24380
24627
  return closePromise;
24381
24628
  };
@@ -24393,7 +24640,7 @@ var StreamChat = class _StreamChat extends ChatApi {
24393
24640
  */
24394
24641
  this.connectAnonymousUser = () => {
24395
24642
  if (this.node && !this.options.allowServerSideConnect) {
24396
- logger17.withExtraTags("connectAnonymousUser").warn(
24643
+ logger18.withExtraTags("connectAnonymousUser").warn(
24397
24644
  'Do not use connectUser server-side. connectUser impacts MAU and concurrent connection usage, and therefore your bill. If you have a valid use case, set "allowServerSideConnect: true" in the client options to disable this warning.'
24398
24645
  );
24399
24646
  }
@@ -24546,7 +24793,7 @@ var StreamChat = class _StreamChat extends ChatApi {
24546
24793
  ...this.listeners.get(event.type) ?? []
24547
24794
  ];
24548
24795
  for (const listener of listeners) {
24549
- invokeEventListener(listener, event, logger17);
24796
+ invokeEventListener(listener, event, logger18);
24550
24797
  }
24551
24798
  };
24552
24799
  /**
@@ -24557,15 +24804,16 @@ var StreamChat = class _StreamChat extends ChatApi {
24557
24804
  * Requests no longer settle against these: waiting for a connection id is the
24558
24805
  * {@link ConnectionIdManager}'s job, applied centrally in `ApiClient`.
24559
24806
  *
24560
- * Called by `StableWSConnection._reconnect()`. Recovery itself is owned by
24561
- * {@link ConnectionRecoveryManager}, which subscribes to the connection lifecycle and so covers
24562
- * every reconnect path — including `closeConnection()` → `openConnection()` (mobile backgrounding),
24563
- * which never reaches `_reconnect()` at all.
24807
+ * Called by `StableWSConnection._reconnect()`, and by `WSConnectionFallback.connect(true)`
24808
+ * for the long-poll's own reconnects. Recovery itself is owned by
24809
+ * {@link ConnectionRecoveryManager}, which subscribes to the connection lifecycle and so
24810
+ * covers every reconnect path — including `closeConnection()` → `openConnection()` (mobile
24811
+ * backgrounding), which reaches neither.
24564
24812
  *
24565
24813
  * @internal
24566
24814
  */
24567
24815
  this._settleConnectPromises = () => {
24568
- logger17.withExtraTags("_settleConnectPromises").info(
24816
+ logger18.withExtraTags("_settleConnectPromises").info(
24569
24817
  `Connection re-established with connection ID ${this.connectionIdManager.connectionId}.`
24570
24818
  );
24571
24819
  if (this._wsPromiseSettled) {
@@ -24655,7 +24903,7 @@ var StreamChat = class _StreamChat extends ChatApi {
24655
24903
  return this.userAgent;
24656
24904
  }
24657
24905
  if (!this.cachedUserAgent) {
24658
- const version = "10.0.0-rc.16";
24906
+ const version = "10.0.0-rc.17";
24659
24907
  const { name: sdkName, version: sdkVersion } = this.sdkIdentifier ?? {};
24660
24908
  const { name: appName, version: appVersion } = this.appIdentifier ?? {};
24661
24909
  const { os, model: deviceModel } = this.deviceIdentifier ?? {};
@@ -24685,11 +24933,21 @@ var StreamChat = class _StreamChat extends ChatApi {
24685
24933
  *
24686
24934
  * @returns The JSON-encoded auth message.
24687
24935
  */
24688
- this._buildWSAuthMessage = () => JSON.stringify({
24689
- // The server requires a non-empty token even for anonymous connections, but
24690
- // skips JWT parsing for any string that is not shaped like one. Anonymous users
24691
- // have no token, so send a placeholder the server accepts and ignores.
24692
- token: this.tokenManager.getToken() || "anonymous",
24936
+ this._buildWSAuthMessage = () => JSON.stringify(this._buildWSAuthPayload(this.tokenManager.getToken()));
24937
+ /**
24938
+ * The auth message itself — what {@link _buildWSAuthMessage} encodes for the WebSocket, and what
24939
+ * the long-poll fallback sends as `longPoll()`'s `json` query param.
24940
+ *
24941
+ * @private
24942
+ *
24943
+ * @param token - The token the message carries. The long-poll passes none: it authenticates
24944
+ * through the request's `Authorization` header instead.
24945
+ */
24946
+ this._buildWSAuthPayload = (token) => ({
24947
+ // The server requires a non-empty token, but skips JWT parsing for any string that is not
24948
+ // shaped like one. Anonymous users have no token, and the long-poll does not send one, so both
24949
+ // send a placeholder the server accepts and ignores.
24950
+ token: token || "anonymous",
24693
24951
  // `connect()` rejects before reaching this when `_user` is unset.
24694
24952
  user_details: this._user,
24695
24953
  products: ["chat"]
@@ -24712,7 +24970,7 @@ var StreamChat = class _StreamChat extends ChatApi {
24712
24970
  this.browser = options.browser ?? typeof window !== "undefined";
24713
24971
  this.node = !this.browser;
24714
24972
  this.options = { ...options };
24715
- this.axiosInstance = import_axios4.default.create({
24973
+ this.axiosInstance = import_axios5.default.create({
24716
24974
  timeout: 3e3,
24717
24975
  withCredentials: false,
24718
24976
  ...this.options.axiosRequestConfig,
@@ -24969,14 +25227,14 @@ var StreamChat = class _StreamChat extends ChatApi {
24969
25227
  const key = callbackOrNothing ? callbackOrString : "all";
24970
25228
  const callback = callbackOrNothing ? callbackOrNothing : callbackOrString;
24971
25229
  const set = this.listeners.get(key) ?? /* @__PURE__ */ new Set();
24972
- logger17.withExtraTags("on").debug(`Attaching a listener for the "${key}" event.`);
25230
+ logger18.withExtraTags("on").debug(`Attaching a listener for the "${key}" event.`);
24973
25231
  set.add(callback);
24974
25232
  if (!this.listeners.has(key)) {
24975
25233
  this.listeners.set(key, set);
24976
25234
  }
24977
25235
  return {
24978
25236
  unsubscribe: () => {
24979
- logger17.withExtraTags("on").debug(`Removing the listener for the "${key}" event.`);
25237
+ logger18.withExtraTags("on").debug(`Removing the listener for the "${key}" event.`);
24980
25238
  set.delete(callback);
24981
25239
  if (!set.size) {
24982
25240
  this.listeners.delete(key);
@@ -24987,7 +25245,7 @@ var StreamChat = class _StreamChat extends ChatApi {
24987
25245
  off(callbackOrString, callbackOrNothing) {
24988
25246
  const key = callbackOrNothing ? callbackOrString : "all";
24989
25247
  const callback = callbackOrNothing ? callbackOrNothing : callbackOrString;
24990
- logger17.withExtraTags("off").debug(`Removing the listener for the "${key}" event.`);
25248
+ logger18.withExtraTags("off").debug(`Removing the listener for the "${key}" event.`);
24991
25249
  const set = this.listeners.get(key);
24992
25250
  set?.delete(callback);
24993
25251
  if (!set?.size) {
@@ -24997,7 +25255,7 @@ var StreamChat = class _StreamChat extends ChatApi {
24997
25255
  _handleClientEvent(event) {
24998
25256
  const client = this;
24999
25257
  const postListenerCallbacks = [];
25000
- logger17.withExtraTags("_handleClientEvent").debug(`Received an event of type "${event.type}".`, { event });
25258
+ logger18.withExtraTags("_handleClientEvent").debug(`Received an event of type "${event.type}".`, { event });
25001
25259
  if (event.type === "user.presence.changed" || event.type === "user.updated" || event.type === "user.deleted") {
25002
25260
  this._handleUserEvent(event);
25003
25261
  }
@@ -25074,10 +25332,13 @@ var StreamChat = class _StreamChat extends ChatApi {
25074
25332
  * Only `Watching` is demoted: a channel the consumer stopped on purpose, or one that was torn
25075
25333
  * down, stays `NotWatching` and must not be resurrected by a reconnect.
25076
25334
  *
25077
- * Invoked from two places, because neither covers the other: `StableWSConnection._setHealth(false)`
25078
- * for an abnormal close/error, and `closeConnection()` for a deliberate shutdown (e.g. mobile
25079
- * backgrounding), whose `disconnect()` writes the status through `_applyHealth` and so never
25080
- * reaches `_setHealth`.
25335
+ * Invoked from two places on the WebSocket, because neither covers the other:
25336
+ * `StableWSConnection._setHealth(false)` for an abnormal close/error, and
25337
+ * `closeConnection()` for a deliberate shutdown (e.g. mobile backgrounding), whose
25338
+ * `disconnect()` writes the status through `_applyHealth` and so never reaches
25339
+ * `_setHealth`. After an `enableWSFallback` switch the long-poll calls it too, from
25340
+ * `WSConnectionFallback._setState()` whenever going closed or disconnected takes the
25341
+ * status offline.
25081
25342
  */
25082
25343
  _markActiveChannelsWatchInterrupted() {
25083
25344
  for (const cid in this.activeChannels) {
@@ -25126,14 +25387,7 @@ var StreamChat = class _StreamChat extends ChatApi {
25126
25387
  if (!this.clientId) {
25127
25388
  throw Error("Property clientId is not set");
25128
25389
  }
25129
- try {
25130
- return await this.wsConnection.connect();
25131
- } catch (error) {
25132
- if (!isWSFailure(error)) {
25133
- this.connectionIdManager.rejectConnectionId(error);
25134
- }
25135
- throw error;
25136
- }
25390
+ return await this.wsConnection.connect();
25137
25391
  }
25138
25392
  /**
25139
25393
  * Queries users and watches user presence.
@@ -25150,6 +25404,31 @@ var StreamChat = class _StreamChat extends ChatApi {
25150
25404
  this.state.updateUsers(data.users);
25151
25405
  return data;
25152
25406
  }
25407
+ /**
25408
+ * Stops watching a channel on this client's connection.
25409
+ *
25410
+ * It carries no `watch` flag, so the request layer does not hold it, but it is connection-scoped
25411
+ * by definition: it tells the server which connection should stop watching. So it waits for this
25412
+ * client's connection id exactly as a watching request does — through a handshake or a reconnect,
25413
+ * until the caller's abort signal fires — and throws when there is no connection and none is being
25414
+ * established.
25415
+ *
25416
+ * @param ...args - `[request, requestOptions]`. `request.connection_id` is replaced by this
25417
+ * client's connection id.
25418
+ * @returns The server response.
25419
+ */
25420
+ async stopWatchingChannel(...args) {
25421
+ const [request, requestOptions] = args;
25422
+ const signal = requestOptions?.signal;
25423
+ const connectionId = await this.connectionIdManager.getConnectionId(
25424
+ // Check if signal is still usable - if it is read from offline DB, it has lost `addEventListener`.
25425
+ typeof signal?.addEventListener === "function" ? signal : void 0
25426
+ );
25427
+ return super.stopWatchingChannel(
25428
+ { ...request, connection_id: connectionId },
25429
+ requestOptions
25430
+ );
25431
+ }
25153
25432
  /**
25154
25433
  * Queries channels and returns the full API response including top-level metadata such as
25155
25434
  * `predefined_filter`.
@@ -26077,6 +26356,11 @@ var WS_CONNECTION_FIELDS = {
26077
26356
  kind: "value",
26078
26357
  type: "number"
26079
26358
  },
26359
+ enableWSFallback: {
26360
+ description: "Falls back to HTTP long-polling (`/api/v2/longpoll`) when the WebSocket cannot connect, for networks that block WebSockets. The WebSocket connects within `connectTimeoutMs`, as without the flag; lower it to switch sooner. Dispatches `connection.fallback_activated` when it switches, and stays on long-poll from then on. Defaults to `false`. Was `StreamChatOptions.enableWSFallback`.",
26361
+ kind: "value",
26362
+ type: "boolean"
26363
+ },
26080
26364
  offlineNotificationDisplayDelayMs: {
26081
26365
  description: "How long a drop must last before a UI reports it. Most drops resolve in under a second, so a banner showing all of them makes a working application look broken. Nothing in this package waits on it: it is here so the UI SDKs share one value. Zero holds nothing back, landing on the next task.",
26082
26366
  kind: "value",
@@ -26135,7 +26419,7 @@ var CLIENT_FIELDS = {
26135
26419
  kind: "group"
26136
26420
  },
26137
26421
  wsConnection: {
26138
- description: "This client's WebSocket: how long to wait for it, how often to ping it, how long it may go quiet before being declared dead, and how long a drop must last before a UI reports it.",
26422
+ description: "This client's WebSocket: how long to wait for it, how often to ping it, how long it may go quiet before being declared dead, how long a drop must last before a UI reports it, and whether to fall back to long-polling when it cannot connect.",
26139
26423
  fields: WS_CONNECTION_FIELDS,
26140
26424
  kind: "group"
26141
26425
  }
@@ -26359,8 +26643,8 @@ var OfflineError = class extends Error {
26359
26643
  };
26360
26644
 
26361
26645
  // src/offline-support/offline_sync_manager.ts
26362
- var import_axios5 = require("axios");
26363
- var logger18 = chatLoggerSystem.getLogger("offline-db");
26646
+ var import_axios6 = require("axios");
26647
+ var logger19 = chatLoggerSystem.getLogger("offline-db");
26364
26648
  var OfflineDBSyncManager = class {
26365
26649
  constructor({
26366
26650
  client,
@@ -26404,7 +26688,7 @@ var OfflineDBSyncManager = class {
26404
26688
  }
26405
26689
  );
26406
26690
  } catch (error) {
26407
- logger18.withExtraTags("init").error("Failed to initialize the offline DB sync manager.", { error });
26691
+ logger19.withExtraTags("init").error("Failed to initialize the offline DB sync manager.", { error });
26408
26692
  }
26409
26693
  };
26410
26694
  /**
@@ -26494,7 +26778,7 @@ var OfflineDBSyncManager = class {
26494
26778
  const { syncMaxEventCount } = this;
26495
26779
  const exceedsLimit = typeof syncMaxEventCount === "number" && syncMaxEventCount > 0 && result.events.length > syncMaxEventCount;
26496
26780
  if (exceedsLimit) {
26497
- logger18.withExtraTags("sync").warn(
26781
+ logger19.withExtraTags("sync").warn(
26498
26782
  `Skipping sync event replay: received ${result.events.length} events, which exceeds the configured limit of ${syncMaxEventCount}. Visible channels are refreshed on reconnect instead.`
26499
26783
  );
26500
26784
  } else {
@@ -26514,8 +26798,8 @@ var OfflineDBSyncManager = class {
26514
26798
  lastSyncedAt: (/* @__PURE__ */ new Date()).toString()
26515
26799
  });
26516
26800
  } catch (e) {
26517
- logger18.withExtraTags("syncAndExecutePendingTasks").error("An error occurred while syncing the database.", { error: e });
26518
- if ((0, import_axios5.isAxiosError)(e) && e.code === "ECONNABORTED") {
26801
+ logger19.withExtraTags("syncAndExecutePendingTasks").error("An error occurred while syncing the database.", { error: e });
26802
+ if ((0, import_axios6.isAxiosError)(e) && e.code === "ECONNABORTED") {
26519
26803
  return;
26520
26804
  }
26521
26805
  const error = e;
@@ -26555,7 +26839,7 @@ var OfflineDBSyncManager = class {
26555
26839
 
26556
26840
  // src/offline-support/offline_support_api.ts
26557
26841
  var import_state_store30 = require("@stream-io/state-store");
26558
- var logger19 = chatLoggerSystem.getLogger("offline-db");
26842
+ var logger20 = chatLoggerSystem.getLogger("offline-db");
26559
26843
  var AbstractOfflineDB = class {
26560
26844
  constructor({
26561
26845
  client,
@@ -26580,7 +26864,7 @@ var AbstractOfflineDB = class {
26580
26864
  }
26581
26865
  } catch (error) {
26582
26866
  this.state.partialNext({ initialized: false, userId: void 0 });
26583
- logger19.withExtraTags("init").error("Failed to initialize the offline database.", { error });
26867
+ logger20.withExtraTags("init").error("Failed to initialize the offline database.", { error });
26584
26868
  }
26585
26869
  };
26586
26870
  /**
@@ -26672,7 +26956,7 @@ var AbstractOfflineDB = class {
26672
26956
  }
26673
26957
  }
26674
26958
  if (!channelData) {
26675
- logger19.withExtraTags("queriesWithChannelGuard").warn(
26959
+ logger20.withExtraTags("queriesWithChannelGuard").warn(
26676
26960
  `Received a "${type}" event for a non-initialized channel that is not in the database. Skipping the event.`,
26677
26961
  { event }
26678
26962
  );
@@ -26683,7 +26967,7 @@ var AbstractOfflineDB = class {
26683
26967
  execute: false
26684
26968
  });
26685
26969
  if (!channelQuery) {
26686
- logger19.withExtraTags("queriesWithChannelGuard").warn(
26970
+ logger20.withExtraTags("queriesWithChannelGuard").warn(
26687
26971
  `Could not create channel queries on a "${type}" event for an initialized channel that is not in the database. Skipping the event.`,
26688
26972
  { event }
26689
26973
  );
@@ -26878,7 +27162,7 @@ var AbstractOfflineDB = class {
26878
27162
  }) => {
26879
27163
  const channel = this.client.activeChannels[cid];
26880
27164
  if (!channel?.initialized || channel.pendingDisposal) {
26881
- logger19.withExtraTags("upsertMessageWithChannelGuard", cid).warn("Skipping message persistence: the channel is not in the database.");
27165
+ logger20.withExtraTags("upsertMessageWithChannelGuard", cid).warn("Skipping message persistence: the channel is not in the database.");
26882
27166
  return [];
26883
27167
  }
26884
27168
  const queries = [