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.
- package/dist/cjs/index.browser.js +371 -87
- package/dist/cjs/index.browser.js.map +4 -4
- package/dist/cjs/index.node.js +371 -87
- package/dist/cjs/index.node.js.map +4 -4
- package/dist/esm/index.mjs +369 -85
- package/dist/esm/index.mjs.map +4 -4
- package/dist/types/api-client.d.ts +6 -1
- package/dist/types/client.d.ts +46 -12
- package/dist/types/connection/ConnectionIdManager.d.ts +9 -5
- package/dist/types/connection/ConnectionRecoveryManager.d.ts +6 -2
- package/dist/types/connection/networkConnection/NetworkConnectionObserver.d.ts +8 -0
- package/dist/types/connection/networkConnection/reporters.d.ts +4 -4
- package/dist/types/connection/wsConnection/WSConnection.d.ts +34 -9
- package/dist/types/connection/wsConnection/WSConnectionFallback.d.ts +55 -0
- package/dist/types/connection/wsConnection/types.d.ts +17 -4
- package/dist/types/errors.d.ts +1 -0
- package/dist/types/gen/chat/ChatApi.d.ts +1 -0
- package/dist/types/types.d.ts +8 -0
- package/package.json +1 -1
- package/src/api-client.ts +25 -24
- package/src/client.ts +79 -33
- package/src/configuration/shape.ts +7 -1
- package/src/connection/ConnectionIdManager.ts +9 -5
- package/src/connection/ConnectionRecoveryManager.ts +11 -6
- package/src/connection/networkConnection/NetworkConnectionObserver.ts +19 -1
- package/src/connection/networkConnection/reporters.ts +4 -4
- package/src/connection/wsConnection/WSConnection.ts +86 -14
- package/src/connection/wsConnection/WSConnectionFallback.ts +259 -0
- package/src/connection/wsConnection/config.ts +1 -0
- package/src/connection/wsConnection/types.ts +17 -4
- package/src/errors.ts +4 -0
- package/src/gen/chat/ChatApi.ts +2 -1
- 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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
18018
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
21328
|
-
*
|
|
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
|
|
21543
|
+
* Records this connection's status. Returns whether it changed.
|
|
21340
21544
|
*
|
|
21341
|
-
* {@link StableWSConnection}
|
|
21342
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21392
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
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,
|
|
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()
|
|
24561
|
-
*
|
|
24562
|
-
*
|
|
24563
|
-
*
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
24690
|
-
|
|
24691
|
-
|
|
24692
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
25078
|
-
* for an abnormal close/error, and
|
|
25079
|
-
*
|
|
25080
|
-
*
|
|
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
|
-
|
|
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,
|
|
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
|
|
26363
|
-
var
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26518
|
-
if ((0,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 = [
|