stream-chat 9.2.0 → 9.4.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.
@@ -192,6 +192,7 @@ __export(index_exports, {
192
192
  MODERATION_ENTITY_TYPES: () => MODERATION_ENTITY_TYPES,
193
193
  MaxPriority: () => MaxPriority,
194
194
  MentionsSearchSource: () => MentionsSearchSource,
195
+ MergedStateStore: () => MergedStateStore,
195
196
  MessageComposer: () => MessageComposer,
196
197
  MessageComposerMiddlewareExecutor: () => MessageComposerMiddlewareExecutor,
197
198
  MessageDraftComposerMiddlewareExecutor: () => MessageDraftComposerMiddlewareExecutor,
@@ -200,6 +201,7 @@ __export(index_exports, {
200
201
  MinPriority: () => MinPriority,
201
202
  Moderation: () => Moderation,
202
203
  OfflineDBSyncManager: () => OfflineDBSyncManager,
204
+ OfflineError: () => OfflineError,
203
205
  Permission: () => Permission,
204
206
  Poll: () => Poll,
205
207
  PollComposer: () => PollComposer,
@@ -4047,26 +4049,14 @@ var ensureIsLocalAttachment = (attachment) => {
4047
4049
 
4048
4050
  // src/store.ts
4049
4051
  var isPatch = (value) => typeof value === "function";
4052
+ var noop2 = () => {
4053
+ };
4050
4054
  var StateStore = class {
4051
4055
  constructor(value) {
4052
4056
  this.value = value;
4053
- this.handlerSet = /* @__PURE__ */ new Set();
4054
- this.next = (newValueOrPatch) => {
4055
- const newValue = isPatch(newValueOrPatch) ? newValueOrPatch(this.value) : newValueOrPatch;
4056
- if (newValue === this.value) return;
4057
- const oldValue = this.value;
4058
- this.value = newValue;
4059
- this.handlerSet.forEach((handler) => handler(this.value, oldValue));
4060
- };
4057
+ this.handlers = /* @__PURE__ */ new Set();
4058
+ this.preprocessors = /* @__PURE__ */ new Set();
4061
4059
  this.partialNext = (partial) => this.next((current) => ({ ...current, ...partial }));
4062
- this.getLatestValue = () => this.value;
4063
- this.subscribe = (handler) => {
4064
- handler(this.value, void 0);
4065
- this.handlerSet.add(handler);
4066
- return () => {
4067
- this.handlerSet.delete(handler);
4068
- };
4069
- };
4070
4060
  this.subscribeWithSelector = (selector, handler) => {
4071
4061
  let previouslySelectedValues;
4072
4062
  const wrappedHandler = (nextValue) => {
@@ -4085,6 +4075,183 @@ var StateStore = class {
4085
4075
  return this.subscribe(wrappedHandler);
4086
4076
  };
4087
4077
  }
4078
+ /**
4079
+ * Allows merging two stores only if their keys differ otherwise there's no way to ensure the data type stability.
4080
+ * @experimental
4081
+ * This method is experimental and may change in future versions.
4082
+ */
4083
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4084
+ merge(stateStore) {
4085
+ return new MergedStateStore({
4086
+ original: this,
4087
+ merged: stateStore
4088
+ });
4089
+ }
4090
+ next(newValueOrPatch) {
4091
+ const newValue = isPatch(newValueOrPatch) ? newValueOrPatch(this.value) : newValueOrPatch;
4092
+ if (newValue === this.value) return;
4093
+ this.preprocessors.forEach((preprocessor) => preprocessor(newValue, this.value));
4094
+ const oldValue = this.value;
4095
+ this.value = newValue;
4096
+ this.handlers.forEach((handler) => handler(this.value, oldValue));
4097
+ }
4098
+ getLatestValue() {
4099
+ return this.value;
4100
+ }
4101
+ subscribe(handler) {
4102
+ handler(this.value, void 0);
4103
+ this.handlers.add(handler);
4104
+ return () => {
4105
+ this.handlers.delete(handler);
4106
+ };
4107
+ }
4108
+ /**
4109
+ * Registers a preprocessor function that will be called before the state is updated.
4110
+ *
4111
+ * Preprocessors are invoked with the new and previous values whenever `next` or `partialNext` methods
4112
+ * are called, allowing you to mutate or react to the new value before it is set. Preprocessors run in the
4113
+ * order they were registered.
4114
+ *
4115
+ * @example
4116
+ * ```ts
4117
+ * const store = new StateStore<{ count: number; isMaxValue: bool; }>({ count: 0, isMaxValue: false });
4118
+ *
4119
+ * store.addPreprocessor((nextValue, prevValue) => {
4120
+ * if (nextValue.count > 10) {
4121
+ * nextValue.count = 10; // Clamp the value to a maximum of 10
4122
+ * }
4123
+ *
4124
+ * if (nextValue.count === 10) {
4125
+ * nextValue.isMaxValue = true; // Set isMaxValue to true if count is 10
4126
+ * } else {
4127
+ * nextValue.isMaxValue = false; // Reset isMaxValue otherwise
4128
+ * }
4129
+ * });
4130
+ *
4131
+ * store.partialNext({ count: 15 });
4132
+ *
4133
+ * store.getLatestValue(); // { count: 10, isMaxValue: true }
4134
+ *
4135
+ * store.partialNext({ count: 5 });
4136
+ *
4137
+ * store.getLatestValue(); // { count: 5, isMaxValue: false }
4138
+ * ```
4139
+ *
4140
+ * @param preprocessor - The function to be called with the next and previous values before the state is updated.
4141
+ * @returns A `RemovePreprocessor` function that removes the preprocessor when called.
4142
+ */
4143
+ addPreprocessor(preprocessor) {
4144
+ this.preprocessors.add(preprocessor);
4145
+ return () => {
4146
+ this.preprocessors.delete(preprocessor);
4147
+ };
4148
+ }
4149
+ };
4150
+ var MergedStateStore = class _MergedStateStore extends StateStore {
4151
+ constructor({ original, merged }) {
4152
+ const originalValue = original.getLatestValue();
4153
+ const mergedValue = merged.getLatestValue();
4154
+ super({
4155
+ ...originalValue,
4156
+ ...mergedValue
4157
+ });
4158
+ // override original methods and "disable" them
4159
+ this.next = () => {
4160
+ console.warn(
4161
+ `${_MergedStateStore.name}.next is disabled, call original.next or merged.next instead`
4162
+ );
4163
+ };
4164
+ this.partialNext = () => {
4165
+ console.warn(
4166
+ `${_MergedStateStore.name}.partialNext is disabled, call original.partialNext or merged.partialNext instead`
4167
+ );
4168
+ };
4169
+ this.cachedOriginalValue = originalValue;
4170
+ this.cachedMergedValue = mergedValue;
4171
+ this.original = original;
4172
+ this.merged = merged;
4173
+ }
4174
+ /**
4175
+ * Subscribes to changes in the merged state store.
4176
+ *
4177
+ * This method extends the base subscribe functionality to handle the merged nature of this store:
4178
+ * 1. The first subscriber triggers registration of helper subscribers that listen to both source stores
4179
+ * 2. Changes from either source store are propagated to this merged store
4180
+ * 3. Source store values are cached to prevent unnecessary updates
4181
+ *
4182
+ * When the first subscriber is added, the method sets up listeners on both original and merged stores.
4183
+ * These listeners update the combined store value whenever either source store changes.
4184
+ * All subscriptions (helpers and the actual handler) are tracked so they can be properly cleaned up.
4185
+ *
4186
+ * @param handler - The callback function that will be executed when the state changes
4187
+ * @returns An unsubscribe function that, when called, removes the subscription and any helper subscriptions
4188
+ */
4189
+ subscribe(handler) {
4190
+ const unsubscribeFunctions = [];
4191
+ if (!this.handlers.size) {
4192
+ const base = (nextValue) => {
4193
+ super.next((currentValue) => ({
4194
+ ...currentValue,
4195
+ ...nextValue
4196
+ }));
4197
+ };
4198
+ unsubscribeFunctions.push(
4199
+ this.original.subscribe((nextValue) => {
4200
+ if (nextValue === this.cachedOriginalValue) return;
4201
+ this.cachedOriginalValue = nextValue;
4202
+ base(nextValue);
4203
+ }),
4204
+ this.merged.subscribe((nextValue) => {
4205
+ if (nextValue === this.cachedMergedValue) return;
4206
+ this.cachedMergedValue = nextValue;
4207
+ base(nextValue);
4208
+ })
4209
+ );
4210
+ }
4211
+ unsubscribeFunctions.push(super.subscribe(handler));
4212
+ return () => {
4213
+ unsubscribeFunctions.forEach((unsubscribe) => unsubscribe());
4214
+ };
4215
+ }
4216
+ /**
4217
+ * Retrieves the latest combined state from both original and merged stores.
4218
+ *
4219
+ * This method extends the base getLatestValue functionality to ensure the merged store
4220
+ * remains in sync with its source stores even when there are no active subscribers.
4221
+ *
4222
+ * When there are no handlers registered, the method:
4223
+ * 1. Fetches the latest values from both source stores
4224
+ * 2. Compares them with the cached values to detect changes
4225
+ * 3. If changes are detected, updates the internal value and caches
4226
+ * the new source values to maintain consistency
4227
+ *
4228
+ * This approach ensures that calling getLatestValue() always returns the most
4229
+ * up-to-date combined state, even if the merged store hasn't been actively
4230
+ * receiving updates through subscriptions.
4231
+ *
4232
+ * @returns The latest combined state from both original and merged stores
4233
+ */
4234
+ getLatestValue() {
4235
+ if (!this.handlers.size) {
4236
+ const originalValue = this.original.getLatestValue();
4237
+ const mergedValue = this.merged.getLatestValue();
4238
+ if (originalValue !== this.cachedOriginalValue || mergedValue !== this.cachedMergedValue) {
4239
+ this.value = {
4240
+ ...originalValue,
4241
+ ...mergedValue
4242
+ };
4243
+ this.cachedMergedValue = mergedValue;
4244
+ this.cachedOriginalValue = originalValue;
4245
+ }
4246
+ }
4247
+ return super.getLatestValue();
4248
+ }
4249
+ addPreprocessor() {
4250
+ console.warn(
4251
+ `${_MergedStateStore.name}.addPreprocessor is disabled, call original.addPreprocessor or merged.addPreprocessor instead`
4252
+ );
4253
+ return noop2;
4254
+ }
4088
4255
  };
4089
4256
 
4090
4257
  // src/utils/mergeWith/mergeWithCore.ts
@@ -4329,6 +4496,12 @@ function createMergeCore(options = {}) {
4329
4496
  return false;
4330
4497
  }
4331
4498
  function createNewTarget(targetValue, srcValue) {
4499
+ if (targetValue === null || typeof targetValue === "undefined") {
4500
+ return srcValue;
4501
+ }
4502
+ if (!Array.isArray(targetValue) && typeof targetValue !== "object") {
4503
+ return srcValue;
4504
+ }
4332
4505
  if (targetValue && typeof targetValue === "object") {
4333
4506
  const isTargetClassInstance = isClassInstance(targetValue);
4334
4507
  const isSourceClassInstance = isClassInstance(srcValue);
@@ -5498,7 +5671,9 @@ var ErrorFromResponse = class extends Error {
5498
5671
  return {
5499
5672
  message: `(${joinable.join(", ")}) - ${this.message}`,
5500
5673
  stack: this.stack,
5501
- name: this.name
5674
+ name: this.name,
5675
+ code: this.code,
5676
+ status: this.status
5502
5677
  };
5503
5678
  }
5504
5679
  };
@@ -7631,7 +7806,7 @@ var initState5 = (composition) => {
7631
7806
  pollId: message.poll_id ?? null
7632
7807
  };
7633
7808
  };
7634
- var noop2 = () => void 0;
7809
+ var noop3 = () => void 0;
7635
7810
  var _MessageComposer = class _MessageComposer extends WithSubscriptions {
7636
7811
  // todo: mediaRecorder: MediaRecorderController;
7637
7812
  constructor({
@@ -7659,7 +7834,7 @@ var _MessageComposer = class _MessageComposer extends WithSubscriptions {
7659
7834
  this.initEditingAuditState = (composition) => initEditingAuditState(composition);
7660
7835
  this.registerSubscriptions = () => {
7661
7836
  if (this.hasSubscriptions) {
7662
- return noop2;
7837
+ return noop3;
7663
7838
  }
7664
7839
  this.addUnsubscribeFunction(this.subscribeMessageComposerSetupStateChange());
7665
7840
  this.addUnsubscribeFunction(this.subscribeMessageUpdated());
@@ -12912,7 +13087,24 @@ var StreamChat = class _StreamChat {
12912
13087
  'data_template': 'data handlebars template',
12913
13088
  'apn_template': 'apn notification handlebars template under v2'
12914
13089
  },
12915
- 'webhook_url': 'https://acme.com/my/awesome/webhook/'
13090
+ 'webhook_url': 'https://acme.com/my/awesome/webhook/',
13091
+ 'event_hooks': [
13092
+ {
13093
+ 'hook_type': 'webhook',
13094
+ 'enabled': true,
13095
+ 'event_types': ['message.new'],
13096
+ 'webhook_url': 'https://acme.com/my/awesome/webhook/'
13097
+ },
13098
+ {
13099
+ 'hook_type': 'sqs',
13100
+ 'enabled': true,
13101
+ 'event_types': ['message.new'],
13102
+ 'sqs_url': 'https://sqs.us-east-1.amazonaws.com/1234567890/my-queue',
13103
+ 'sqs_auth_type': 'key',
13104
+ 'sqs_key': 'my-access-key',
13105
+ 'sqs_secret': 'my-secret-key'
13106
+ }
13107
+ ]
12916
13108
  }
12917
13109
  */
12918
13110
  async updateAppSettings(options) {
@@ -14256,13 +14448,15 @@ var StreamChat = class _StreamChat {
14256
14448
  } else {
14257
14449
  await this.offlineDb.softDeleteMessage({ id: messageID });
14258
14450
  }
14259
- return await this.offlineDb.queueTask({
14260
- task: {
14261
- messageId: messageID,
14262
- payload: [messageID, hardDelete],
14263
- type: "delete-message"
14451
+ return await this.offlineDb.queueTask(
14452
+ {
14453
+ task: {
14454
+ messageId: messageID,
14455
+ payload: [messageID, hardDelete],
14456
+ type: "delete-message"
14457
+ }
14264
14458
  }
14265
- });
14459
+ );
14266
14460
  }
14267
14461
  } catch (error) {
14268
14462
  this.logger("error", `offlineDb:deleteMessage`, {
@@ -14416,7 +14610,7 @@ var StreamChat = class _StreamChat {
14416
14610
  if (this.userAgent) {
14417
14611
  return this.userAgent;
14418
14612
  }
14419
- const version = "9.2.0";
14613
+ const version = "9.4.0";
14420
14614
  const clientBundle = "browser-cjs";
14421
14615
  let userAgentString = "";
14422
14616
  if (this.sdkIdentifier) {
@@ -15592,6 +15786,26 @@ var BuiltinPermissions = {
15592
15786
  UseFrozenChannel: "Send messages and reactions to frozen channels"
15593
15787
  };
15594
15788
 
15789
+ // src/offline-support/types.ts
15790
+ var OfflineError = class extends Error {
15791
+ constructor(message, {
15792
+ type
15793
+ }) {
15794
+ super(message);
15795
+ this.name = "OfflineError";
15796
+ this.type = type;
15797
+ }
15798
+ // Vitest helper (serialized errors are too large to read)
15799
+ // https://github.com/vitest-dev/vitest/blob/v3.1.3/packages/utils/src/error.ts#L60-L62
15800
+ toJSON() {
15801
+ return {
15802
+ message: `${this.type} - ${this.message}`,
15803
+ stack: this.stack,
15804
+ name: this.name
15805
+ };
15806
+ }
15807
+ };
15808
+
15595
15809
  // src/offline-support/offline_sync_manager.ts
15596
15810
  var OfflineDBSyncManager = class {
15597
15811
  constructor({
@@ -16197,20 +16411,23 @@ var AbstractOfflineDB = class {
16197
16411
  * @param task - the pending task we want to execute
16198
16412
  */
16199
16413
  this.queueTask = async ({ task }) => {
16200
- let response;
16201
- try {
16414
+ const attemptTaskExecution = async () => {
16202
16415
  if (!this.client.wsConnection?.isHealthy) {
16203
- await this.addPendingTask(task);
16204
- return;
16416
+ throw new OfflineError(
16417
+ "Cannot execute task because the connection has been lost.",
16418
+ { type: "connection:lost" }
16419
+ );
16205
16420
  }
16206
- response = await this.executeTask({ task });
16421
+ return await this.executeTask({ task });
16422
+ };
16423
+ try {
16424
+ return await attemptTaskExecution();
16207
16425
  } catch (e) {
16208
16426
  if (!this.shouldSkipQueueingTask(e)) {
16209
16427
  await this.addPendingTask(task);
16210
- throw e;
16211
16428
  }
16429
+ throw e;
16212
16430
  }
16213
- return response;
16214
16431
  };
16215
16432
  /**
16216
16433
  * A utility method that determines if a failed task should be added to the