stream-chat 9.29.0 → 9.30.1

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;
@@ -2408,305 +2802,98 @@ var createUploadErrorHandlerMiddleware = (composer) => ({
2408
2802
  composer.client.notifications.addError({
2409
2803
  message: "Error uploading attachment",
2410
2804
  origin: {
2411
- emitter: "AttachmentManager",
2412
- context: { attachment }
2413
- },
2414
- options: {
2415
- type: "api:attachment:upload:failed",
2416
- metadata: { reason },
2417
- originalError: error
2418
- }
2419
- });
2420
- return forward();
2421
- }
2422
- }
2423
- });
2424
-
2425
- // src/messageComposer/middleware/attachmentManager/postUpload/AttachmentPostUploadMiddlewareExecutor.ts
2426
- var AttachmentPostUploadMiddlewareExecutor = class extends MiddlewareExecutor {
2427
- constructor({ composer }) {
2428
- super();
2429
- this.use([
2430
- createUploadErrorHandlerMiddleware(composer),
2431
- createPostUploadAttachmentEnrichmentMiddleware()
2432
- ]);
2433
- }
2434
- };
2435
-
2436
- // src/messageComposer/middleware/attachmentManager/preUpload/serverUploadConfigCheck.ts
2437
- var createUploadConfigCheckMiddleware = (composer) => ({
2438
- id: "stream-io/attachment-manager-middleware/file-upload-config-check",
2439
- handlers: {
2440
- 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
- };
2805
+ emitter: "AttachmentManager",
2806
+ context: { attachment }
2807
+ },
2808
+ options: {
2809
+ type: "api:attachment:upload:failed",
2810
+ metadata: { reason },
2811
+ originalError: error
2812
+ }
2813
+ });
2814
+ return forward();
2815
+ }
2816
+ }
2817
+ });
2818
+
2819
+ // src/messageComposer/middleware/attachmentManager/postUpload/AttachmentPostUploadMiddlewareExecutor.ts
2820
+ var AttachmentPostUploadMiddlewareExecutor = class extends MiddlewareExecutor {
2821
+ constructor({ composer }) {
2822
+ super();
2823
+ this.use([
2824
+ createUploadErrorHandlerMiddleware(composer),
2825
+ createPostUploadAttachmentEnrichmentMiddleware()
2826
+ ]);
2604
2827
  }
2605
2828
  };
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`
2829
+
2830
+ // src/messageComposer/middleware/attachmentManager/preUpload/serverUploadConfigCheck.ts
2831
+ var createUploadConfigCheckMiddleware = (composer) => ({
2832
+ id: "stream-io/attachment-manager-middleware/file-upload-config-check",
2833
+ handlers: {
2834
+ prepare: async ({
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;
@@ -6342,62 +6528,6 @@ var TextComposer = class {
6342
6528
  // --- END TEXT PROCESSING ----
6343
6529
  };
6344
6530
 
6345
- // src/utils/WithSubscriptions.ts
6346
- var _WithSubscriptions = class _WithSubscriptions {
6347
- constructor() {
6348
- this.unsubscribeFunctions = /* @__PURE__ */ new Set();
6349
- this.refCount = 0;
6350
- }
6351
- /**
6352
- * Returns a boolean, provides information of whether `registerSubscriptions`
6353
- * method has already been called for this instance.
6354
- */
6355
- get hasSubscriptions() {
6356
- return this.unsubscribeFunctions.size > 0;
6357
- }
6358
- addUnsubscribeFunction(unsubscribeFunction) {
6359
- this.unsubscribeFunctions.add(unsubscribeFunction);
6360
- }
6361
- /**
6362
- * Increments `refCount` by one and returns new value.
6363
- */
6364
- incrementRefCount() {
6365
- return ++this.refCount;
6366
- }
6367
- /**
6368
- * If you re-declare `unregisterSubscriptions` method within your class
6369
- * make sure to run the original too.
6370
- *
6371
- * @example
6372
- * ```ts
6373
- * class T extends WithSubscriptions {
6374
- * ...
6375
- * public unregisterSubscriptions = () => {
6376
- * this.customThing();
6377
- * return super.unregisterSubscriptions();
6378
- * }
6379
- * }
6380
- * ```
6381
- */
6382
- unregisterSubscriptions() {
6383
- if (this.refCount > 1) {
6384
- this.refCount--;
6385
- return _WithSubscriptions.symbol;
6386
- }
6387
- this.unsubscribeFunctions.forEach((unsubscribe) => unsubscribe());
6388
- this.unsubscribeFunctions.clear();
6389
- this.refCount = 0;
6390
- return _WithSubscriptions.symbol;
6391
- }
6392
- };
6393
- /**
6394
- * Workaround for the missing TS keyword - ensures that inheritants
6395
- * overriding `unregisterSubscriptions` call the base method and return
6396
- * its unique symbol value.
6397
- */
6398
- _WithSubscriptions.symbol = Symbol(_WithSubscriptions.name);
6399
- var WithSubscriptions = _WithSubscriptions;
6400
-
6401
6531
  // src/thread.ts
6402
6532
  var DEFAULT_PAGE_LIMIT = 50;
6403
6533
  var DEFAULT_SORT = [{ created_at: -1 }];
@@ -7984,6 +8114,7 @@ var Channel = class {
7984
8114
  return msg && { timestampMs, msgId: msg.id };
7985
8115
  }
7986
8116
  });
8117
+ this.cooldownTimer = new CooldownTimer({ channel: this });
7987
8118
  }
7988
8119
  /**
7989
8120
  * getClient - Get the chat client for this channel. If client.disconnect() was called, this function will error
@@ -9119,6 +9250,7 @@ var Channel = class {
9119
9250
  ].sort().join();
9120
9251
  this.data = state.channel;
9121
9252
  this.offlineMode = false;
9253
+ this.cooldownTimer.refresh();
9122
9254
  if (areCapabilitiesChanged) {
9123
9255
  this.getClient().dispatchEvent({
9124
9256
  type: "capabilities.changed",
@@ -9500,6 +9632,9 @@ var Channel = class {
9500
9632
  channelState.addPinnedMessage(event.message);
9501
9633
  }
9502
9634
  const preventUnreadCountUpdate = ownMessage || isThreadMessage;
9635
+ if (ownMessage) {
9636
+ this.cooldownTimer.refresh();
9637
+ }
9503
9638
  if (preventUnreadCountUpdate) break;
9504
9639
  if (event.user?.id) {
9505
9640
  for (const userId in channelState.read) {
@@ -9634,6 +9769,7 @@ var Channel = class {
9634
9769
  own_capabilities: event.channel?.own_capabilities ?? channel.data?.own_capabilities
9635
9770
  };
9636
9771
  channel.data = newChannelData;
9772
+ this.cooldownTimer.refresh();
9637
9773
  }
9638
9774
  break;
9639
9775
  case "reaction.new":
@@ -9818,6 +9954,7 @@ var Channel = class {
9818
9954
  }
9819
9955
  );
9820
9956
  this.disconnected = true;
9957
+ this.cooldownTimer.clearTimeout();
9821
9958
  this.state.setIsUpToDate(false);
9822
9959
  }
9823
9960
  };
@@ -10954,14 +11091,17 @@ var Moderation = class {
10954
11091
  * @returns
10955
11092
  */
10956
11093
  async check(entityType, entityID, entityCreatorID, moderationPayload, configKey, options) {
10957
- return await this.client.post(this.client.baseURL + `/api/v2/moderation/check`, {
10958
- entity_type: entityType,
10959
- entity_id: entityID,
10960
- entity_creator_id: entityCreatorID,
10961
- moderation_payload: moderationPayload,
10962
- config_key: configKey,
10963
- options
10964
- });
11094
+ return await this.client.post(
11095
+ this.client.baseURL + `/api/v2/moderation/check`,
11096
+ {
11097
+ entity_type: entityType,
11098
+ entity_id: entityID,
11099
+ entity_creator_id: entityCreatorID,
11100
+ moderation_payload: moderationPayload,
11101
+ config_key: configKey,
11102
+ options
11103
+ }
11104
+ );
10965
11105
  }
10966
11106
  /**
10967
11107
  * Experimental: Check user profile
@@ -11024,13 +11164,16 @@ var Moderation = class {
11024
11164
  * @returns
11025
11165
  */
11026
11166
  async addCustomFlags(entityType, entityID, entityCreatorID, moderationPayload, flags) {
11027
- return await this.client.post(this.client.baseURL + `/api/v2/moderation/custom_check`, {
11028
- entity_type: entityType,
11029
- entity_id: entityID,
11030
- entity_creator_id: entityCreatorID,
11031
- moderation_payload: moderationPayload,
11032
- flags
11033
- });
11167
+ return await this.client.post(
11168
+ this.client.baseURL + `/api/v2/moderation/custom_check`,
11169
+ {
11170
+ entity_type: entityType,
11171
+ entity_id: entityID,
11172
+ entity_creator_id: entityCreatorID,
11173
+ moderation_payload: moderationPayload,
11174
+ flags
11175
+ }
11176
+ );
11034
11177
  }
11035
11178
  /**
11036
11179
  * Add custom flags to a message
@@ -13954,6 +14097,7 @@ var StreamChat = class _StreamChat {
13954
14097
  this.reminders.hydrateState(channelState.messages);
13955
14098
  }
13956
14099
  c.messageComposer.initStateFromChannelResponse(channelState);
14100
+ c.cooldownTimer.refresh();
13957
14101
  channels.push(c);
13958
14102
  }
13959
14103
  this.syncDeliveredCandidates(channels);
@@ -14949,7 +15093,7 @@ var StreamChat = class _StreamChat {
14949
15093
  if (this.userAgent) {
14950
15094
  return this.userAgent;
14951
15095
  }
14952
- const version = "9.29.0";
15096
+ const version = "9.30.1";
14953
15097
  const clientBundle = "browser-cjs";
14954
15098
  let userAgentString = "";
14955
15099
  if (this.sdkIdentifier) {