stream-chat 9.28.0 → 9.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2132,6 +2132,399 @@ var ChannelState = class {
2132
2132
  }
2133
2133
  };
2134
2134
 
2135
+ // src/store.ts
2136
+ var isPatch = (value) => typeof value === "function";
2137
+ var noop = () => {
2138
+ };
2139
+ var StateStore = class {
2140
+ constructor(value) {
2141
+ this.value = value;
2142
+ this.handlers = /* @__PURE__ */ new Set();
2143
+ this.preprocessors = /* @__PURE__ */ new Set();
2144
+ this.partialNext = (partial) => this.next((current) => ({ ...current, ...partial }));
2145
+ this.subscribeWithSelector = (selector, handler) => {
2146
+ let previouslySelectedValues;
2147
+ const wrappedHandler = (nextValue) => {
2148
+ const newlySelectedValues = selector(nextValue);
2149
+ let hasUpdatedValues = typeof previouslySelectedValues === "undefined";
2150
+ for (const key in previouslySelectedValues) {
2151
+ if (previouslySelectedValues[key] === newlySelectedValues[key]) continue;
2152
+ hasUpdatedValues = true;
2153
+ break;
2154
+ }
2155
+ if (!hasUpdatedValues) return;
2156
+ const previouslySelectedValuesCopy = previouslySelectedValues;
2157
+ previouslySelectedValues = newlySelectedValues;
2158
+ handler(newlySelectedValues, previouslySelectedValuesCopy);
2159
+ };
2160
+ return this.subscribe(wrappedHandler);
2161
+ };
2162
+ }
2163
+ /**
2164
+ * Allows merging two stores only if their keys differ otherwise there's no way to ensure the data type stability.
2165
+ * @experimental
2166
+ * This method is experimental and may change in future versions.
2167
+ */
2168
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2169
+ merge(stateStore) {
2170
+ return new MergedStateStore({
2171
+ original: this,
2172
+ merged: stateStore
2173
+ });
2174
+ }
2175
+ next(newValueOrPatch) {
2176
+ const newValue = isPatch(newValueOrPatch) ? newValueOrPatch(this.value) : newValueOrPatch;
2177
+ if (newValue === this.value) return;
2178
+ this.preprocessors.forEach((preprocessor) => preprocessor(newValue, this.value));
2179
+ const oldValue = this.value;
2180
+ this.value = newValue;
2181
+ this.handlers.forEach((handler) => handler(this.value, oldValue));
2182
+ }
2183
+ getLatestValue() {
2184
+ return this.value;
2185
+ }
2186
+ subscribe(handler) {
2187
+ handler(this.value, void 0);
2188
+ this.handlers.add(handler);
2189
+ return () => {
2190
+ this.handlers.delete(handler);
2191
+ };
2192
+ }
2193
+ /**
2194
+ * Registers a preprocessor function that will be called before the state is updated.
2195
+ *
2196
+ * Preprocessors are invoked with the new and previous values whenever `next` or `partialNext` methods
2197
+ * are called, allowing you to mutate or react to the new value before it is set. Preprocessors run in the
2198
+ * order they were registered.
2199
+ *
2200
+ * @example
2201
+ * ```ts
2202
+ * const store = new StateStore<{ count: number; isMaxValue: bool; }>({ count: 0, isMaxValue: false });
2203
+ *
2204
+ * store.addPreprocessor((nextValue, prevValue) => {
2205
+ * if (nextValue.count > 10) {
2206
+ * nextValue.count = 10; // Clamp the value to a maximum of 10
2207
+ * }
2208
+ *
2209
+ * if (nextValue.count === 10) {
2210
+ * nextValue.isMaxValue = true; // Set isMaxValue to true if count is 10
2211
+ * } else {
2212
+ * nextValue.isMaxValue = false; // Reset isMaxValue otherwise
2213
+ * }
2214
+ * });
2215
+ *
2216
+ * store.partialNext({ count: 15 });
2217
+ *
2218
+ * store.getLatestValue(); // { count: 10, isMaxValue: true }
2219
+ *
2220
+ * store.partialNext({ count: 5 });
2221
+ *
2222
+ * store.getLatestValue(); // { count: 5, isMaxValue: false }
2223
+ * ```
2224
+ *
2225
+ * @param preprocessor - The function to be called with the next and previous values before the state is updated.
2226
+ * @returns A `RemovePreprocessor` function that removes the preprocessor when called.
2227
+ */
2228
+ addPreprocessor(preprocessor) {
2229
+ this.preprocessors.add(preprocessor);
2230
+ return () => {
2231
+ this.preprocessors.delete(preprocessor);
2232
+ };
2233
+ }
2234
+ };
2235
+ var MergedStateStore = class _MergedStateStore extends StateStore {
2236
+ constructor({ original, merged }) {
2237
+ const originalValue = original.getLatestValue();
2238
+ const mergedValue = merged.getLatestValue();
2239
+ super({
2240
+ ...originalValue,
2241
+ ...mergedValue
2242
+ });
2243
+ // override original methods and "disable" them
2244
+ this.next = () => {
2245
+ console.warn(
2246
+ `${_MergedStateStore.name}.next is disabled, call original.next or merged.next instead`
2247
+ );
2248
+ };
2249
+ this.partialNext = () => {
2250
+ console.warn(
2251
+ `${_MergedStateStore.name}.partialNext is disabled, call original.partialNext or merged.partialNext instead`
2252
+ );
2253
+ };
2254
+ this.cachedOriginalValue = originalValue;
2255
+ this.cachedMergedValue = mergedValue;
2256
+ this.original = original;
2257
+ this.merged = merged;
2258
+ }
2259
+ /**
2260
+ * Subscribes to changes in the merged state store.
2261
+ *
2262
+ * This method extends the base subscribe functionality to handle the merged nature of this store:
2263
+ * 1. The first subscriber triggers registration of helper subscribers that listen to both source stores
2264
+ * 2. Changes from either source store are propagated to this merged store
2265
+ * 3. Source store values are cached to prevent unnecessary updates
2266
+ *
2267
+ * When the first subscriber is added, the method sets up listeners on both original and merged stores.
2268
+ * These listeners update the combined store value whenever either source store changes.
2269
+ * All subscriptions (helpers and the actual handler) are tracked so they can be properly cleaned up.
2270
+ *
2271
+ * @param handler - The callback function that will be executed when the state changes
2272
+ * @returns An unsubscribe function that, when called, removes the subscription and any helper subscriptions
2273
+ */
2274
+ subscribe(handler) {
2275
+ const unsubscribeFunctions = [];
2276
+ if (!this.handlers.size) {
2277
+ const base = (nextValue) => {
2278
+ super.next((currentValue) => ({
2279
+ ...currentValue,
2280
+ ...nextValue
2281
+ }));
2282
+ };
2283
+ unsubscribeFunctions.push(
2284
+ this.original.subscribe((nextValue) => {
2285
+ if (nextValue === this.cachedOriginalValue) return;
2286
+ this.cachedOriginalValue = nextValue;
2287
+ base(nextValue);
2288
+ }),
2289
+ this.merged.subscribe((nextValue) => {
2290
+ if (nextValue === this.cachedMergedValue) return;
2291
+ this.cachedMergedValue = nextValue;
2292
+ base(nextValue);
2293
+ })
2294
+ );
2295
+ }
2296
+ unsubscribeFunctions.push(super.subscribe(handler));
2297
+ return () => {
2298
+ unsubscribeFunctions.forEach((unsubscribe) => unsubscribe());
2299
+ };
2300
+ }
2301
+ /**
2302
+ * Retrieves the latest combined state from both original and merged stores.
2303
+ *
2304
+ * This method extends the base getLatestValue functionality to ensure the merged store
2305
+ * remains in sync with its source stores even when there are no active subscribers.
2306
+ *
2307
+ * When there are no handlers registered, the method:
2308
+ * 1. Fetches the latest values from both source stores
2309
+ * 2. Compares them with the cached values to detect changes
2310
+ * 3. If changes are detected, updates the internal value and caches
2311
+ * the new source values to maintain consistency
2312
+ *
2313
+ * This approach ensures that calling getLatestValue() always returns the most
2314
+ * up-to-date combined state, even if the merged store hasn't been actively
2315
+ * receiving updates through subscriptions.
2316
+ *
2317
+ * @returns The latest combined state from both original and merged stores
2318
+ */
2319
+ getLatestValue() {
2320
+ if (!this.handlers.size) {
2321
+ const originalValue = this.original.getLatestValue();
2322
+ const mergedValue = this.merged.getLatestValue();
2323
+ if (originalValue !== this.cachedOriginalValue || mergedValue !== this.cachedMergedValue) {
2324
+ this.value = {
2325
+ ...originalValue,
2326
+ ...mergedValue
2327
+ };
2328
+ this.cachedMergedValue = mergedValue;
2329
+ this.cachedOriginalValue = originalValue;
2330
+ }
2331
+ }
2332
+ return super.getLatestValue();
2333
+ }
2334
+ addPreprocessor() {
2335
+ console.warn(
2336
+ `${_MergedStateStore.name}.addPreprocessor is disabled, call original.addPreprocessor or merged.addPreprocessor instead`
2337
+ );
2338
+ return noop;
2339
+ }
2340
+ };
2341
+
2342
+ // src/utils/WithSubscriptions.ts
2343
+ var _WithSubscriptions = class _WithSubscriptions {
2344
+ constructor() {
2345
+ this.unsubscribeFunctions = /* @__PURE__ */ new Set();
2346
+ this.refCount = 0;
2347
+ }
2348
+ /**
2349
+ * Returns a boolean, provides information of whether `registerSubscriptions`
2350
+ * method has already been called for this instance.
2351
+ */
2352
+ get hasSubscriptions() {
2353
+ return this.unsubscribeFunctions.size > 0;
2354
+ }
2355
+ addUnsubscribeFunction(unsubscribeFunction) {
2356
+ this.unsubscribeFunctions.add(unsubscribeFunction);
2357
+ }
2358
+ /**
2359
+ * Increments `refCount` by one and returns new value.
2360
+ */
2361
+ incrementRefCount() {
2362
+ return ++this.refCount;
2363
+ }
2364
+ /**
2365
+ * If you re-declare `unregisterSubscriptions` method within your class
2366
+ * make sure to run the original too.
2367
+ *
2368
+ * @example
2369
+ * ```ts
2370
+ * class T extends WithSubscriptions {
2371
+ * ...
2372
+ * public unregisterSubscriptions = () => {
2373
+ * this.customThing();
2374
+ * return super.unregisterSubscriptions();
2375
+ * }
2376
+ * }
2377
+ * ```
2378
+ */
2379
+ unregisterSubscriptions() {
2380
+ if (this.refCount > 1) {
2381
+ this.refCount--;
2382
+ return _WithSubscriptions.symbol;
2383
+ }
2384
+ this.unsubscribeFunctions.forEach((unsubscribe) => unsubscribe());
2385
+ this.unsubscribeFunctions.clear();
2386
+ this.refCount = 0;
2387
+ return _WithSubscriptions.symbol;
2388
+ }
2389
+ };
2390
+ /**
2391
+ * Workaround for the missing TS keyword - ensures that inheritants
2392
+ * overriding `unregisterSubscriptions` call the base method and return
2393
+ * its unique symbol value.
2394
+ */
2395
+ _WithSubscriptions.symbol = Symbol(_WithSubscriptions.name);
2396
+ var WithSubscriptions = _WithSubscriptions;
2397
+
2398
+ // src/CooldownTimer.ts
2399
+ var toDateOrUndefined = (value) => {
2400
+ if (value instanceof Date) return value;
2401
+ if (typeof value === "string" || typeof value === "number") {
2402
+ const parsed = new Date(value);
2403
+ if (!Number.isNaN(parsed.getTime())) return parsed;
2404
+ }
2405
+ return void 0;
2406
+ };
2407
+ var CooldownTimer = class extends WithSubscriptions {
2408
+ constructor({ channel }) {
2409
+ super();
2410
+ this.timeout = null;
2411
+ this.registerSubscriptions = () => {
2412
+ this.incrementRefCount();
2413
+ if (this.hasSubscriptions) return;
2414
+ this.addUnsubscribeFunction(
2415
+ this.channel.on("message.new", (event) => {
2416
+ const isOwnMessage = event.message?.user?.id && event.message.user.id === this.getOwnUserId();
2417
+ if (!isOwnMessage) return;
2418
+ this.setOwnLatestMessageDate(toDateOrUndefined(event.message?.created_at));
2419
+ }).unsubscribe
2420
+ );
2421
+ this.addUnsubscribeFunction(
2422
+ this.channel.on("channel.updated", (event) => {
2423
+ const cooldownChanged = event.channel?.cooldown !== this.cooldownConfigSeconds;
2424
+ if (!cooldownChanged) return;
2425
+ this.refresh();
2426
+ }).unsubscribe
2427
+ );
2428
+ };
2429
+ this.setCooldownRemaining = (cooldownRemaining) => {
2430
+ this.state.partialNext({ cooldownRemaining });
2431
+ };
2432
+ this.clearTimeout = () => {
2433
+ if (!this.timeout) return;
2434
+ clearTimeout(this.timeout);
2435
+ this.timeout = null;
2436
+ };
2437
+ this.refresh = () => {
2438
+ const { cooldown: cooldownConfigSeconds = 0, own_capabilities } = this.channel.data ?? {};
2439
+ const canSkipCooldown = (own_capabilities ?? []).includes("skip-slow-mode");
2440
+ const ownLatestMessageDate = this.findOwnLatestMessageDate({
2441
+ messages: this.channel.state.latestMessages
2442
+ });
2443
+ if (cooldownConfigSeconds !== this.cooldownConfigSeconds || ownLatestMessageDate?.getTime() !== this.ownLatestMessageDate?.getTime() || canSkipCooldown !== this.canSkipCooldown) {
2444
+ this.state.partialNext({
2445
+ cooldownConfigSeconds,
2446
+ ownLatestMessageDate,
2447
+ canSkipCooldown
2448
+ });
2449
+ }
2450
+ if (this.canSkipCooldown || this.cooldownConfigSeconds === 0) {
2451
+ this.clearTimeout();
2452
+ if (this.cooldownRemaining !== 0) {
2453
+ this.setCooldownRemaining(0);
2454
+ }
2455
+ return;
2456
+ }
2457
+ this.recalculate();
2458
+ };
2459
+ /**
2460
+ * Updates the known latest own message date and recomputes remaining time.
2461
+ * Prefer calling this when you already know the message date (e.g. from an event).
2462
+ */
2463
+ this.setOwnLatestMessageDate = (date) => {
2464
+ this.state.partialNext({ ownLatestMessageDate: date });
2465
+ this.recalculate();
2466
+ };
2467
+ this.recalculate = () => {
2468
+ this.clearTimeout();
2469
+ const { cooldownConfigSeconds, ownLatestMessageDate, canSkipCooldown } = this.state.getLatestValue();
2470
+ const timeSinceOwnLastMessage = ownLatestMessageDate != null ? (
2471
+ // prevent negative values
2472
+ Math.max(0, (Date.now() - ownLatestMessageDate.getTime()) / 1e3)
2473
+ ) : void 0;
2474
+ const remaining = !canSkipCooldown && typeof timeSinceOwnLastMessage !== "undefined" && cooldownConfigSeconds > timeSinceOwnLastMessage ? Math.round(cooldownConfigSeconds - timeSinceOwnLastMessage) : 0;
2475
+ if (remaining !== this.cooldownRemaining) {
2476
+ this.setCooldownRemaining(remaining);
2477
+ }
2478
+ if (remaining <= 0) return;
2479
+ this.timeout = setTimeout(() => {
2480
+ this.recalculate();
2481
+ }, 1e3);
2482
+ };
2483
+ this.channel = channel;
2484
+ this.state = new StateStore({
2485
+ cooldownConfigSeconds: 0,
2486
+ cooldownRemaining: 0,
2487
+ ownLatestMessageDate: void 0,
2488
+ canSkipCooldown: false
2489
+ });
2490
+ this.refresh();
2491
+ }
2492
+ get cooldownConfigSeconds() {
2493
+ return this.state.getLatestValue().cooldownConfigSeconds;
2494
+ }
2495
+ get cooldownRemaining() {
2496
+ return this.state.getLatestValue().cooldownRemaining;
2497
+ }
2498
+ get canSkipCooldown() {
2499
+ return this.state.getLatestValue().canSkipCooldown;
2500
+ }
2501
+ get ownLatestMessageDate() {
2502
+ return this.state.getLatestValue().ownLatestMessageDate;
2503
+ }
2504
+ getOwnUserId() {
2505
+ const client = this.channel.getClient();
2506
+ return client.userID ?? client.user?.id;
2507
+ }
2508
+ findOwnLatestMessageDate({
2509
+ messages
2510
+ }) {
2511
+ const ownUserId = this.getOwnUserId();
2512
+ if (!ownUserId) return void 0;
2513
+ let latest;
2514
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
2515
+ const message = messages[i];
2516
+ if (message.user?.id !== ownUserId) continue;
2517
+ const createdAt = toDateOrUndefined(message.created_at);
2518
+ if (!createdAt) continue;
2519
+ if (!latest || createdAt.getTime() > latest.getTime()) {
2520
+ latest = createdAt;
2521
+ }
2522
+ if (latest.getTime() > createdAt.getTime()) break;
2523
+ }
2524
+ return latest;
2525
+ }
2526
+ };
2527
+
2135
2528
  // src/messageComposer/attachmentIdentity.ts
