stream-chat 9.53.0 → 9.54.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.
@@ -5910,10 +5910,11 @@ var createLinkPreviewsCompositionMiddleware = (composer) => ({
5910
5910
  const linkPreviews = linkPreviewsManager.loadingPreviews.length > 0 ? [] : linkPreviewsManager.loadedPreviews.map(
5911
5911
  (preview) => LinkPreviewsManager.getPreviewData(preview)
5912
5912
  );
5913
- const attachments = (state.message.attachments ?? []).concat(
5913
+ const messageAttachments = (state.message.attachments ?? []).concat(
5914
5914
  linkPreviews
5915
5915
  );
5916
- if (!attachments.length) return forward();
5916
+ const localAttachments = (state.localMessage.attachments ?? []).concat(linkPreviews);
5917
+ if (!messageAttachments.length && !localAttachments.length) return forward();
5917
5918
  const sendOptions = { ...state.sendOptions };
5918
5919
  const skip_enrich_url = !someLinkPreviewsLoading && linkPreviews.length > 0 || someLinkPreviewsDismissed;
5919
5920
  if (skip_enrich_url) {
@@ -5921,14 +5922,14 @@ var createLinkPreviewsCompositionMiddleware = (composer) => ({
5921
5922
  }
5922
5923
  return next({
5923
5924
  ...state,
5924
- message: {
5925
+ message: messageAttachments.length ? {
5925
5926
  ...state.message,
5926
- attachments
5927
- },
5928
- localMessage: {
5927
+ attachments: messageAttachments
5928
+ } : state.message,
5929
+ localMessage: localAttachments.length ? {
5929
5930
  ...state.localMessage,
5930
- attachments
5931
- },
5931
+ attachments: localAttachments
5932
+ } : state.localMessage,
5932
5933
  sendOptions
5933
5934
  });
5934
5935
  }
@@ -13157,6 +13158,242 @@ var ThreadManager = class extends WithSubscriptions {
13157
13158
  }
13158
13159
  };
13159
13160
 
13161
+ // src/poll_optimistic_votes.ts
13162
+ var CATCH_UP_TIMEOUT_MS = 3e3;
13163
+ var OptimisticPollVotes = class {
13164
+ constructor({ client, poll }) {
13165
+ // Own vote changes not confirmed by a WS event yet
13166
+ this.pendingCount = 0;
13167
+ // Own vote changes whose requests succeeded since the server's vote state was last shown
13168
+ this.succeededChanges = [];
13169
+ // Vote requests with this tag are sent one after another. A symbol, so it is unique per instance.
13170
+ this.requestTag = /* @__PURE__ */ Symbol("poll-votes");
13171
+ this.withServerVotes = (pollState) => ({
13172
+ ...pollState,
13173
+ ...this.serverVoteState
13174
+ });
13175
+ /**
13176
+ * Casts a vote for an option, shown right away through a placeholder vote. In polls with unique
13177
+ * votes, it replaces the existing own votes.
13178
+ */
13179
+ this.castVote = (optionId, messageId) => {
13180
+ const sendRequest = () => this.client.castPollVote(messageId, this.poll.id, { option_id: optionId });
13181
+ const state = this.poll.data;
13182
+ if (state.ownVotesByOptionId[optionId]) return this.send(sendRequest);
13183
+ const replacedVotes = state.enforce_unique_vote ? Object.values(state.ownVotesByOptionId) : [];
13184
+ this.poll.state.partialNext(
13185
+ applyOwnVoteDelta(state, {
13186
+ add: [this.createPlaceholderVote(optionId)],
13187
+ remove: replacedVotes
13188
+ })
13189
+ );
13190
+ return this.track(sendRequest, (response) => ({
13191
+ optionId,
13192
+ vote: response.vote
13193
+ }));
13194
+ };
13195
+ // Removes a vote, right away.
13196
+ this.removeVote = (voteId, messageId) => {
13197
+ const sendRequest = (id) => this.client.removePollVote(messageId, this.poll.id, id);
13198
+ const state = this.poll.data;
13199
+ const vote = Object.values(state.ownVotesByOptionId).find(({ id }) => id === voteId);
13200
+ if (!vote?.option_id) return this.send(() => sendRequest(voteId));
13201
+ const optionId = vote.option_id;
13202
+ this.poll.state.partialNext(applyOwnVoteDelta(state, { remove: [vote] }));
13203
+ return this.track(
13204
+ // runs when the removal's turn comes, so a preceding cast of the vote has finished
13205
+ () => {
13206
+ const serverVoteId = this.getServerVoteId(vote);
13207
+ return serverVoteId ? sendRequest(serverVoteId) : Promise.resolve(void 0);
13208
+ },
13209
+ () => ({ optionId })
13210
+ );
13211
+ };
13212
+ // Sends a vote request once the previous one has finished, whether it succeeded or not.
13213
+ this.send = async (sendRequest) => {
13214
+ try {
13215
+ return await withoutConcurrency(this.requestTag, sendRequest);
13216
+ } finally {
13217
+ if (!hasPending(this.requestTag)) this.scheduleCatchUp();
13218
+ }
13219
+ };
13220
+ // Stores the vote state of a WS vote event and returns the vote fields to show, if any.
13221
+ this.handleVoteEvent = (poll, {
13222
+ confirmsOwnVote,
13223
+ ownVotesByOptionId
13224
+ }) => {
13225
+ this.serverVoteState = {
13226
+ latest_votes_by_option: poll.latest_votes_by_option,
13227
+ ownVotesByOptionId,
13228
+ vote_count: poll.vote_count,
13229
+ vote_counts_by_option: poll.vote_counts_by_option
13230
+ };
13231
+ if (confirmsOwnVote) this.pendingCount = Math.max(this.pendingCount - 1, 0);
13232
+ return this.pendingCount > 0 ? {} : this.takeServerVoteState();
13233
+ };
13234
+ // Fresh server state (e.g. a refetch) replaces everything, pending changes are dropped.
13235
+ this.reset = (pollState) => {
13236
+ this.serverVoteState = pickVoteState(pollState);
13237
+ this.pendingCount = 0;
13238
+ this.succeededChanges = [];
13239
+ clearTimeout(this.catchUpTimeout);
13240
+ };
13241
+ // Tracks an own vote change until its request settles. `sendRequest` resolves to undefined
13242
+ // when no request was needed.
13243
+ this.track = async (sendRequest, toChange) => {
13244
+ this.pendingCount += 1;
13245
+ clearTimeout(this.catchUpTimeout);
13246
+ let response;
13247
+ try {
13248
+ response = await this.send(async () => {
13249
+ const result = await sendRequest();
13250
+ if (result !== void 0) this.succeededChanges.push(toChange(result));
13251
+ return result;
13252
+ });
13253
+ } catch (error) {
13254
+ this.settleWithoutEvent();
13255
+ throw error;
13256
+ }
13257
+ if (response === void 0) this.settleWithoutEvent();
13258
+ return response;
13259
+ };
13260
+ // A failed or skipped request gets no WS event, so it counts as confirmed right away.
13261
+ this.settleWithoutEvent = () => {
13262
+ this.pendingCount = Math.max(this.pendingCount - 1, 0);
13263
+ if (this.pendingCount === 0) this.poll.state.partialNext(this.takeServerVoteState());
13264
+ };
13265
+ // The vote shown for a cast until the server's vote arrives
13266
+ this.createPlaceholderVote = (optionId) => {
13267
+ const now = (/* @__PURE__ */ new Date()).toISOString();
13268
+ return {
13269
+ created_at: now,
13270
+ id: `local-${generateUUIDv4()}`,
13271
+ option_id: optionId,
13272
+ poll_id: this.poll.id,
13273
+ updated_at: now,
13274
+ user: this.client.user,
13275
+ user_id: this.client.userID
13276
+ };
13277
+ };
13278
+ /**
13279
+ * The server id of a vote to remove, once everything sent before the removal has finished. Casts
13280
+ * and removals of an option alternate, so the option's last succeeded change tells: a cast is the
13281
+ * vote being removed (e.g. a placeholder), a removal means the cast in between failed. Without
13282
+ * one, the server's own vote of the option is the vote being removed.
13283
+ */
13284
+ this.getServerVoteId = (vote) => {
13285
+ for (let i = this.succeededChanges.length - 1; i >= 0; i--) {
13286
+ const change = this.succeededChanges[i];
13287
+ if (change.optionId === vote.option_id) return change.vote?.id;
13288
+ }
13289
+ return this.serverVoteState.ownVotesByOptionId[vote.option_id]?.id;
13290
+ };
13291
+ this.takeServerVoteState = () => {
13292
+ clearTimeout(this.catchUpTimeout);
13293
+ this.succeededChanges = [];
13294
+ return {
13295
+ ...this.serverVoteState,
13296
+ maxVotedOptionIds: getMaxVotedOptionIds(this.serverVoteState.vote_counts_by_option)
13297
+ };
13298
+ };
13299
+ this.scheduleCatchUp = () => {
13300
+ clearTimeout(this.catchUpTimeout);
13301
+ if (this.pendingCount === 0) return;
13302
+ this.catchUpTimeout = setTimeout(this.applySucceededChanges, CATCH_UP_TIMEOUT_MS);
13303
+ };
13304
+ /**
13305
+ * A WS event of an own vote change got lost, so the server's own votes are stale. All requests
13306
+ * have finished (the timeout starts once nothing is left to send and a new request cancels it),
13307
+ * so applying the succeeded changes in order gives the server's own votes (changes whose events
13308
+ * did arrive end up the same). They are shown with the vote counts of the last WS event, which
13309
+ * contain the other users' votes too.
13310
+ */
13311
+ this.applySucceededChanges = () => {
13312
+ const { enforce_unique_vote } = this.poll.data;
13313
+ let ownVotesByOptionId = { ...this.serverVoteState.ownVotesByOptionId };
13314
+ for (const { optionId, vote } of this.succeededChanges) {
13315
+ if (!vote) {
13316
+ delete ownVotesByOptionId[optionId];
13317
+ } else if (enforce_unique_vote) {
13318
+ ownVotesByOptionId = { [optionId]: vote };
13319
+ } else {
13320
+ ownVotesByOptionId[optionId] = vote;
13321
+ }
13322
+ }
13323
+ this.serverVoteState = { ...this.serverVoteState, ownVotesByOptionId };
13324
+ this.pendingCount = 0;
13325
+ this.poll.state.partialNext(this.takeServerVoteState());
13326
+ };
13327
+ this.client = client;
13328
+ this.poll = poll;
13329
+ this.serverVoteState = pickVoteState(poll.data);
13330
+ }
13331
+ get serverOwnVotes() {
13332
+ return this.serverVoteState.ownVotesByOptionId;
13333
+ }
13334
+ };
13335
+ function pickVoteState(state) {
13336
+ return {
13337
+ latest_votes_by_option: state.latest_votes_by_option,
13338
+ ownVotesByOptionId: state.ownVotesByOptionId,
13339
+ vote_count: state.vote_count,
13340
+ vote_counts_by_option: state.vote_counts_by_option
13341
+ };
13342
+ }
13343
+ function applyOwnVoteDelta(state, { add = [], remove = [] }) {
13344
+ const ownVotesByOptionId = { ...state.ownVotesByOptionId };
13345
+ const voteCountsByOption = { ...state.vote_counts_by_option };
13346
+ const latestVotesByOption = state.latest_votes_by_option ? { ...state.latest_votes_by_option } : state.latest_votes_by_option;
13347
+ let voteCount = state.vote_count ?? 0;
13348
+ for (const vote of remove) {
13349
+ const optionId = vote.option_id;
13350
+ if (!optionId) continue;
13351
+ delete ownVotesByOptionId[optionId];
13352
+ const optionVoteCount = (voteCountsByOption[optionId] ?? 0) - 1;
13353
+ if (optionVoteCount > 0) {
13354
+ voteCountsByOption[optionId] = optionVoteCount;
13355
+ } else {
13356
+ delete voteCountsByOption[optionId];
13357
+ }
13358
+ voteCount = Math.max(voteCount - 1, 0);
13359
+ if (latestVotesByOption?.[optionId]) {
13360
+ latestVotesByOption[optionId] = latestVotesByOption[optionId].filter(
13361
+ (latestVote) => latestVote.id !== vote.id
13362
+ );
13363
+ }
13364
+ }
13365
+ for (const vote of add) {
13366
+ const optionId = vote.option_id;
13367
+ if (!optionId) continue;
13368
+ ownVotesByOptionId[optionId] = vote;
13369
+ voteCountsByOption[optionId] = (voteCountsByOption[optionId] ?? 0) + 1;
13370
+ voteCount += 1;
13371
+ if (latestVotesByOption && state.voting_visibility !== "anonymous") {
13372
+ latestVotesByOption[optionId] = [vote, ...latestVotesByOption[optionId] ?? []];
13373
+ }
13374
+ }
13375
+ return {
13376
+ latest_votes_by_option: latestVotesByOption,
13377
+ maxVotedOptionIds: getMaxVotedOptionIds(voteCountsByOption),
13378
+ ownVotesByOptionId,
13379
+ vote_count: voteCount,
13380
+ vote_counts_by_option: voteCountsByOption
13381
+ };
13382
+ }
13383
+ function getMaxVotedOptionIds(voteCountsByOption) {
13384
+ let maxVotes = 0;
13385
+ let winningOptions = [];
13386
+ for (const [id, count] of Object.entries(voteCountsByOption ?? {})) {
13387
+ if (count > maxVotes) {
13388
+ winningOptions = [id];
13389
+ maxVotes = count;
13390
+ } else if (count === maxVotes) {
13391
+ winningOptions.push(id);
13392
+ }
13393
+ }
13394
+ return winningOptions;
13395
+ }
13396
+
13160
13397
  // src/poll.ts
13161
13398
  var isPollUpdatedEvent = (e) => e.type === "poll.updated";
13162
13399
  var isPollClosedEventEvent = (e) => e.type === "poll.closed";
@@ -13166,6 +13403,9 @@ var isPollVoteRemovedEvent = (e) => e.type === "poll.vote_removed";
13166
13403
  var isVoteAnswer = (vote) => !!vote?.answer_text;
13167
13404
  var Poll = class {
13168
13405
  constructor({ client, poll }) {
13406
+ // Set whenever server data (WS event or rehydration) confirms that the poll is closed,
13407
+ // so that a failed optimistic close does not reopen a poll that is actually closed.
13408
+ this.closeConfirmed = false;
13169
13409
  this.getInitialStateFromPollResponse = (poll) => {
13170
13410
  const { own_votes, id, ...pollResponseForState } = poll;
13171
13411
  const { ownAnswer, ownVotes } = own_votes?.reduce(
@@ -13189,25 +13429,47 @@ var Poll = class {
13189
13429
  ownVotesByOptionId: getOwnVotesByOptionId(ownVotes)
13190
13430
  };
13191
13431
  };
13432
+ // persists only the server's votes, never the optimistic ones
13192
13433
  this.upsertOfflineDb = () => {
13434
+ const serverState = this.optimisticVotes.withServerVotes(this.data);
13193
13435
  this.client.offlineDb?.executeQuerySafely(
13194
- (db) => db.upsertPoll({ poll: mapPollStateToResponse(this) }),
13436
+ (db) => db.upsertPoll({ poll: mapPollStateToResponse(this, serverState) }),
13195
13437
  { method: "upsertPoll" }
13196
13438
  );
13197
13439
  };
13198
13440
  this.reinitializeState = (poll) => {
13199
- this.state.partialNext(this.getInitialStateFromPollResponse(poll));
13441
+ if (poll.is_closed) this.closeConfirmed = true;
13442
+ const initialState = this.getInitialStateFromPollResponse(poll);
13443
+ this.optimisticVotes.reset(initialState);
13444
+ this.state.partialNext(initialState);
13445
+ };
13446
+ // Hands the vote state of a WS vote event to the optimistic votes, which decide whether to show it.
13447
+ this.updateFromVoteEvent = (event, ownVotesByOptionId, answerState) => {
13448
+ const isOwnVote = event.poll_vote.user_id === this.client.userID;
13449
+ const shownVoteState = this.optimisticVotes.handleVoteEvent(event.poll, {
13450
+ confirmsOwnVote: isOwnVote && !isVoteAnswer(event.poll_vote) && !!event.poll_vote.option_id,
13451
+ ownVotesByOptionId
13452
+ });
13453
+ this.state.partialNext({
13454
+ ...answerState,
13455
+ answers_count: event.poll.answers_count,
13456
+ lastActivityAt: new Date(event.created_at),
13457
+ ...shownVoteState
13458
+ });
13459
+ this.upsertOfflineDb();
13200
13460
  };
13201
13461
  this.handlePollUpdated = (event) => {
13202
13462
  if (event.poll?.id && event.poll.id !== this.id) return;
13203
13463
  if (!isPollUpdatedEvent(event)) return;
13204
13464
  const { id, ...pollData } = extractPollData(event.poll);
13465
+ if (pollData.is_closed) this.closeConfirmed = true;
13205
13466
  this.state.partialNext({ ...pollData, lastActivityAt: new Date(event.created_at) });
13206
13467
  this.upsertOfflineDb();
13207
13468
  };
13208
13469
  this.handlePollClosed = (event) => {
13209
13470
  if (event.poll?.id && event.poll.id !== this.id) return;
13210
13471
  if (!isPollClosedEventEvent(event)) return;
13472
+ this.closeConfirmed = true;
13211
13473
  this.state.partialNext({
13212
13474
  is_closed: true,
13213
13475
  lastActivityAt: new Date(event.created_at)
@@ -13221,8 +13483,7 @@ var Poll = class {
13221
13483
  const isOwnVote = event.poll_vote.user_id === this.client.userID;
13222
13484
  let latestAnswers = [...currentState.latest_answers];
13223
13485
  let ownAnswer = currentState.ownAnswer;
13224
- const ownVotesByOptionId = currentState.ownVotesByOptionId;
13225
- let maxVotedOptionIds = currentState.maxVotedOptionIds;
13486
+ const ownVotesByOptionId = { ...this.optimisticVotes.serverOwnVotes };
13226
13487
  if (isOwnVote) {
13227
13488
  if (isVoteAnswer(event.poll_vote)) {
13228
13489
  ownAnswer = event.poll_vote;
@@ -13232,19 +13493,11 @@ var Poll = class {
13232
13493
  }
13233
13494
  if (isVoteAnswer(event.poll_vote)) {
13234
13495
  latestAnswers = [event.poll_vote, ...latestAnswers];
13235
- } else {
13236
- maxVotedOptionIds = getMaxVotedOptionIds(event.poll.vote_counts_by_option);
13237
13496
  }
13238
- const pollEnrichData = extractPollEnrichedData(event.poll);
13239
- this.state.partialNext({
13240
- ...pollEnrichData,
13497
+ this.updateFromVoteEvent(event, ownVotesByOptionId, {
13241
13498
  latest_answers: latestAnswers,
13242
- lastActivityAt: new Date(event.created_at),
13243
- ownAnswer,
13244
- ownVotesByOptionId,
13245
- maxVotedOptionIds
13499
+ ownAnswer
13246
13500
  });
13247
- this.upsertOfflineDb();
13248
13501
  };
13249
13502
  this.handleVoteChanged = (event) => {
13250
13503
  if (event.poll?.id && event.poll.id !== this.id) return;
@@ -13253,8 +13506,7 @@ var Poll = class {
13253
13506
  const isOwnVote = event.poll_vote.user_id === this.client.userID;
13254
13507
  let latestAnswers = [...currentState.latest_answers];
13255
13508
  let ownAnswer = currentState.ownAnswer;
13256
- let ownVotesByOptionId = currentState.ownVotesByOptionId;
13257
- let maxVotedOptionIds = currentState.maxVotedOptionIds;
13509
+ let ownVotesByOptionId = this.optimisticVotes.serverOwnVotes;
13258
13510
  if (isOwnVote) {
13259
13511
  if (isVoteAnswer(event.poll_vote)) {
13260
13512
  latestAnswers = [
@@ -13278,23 +13530,14 @@ var Poll = class {
13278
13530
  if (ownAnswer?.id === event.poll_vote.id) {
13279
13531
  ownAnswer = void 0;
13280
13532
  }
13281
- maxVotedOptionIds = getMaxVotedOptionIds(event.poll.vote_counts_by_option);
13282
13533
  }
13283
13534
  } else if (isVoteAnswer(event.poll_vote)) {
13284
13535
  latestAnswers = [event.poll_vote, ...latestAnswers];
13285
- } else {
13286
- maxVotedOptionIds = getMaxVotedOptionIds(event.poll.vote_counts_by_option);
13287
13536
  }
13288
- const pollEnrichData = extractPollEnrichedData(event.poll);
13289
- this.state.partialNext({
13290
- ...pollEnrichData,
13537
+ this.updateFromVoteEvent(event, ownVotesByOptionId, {
13291
13538
  latest_answers: latestAnswers,
13292
- lastActivityAt: new Date(event.created_at),
13293
- ownAnswer,
13294
- ownVotesByOptionId,
13295
- maxVotedOptionIds
13539
+ ownAnswer
13296
13540
  });
13297
- this.upsertOfflineDb();
13298
13541
  };
13299
13542
  this.handleVoteRemoved = (event) => {
13300
13543
  if (event.poll?.id && event.poll.id !== this.id) return;
@@ -13303,29 +13546,19 @@ var Poll = class {
13303
13546
  const isOwnVote = event.poll_vote.user_id === this.client.userID;
13304
13547
  let latestAnswers = [...currentState.latest_answers];
13305
13548
  let ownAnswer = currentState.ownAnswer;
13306
- const ownVotesByOptionId = { ...currentState.ownVotesByOptionId };
13307
- let maxVotedOptionIds = currentState.maxVotedOptionIds;
13549
+ const ownVotesByOptionId = { ...this.optimisticVotes.serverOwnVotes };
13308
13550
  if (isVoteAnswer(event.poll_vote)) {
13309
13551
  latestAnswers = latestAnswers.filter((answer) => answer.id !== event.poll_vote.id);
13310
13552
  if (isOwnVote) {
13311
13553
  ownAnswer = void 0;
13312
13554
  }
13313
- } else {
13314
- maxVotedOptionIds = getMaxVotedOptionIds(event.poll.vote_counts_by_option);
13315
- if (isOwnVote && event.poll_vote.option_id) {
13316
- delete ownVotesByOptionId[event.poll_vote.option_id];
13317
- }
13555
+ } else if (isOwnVote && event.poll_vote.option_id) {
13556
+ delete ownVotesByOptionId[event.poll_vote.option_id];
13318
13557
  }
13319
- const pollEnrichData = extractPollEnrichedData(event.poll);
13320
- this.state.partialNext({
13321
- ...pollEnrichData,
13558
+ this.updateFromVoteEvent(event, ownVotesByOptionId, {
13322
13559
  latest_answers: latestAnswers,
13323
- lastActivityAt: new Date(event.created_at),
13324
- ownAnswer,
13325
- ownVotesByOptionId,
13326
- maxVotedOptionIds
13560
+ ownAnswer
13327
13561
  });
13328
- this.upsertOfflineDb();
13329
13562
  };
13330
13563
  this.query = async (id) => {
13331
13564
  const { poll } = await this.client.getPoll(id);
@@ -13334,11 +13567,33 @@ var Poll = class {
13334
13567
  };
13335
13568
  this.update = async (data) => await this.client.updatePoll({ ...data, id: this.id });
13336
13569
  this.partialUpdate = async (partialPollObject) => await this.client.partialUpdatePoll(this.id, partialPollObject);
13337
- this.close = async () => await this.client.closePoll(this.id);
13570
+ /**
13571
+ * Closes the poll. The poll is marked as closed optimistically and the change is
13572
+ * reverted if the request fails, unless the server confirmed the close meanwhile.
13573
+ * Does nothing if the poll is already closed.
13574
+ */
13575
+ this.close = async () => {
13576
+ if (this.data.is_closed) return;
13577
+ this.closeConfirmed = false;
13578
+ this.state.partialNext({ is_closed: true });
13579
+ try {
13580
+ return await this.client.closePoll(this.id);
13581
+ } catch (error) {
13582
+ if (!this.closeConfirmed) {
13583
+ this.state.partialNext({ is_closed: false });
13584
+ }
13585
+ throw error;
13586
+ }
13587
+ };
13338
13588
  this.delete = async () => await this.client.deletePoll(this.id);
13339
13589
  this.createOption = async (option) => await this.client.createPollOption(this.id, option);
13340
13590
  this.updateOption = async (option) => await this.client.updatePollOption(this.id, option);
13341
13591
  this.deleteOption = async (optionId) => await this.client.deletePollOption(this.id, optionId);
13592
+ /**
13593
+ * Casts a vote for an option. The vote is shown right away through a local placeholder vote,
13594
+ * which the server's vote replaces once the WS events of all pending vote changes arrived.
13595
+ * If the request fails, the change is reverted.
13596
+ */
13342
13597
  this.castVote = async (optionId, messageId) => {
13343
13598
  const { max_votes_allowed, ownVotesByOptionId } = this.data;
13344
13599
  const reachedVoteLimit = max_votes_allowed && max_votes_allowed === Object.keys(ownVotesByOptionId).length;
@@ -13355,11 +13610,14 @@ var Poll = class {
13355
13610
  });
13356
13611
  return;
13357
13612
  }
13358
- return await this.client.castPollVote(messageId, this.id, {
13359
- option_id: optionId
13360
- });
13613
+ return await this.optimisticVotes.castVote(optionId, messageId);
13361
13614
  };
13362
- this.removeVote = async (voteId, messageId) => await this.client.removePollVote(messageId, this.id, voteId);
13615
+ /**
13616
+ * Removes a vote. The vote is removed right away and restored if the request fails. Vote
13617
+ * requests are sent in order, so removing a vote whose cast is not confirmed yet uses the vote
13618
+ * id from the cast's response; if that cast failed, there is nothing to remove.
13619
+ */
13620
+ this.removeVote = async (voteId, messageId) => await this.optimisticVotes.removeVote(voteId, messageId);
13363
13621
  this.addAnswer = async (answerText, messageId) => await this.client.addPollAnswer(messageId, this.id, answerText);
13364
13622
  this.removeAnswer = async (answerId, messageId) => await this.client.removePollVote(messageId, this.id, answerId);
13365
13623
  this.queryAnswers = async (params) => await this.client.queryPollAnswers(
@@ -13377,24 +13635,12 @@ var Poll = class {
13377
13635
  this.client = client;
13378
13636
  this.id = poll.id;
13379
13637
  this.state = new StateStore(this.getInitialStateFromPollResponse(poll));
13638
+ this.optimisticVotes = new OptimisticPollVotes({ client: this.client, poll: this });
13380
13639
  }
13381
13640
  get data() {
13382
13641
  return this.state.getLatestValue();
13383
13642
  }
13384
13643
  };
13385
- function getMaxVotedOptionIds(voteCountsByOption) {
13386
- let maxVotes = 0;
13387
- let winningOptions = [];
13388
- for (const [id, count] of Object.entries(voteCountsByOption ?? {})) {
13389
- if (count > maxVotes) {
13390
- winningOptions = [id];
13391
- maxVotes = count;
13392
- } else if (count === maxVotes) {
13393
- winningOptions.push(id);
13394
- }
13395
- }
13396
- return winningOptions;
13397
- }
13398
13644
  function getOwnVotesByOptionId(ownVotes) {
13399
13645
  return !ownVotes ? {} : ownVotes.reduce((acc, vote) => {
13400
13646
  if (isVoteAnswer(vote) || !vote.option_id) return acc;
@@ -13416,7 +13662,7 @@ function extractPollData(pollResponse) {
13416
13662
  voting_visibility: pollResponse.voting_visibility
13417
13663
  };
13418
13664
  }
13419
- function mapPollStateToResponse(poll) {
13665
+ function mapPollStateToResponse(poll, state = poll.data) {
13420
13666
  const {
13421
13667
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
13422
13668
  lastActivityAt,
@@ -13425,7 +13671,7 @@ function mapPollStateToResponse(poll) {
13425
13671
  ownVotesByOptionId,
13426
13672
  ownAnswer,
13427
13673
  ...restState
13428
- } = poll.data;
13674
+ } = state;
13429
13675
  const ownVotes = [
13430
13676
  ...Object.values(ownVotesByOptionId),
13431
13677
  ...ownAnswer ? [ownAnswer] : []
@@ -15210,7 +15456,7 @@ var StreamChat = class _StreamChat {
15210
15456
  return this.userAgent;
15211
15457
  }
15212
15458
  if (!this.cachedUserAgent) {
15213
- const version = "9.53.0";
15459
+ const version = "9.54.0";
15214
15460
  const { name: sdkName, version: sdkVersion } = this.sdkIdentifier ?? {};
15215
15461
  const { name: appName, version: appVersion } = this.appIdentifier ?? {};
15216
15462
  const { os, model: deviceModel } = this.deviceIdentifier ?? {};