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.
@@ -1944,6 +1944,399 @@ var ChannelState = class {
1944
1944
  }
1945
1945
  };
1946
1946
 
1947
+ // src/store.ts
1948
+ var isPatch = (value) => typeof value === "function";
1949
+ var noop = () => {
1950
+ };
1951
+ var StateStore = class {
1952
+ constructor(value) {
1953
+ this.value = value;
1954
+ this.handlers = /* @__PURE__ */ new Set();
1955
+ this.preprocessors = /* @__PURE__ */ new Set();
1956
+ this.partialNext = (partial) => this.next((current) => ({ ...current, ...partial }));
1957
+ this.subscribeWithSelector = (selector, handler) => {
1958
+ let previouslySelectedValues;
1959
+ const wrappedHandler = (nextValue) => {
1960
+ const newlySelectedValues = selector(nextValue);
1961
+ let hasUpdatedValues = typeof previouslySelectedValues === "undefined";
1962
+ for (const key in previouslySelectedValues) {
1963
+ if (previouslySelectedValues[key] === newlySelectedValues[key]) continue;
1964
+ hasUpdatedValues = true;
1965
+ break;
1966
+ }
1967
+ if (!hasUpdatedValues) return;
1968
+ const previouslySelectedValuesCopy = previouslySelectedValues;
1969
+ previouslySelectedValues = newlySelectedValues;
1970
+ handler(newlySelectedValues, previouslySelectedValuesCopy);
1971
+ };
1972
+ return this.subscribe(wrappedHandler);
1973
+ };
1974
+ }
1975
+ /**
1976
+ * Allows merging two stores only if their keys differ otherwise there's no way to ensure the data type stability.
1977
+ * @experimental
1978
+ * This method is experimental and may change in future versions.
1979
+ */
1980
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1981
+ merge(stateStore) {
1982
+ return new MergedStateStore({
1983
+ original: this,
1984
+ merged: stateStore
1985
+ });
1986
+ }
1987
+ next(newValueOrPatch) {
1988
+ const newValue = isPatch(newValueOrPatch) ? newValueOrPatch(this.value) : newValueOrPatch;
1989
+ if (newValue === this.value) return;
1990
+ this.preprocessors.forEach((preprocessor) => preprocessor(newValue, this.value));
1991
+ const oldValue = this.value;
1992
+ this.value = newValue;
1993
+ this.handlers.forEach((handler) => handler(this.value, oldValue));
1994
+ }
1995
+ getLatestValue() {
1996
+ return this.value;
1997
+ }
1998
+ subscribe(handler) {
1999
+ handler(this.value, void 0);
2000
+ this.handlers.add(handler);
2001
+ return () => {
2002
+ this.handlers.delete(handler);
2003
+ };
2004
+ }
2005
+ /**
2006
+ * Registers a preprocessor function that will be called before the state is updated.
2007
+ *
2008
+ * Preprocessors are invoked with the new and previous values whenever `next` or `partialNext` methods
2009
+ * are called, allowing you to mutate or react to the new value before it is set. Preprocessors run in the
2010
+ * order they were registered.
2011
+ *
2012
+ * @example
2013
+ * ```ts
2014
+ * const store = new StateStore<{ count: number; isMaxValue: bool; }>({ count: 0, isMaxValue: false });
2015
+ *
2016
+ * store.addPreprocessor((nextValue, prevValue) => {
2017
+ * if (nextValue.count > 10) {
2018
+ * nextValue.count = 10; // Clamp the value to a maximum of 10
2019
+ * }
2020
+ *
2021
+ * if (nextValue.count === 10) {
2022
+ * nextValue.isMaxValue = true; // Set isMaxValue to true if count is 10
2023
+ * } else {
2024
+ * nextValue.isMaxValue = false; // Reset isMaxValue otherwise
2025
+ * }
2026
+ * });
2027
+ *
2028
+ * store.partialNext({ count: 15 });
2029
+ *
2030
+ * store.getLatestValue(); // { count: 10, isMaxValue: true }
2031
+ *
2032
+ * store.partialNext({ count: 5 });
2033
+ *
2034
+ * store.getLatestValue(); // { count: 5, isMaxValue: false }
2035
+ * ```
2036
+ *
2037
+ * @param preprocessor - The function to be called with the next and previous values before the state is updated.
2038
+ * @returns A `RemovePreprocessor` function that removes the preprocessor when called.
2039
+ */
2040
+ addPreprocessor(preprocessor) {
2041
+ this.preprocessors.add(preprocessor);
2042
+ return () => {
2043
+ this.preprocessors.delete(preprocessor);
2044
+ };
2045
+ }
2046
+ };
2047
+ var MergedStateStore = class _MergedStateStore extends StateStore {
2048
+ constructor({ original, merged }) {
2049
+ const originalValue = original.getLatestValue();
2050
+ const mergedValue = merged.getLatestValue();
2051
+ super({
2052
+ ...originalValue,
2053
+ ...mergedValue
2054
+ });
2055
+ // override original methods and "disable" them
2056
+ this.next = () => {
2057
+ console.warn(
2058
+ `${_MergedStateStore.name}.next is disabled, call original.next or merged.next instead`
2059
+ );
2060
+ };
2061
+ this.partialNext = () => {
2062
+ console.warn(
2063
+ `${_MergedStateStore.name}.partialNext is disabled, call original.partialNext or merged.partialNext instead`
2064
+ );
2065
+ };
2066
+ this.cachedOriginalValue = originalValue;
2067
+ this.cachedMergedValue = mergedValue;
2068
+ this.original = original;
2069
+ this.merged = merged;
2070
+ }
2071
+ /**
2072
+ * Subscribes to changes in the merged state store.
2073
+ *
2074
+ * This method extends the base subscribe functionality to handle the merged nature of this store:
2075
+ * 1. The first subscriber triggers registration of helper subscribers that listen to both source stores
2076
+ * 2. Changes from either source store are propagated to this merged store
2077
+ * 3. Source store values are cached to prevent unnecessary updates
2078
+ *
2079
+ * When the first subscriber is added, the method sets up listeners on both original and merged stores.
2080
+ * These listeners update the combined store value whenever either source store changes.
2081
+ * All subscriptions (helpers and the actual handler) are tracked so they can be properly cleaned up.
2082
+ *
2083
+ * @param handler - The callback function that will be executed when the state changes
2084
+ * @returns An unsubscribe function that, when called, removes the subscription and any helper subscriptions
2085
+ */
2086
+ subscribe(handler) {
2087
+ const unsubscribeFunctions = [];
2088
+ if (!this.handlers.size) {
2089
+ const base = (nextValue) => {
2090
+ super.next((currentValue) => ({
2091
+ ...currentValue,
2092
+ ...nextValue
2093
+ }));
2094
+ };
2095
+ unsubscribeFunctions.push(
2096
+ this.original.subscribe((nextValue) => {
2097
+ if (nextValue === this.cachedOriginalValue) return;
2098
+ this.cachedOriginalValue = nextValue;
2099
+ base(nextValue);
2100
+ }),
2101
+ this.merged.subscribe((nextValue) => {
2102
+ if (nextValue === this.cachedMergedValue) return;
2103
+ this.cachedMergedValue = nextValue;
2104
+ base(nextValue);
2105
+ })
2106
+ );
2107
+ }
2108
+ unsubscribeFunctions.push(super.subscribe(handler));
2109
+ return () => {
2110
+ unsubscribeFunctions.forEach((unsubscribe) => unsubscribe());
2111
+ };
2112
+ }
2113
+ /**
2114
+ * Retrieves the latest combined state from both original and merged stores.
2115
+ *
2116
+ * This method extends the base getLatestValue functionality to ensure the merged store
2117
+ * remains in sync with its source stores even when there are no active subscribers.
2118
+ *
2119
+ * When there are no handlers registered, the method:
2120
+ * 1. Fetches the latest values from both source stores
2121
+ * 2. Compares them with the cached values to detect changes
2122
+ * 3. If changes are detected, updates the internal value and caches
2123
+ * the new source values to maintain consistency
2124
+ *
2125
+ * This approach ensures that calling getLatestValue() always returns the most
2126
+ * up-to-date combined state, even if the merged store hasn't been actively
2127
+ * receiving updates through subscriptions.
2128
+ *
2129
+ * @returns The latest combined state from both original and merged stores
2130
+ */
2131
+ getLatestValue() {
2132
+ if (!this.handlers.size) {
2133
+ const originalValue = this.original.getLatestValue();
2134
+ const mergedValue = this.merged.getLatestValue();
2135
+ if (originalValue !== this.cachedOriginalValue || mergedValue !== this.cachedMergedValue) {
2136
+ this.value = {
2137
+ ...originalValue,
2138
+ ...mergedValue
2139
+ };
2140
+ this.cachedMergedValue = mergedValue;
2141
+ this.cachedOriginalValue = originalValue;
2142
+ }
2143
+ }
2144
+ return super.getLatestValue();
2145
+ }
2146
+ addPreprocessor() {
2147
+ console.warn(
2148
+ `${_MergedStateStore.name}.addPreprocessor is disabled, call original.addPreprocessor or merged.addPreprocessor instead`
2149
+ );
2150
+ return noop;
2151
+ }
2152
+ };
2153
+
2154
+ // src/utils/WithSubscriptions.ts
2155
+ var _WithSubscriptions = class _WithSubscriptions {
2156
+ constructor() {
2157
+ this.unsubscribeFunctions = /* @__PURE__ */ new Set();
2158
+ this.refCount = 0;
2159
+ }
2160
+ /**
2161
+ * Returns a boolean, provides information of whether `registerSubscriptions`
2162
+ * method has already been called for this instance.
2163
+ */
2164
+ get hasSubscriptions() {
2165
+ return this.unsubscribeFunctions.size > 0;
2166
+ }
2167
+ addUnsubscribeFunction(unsubscribeFunction) {
2168
+ this.unsubscribeFunctions.add(unsubscribeFunction);
2169
+ }
2170
+ /**
2171
+ * Increments `refCount` by one and returns new value.
2172
+ */
2173
+ incrementRefCount() {
2174
+ return ++this.refCount;
2175
+ }
2176
+ /**
2177
+ * If you re-declare `unregisterSubscriptions` method within your class
2178
+ * make sure to run the original too.
2179
+ *
2180
+ * @example
2181
+ * ```ts
2182
+ * class T extends WithSubscriptions {
2183
+ * ...
2184
+ * public unregisterSubscriptions = () => {
2185
+ * this.customThing();
2186
+ * return super.unregisterSubscriptions();
2187
+ * }
2188
+ * }
2189
+ * ```
2190
+ */
2191
+ unregisterSubscriptions() {
2192
+ if (this.refCount > 1) {
2193
+ this.refCount--;
2194
+ return _WithSubscriptions.symbol;
2195
+ }
2196
+ this.unsubscribeFunctions.forEach((unsubscribe) => unsubscribe());
2197
+ this.unsubscribeFunctions.clear();
2198
+ this.refCount = 0;
2199
+ return _WithSubscriptions.symbol;
2200
+ }
2201
+ };
2202
+ /**
2203
+ * Workaround for the missing TS keyword - ensures that inheritants
2204
+ * overriding `unregisterSubscriptions` call the base method and return
2205
+ * its unique symbol value.
2206
+ */
2207
+ _WithSubscriptions.symbol = Symbol(_WithSubscriptions.name);
2208
+ var WithSubscriptions = _WithSubscriptions;
2209
+
2210
+ // src/CooldownTimer.ts
2211
+ var toDateOrUndefined = (value) => {
2212
+ if (value instanceof Date) return value;
2213
+ if (typeof value === "string" || typeof value === "number") {
2214
+ const parsed = new Date(value);
2215
+ if (!Number.isNaN(parsed.getTime())) return parsed;
2216
+ }
2217
+ return void 0;
2218
+ };
2219
+ var CooldownTimer = class extends WithSubscriptions {
2220
+ constructor({ channel }) {
2221
+ super();
2222
+ this.timeout = null;
2223
+ this.registerSubscriptions = () => {
2224
+ this.incrementRefCount();
2225
+ if (this.hasSubscriptions) return;
2226
+ this.addUnsubscribeFunction(
2227
+ this.channel.on("message.new", (event) => {
2228
+ const isOwnMessage = event.message?.user?.id && event.message.user.id === this.getOwnUserId();
2229
+ if (!isOwnMessage) return;
2230
+ this.setOwnLatestMessageDate(toDateOrUndefined(event.message?.created_at));
2231
+ }).unsubscribe
2232
+ );
2233
+ this.addUnsubscribeFunction(
2234
+ this.channel.on("channel.updated", (event) => {
2235
+ const cooldownChanged = event.channel?.cooldown !== this.cooldownConfigSeconds;
2236
+ if (!cooldownChanged) return;
2237
+ this.refresh();
2238
+ }).unsubscribe
2239
+ );
2240
+ };
2241
+ this.setCooldownRemaining = (cooldownRemaining) => {
2242
+ this.state.partialNext({ cooldownRemaining });
2243
+ };
2244
+ this.clearTimeout = () => {
2245
+ if (!this.timeout) return;
2246
+ clearTimeout(this.timeout);
2247
+ this.timeout = null;
2248
+ };
2249
+ this.refresh = () => {
2250
+ const { cooldown: cooldownConfigSeconds = 0, own_capabilities } = this.channel.data ?? {};
2251
+ const canSkipCooldown = (own_capabilities ?? []).includes("skip-slow-mode");
2252
+ const ownLatestMessageDate = this.findOwnLatestMessageDate({
2253
+ messages: this.channel.state.latestMessages
2254
+ });
2255
+ if (cooldownConfigSeconds !== this.cooldownConfigSeconds || ownLatestMessageDate?.getTime() !== this.ownLatestMessageDate?.getTime() || canSkipCooldown !== this.canSkipCooldown) {
2256
+ this.state.partialNext({
2257
+ cooldownConfigSeconds,
2258
+ ownLatestMessageDate,
2259
+ canSkipCooldown
2260
+ });
2261
+ }
2262
+ if (this.canSkipCooldown || this.cooldownConfigSeconds === 0) {
2263
+ this.clearTimeout();
2264
+ if (this.cooldownRemaining !== 0) {
2265
+ this.setCooldownRemaining(0);
2266
+ }
2267
+ return;
2268
+ }
2269
+ this.recalculate();
2270
+ };
2271
+ /**
2272
+ * Updates the known latest own message date and recomputes remaining time.
2273
+ * Prefer calling this when you already know the message date (e.g. from an event).
2274
+ */
2275
+ this.setOwnLatestMessageDate = (date) => {
2276
+ this.state.partialNext({ ownLatestMessageDate: date });
2277
+ this.recalculate();
2278
+ };
2279
+ this.recalculate = () => {
2280
+ this.clearTimeout();
2281
+ const { cooldownConfigSeconds, ownLatestMessageDate, canSkipCooldown } = this.state.getLatestValue();
2282
+ const timeSinceOwnLastMessage = ownLatestMessageDate != null ? (
2283
+ // prevent negative values
2284
+ Math.max(0, (Date.now() - ownLatestMessageDate.getTime()) / 1e3)
2285
+ ) : void 0;
2286
+ const remaining = !canSkipCooldown && typeof timeSinceOwnLastMessage !== "undefined" && cooldownConfigSeconds > timeSinceOwnLastMessage ? Math.round(cooldownConfigSeconds - timeSinceOwnLastMessage) : 0;
2287
+ if (remaining !== this.cooldownRemaining) {
2288
+ this.setCooldownRemaining(remaining);
2289
+ }
2290
+ if (remaining <= 0) return;
2291
+ this.timeout = setTimeout(() => {
2292
+ this.recalculate();
2293
+ }, 1e3);
2294
+ };
2295
+ this.channel = channel;
2296
+ this.state = new StateStore({
2297
+ cooldownConfigSeconds: 0,
2298
+ cooldownRemaining: 0,
2299
+ ownLatestMessageDate: void 0,
2300
+ canSkipCooldown: false
2301
+ });
2302
+ this.refresh();
2303
+ }
2304
+ get cooldownConfigSeconds() {
2305
+ return this.state.getLatestValue().cooldownConfigSeconds;
2306
+ }
2307
+ get cooldownRemaining() {
2308
+ return this.state.getLatestValue().cooldownRemaining;
2309
+ }
2310
+ get canSkipCooldown() {
2311
+ return this.state.getLatestValue().canSkipCooldown;
2312
+ }
2313
+ get ownLatestMessageDate() {
2314
+ return this.state.getLatestValue().ownLatestMessageDate;
2315
+ }
2316
+ getOwnUserId() {
2317
+ const client = this.channel.getClient();
2318
+ return client.userID ?? client.user?.id;
2319
+ }
2320
+ findOwnLatestMessageDate({
2321
+ messages
2322
+ }) {
2323
+ const ownUserId = this.getOwnUserId();
2324
+ if (!ownUserId) return void 0;
2325
+ let latest;
2326
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
2327
+ const message = messages[i];
2328
+ if (message.user?.id !== ownUserId) continue;
2329
+ const createdAt = toDateOrUndefined(message.created_at);
2330
+ if (!createdAt) continue;
2331
+ if (!latest || createdAt.getTime() > latest.getTime()) {
2332
+ latest = createdAt;
2333
+ }
2334
+ if (latest.getTime() > createdAt.getTime()) break;
2335
+ }
2336
+ return latest;
2337
+ }
2338
+ };
2339
+
1947
2340
  // src/messageComposer/attachmentIdentity.ts