2136
2529
  var isScrapedContent = (attachment) => !!attachment?.og_scrape_url || !!attachment?.title_link;
2137
2530
  var isLocalAttachment = (attachment) => !!attachment?.localMetadata?.id;
@@ -2223,11 +2616,12 @@ var createPostUploadAttachmentEnrichmentMiddleware = () => ({
2223
2616
  if (error) return forward();
2224
2617
  if (!attachment || !response) return discard();
2225
2618
  const enrichedAttachment = { ...attachment };
2619
+ const previewUri = attachment.localMetadata.previewUri;
2620
+ if (previewUri) {
2621
+ if (previewUri.startsWith("blob:")) URL.revokeObjectURL(previewUri);
2622
+ delete enrichedAttachment.localMetadata.previewUri;
2623
+ }
2226
2624
  if (isLocalImageAttachment(attachment)) {
2227
- if (attachment.localMetadata.previewUri) {
2228
- URL.revokeObjectURL(attachment.localMetadata.previewUri);
2229
- delete enrichedAttachment.localMetadata.previewUri;
2230
- }
2231
2625
  enrichedAttachment.image_url = response.file;
2232
2626
  } else {
2233
2627
  enrichedAttachment.asset_url = response.file;
@@ -2438,275 +2832,68 @@ var createUploadConfigCheckMiddleware = (composer) => ({
2438
2832
  id: "stream-io/attachment-manager-middleware/file-upload-config-check",
2439
2833
  handlers: {
2440
2834
  prepare: async ({
2441
- state,
2442
- next,
2443
- discard
2444
- }) => {
2445
- const { attachmentManager } = composer;
2446
- if (!attachmentManager || !state.attachment) return discard();
2447
- const uploadPermissionCheck = await attachmentManager.getUploadConfigCheck(
2448
- state.attachment.localMetadata.file
2449
- );
2450
- const attachment = {
2451
- ...state.attachment,
2452
- localMetadata: {
2453
- ...state.attachment.localMetadata,
2454
- uploadPermissionCheck,
2455
- uploadState: uploadPermissionCheck.uploadBlocked ? "blocked" : "pending"
2456
- }
2457
- };
2458
- return next({
2459
- ...state,
2460
- attachment
2461
- });
2462
- }
2463
- }
2464
- });
2465
-
2466
- // src/messageComposer/middleware/attachmentManager/preUpload/blockedUploadNotification.ts
2467
- var createBlockedAttachmentUploadNotificationMiddleware = (composer) => ({
2468
- id: "stream-io/attachment-manager-middleware/blocked-upload-notification",
2469
- handlers: {
2470
- prepare: ({
2471
- state: { attachment },
2472
- forward
2473
- }) => {
2474
- if (!attachment) return forward();
2475
- if (attachment.localMetadata.uploadPermissionCheck?.uploadBlocked) {
2476
- composer.client.notifications.addError({
2477
- message: `The attachment upload was blocked`,
2478
- origin: {
2479
- emitter: "AttachmentManager",
2480
- context: { blockedAttachment: attachment }
2481
- },
2482
- options: {
2483
- type: "validation:attachment:upload:blocked",
2484
- metadata: {
2485
- reason: attachment.localMetadata.uploadPermissionCheck?.reason
2486
- }
2487
- }
2488
- });
2489
- }
2490
- return forward();
2491
- }
2492
- }
2493
- });
2494
-
2495
- // src/messageComposer/middleware/attachmentManager/preUpload/AttachmentPreUploadMiddlewareExecutor.ts
2496
- var AttachmentPreUploadMiddlewareExecutor = class extends MiddlewareExecutor {
2497
- constructor({ composer }) {
2498
- super();
2499
- this.use([
2500
- createUploadConfigCheckMiddleware(composer),
2501
- createBlockedAttachmentUploadNotificationMiddleware(composer)
2502
- ]);
2503
- }
2504
- };
2505
-
2506
- // src/store.ts
2507
- var isPatch = (value) => typeof value === "function";
2508
- var noop = () => {
2509
- };
2510
- var StateStore = class {
2511
- constructor(value) {
2512
- this.value = value;
2513
- this.handlers = /* @__PURE__ */ new Set();
2514
- this.preprocessors = /* @__PURE__ */ new Set();
2515
- this.partialNext = (partial) => this.next((current) => ({ ...current, ...partial }));
2516
- this.subscribeWithSelector = (selector, handler) => {
2517
- let previouslySelectedValues;
2518
- const wrappedHandler = (nextValue) => {
2519
- const newlySelectedValues = selector(nextValue);
2520
- let hasUpdatedValues = typeof previouslySelectedValues === "undefined";
2521
- for (const key in previouslySelectedValues) {
2522
- if (previouslySelectedValues[key] === newlySelectedValues[key]) continue;
2523
- hasUpdatedValues = true;
2524
- break;
2525
- }
2526
- if (!hasUpdatedValues) return;
2527
- const previouslySelectedValuesCopy = previouslySelectedValues;
2528
- previouslySelectedValues = newlySelectedValues;
2529
- handler(newlySelectedValues, previouslySelectedValuesCopy);
2530
- };
2531
- return this.subscribe(wrappedHandler);
2532
- };
2533
- }
2534
- /**
2535
- * Allows merging two stores only if their keys differ otherwise there's no way to ensure the data type stability.
2536
- * @experimental
2537
- * This method is experimental and may change in future versions.
2538
- */
2539
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2540
- merge(stateStore) {
2541
- return new MergedStateStore({
2542
- original: this,
2543
- merged: stateStore
2544
- });
2545
- }
2546
- next(newValueOrPatch) {
2547
- const newValue = isPatch(newValueOrPatch) ? newValueOrPatch(this.value) : newValueOrPatch;
2548
- if (newValue === this.value) return;
2549
- this.preprocessors.forEach((preprocessor) => preprocessor(newValue, this.value));
2550
- const oldValue = this.value;
2551
- this.value = newValue;
2552
- this.handlers.forEach((handler) => handler(this.value, oldValue));
2553
- }
2554
- getLatestValue() {
2555
- return this.value;
2556
- }
2557
- subscribe(handler) {
2558
- handler(this.value, void 0);
2559
- this.handlers.add(handler);
2560
- return () => {
2561
- this.handlers.delete(handler);
2562
- };
2563
- }
2564
- /**
2565
- * Registers a preprocessor function that will be called before the state is updated.
2566
- *
2567
- * Preprocessors are invoked with the new and previous values whenever `next` or `partialNext` methods
2568
- * are called, allowing you to mutate or react to the new value before it is set. Preprocessors run in the
2569
- * order they were registered.
2570
- *
2571
- * @example
2572
- * ```ts
2573
- * const store = new StateStore<{ count: number; isMaxValue: bool; }>({ count: 0, isMaxValue: false });
2574
- *
2575
- * store.addPreprocessor((nextValue, prevValue) => {
2576
- * if (nextValue.count > 10) {
2577
- * nextValue.count = 10; // Clamp the value to a maximum of 10
2578
- * }
2579
- *
2580
- * if (nextValue.count === 10) {
2581
- * nextValue.isMaxValue = true; // Set isMaxValue to true if count is 10
2582
- * } else {
2583
- * nextValue.isMaxValue = false; // Reset isMaxValue otherwise
2584
- * }
2585
- * });
2586
- *
2587
- * store.partialNext({ count: 15 });
2588
- *
2589
- * store.getLatestValue(); // { count: 10, isMaxValue: true }
2590
- *
2591
- * store.partialNext({ count: 5 });
2592
- *
2593
- * store.getLatestValue(); // { count: 5, isMaxValue: false }
2594
- * ```
2595
- *
2596
- * @param preprocessor - The function to be called with the next and previous values before the state is updated.
2597
- * @returns A `RemovePreprocessor` function that removes the preprocessor when called.
2598
- */
2599
- addPreprocessor(preprocessor) {
2600
- this.preprocessors.add(preprocessor);
2601
- return () => {
2602
- this.preprocessors.delete(preprocessor);
2603
- };
2604
- }
2605
- };
2606
- var MergedStateStore = class _MergedStateStore extends StateStore {
2607
- constructor({ original, merged }) {
2608
- const originalValue = original.getLatestValue();
2609
- const mergedValue = merged.getLatestValue();
2610
- super({
2611
- ...originalValue,
2612
- ...mergedValue
2613
- });
2614
- // override original methods and "disable" them
2615
- this.next = () => {
2616
- console.warn(
2617
- `${_MergedStateStore.name}.next is disabled, call original.next or merged.next instead`
2618
- );
2619
- };
2620
- this.partialNext = () => {
2621
- console.warn(
2622
- `${_MergedStateStore.name}.partialNext is disabled, call original.partialNext or merged.partialNext instead`
2835
+ state,
2836
+ next,
2837
+ discard
2838
+ }) => {
2839
+ const { attachmentManager } = composer;
2840
+ if (!attachmentManager || !state.attachment) return discard();
2841
+ const uploadPermissionCheck = await attachmentManager.getUploadConfigCheck(
2842
+ state.attachment.localMetadata.file
2623
2843
  );
2624
- };
2625
- this.cachedOriginalValue = originalValue;
2626
- this.cachedMergedValue = mergedValue;
2627
- this.original = original;
2628
- this.merged = merged;
2629
- }
2630
- /**
2631
- * Subscribes to changes in the merged state store.
2632
- *
2633
- * This method extends the base subscribe functionality to handle the merged nature of this store:
2634
- * 1. The first subscriber triggers registration of helper subscribers that listen to both source stores
2635
- * 2. Changes from either source store are propagated to this merged store
2636
- * 3. Source store values are cached to prevent unnecessary updates
2637
- *
2638
- * When the first subscriber is added, the method sets up listeners on both original and merged stores.
2639
- * These listeners update the combined store value whenever either source store changes.
2640
- * All subscriptions (helpers and the actual handler) are tracked so they can be properly cleaned up.
2641
- *
2642
- * @param handler - The callback function that will be executed when the state changes
2643
- * @returns An unsubscribe function that, when called, removes the subscription and any helper subscriptions
2644
- */
2645
- subscribe(handler) {
2646
- const unsubscribeFunctions = [];
2647
- if (!this.handlers.size) {
2648
- const base = (nextValue) => {
2649
- super.next((currentValue) => ({
2650
- ...currentValue,
2651
- ...nextValue
2652
- }));
2844
+ const attachment = {
2845
+ ...state.attachment,
2846
+ localMetadata: {
2847
+ ...state.attachment.localMetadata,
2848
+ uploadPermissionCheck,
2849
+ uploadState: uploadPermissionCheck.uploadBlocked ? "blocked" : "pending"
2850
+ }
2653
2851
  };
2654
- unsubscribeFunctions.push(
2655
- this.original.subscribe((nextValue) => {
2656
- if (nextValue === this.cachedOriginalValue) return;
2657
- this.cachedOriginalValue = nextValue;
2658
- base(nextValue);
2659
- }),
2660
- this.merged.subscribe((nextValue) => {
2661
- if (nextValue === this.cachedMergedValue) return;
2662
- this.cachedMergedValue = nextValue;
2663
- base(nextValue);
2664
- })
2665
- );
2852
+ return next({
2853
+ ...state,
2854
+ attachment
2855
+ });
2666
2856
  }
2667
- unsubscribeFunctions.push(super.subscribe(handler));
2668
- return () => {
2669
- unsubscribeFunctions.forEach((unsubscribe) => unsubscribe());
2670
- };
2671
2857
  }
2672
- /**
2673
- * Retrieves the latest combined state from both original and merged stores.
2674
- *
2675
- * This method extends the base getLatestValue functionality to ensure the merged store
2676
- * remains in sync with its source stores even when there are no active subscribers.
2677
- *
2678
- * When there are no handlers registered, the method:
2679
- * 1. Fetches the latest values from both source stores
2680
- * 2. Compares them with the cached values to detect changes
2681
- * 3. If changes are detected, updates the internal value and caches
2682
- * the new source values to maintain consistency
2683
- *
2684
- * This approach ensures that calling getLatestValue() always returns the most
2685
- * up-to-date combined state, even if the merged store hasn't been actively
2686
- * receiving updates through subscriptions.
2687
- *
2688
- * @returns The latest combined state from both original and merged stores
2689
- */
2690
- getLatestValue() {
2691
- if (!this.handlers.size) {
2692
- const originalValue = this.original.getLatestValue();
2693
- const mergedValue = this.merged.getLatestValue();
2694
- if (originalValue !== this.cachedOriginalValue || mergedValue !== this.cachedMergedValue) {
2695
- this.value = {
2696
- ...originalValue,
2697
- ...mergedValue
2698
- };
2699
- this.cachedMergedValue = mergedValue;
2700
- this.cachedOriginalValue = originalValue;
2858
+ });
2859
+
2860
+ // src/messageComposer/middleware/attachmentManager/preUpload/blockedUploadNotification.ts
2861
+ var createBlockedAttachmentUploadNotificationMiddleware = (composer) => ({
2862
+ id: "stream-io/attachment-manager-middleware/blocked-upload-notification",
2863
+ handlers: {
2864
+ prepare: ({
2865
+ state: { attachment },
2866
+ forward
2867
+ }) => {
2868
+ if (!attachment) return forward();
2869
+ if (attachment.localMetadata.uploadPermissionCheck?.uploadBlocked) {
2870
+ composer.client.notifications.addError({
2871
+ message: `The attachment upload was blocked`,
2872
+ origin: {
2873
+ emitter: "AttachmentManager",
2874
+ context: { blockedAttachment: attachment }
2875
+ },
2876
+ options: {
2877
+ type: "validation:attachment:upload:blocked",
2878
+ metadata: {
2879
+ reason: attachment.localMetadata.uploadPermissionCheck?.reason
2880
+ }
2881
+ }
2882
+ });
2701
2883
  }
2884
+ return forward();
2702
2885
  }
2703
- return super.getLatestValue();
2704
2886
  }
2705
- addPreprocessor() {
2706
- console.warn(
2707
- `${_MergedStateStore.name}.addPreprocessor is disabled, call original.addPreprocessor or merged.addPreprocessor instead`
2708
- );
2709
- return noop;
2887
+ });
2888
+
2889
+ // src/messageComposer/middleware/attachmentManager/preUpload/AttachmentPreUploadMiddlewareExecutor.ts
2890
+ var AttachmentPreUploadMiddlewareExecutor = class extends MiddlewareExecutor {
2891
+ constructor({ composer }) {
2892
+ super();
2893
+ this.use([
2894
+ createUploadConfigCheckMiddleware(composer),
2895
+ createBlockedAttachmentUploadNotificationMiddleware(composer)
2896
+ ]);
2710
2897
  }
2711
2898
  };
2712
2899
 
@@ -3336,11 +3523,12 @@ var _AttachmentManager = class _AttachmentManager {
3336
3523
  uploadState: "finished"
3337
3524
  }
3338
3525
  };
3526
+ const previewUri = uploadedAttachment.localMetadata.previewUri;
3527
+ if (previewUri) {
3528
+ if (previewUri.startsWith("blob:")) URL.revokeObjectURL(previewUri);
3529
+ delete uploadedAttachment.localMetadata.previewUri;
3530
+ }
3339
3531
  if (isLocalImageAttachment(uploadedAttachment)) {
3340
- if (uploadedAttachment.localMetadata.previewUri) {
3341
- URL.revokeObjectURL(uploadedAttachment.localMetadata.previewUri);
3342
- delete uploadedAttachment.localMetadata.previewUri;
3343
- }
3344
3532
  uploadedAttachment.image_url = response.file;
3345
3533
  } else {
3346
3534
  uploadedAttachment.asset_url = response.file;
@@ -3521,12 +3709,10 @@ _AttachmentManager.toLocalUploadAttachment = (fileLike) => {
3521
3709
  type: getAttachmentTypeFromMimeType(file.type)
3522
3710
  };
3523
3711
  localAttachment[isImageFile(file) ? "fallback" : "title"] = file.name;
3524
- if (isImageFile(file)) {
3525
- localAttachment.localMetadata.previewUri = isFileReference(fileLike) ? fileLike.uri : URL.createObjectURL?.(fileLike);
3526
- if (isFileReference(fileLike) && fileLike.height && fileLike.width) {
3527
- localAttachment.original_height = fileLike.height;
3528
- localAttachment.original_width = fileLike.width;
3529
- }
3712
+ localAttachment.localMetadata.previewUri = isFileReference(fileLike) ? fileLike.uri : URL.createObjectURL?.(fileLike);
3713
+ if (isFileReference(fileLike) && fileLike.height && fileLike.width && isImageFile(file)) {
3714
+ localAttachment.original_height = fileLike.height;
3715
+ localAttachment.original_width = fileLike.width;
3530
3716
  }
3531
3717
  if (isFileReference(fileLike) && fileLike.thumb_url) {
3532
3718
  localAttachment.thumb_url = fileLike.thumb_url;
@@ -5560,10 +5746,11 @@ var getTriggerCharWithToken = ({
5560
5746
  isCommand = false,
5561
5747
  acceptTrailingSpaces = true
5562
5748
  }) => {
5563
- const triggerNorWhitespace = `[^\\s${trigger}]*`;
5749
+ const escapedTrigger = escapeRegExp(trigger);
5750
+ const triggerNorWhitespace = `[^\\s${escapedTrigger}]*`;
5564
5751
  const match = text.match(
5565
5752
  new RegExp(
5566
- isCommand ? `^[${trigger}]${triggerNorWhitespace}$` : acceptTrailingSpaces ? `(?!^|\\W)?[${trigger}]${triggerNorWhitespace}\\s?${triggerNorWhitespace}$` : `(?!^|\\W)?[${trigger}]${triggerNorWhitespace}$`,
5753
+ isCommand ? `^[${escapedTrigger}]${triggerNorWhitespace}$` : acceptTrailingSpaces ? `(?!^|\\W)?[${escapedTrigger}]${triggerNorWhitespace}\\s?${triggerNorWhitespace}$` : `(?!^|\\W)?[${escapedTrigger}]${triggerNorWhitespace}$`,
5567
5754
  "g"
5568
5755
  )
5569
5756
  );
@@ -6341,62 +6528,6 @@ var TextComposer = class {
6341
6528
  // --- END TEXT PROCESSING ----
6342
6529
  };
6343
6530
 
6344
- // src/utils/WithSubscriptions.ts
6345
- var _WithSubscriptions = class _WithSubscriptions {
6346
- constructor() {
6347
- this.unsubscribeFunctions = /* @__PURE__ */ new Set();
6348
- this.refCount = 0;
6349
- }
6350
- /**
6351
- * Returns a boolean, provides information of whether `registerSubscriptions`
6352
- * method has already been called for this instance.
6353
- */
6354
- get hasSubscriptions() {
6355
- return this.unsubscribeFunctions.size > 0;
6356
- }
6357
- addUnsubscribeFunction(unsubscribeFunction) {
6358
- this.unsubscribeFunctions.add(unsubscribeFunction);
6359
- }
6360
- /**
6361
- * Increments `refCount` by one and returns new value.
6362
- */
6363
- incrementRefCount() {
6364
- return ++this.refCount;
6365
- }
6366
- /**
6367
- * If you re-declare `unregisterSubscriptions` method within your class
6368
- * make sure to run the original too.
6369
- *
6370
- * @example
6371
- * ```ts
6372
- * class T extends WithSubscriptions {
6373
- * ...
6374
- * public unregisterSubscriptions = () => {
6375
- * this.customThing();
6376
- * return super.unregisterSubscriptions();
6377
- * }
6378
- * }
6379
- * ```
6380
- */
6381
- unregisterSubscriptions() {
6382
- if (this.refCount > 1) {
6383
- this.refCount--;
6384
- return _WithSubscriptions.symbol;
6385
- }
6386
- this.unsubscribeFunctions.forEach((unsubscribe) => unsubscribe());
6387
- this.unsubscribeFunctions.clear();
6388
- this.refCount = 0;
6389
- return _WithSubscriptions.symbol;
6390
- }
6391
- };
6392
- /**
6393
- * Workaround for the missing TS keyword - ensures that inheritants
6394
- * overriding `unregisterSubscriptions` call the base method and return
6395
- * its unique symbol value.
6396
- */
6397
- _WithSubscriptions.symbol = Symbol(_WithSubscriptions.name);
6398
- var WithSubscriptions = _WithSubscriptions;
6399
-
6400
6531
  // src/thread.ts
6401
6532
  var DEFAULT_PAGE_LIMIT = 50;
6402
6533
  var DEFAULT_SORT = [{ created_at: -1 }];
@@ -7983,6 +8114,7 @@ var Channel = class {
7983
8114
  return msg && { timestampMs, msgId: msg.id };
7984
8115
  }
7985
8116
  });
8117
+ this.cooldownTimer = new CooldownTimer({ channel: this });
7986
8118
  }
7987
8119
  /**
7988
8120
  * getClient - Get the chat client for this channel. If client.disconnect() was called, this function will error
@@ -9118,6 +9250,7 @@ var Channel = class {
9118
9250
  ].sort().join();
9119
9251
  this.data = state.channel;
9120
9252
  this.offlineMode = false;
9253
+ this.cooldownTimer.refresh();
9121
9254
  if (areCapabilitiesChanged) {
9122
9255
  this.getClient().dispatchEvent({
9123
9256
  type: "capabilities.changed",
@@ -9188,11 +9321,13 @@ var Channel = class {
9188
9321
  * unbanUser - Removes the bans for a user on a channel
9189
9322
  *
9190
9323
  * @param {string} targetUserID
9324
+ * @param {UnBanUserOptions} options
9191
9325
  * @returns {Promise<APIResponse>}
9192
9326
  */
9193
- async unbanUser(targetUserID) {
9327
+ async unbanUser(targetUserID, options) {
9194
9328
  this._checkInitialized();
9195
9329
  return await this.getClient().unbanUser(targetUserID, {
9330
+ ...options,
9196
9331
  type: this.type,
9197
9332
  id: this.id
9198
9333
  });
@@ -9497,6 +9632,9 @@ var Channel = class {
9497
9632
  channelState.addPinnedMessage(event.message);
9498
9633
  }
9499
9634
  const preventUnreadCountUpdate = ownMessage || isThreadMessage;
9635
+ if (ownMessage) {
9636
+ this.cooldownTimer.refresh();
9637
+ }
9500
9638
  if (preventUnreadCountUpdate) break;
9501
9639
  if (event.user?.id) {
9502
9640
  for (const userId in channelState.read) {
@@ -9631,6 +9769,7 @@ var Channel = class {
9631
9769
  own_capabilities: event.channel?.own_capabilities ?? channel.data?.own_capabilities
9632
9770
  };
9633
9771
  channel.data = newChannelData;
9772
+ this.cooldownTimer.refresh();
9634
9773
  }
9635
9774
  break;
9636
9775
  case "reaction.new":
@@ -9815,6 +9954,7 @@ var Channel = class {
9815
9954
  }
9816
9955
  );
9817
9956
  this.disconnected = true;
9957
+ this.cooldownTimer.clearTimeout();
9818
9958
  this.state.setIsUpToDate(false);
9819
9959
  }
9820
9960
  };
@@ -13778,6 +13918,20 @@ var StreamChat = class _StreamChat {
13778
13918
  }
13779
13919
  });
13780
13920
  }
13921
+ /**
13922
+ * queryFutureChannelBans - Query future channel bans created by a user
13923
+ *
13924
+ * @param {QueryFutureChannelBansOptions} options Option object with user_id, exclude_expired_bans, limit, offset
13925
+ * @returns {Promise<FutureChannelBansResponse>} Future Channel Bans Response
13926
+ */
13927
+ async queryFutureChannelBans(options = {}) {
13928
+ return await this.get(
13929
+ this.baseURL + "/query_future_channel_bans",
13930
+ {
13931
+ payload: options
13932
+ }
13933
+ );
13934
+ }
13781
13935
  /**
13782
13936
  * queryMessageFlags - Query message flags
13783
13937
  *
@@ -13797,10 +13951,10 @@ var StreamChat = class _StreamChat {
13797
13951
  /**
13798
13952
  * queryChannelsRequest - Queries channels and returns the raw response
13799
13953
  *
13800
- * @param {ChannelFilters} filterConditions object MongoDB style filters
13954
+ * @param {ChannelFilters} filterConditions object MongoDB style filters. Can be empty object when using predefined_filter in options.
13801
13955
  * @param {ChannelSort} [sort] Sort options, for instance {created_at: -1}.
13802
13956
  * When using multiple fields, make sure you use array of objects to guarantee field order, for instance [{last_updated: -1}, {created_at: 1}]
13803
- * @param {ChannelOptions} [options] Options object
13957
+ * @param {ChannelOptions} [options] Options object. Can include predefined_filter, filter_values, and sort_values for using predefined filters.
13804
13958
  *
13805
13959
  * @return {Promise<Array<ChannelAPIResponse>>} search channels response
13806
13960
  */
@@ -13814,11 +13968,18 @@ var StreamChat = class _StreamChat {
13814
13968
  if (!this._hasConnectionID()) {
13815
13969
  defaultOptions.watch = false;
13816
13970
  }
13817
- const payload = {
13971
+ const { predefined_filter, filter_values, sort_values, ...restOptions } = options;
13972
+ const payload = predefined_filter ? {
13973
+ predefined_filter,
13974
+ filter_values,
13975
+ sort_values,
13976
+ ...defaultOptions,
13977
+ ...restOptions
13978
+ } : {
13818
13979
  filter_conditions: filterConditions,
13819
13980
  sort: normalizeQuerySort(sort),
13820
13981
  ...defaultOptions,
13821
- ...options
13982
+ ...restOptions
13822
13983
  };
13823
13984
  const data = await this.post(
13824
13985
  this.baseURL + "/channels",
@@ -13930,6 +14091,7 @@ var StreamChat = class _StreamChat {
13930
14091
  this.reminders.hydrateState(channelState.messages);
13931
14092
  }
13932
14093
  c.messageComposer.initStateFromChannelResponse(channelState);
14094
+ c.cooldownTimer.refresh();
13933
14095
  channels.push(c);
13934
14096
  }
13935
14097
  this.syncDeliveredCandidates(channels);
@@ -14925,7 +15087,7 @@ var StreamChat = class _StreamChat {
14925
15087
  if (this.userAgent) {
14926
15088
  return this.userAgent;
14927
15089
  }
14928
- const version = "9.28.0";
15090
+ const version = "9.30.0";
14929
15091
  const clientBundle = "browser-cjs";
14930
15092
  let userAgentString = "";
14931
15093
  if (this.sdkIdentifier) {
@@ -15241,7 +15403,7 @@ var StreamChat = class _StreamChat {
15241
15403
  validateServerSideAuth() {
15242
15404
  if (!this.secret) {
15243
15405
  throw new Error(
15244
- "Campaigns is a server-side only feature. Please initialize the client with a secret to use this feature."
15406
+ "This feature can be used server-side only. Please initialize the client with a secret to use this feature."
15245
15407
  );
15246
15408
  }
15247
15409
  }
@@ -16106,6 +16268,79 @@ var StreamChat = class _StreamChat {
16106
16268
  payload
16107
16269
  );
16108
16270
  }
16271
+ /**
16272
+ * createPredefinedFilter - Creates a new predefined filter (server-side only)
16273
+ *
16274
+ * @param {CreatePredefinedFilterOptions} options Predefined filter options
16275
+ *
16276
+ * @return {Promise<PredefinedFilterResponse>} The created predefined filter
16277
+ */
16278
+ async createPredefinedFilter(options) {
16279
+ this.validateServerSideAuth();
16280
+ return await this.post(
16281
+ `${this.baseURL}/predefined_filters`,
16282
+ options
16283
+ );
16284
+ }
16285
+ /**
16286
+ * getPredefinedFilter - Gets a predefined filter by name (server-side only)
16287
+ *
16288
+ * @param {string} name Predefined filter name
16289
+ *
16290
+ * @return {Promise<PredefinedFilterResponse>} The predefined filter
16291
+ */
16292
+ async getPredefinedFilter(name) {
16293
+ this.validateServerSideAuth();
16294
+ return await this.get(
16295
+ `${this.baseURL}/predefined_filters/${encodeURIComponent(name)}`
16296
+ );
16297
+ }
16298
+ /**
16299
+ * updatePredefinedFilter - Updates a predefined filter (server-side only)
16300
+ *
16301
+ * @param {string} name Predefined filter name
16302
+ * @param {UpdatePredefinedFilterOptions} options Predefined filter options
16303
+ *
16304
+ * @return {Promise<PredefinedFilterResponse>} The updated predefined filter
16305
+ */
16306
+ async updatePredefinedFilter(name, options) {
16307
+ this.validateServerSideAuth();
16308
+ return await this.put(
16309
+ `${this.baseURL}/predefined_filters/${encodeURIComponent(name)}`,
16310
+ options
16311
+ );
16312
+ }
16313
+ /**
16314
+ * deletePredefinedFilter - Deletes a predefined filter (server-side only)
16315
+ *
16316
+ * @param {string} name Predefined filter name
16317
+ *
16318
+ * @return {Promise<APIResponse>} The server response
16319
+ */
16320
+ async deletePredefinedFilter(name) {
16321
+ this.validateServerSideAuth();
16322
+ return await this.delete(
16323
+ `${this.baseURL}/predefined_filters/${encodeURIComponent(name)}`
16324
+ );
16325
+ }
16326
+ /**
16327
+ * listPredefinedFilters - Lists all predefined filters (server-side only)
16328
+ *
16329
+ * @param {ListPredefinedFiltersOptions} options Query options
16330
+ *
16331
+ * @return {Promise<ListPredefinedFiltersResponse>} The list of predefined filters
16332
+ */
16333
+ async listPredefinedFilters(options = {}) {
16334
+ this.validateServerSideAuth();
16335
+ const { sort, ...paginationOptions } = options;
16336
+ return await this.get(
16337
+ `${this.baseURL}/predefined_filters`,
16338
+ {
16339
+ ...paginationOptions,
16340
+ ...sort ? { sort: JSON.stringify(sort) } : {}
16341
+ }
16342
+ );
16343
+ }
16109
16344
  };
16110
16345
 
16111
16346
  // src/events.ts