1948
2341
  var isScrapedContent = (attachment) => !!attachment?.og_scrape_url || !!attachment?.title_link;
1949
2342
  var isLocalAttachment = (attachment) => !!attachment?.localMetadata?.id;
@@ -2035,11 +2428,12 @@ var createPostUploadAttachmentEnrichmentMiddleware = () => ({
2035
2428
  if (error) return forward();
2036
2429
  if (!attachment || !response) return discard();
2037
2430
  const enrichedAttachment = { ...attachment };
2431
+ const previewUri = attachment.localMetadata.previewUri;
2432
+ if (previewUri) {
2433
+ if (previewUri.startsWith("blob:")) URL.revokeObjectURL(previewUri);
2434
+ delete enrichedAttachment.localMetadata.previewUri;
2435
+ }
2038
2436
  if (isLocalImageAttachment(attachment)) {
2039
- if (attachment.localMetadata.previewUri) {
2040
- URL.revokeObjectURL(attachment.localMetadata.previewUri);
2041
- delete enrichedAttachment.localMetadata.previewUri;
2042
- }
2043
2437
  enrichedAttachment.image_url = response.file;
2044
2438
  } else {
2045
2439
  enrichedAttachment.asset_url = response.file;
@@ -2250,275 +2644,68 @@ var createUploadConfigCheckMiddleware = (composer) => ({
2250
2644
  id: "stream-io/attachment-manager-middleware/file-upload-config-check",
2251
2645
  handlers: {
2252
2646
  prepare: async ({
2253
- state,
2254
- next,
2255
- discard
2256
- }) => {
2257
- const { attachmentManager } = composer;
2258
- if (!attachmentManager || !state.attachment) return discard();
2259
- const uploadPermissionCheck = await attachmentManager.getUploadConfigCheck(
2260
- state.attachment.localMetadata.file
2261
- );
2262
- const attachment = {
2263
- ...state.attachment,
2264
- localMetadata: {
2265
- ...state.attachment.localMetadata,
2266
- uploadPermissionCheck,
2267
- uploadState: uploadPermissionCheck.uploadBlocked ? "blocked" : "pending"
2268
- }
2269
- };
2270
- return next({
2271
- ...state,
2272
- attachment
2273
- });
2274
- }
2275
- }
2276
- });
2277
-
2278
- // src/messageComposer/middleware/attachmentManager/preUpload/blockedUploadNotification.ts
2279
- var createBlockedAttachmentUploadNotificationMiddleware = (composer) => ({
2280
- id: "stream-io/attachment-manager-middleware/blocked-upload-notification",
2281
- handlers: {
2282
- prepare: ({
2283
- state: { attachment },
2284
- forward
2285
- }) => {
2286
- if (!attachment) return forward();
2287
- if (attachment.localMetadata.uploadPermissionCheck?.uploadBlocked) {
2288
- composer.client.notifications.addError({
2289
- message: `The attachment upload was blocked`,
2290
- origin: {
2291
- emitter: "AttachmentManager",
2292
- context: { blockedAttachment: attachment }
2293
- },
2294
- options: {
2295
- type: "validation:attachment:upload:blocked",
2296
- metadata: {
2297
- reason: attachment.localMetadata.uploadPermissionCheck?.reason
2298
- }
2299
- }
2300
- });
2301
- }
2302
- return forward();
2303
- }
2304
- }
2305
- });
2306
-
2307
- // src/messageComposer/middleware/attachmentManager/preUpload/AttachmentPreUploadMiddlewareExecutor.ts
2308
- var AttachmentPreUploadMiddlewareExecutor = class extends MiddlewareExecutor {
2309
- constructor({ composer }) {
2310
- super();
2311
- this.use([
2312
- createUploadConfigCheckMiddleware(composer),
2313
- createBlockedAttachmentUploadNotificationMiddleware(composer)
2314
- ]);
2315
- }
2316
- };
2317
-
2318
- // src/store.ts
2319
- var isPatch = (value) => typeof value === "function";
2320
- var noop = () => {
2321
- };
2322
- var StateStore = class {
2323
- constructor(value) {
2324
- this.value = value;
2325
- this.handlers = /* @__PURE__ */ new Set();
2326
- this.preprocessors = /* @__PURE__ */ new Set();
2327
- this.partialNext = (partial) => this.next((current) => ({ ...current, ...partial }));
2328
- this.subscribeWithSelector = (selector, handler) => {
2329
- let previouslySelectedValues;
2330
- const wrappedHandler = (nextValue) => {
2331
- const newlySelectedValues = selector(nextValue);
2332
- let hasUpdatedValues = typeof previouslySelectedValues === "undefined";
2333
- for (const key in previouslySelectedValues) {
2334
- if (previouslySelectedValues[key] === newlySelectedValues[key]) continue;
2335
- hasUpdatedValues = true;
2336
- break;
2337
- }
2338
- if (!hasUpdatedValues) return;
2339
- const previouslySelectedValuesCopy = previouslySelectedValues;
2340
- previouslySelectedValues = newlySelectedValues;
2341
- handler(newlySelectedValues, previouslySelectedValuesCopy);
2342
- };
2343
- return this.subscribe(wrappedHandler);
2344
- };
2345
- }
2346
- /**
2347
- * Allows merging two stores only if their keys differ otherwise there's no way to ensure the data type stability.
2348
- * @experimental
2349
- * This method is experimental and may change in future versions.
2350
- */
2351
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2352
- merge(stateStore) {
2353
- return new MergedStateStore({
2354
- original: this,
2355
- merged: stateStore
2356
- });
2357
- }
2358
- next(newValueOrPatch) {
2359
- const newValue = isPatch(newValueOrPatch) ? newValueOrPatch(this.value) : newValueOrPatch;
2360
- if (newValue === this.value) return;
2361
- this.preprocessors.forEach((preprocessor) => preprocessor(newValue, this.value));
2362
- const oldValue = this.value;
2363
- this.value = newValue;
2364
- this.handlers.forEach((handler) => handler(this.value, oldValue));
2365
- }
2366
- getLatestValue() {
2367
- return this.value;
2368
- }
2369
- subscribe(handler) {
2370
- handler(this.value, void 0);
2371
- this.handlers.add(handler);
2372
- return () => {
2373
- this.handlers.delete(handler);
2374
- };
2375
- }
2376
- /**
2377
- * Registers a preprocessor function that will be called before the state is updated.
2378
- *
2379
- * Preprocessors are invoked with the new and previous values whenever `next` or `partialNext` methods
2380
- * are called, allowing you to mutate or react to the new value before it is set. Preprocessors run in the
2381
- * order they were registered.
2382
- *
2383
- * @example
2384
- * ```ts
2385
- * const store = new StateStore<{ count: number; isMaxValue: bool; }>({ count: 0, isMaxValue: false });
2386
- *
2387
- * store.addPreprocessor((nextValue, prevValue) => {
2388
- * if (nextValue.count > 10) {
2389
- * nextValue.count = 10; // Clamp the value to a maximum of 10
2390
- * }
2391
- *
2392
- * if (nextValue.count === 10) {
2393
- * nextValue.isMaxValue = true; // Set isMaxValue to true if count is 10
2394
- * } else {
2395
- * nextValue.isMaxValue = false; // Reset isMaxValue otherwise
2396
- * }
2397
- * });
2398
- *
2399
- * store.partialNext({ count: 15 });
2400
- *
2401
- * store.getLatestValue(); // { count: 10, isMaxValue: true }
2402
- *
2403
- * store.partialNext({ count: 5 });
2404
- *
2405
- * store.getLatestValue(); // { count: 5, isMaxValue: false }
2406
- * ```
2407
- *
2408
- * @param preprocessor - The function to be called with the next and previous values before the state is updated.
2409
- * @returns A `RemovePreprocessor` function that removes the preprocessor when called.
2410
- */
2411
- addPreprocessor(preprocessor) {
2412
- this.preprocessors.add(preprocessor);
2413
- return () => {
2414
- this.preprocessors.delete(preprocessor);
2415
- };
2416
- }
2417
- };
2418
- var MergedStateStore = class _MergedStateStore extends StateStore {
2419
- constructor({ original, merged }) {
2420
- const originalValue = original.getLatestValue();
2421
- const mergedValue = merged.getLatestValue();
2422
- super({
2423
- ...originalValue,
2424
- ...mergedValue
2425
- });
2426
- // override original methods and "disable" them
2427
- this.next = () => {
2428
- console.warn(
2429
- `${_MergedStateStore.name}.next is disabled, call original.next or merged.next instead`
2430
- );
2431
- };
2432
- this.partialNext = () => {
2433
- console.warn(
2434
- `${_MergedStateStore.name}.partialNext is disabled, call original.partialNext or merged.partialNext instead`
2647
+ state,
2648
+ next,
2649
+ discard
2650
+ }) => {
2651
+ const { attachmentManager } = composer;
2652
+ if (!attachmentManager || !state.attachment) return discard();
2653
+ const uploadPermissionCheck = await attachmentManager.getUploadConfigCheck(
2654
+ state.attachment.localMetadata.file
2435
2655
  );
2436
- };
2437
- this.cachedOriginalValue = originalValue;
2438
- this.cachedMergedValue = mergedValue;
2439
- this.original = original;
2440
- this.merged = merged;
2441
- }
2442
- /**
2443
- * Subscribes to changes in the merged state store.
2444
- *
2445
- * This method extends the base subscribe functionality to handle the merged nature of this store:
2446
- * 1. The first subscriber triggers registration of helper subscribers that listen to both source stores
2447
- * 2. Changes from either source store are propagated to this merged store
2448
- * 3. Source store values are cached to prevent unnecessary updates
2449
- *
2450
- * When the first subscriber is added, the method sets up listeners on both original and merged stores.
2451
- * These listeners update the combined store value whenever either source store changes.
2452
- * All subscriptions (helpers and the actual handler) are tracked so they can be properly cleaned up.
2453
- *
2454
- * @param handler - The callback function that will be executed when the state changes
2455
- * @returns An unsubscribe function that, when called, removes the subscription and any helper subscriptions
2456
- */
2457
- subscribe(handler) {
2458
- const unsubscribeFunctions = [];
2459
- if (!this.handlers.size) {
2460
- const base = (nextValue) => {
2461
- super.next((currentValue) => ({
2462
- ...currentValue,
2463
- ...nextValue
2464
- }));
2656
+ const attachment = {
2657
+ ...state.attachment,
2658
+ localMetadata: {
2659
+ ...state.attachment.localMetadata,
2660
+ uploadPermissionCheck,
2661
+ uploadState: uploadPermissionCheck.uploadBlocked ? "blocked" : "pending"
2662
+ }
2465
2663
  };
2466
- unsubscribeFunctions.push(
2467
- this.original.subscribe((nextValue) => {
2468
- if (nextValue === this.cachedOriginalValue) return;
2469
- this.cachedOriginalValue = nextValue;
2470
- base(nextValue);
2471
- }),
2472
- this.merged.subscribe((nextValue) => {
2473
- if (nextValue === this.cachedMergedValue) return;
2474
- this.cachedMergedValue = nextValue;
2475
- base(nextValue);
2476
- })
2477
- );
2664
+ return next({
2665
+ ...state,
2666
+ attachment
2667
+ });
2478
2668
  }
2479
- unsubscribeFunctions.push(super.subscribe(handler));
2480
- return () => {
2481
- unsubscribeFunctions.forEach((unsubscribe) => unsubscribe());
2482
- };
2483
2669
  }
2484
- /**
2485
- * Retrieves the latest combined state from both original and merged stores.
2486
- *
2487
- * This method extends the base getLatestValue functionality to ensure the merged store
2488
- * remains in sync with its source stores even when there are no active subscribers.
2489
- *
2490
- * When there are no handlers registered, the method:
2491
- * 1. Fetches the latest values from both source stores
2492
- * 2. Compares them with the cached values to detect changes
2493
- * 3. If changes are detected, updates the internal value and caches
2494
- * the new source values to maintain consistency
2495
- *
2496
- * This approach ensures that calling getLatestValue() always returns the most
2497
- * up-to-date combined state, even if the merged store hasn't been actively
2498
- * receiving updates through subscriptions.
2499
- *
2500
- * @returns The latest combined state from both original and merged stores
2501
- */
2502
- getLatestValue() {
2503
- if (!this.handlers.size) {
2504
- const originalValue = this.original.getLatestValue();
2505
- const mergedValue = this.merged.getLatestValue();
2506
- if (originalValue !== this.cachedOriginalValue || mergedValue !== this.cachedMergedValue) {
2507
- this.value = {
2508
- ...originalValue,
2509
- ...mergedValue
2510
- };
2511
- this.cachedMergedValue = mergedValue;
2512
- this.cachedOriginalValue = originalValue;
2670
+ });
2671
+
2672
+ // src/messageComposer/middleware/attachmentManager/preUpload/blockedUploadNotification.ts
2673
+ var createBlockedAttachmentUploadNotificationMiddleware = (composer) => ({
2674
+ id: "stream-io/attachment-manager-middleware/blocked-upload-notification",
2675
+ handlers: {
2676
+ prepare: ({
2677
+ state: { attachment },
2678
+ forward
2679
+ }) => {
2680
+ if (!attachment) return forward();
2681
+ if (attachment.localMetadata.uploadPermissionCheck?.uploadBlocked) {
2682
+ composer.client.notifications.addError({
2683
+ message: `The attachment upload was blocked`,
2684
+ origin: {
2685
+ emitter: "AttachmentManager",
2686
+ context: { blockedAttachment: attachment }
2687
+ },
2688
+ options: {
2689
+ type: "validation:attachment:upload:blocked",
2690
+ metadata: {
2691
+ reason: attachment.localMetadata.uploadPermissionCheck?.reason
2692
+ }
2693
+ }
2694
+ });
2513
2695
  }
2696
+ return forward();
2514
2697
  }
2515
- return super.getLatestValue();
2516
2698
  }
2517
- addPreprocessor() {
2518
- console.warn(
2519
- `${_MergedStateStore.name}.addPreprocessor is disabled, call original.addPreprocessor or merged.addPreprocessor instead`
2520
- );
2521
- return noop;
2699
+ });
2700
+
2701
+ // src/messageComposer/middleware/attachmentManager/preUpload/AttachmentPreUploadMiddlewareExecutor.ts
2702
+ var AttachmentPreUploadMiddlewareExecutor = class extends MiddlewareExecutor {
2703
+ constructor({ composer }) {
2704
+ super();
2705
+ this.use([
2706
+ createUploadConfigCheckMiddleware(composer),
2707
+ createBlockedAttachmentUploadNotificationMiddleware(composer)
2708
+ ]);
2522
2709
  }
2523
2710
  };
2524
2711
 
@@ -3148,11 +3335,12 @@ var _AttachmentManager = class _AttachmentManager {
3148
3335
  uploadState: "finished"
3149
3336
  }
3150
3337
  };
3338
+ const previewUri = uploadedAttachment.localMetadata.previewUri;
3339
+ if (previewUri) {
3340
+ if (previewUri.startsWith("blob:")) URL.revokeObjectURL(previewUri);
3341
+ delete uploadedAttachment.localMetadata.previewUri;
3342
+ }
3151
3343
  if (isLocalImageAttachment(uploadedAttachment)) {
3152
- if (uploadedAttachment.localMetadata.previewUri) {
3153
- URL.revokeObjectURL(uploadedAttachment.localMetadata.previewUri);
3154
- delete uploadedAttachment.localMetadata.previewUri;
3155
- }
3156
3344
  uploadedAttachment.image_url = response.file;
3157
3345
  } else {
3158
3346
  uploadedAttachment.asset_url = response.file;
@@ -3333,12 +3521,10 @@ _AttachmentManager.toLocalUploadAttachment = (fileLike) => {
3333
3521
  type: getAttachmentTypeFromMimeType(file.type)
3334
3522
  };
3335
3523
  localAttachment[isImageFile(file) ? "fallback" : "title"] = file.name;
3336
- if (isImageFile(file)) {
3337
- localAttachment.localMetadata.previewUri = isFileReference(fileLike) ? fileLike.uri : URL.createObjectURL?.(fileLike);
3338
- if (isFileReference(fileLike) && fileLike.height && fileLike.width) {
3339
- localAttachment.original_height = fileLike.height;
3340
- localAttachment.original_width = fileLike.width;
3341
- }
3524
+ localAttachment.localMetadata.previewUri = isFileReference(fileLike) ? fileLike.uri : URL.createObjectURL?.(fileLike);
3525
+ if (isFileReference(fileLike) && fileLike.height && fileLike.width && isImageFile(file)) {
3526
+ localAttachment.original_height = fileLike.height;
3527
+ localAttachment.original_width = fileLike.width;
3342
3528
  }
3343
3529
  if (isFileReference(fileLike) && fileLike.thumb_url) {
3344
3530
  localAttachment.thumb_url = fileLike.thumb_url;
@@ -5372,10 +5558,11 @@ var getTriggerCharWithToken = ({
5372
5558
  isCommand = false,
5373
5559
  acceptTrailingSpaces = true
5374
5560
  }) => {
5375
- const triggerNorWhitespace = `[^\\s${trigger}]*`;
5561
+ const escapedTrigger = escapeRegExp(trigger);
5562
+ const triggerNorWhitespace = `[^\\s${escapedTrigger}]*`;
5376
5563
  const match = text.match(
5377
5564
  new RegExp(
5378
- isCommand ? `^[${trigger}]${triggerNorWhitespace}$` : acceptTrailingSpaces ? `(?!^|\\W)?[${trigger}]${triggerNorWhitespace}\\s?${triggerNorWhitespace}$` : `(?!^|\\W)?[${trigger}]${triggerNorWhitespace}$`,
5565
+ isCommand ? `^[${escapedTrigger}]${triggerNorWhitespace}$` : acceptTrailingSpaces ? `(?!^|\\W)?[${escapedTrigger}]${triggerNorWhitespace}\\s?${triggerNorWhitespace}$` : `(?!^|\\W)?[${escapedTrigger}]${triggerNorWhitespace}$`,
5379
5566
  "g"
5380
5567
  )
5381
5568
  );
@@ -6153,62 +6340,6 @@ var TextComposer = class {
6153
6340
  // --- END TEXT PROCESSING ----
6154
6341
  };
6155
6342
 
6156
- // src/utils/WithSubscriptions.ts
6157
- var _WithSubscriptions = class _WithSubscriptions {
6158
- constructor() {
6159
- this.unsubscribeFunctions = /* @__PURE__ */ new Set();
6160
- this.refCount = 0;
6161
- }
6162
- /**
6163
- * Returns a boolean, provides information of whether `registerSubscriptions`
6164
- * method has already been called for this instance.
6165
- */
6166
- get hasSubscriptions() {
6167
- return this.unsubscribeFunctions.size > 0;
6168
- }
6169
- addUnsubscribeFunction(unsubscribeFunction) {
6170
- this.unsubscribeFunctions.add(unsubscribeFunction);
6171
- }
6172
- /**
6173
- * Increments `refCount` by one and returns new value.
6174
- */
6175
- incrementRefCount() {
6176
- return ++this.refCount;
6177
- }
6178
- /**
6179
- * If you re-declare `unregisterSubscriptions` method within your class
6180
- * make sure to run the original too.
6181
- *
6182
- * @example
6183
- * ```ts
6184
- * class T extends WithSubscriptions {
6185
- * ...
6186
- * public unregisterSubscriptions = () => {
6187
- * this.customThing();
6188
- * return super.unregisterSubscriptions();
6189
- * }
6190
- * }
6191
- * ```
6192
- */
6193
- unregisterSubscriptions() {
6194
- if (this.refCount > 1) {
6195
- this.refCount--;
6196
- return _WithSubscriptions.symbol;
6197
- }
6198
- this.unsubscribeFunctions.forEach((unsubscribe) => unsubscribe());
6199
- this.unsubscribeFunctions.clear();
6200
- this.refCount = 0;
6201
- return _WithSubscriptions.symbol;
6202
- }
6203
- };
6204
- /**
6205
- * Workaround for the missing TS keyword - ensures that inheritants
6206
- * overriding `unregisterSubscriptions` call the base method and return
6207
- * its unique symbol value.
6208
- */
6209
- _WithSubscriptions.symbol = Symbol(_WithSubscriptions.name);
6210
- var WithSubscriptions = _WithSubscriptions;
6211
-
6212
6343
  // src/thread.ts
6213
6344
  var DEFAULT_PAGE_LIMIT = 50;
6214
6345
  var DEFAULT_SORT = [{ created_at: -1 }];
@@ -7795,6 +7926,7 @@ var Channel = class {
7795
7926
  return msg && { timestampMs, msgId: msg.id };
7796
7927
  }
7797
7928
  });
7929
+ this.cooldownTimer = new CooldownTimer({ channel: this });
7798
7930
  }
7799
7931
  /**
7800
7932
  * getClient - Get the chat client for this channel. If client.disconnect() was called, this function will error
@@ -8930,6 +9062,7 @@ var Channel = class {
8930
9062
  ].sort().join();
8931
9063
  this.data = state.channel;
8932
9064
  this.offlineMode = false;
9065
+ this.cooldownTimer.refresh();
8933
9066
  if (areCapabilitiesChanged) {
8934
9067
  this.getClient().dispatchEvent({
8935
9068
  type: "capabilities.changed",
@@ -9000,11 +9133,13 @@ var Channel = class {
9000
9133
  * unbanUser - Removes the bans for a user on a channel
9001
9134
  *
9002
9135
  * @param {string} targetUserID
9136
+ * @param {UnBanUserOptions} options
9003
9137
  * @returns {Promise<APIResponse>}
9004
9138
  */
9005
- async unbanUser(targetUserID) {
9139
+ async unbanUser(targetUserID, options) {
9006
9140
  this._checkInitialized();
9007
9141
  return await this.getClient().unbanUser(targetUserID, {
9142
+ ...options,
9008
9143
  type: this.type,
9009
9144
  id: this.id
9010
9145
  });
@@ -9309,6 +9444,9 @@ var Channel = class {
9309
9444
  channelState.addPinnedMessage(event.message);
9310
9445
  }
9311
9446
  const preventUnreadCountUpdate = ownMessage || isThreadMessage;
9447
+ if (ownMessage) {
9448
+ this.cooldownTimer.refresh();
9449
+ }
9312
9450
  if (preventUnreadCountUpdate) break;
9313
9451
  if (event.user?.id) {
9314
9452
  for (const userId in channelState.read) {
@@ -9443,6 +9581,7 @@ var Channel = class {
9443
9581
  own_capabilities: event.channel?.own_capabilities ?? channel.data?.own_capabilities
9444
9582
  };
9445
9583
  channel.data = newChannelData;
9584
+ this.cooldownTimer.refresh();
9446
9585
  }
9447
9586
  break;
9448
9587
  case "reaction.new":
@@ -9627,6 +9766,7 @@ var Channel = class {
9627
9766
  }
9628
9767
  );
9629
9768
  this.disconnected = true;
9769
+ this.cooldownTimer.clearTimeout();
9630
9770
  this.state.setIsUpToDate(false);
9631
9771
  }
9632
9772
  };
@@ -13590,6 +13730,20 @@ var StreamChat = class _StreamChat {
13590
13730
  }
13591
13731
  });
13592
13732
  }
13733
+ /**
13734
+ * queryFutureChannelBans - Query future channel bans created by a user
13735
+ *
13736
+ * @param {QueryFutureChannelBansOptions} options Option object with user_id, exclude_expired_bans, limit, offset
13737
+ * @returns {Promise<FutureChannelBansResponse>} Future Channel Bans Response
13738
+ */
13739
+ async queryFutureChannelBans(options = {}) {
13740
+ return await this.get(
13741
+ this.baseURL + "/query_future_channel_bans",
13742
+ {
13743
+ payload: options
13744
+ }
13745
+ );
13746
+ }
13593
13747
  /**
13594
13748
  * queryMessageFlags - Query message flags
13595
13749
  *
@@ -13609,10 +13763,10 @@ var StreamChat = class _StreamChat {
13609
13763
  /**
13610
13764
  * queryChannelsRequest - Queries channels and returns the raw response
13611
13765
  *
13612
- * @param {ChannelFilters} filterConditions object MongoDB style filters
13766
+ * @param {ChannelFilters} filterConditions object MongoDB style filters. Can be empty object when using predefined_filter in options.
13613
13767
  * @param {ChannelSort} [sort] Sort options, for instance {created_at: -1}.
13614
13768
  * When using multiple fields, make sure you use array of objects to guarantee field order, for instance [{last_updated: -1}, {created_at: 1}]
13615
- * @param {ChannelOptions} [options] Options object
13769
+ * @param {ChannelOptions} [options] Options object. Can include predefined_filter, filter_values, and sort_values for using predefined filters.
13616
13770
  *
13617
13771
  * @return {Promise<Array<ChannelAPIResponse>>} search channels response
13618
13772
  */
@@ -13626,11 +13780,18 @@ var StreamChat = class _StreamChat {
13626
13780
  if (!this._hasConnectionID()) {
13627
13781
  defaultOptions.watch = false;
13628
13782
  }
13629
- const payload = {
13783
+ const { predefined_filter, filter_values, sort_values, ...restOptions } = options;
13784
+ const payload = predefined_filter ? {
13785
+ predefined_filter,
13786
+ filter_values,
13787
+ sort_values,
13788
+ ...defaultOptions,
13789
+ ...restOptions
13790
+ } : {
13630
13791
  filter_conditions: filterConditions,
13631
13792
  sort: normalizeQuerySort(sort),
13632
13793
  ...defaultOptions,
13633
- ...options
13794
+ ...restOptions
13634
13795
  };
13635
13796
  const data = await this.post(
13636
13797
  this.baseURL + "/channels",
@@ -13742,6 +13903,7 @@ var StreamChat = class _StreamChat {
13742
13903
  this.reminders.hydrateState(channelState.messages);
13743
13904
  }
13744
13905
  c.messageComposer.initStateFromChannelResponse(channelState);
13906
+ c.cooldownTimer.refresh();
13745
13907
  channels.push(c);
13746
13908
  }
13747
13909
  this.syncDeliveredCandidates(channels);
@@ -14737,7 +14899,7 @@ var StreamChat = class _StreamChat {
14737
14899
  if (this.userAgent) {
14738
14900
  return this.userAgent;
14739
14901
  }
14740
- const version = "9.28.0";
14902
+ const version = "9.30.0";
14741
14903
  const clientBundle = "browser-esm";
14742
14904
  let userAgentString = "";
14743
14905
  if (this.sdkIdentifier) {
@@ -15053,7 +15215,7 @@ var StreamChat = class _StreamChat {
15053
15215
  validateServerSideAuth() {
15054
15216
  if (!this.secret) {
15055
15217
  throw new Error(
15056
- "Campaigns is a server-side only feature. Please initialize the client with a secret to use this feature."
15218
+ "This feature can be used server-side only. Please initialize the client with a secret to use this feature."
15057
15219
  );
15058
15220
  }
15059
15221
  }
@@ -15918,6 +16080,79 @@ var StreamChat = class _StreamChat {
15918
16080
  payload
15919
16081
  );
15920
16082
  }
16083
+ /**
16084
+ * createPredefinedFilter - Creates a new predefined filter (server-side only)
16085
+ *
16086
+ * @param {CreatePredefinedFilterOptions} options Predefined filter options
16087
+ *
16088
+ * @return {Promise<PredefinedFilterResponse>} The created predefined filter
16089
+ */
16090
+ async createPredefinedFilter(options) {
16091
+ this.validateServerSideAuth();
16092
+ return await this.post(
16093
+ `${this.baseURL}/predefined_filters`,
16094
+ options
16095
+ );
16096
+ }
16097
+ /**
16098
+ * getPredefinedFilter - Gets a predefined filter by name (server-side only)
16099
+ *
16100
+ * @param {string} name Predefined filter name
16101
+ *
16102
+ * @return {Promise<PredefinedFilterResponse>} The predefined filter
16103
+ */
16104
+ async getPredefinedFilter(name) {
16105
+ this.validateServerSideAuth();
16106
+ return await this.get(
16107
+ `${this.baseURL}/predefined_filters/${encodeURIComponent(name)}`
16108
+ );
16109
+ }
16110
+ /**
16111
+ * updatePredefinedFilter - Updates a predefined filter (server-side only)
16112
+ *
16113
+ * @param {string} name Predefined filter name
16114
+ * @param {UpdatePredefinedFilterOptions} options Predefined filter options
16115
+ *
16116
+ * @return {Promise<PredefinedFilterResponse>} The updated predefined filter
16117
+ */
16118
+ async updatePredefinedFilter(name, options) {
16119
+ this.validateServerSideAuth();
16120
+ return await this.put(
16121
+ `${this.baseURL}/predefined_filters/${encodeURIComponent(name)}`,
16122
+ options
16123
+ );
16124
+ }
16125
+ /**
16126
+ * deletePredefinedFilter - Deletes a predefined filter (server-side only)
16127
+ *
16128
+ * @param {string} name Predefined filter name
16129
+ *
16130
+ * @return {Promise<APIResponse>} The server response
16131
+ */
16132
+ async deletePredefinedFilter(name) {
16133
+ this.validateServerSideAuth();
16134
+ return await this.delete(
16135
+ `${this.baseURL}/predefined_filters/${encodeURIComponent(name)}`
16136
+ );
16137
+ }
16138
+ /**
16139
+ * listPredefinedFilters - Lists all predefined filters (server-side only)
16140
+ *
16141
+ * @param {ListPredefinedFiltersOptions} options Query options
16142
+ *
16143
+ * @return {Promise<ListPredefinedFiltersResponse>} The list of predefined filters
16144
+ */
16145
+ async listPredefinedFilters(options = {}) {
16146
+ this.validateServerSideAuth();
16147
+ const { sort, ...paginationOptions } = options;
16148
+ return await this.get(
16149
+ `${this.baseURL}/predefined_filters`,
16150
+ {
16151
+ ...paginationOptions,
16152
+ ...sort ? { sort: JSON.stringify(sort) } : {}
16153
+ }
16154
+ );
16155
+ }
15921
16156
  };
15922
16157
 
15923
16158
  // src/events.ts