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.
@@ -5687,10 +5687,11 @@ var createLinkPreviewsCompositionMiddleware = (composer) => ({
5687
5687
  const linkPreviews = linkPreviewsManager.loadingPreviews.length > 0 ? [] : linkPreviewsManager.loadedPreviews.map(
5688
5688
  (preview) => LinkPreviewsManager.getPreviewData(preview)
5689
5689
  );
5690
- const attachments = (state.message.attachments ?? []).concat(
5690
+ const messageAttachments = (state.message.attachments ?? []).concat(
5691
5691
  linkPreviews
5692
5692
  );
5693
- if (!attachments.length) return forward();
5693
+ const localAttachments = (state.localMessage.attachments ?? []).concat(linkPreviews);
5694
+ if (!messageAttachments.length && !localAttachments.length) return forward();
5694
5695
  const sendOptions = { ...state.sendOptions };
5695
5696
  const skip_enrich_url = !someLinkPreviewsLoading && linkPreviews.length > 0 || someLinkPreviewsDismissed;
5696
5697
  if (skip_enrich_url) {
@@ -5698,14 +5699,14 @@ var createLinkPreviewsCompositionMiddleware = (composer) => ({
5698
5699
  }
5699
5700
  return next({
5700
5701
  ...state,
5701
- message: {
5702
+ message: messageAttachments.length ? {
5702
5703
  ...state.message,
5703
- attachments
5704
- },
5705
- localMessage: {
5704
+ attachments: messageAttachments
5705
+ } : state.message,
5706
+ localMessage: localAttachments.length ? {
5706
5707
  ...state.localMessage,
5707
- attachments
5708
- },
5708
+ attachments: localAttachments
5709
+ } : state.localMessage,
5709
5710
  sendOptions
5710
5711
  });
5711
5712
  }
@@ -12934,6 +12935,242 @@ var ThreadManager = class extends WithSubscriptions {
12934
12935
  }
12935
12936
  };
12936
12937
 
12938
+ // src/poll_optimistic_votes.ts
12939
+ var CATCH_UP_TIMEOUT_MS = 3e3;
12940
+ var OptimisticPollVotes = class {
12941
+ constructor({ client, poll }) {
12942
+ // Own vote changes not confirmed by a WS event yet
12943
+ this.pendingCount = 0;
12944
+ // Own vote changes whose requests succeeded since the server's vote state was last shown
12945
+ this.succeededChanges = [];
12946
+ // Vote requests with this tag are sent one after another. A symbol, so it is unique per instance.
12947
+ this.requestTag = /* @__PURE__ */ Symbol("poll-votes");
12948
+ this.withServerVotes = (pollState) => ({
12949
+ ...pollState,
12950
+ ...this.serverVoteState
12951
+ });
12952
+ /**
12953
+ * Casts a vote for an option, shown right away through a placeholder vote. In polls with unique
12954
+ * votes, it replaces the existing own votes.
12955
+ */
12956
+ this.castVote = (optionId, messageId) => {
12957
+ const sendRequest = () => this.client.castPollVote(messageId, this.poll.id, { option_id: optionId });
12958
+ const state = this.poll.data;
12959
+ if (state.ownVotesByOptionId[optionId]) return this.send(sendRequest);
12960
+ const replacedVotes = state.enforce_unique_vote ? Object.values(state.ownVotesByOptionId) : [];
12961
+ this.poll.state.partialNext(
12962
+ applyOwnVoteDelta(state, {
12963
+ add: [this.createPlaceholderVote(optionId)],
12964
+ remove: replacedVotes
12965
+ })
12966
+ );
12967
+ return this.track(sendRequest, (response) => ({
12968
+ optionId,
12969
+ vote: response.vote
12970
+ }));
12971
+ };
12972
+ // Removes a vote, right away.
12973
+ this.removeVote = (voteId, messageId) => {
12974
+ const sendRequest = (id) => this.client.removePollVote(messageId, this.poll.id, id);
12975
+ const state = this.poll.data;
12976
+ const vote = Object.values(state.ownVotesByOptionId).find(({ id }) => id === voteId);
12977
+ if (!vote?.option_id) return this.send(() => sendRequest(voteId));
12978
+ const optionId = vote.option_id;
12979
+ this.poll.state.partialNext(applyOwnVoteDelta(state, { remove: [vote] }));
12980
+ return this.track(
12981
+ // runs when the removal's turn comes, so a preceding cast of the vote has finished
12982
+ () => {
12983
+ const serverVoteId = this.getServerVoteId(vote);
12984
+ return serverVoteId ? sendRequest(serverVoteId) : Promise.resolve(void 0);
12985
+ },
12986
+ () => ({ optionId })
12987
+ );
12988
+ };
12989
+ // Sends a vote request once the previous one has finished, whether it succeeded or not.
12990
+ this.send = async (sendRequest) => {
12991
+ try {
12992
+ return await withoutConcurrency(this.requestTag, sendRequest);
12993
+ } finally {
12994
+ if (!hasPending(this.requestTag)) this.scheduleCatchUp();
12995
+ }
12996
+ };
12997
+ // Stores the vote state of a WS vote event and returns the vote fields to show, if any.
12998
+ this.handleVoteEvent = (poll, {
12999
+ confirmsOwnVote,
13000
+ ownVotesByOptionId
13001
+ }) => {
13002
+ this.serverVoteState = {
13003
+ latest_votes_by_option: poll.latest_votes_by_option,
13004
+ ownVotesByOptionId,
13005
+ vote_count: poll.vote_count,
13006
+ vote_counts_by_option: poll.vote_counts_by_option
13007
+ };
13008
+ if (confirmsOwnVote) this.pendingCount = Math.max(this.pendingCount - 1, 0);
13009
+ return this.pendingCount > 0 ? {} : this.takeServerVoteState();
13010
+ };
13011
+ // Fresh server state (e.g. a refetch) replaces everything, pending changes are dropped.
13012
+ this.reset = (pollState) => {
13013
+ this.serverVoteState = pickVoteState(pollState);
13014
+ this.pendingCount = 0;
13015
+ this.succeededChanges = [];
13016
+ clearTimeout(this.catchUpTimeout);
13017
+ };
13018
+ // Tracks an own vote change until its request settles. `sendRequest` resolves to undefined
13019
+ // when no request was needed.
13020
+ this.track = async (sendRequest, toChange) => {
13021
+ this.pendingCount += 1;
13022
+ clearTimeout(this.catchUpTimeout);
13023
+ let response;
13024
+ try {
13025
+ response = await this.send(async () => {
13026
+ const result = await sendRequest();
13027
+ if (result !== void 0) this.succeededChanges.push(toChange(result));
13028
+ return result;
13029
+ });
13030
+ } catch (error) {
13031
+ this.settleWithoutEvent();
13032
+ throw error;
13033
+ }
13034
+ if (response === void 0) this.settleWithoutEvent();
13035
+ return response;
13036
+ };
13037
+ // A failed or skipped request gets no WS event, so it counts as confirmed right away.
13038
+ this.settleWithoutEvent = () => {
13039
+ this.pendingCount = Math.max(this.pendingCount - 1, 0);
13040
+ if (this.pendingCount === 0) this.poll.state.partialNext(this.takeServerVoteState());
13041
+ };
13042
+ // The vote shown for a cast until the server's vote arrives
13043
+ this.createPlaceholderVote = (optionId) => {
13044
+ const now = (/* @__PURE__ */ new Date()).toISOString();
13045
+ return {
13046
+ created_at: now,
13047
+ id: `local-${generateUUIDv4()}`,
13048
+ option_id: optionId,
13049
+ poll_id: this.poll.id,
13050
+ updated_at: now,
13051
+ user: this.client.user,
13052
+ user_id: this.client.userID
13053
+ };
13054
+ };
13055
+ /**
13056
+ * The server id of a vote to remove, once everything sent before the removal has finished. Casts
13057
+ * and removals of an option alternate, so the option's last succeeded change tells: a cast is the
13058
+ * vote being removed (e.g. a placeholder), a removal means the cast in between failed. Without
13059
+ * one, the server's own vote of the option is the vote being removed.
13060
+ */
13061
+ this.getServerVoteId = (vote) => {
13062
+ for (let i = this.succeededChanges.length - 1; i >= 0; i--) {
13063
+ const change = this.succeededChanges[i];
13064
+ if (change.optionId === vote.option_id) return change.vote?.id;
13065
+ }
13066
+ return this.serverVoteState.ownVotesByOptionId[vote.option_id]?.id;
13067
+ };
13068
+ this.takeServerVoteState = () => {
13069
+ clearTimeout(this.catchUpTimeout);
13070
+ this.succeededChanges = [];
13071
+ return {
13072
+ ...this.serverVoteState,
13073
+ maxVotedOptionIds: getMaxVotedOptionIds(this.serverVoteState.vote_counts_by_option)
13074
+ };
13075
+ };
13076
+ this.scheduleCatchUp = () => {
13077
+ clearTimeout(this.catchUpTimeout);
13078
+ if (this.pendingCount === 0) return;
13079
+ this.catchUpTimeout = setTimeout(this.applySucceededChanges, CATCH_UP_TIMEOUT_MS);
13080
+ };
13081
+ /**
13082
+ * A WS event of an own vote change got lost, so the server's own votes are stale. All requests
13083
+ * have finished (the timeout starts once nothing is left to send and a new request cancels it),
13084
+ * so applying the succeeded changes in order gives the server's own votes (changes whose events
13085
+ * did arrive end up the same). They are shown with the vote counts of the last WS event, which
13086
+ * contain the other users' votes too.
13087
+ */
13088
+ this.applySucceededChanges = () => {
13089
+ const { enforce_unique_vote } = this.poll.data;
13090
+ let ownVotesByOptionId = { ...this.serverVoteState.ownVotesByOptionId };
13091
+ for (const { optionId, vote } of this.succeededChanges) {
13092
+ if (!vote) {
13093
+ delete ownVotesByOptionId[optionId];
13094
+ } else if (enforce_unique_vote) {
13095
+ ownVotesByOptionId = { [optionId]: vote };
13096
+ } else {
13097
+ ownVotesByOptionId[optionId] = vote;
13098
+ }
13099
+ }
13100
+ this.serverVoteState = { ...this.serverVoteState, ownVotesByOptionId };
13101
+ this.pendingCount = 0;
13102
+ this.poll.state.partialNext(this.takeServerVoteState());
13103
+ };
13104
+ this.client = client;
13105
+ this.poll = poll;
13106
+ this.serverVoteState = pickVoteState(poll.data);
13107
+ }
13108
+ get serverOwnVotes() {
13109
+ return this.serverVoteState.ownVotesByOptionId;
13110
+ }
13111
+ };
13112
+ function pickVoteState(state) {
13113
+ return {
13114
+ latest_votes_by_option: state.latest_votes_by_option,
13115
+ ownVotesByOptionId: state.ownVotesByOptionId,
13116
+ vote_count: state.vote_count,
13117
+ vote_counts_by_option: state.vote_counts_by_option
13118
+ };
13119
+ }
13120
+ function applyOwnVoteDelta(state, { add = [], remove = [] }) {
13121
+ const ownVotesByOptionId = { ...state.ownVotesByOptionId };
13122
+ const voteCountsByOption = { ...state.vote_counts_by_option };
13123
+ const latestVotesByOption = state.latest_votes_by_option ? { ...state.latest_votes_by_option } : state.latest_votes_by_option;
13124
+ let voteCount = state.vote_count ?? 0;
13125
+ for (const vote of remove) {
13126
+ const optionId = vote.option_id;
13127
+ if (!optionId) continue;
13128
+ delete ownVotesByOptionId[optionId];
13129
+ const optionVoteCount = (voteCountsByOption[optionId] ?? 0) - 1;
13130
+ if (optionVoteCount > 0) {
13131
+ voteCountsByOption[optionId] = optionVoteCount;
13132
+ } else {
13133
+ delete voteCountsByOption[optionId];
13134
+ }
13135
+ voteCount = Math.max(voteCount - 1, 0);
13136
+ if (latestVotesByOption?.[optionId]) {
13137
+ latestVotesByOption[optionId] = latestVotesByOption[optionId].filter(
13138
+ (latestVote) => latestVote.id !== vote.id
13139
+ );
13140
+ }
13141
+ }
13142
+ for (const vote of add) {
13143
+ const optionId = vote.option_id;
13144
+ if (!optionId) continue;
13145
+ ownVotesByOptionId[optionId] = vote;
13146
+ voteCountsByOption[optionId] = (voteCountsByOption[optionId] ?? 0) + 1;
13147
+ voteCount += 1;
13148
+ if (latestVotesByOption && state.voting_visibility !== "anonymous") {
13149
+ latestVotesByOption[optionId] = [vote, ...latestVotesByOption[optionId] ?? []];
13150
+ }
13151
+ }
13152
+ return {
13153
+ latest_votes_by_option: latestVotesByOption,
13154
+ maxVotedOptionIds: getMaxVotedOptionIds(voteCountsByOption),
13155
+ ownVotesByOptionId,
13156
+ vote_count: voteCount,
13157
+ vote_counts_by_option: voteCountsByOption
13158
+ };
13159
+ }
13160
+ function getMaxVotedOptionIds(voteCountsByOption) {
13161
+ let maxVotes = 0;
13162
+ let winningOptions = [];
13163
+ for (const [id, count] of Object.entries(voteCountsByOption ?? {})) {
13164
+ if (count > maxVotes) {
13165
+ winningOptions = [id];
13166
+ maxVotes = count;
13167
+ } else if (count === maxVotes) {
13168
+ winningOptions.push(id);
13169
+ }
13170
+ }
13171
+ return winningOptions;
13172
+ }
13173
+
12937
13174
  // src/poll.ts
12938
13175
  var isPollUpdatedEvent = (e) => e.type === "poll.updated";
12939
13176
  var isPollClosedEventEvent = (e) => e.type === "poll.closed";
@@ -12943,6 +13180,9 @@ var isPollVoteRemovedEvent = (e) => e.type === "poll.vote_removed";
12943
13180
  var isVoteAnswer = (vote) => !!vote?.answer_text;
12944
13181
  var Poll = class {
12945
13182
  constructor({ client, poll }) {
13183
+ // Set whenever server data (WS event or rehydration) confirms that the poll is closed,
13184
+ // so that a failed optimistic close does not reopen a poll that is actually closed.
13185
+ this.closeConfirmed = false;
12946
13186
  this.getInitialStateFromPollResponse = (poll) => {
12947
13187
  const { own_votes, id, ...pollResponseForState } = poll;
12948
13188
  const { ownAnswer, ownVotes } = own_votes?.reduce(
@@ -12966,25 +13206,47 @@ var Poll = class {
12966
13206
  ownVotesByOptionId: getOwnVotesByOptionId(ownVotes)
12967
13207
  };
12968
13208
  };
13209
+ // persists only the server's votes, never the optimistic ones
12969
13210
  this.upsertOfflineDb = () => {
13211
+ const serverState = this.optimisticVotes.withServerVotes(this.data);
12970
13212
  this.client.offlineDb?.executeQuerySafely(
12971
- (db) => db.upsertPoll({ poll: mapPollStateToResponse(this) }),
13213
+ (db) => db.upsertPoll({ poll: mapPollStateToResponse(this, serverState) }),
12972
13214
  { method: "upsertPoll" }
12973
13215
  );
12974
13216
  };
12975
13217
  this.reinitializeState = (poll) => {
12976
- this.state.partialNext(this.getInitialStateFromPollResponse(poll));
13218
+ if (poll.is_closed) this.closeConfirmed = true;
13219
+ const initialState = this.getInitialStateFromPollResponse(poll);
13220
+ this.optimisticVotes.reset(initialState);
13221
+ this.state.partialNext(initialState);
13222
+ };
13223
+ // Hands the vote state of a WS vote event to the optimistic votes, which decide whether to show it.
13224
+ this.updateFromVoteEvent = (event, ownVotesByOptionId, answerState) => {
13225
+ const isOwnVote = event.poll_vote.user_id === this.client.userID;
13226
+ const shownVoteState = this.optimisticVotes.handleVoteEvent(event.poll, {
13227
+ confirmsOwnVote: isOwnVote && !isVoteAnswer(event.poll_vote) && !!event.poll_vote.option_id,
13228
+ ownVotesByOptionId
13229
+ });
13230
+ this.state.partialNext({
13231
+ ...answerState,
13232
+ answers_count: event.poll.answers_count,
13233
+ lastActivityAt: new Date(event.created_at),
13234
+ ...shownVoteState
13235
+ });
13236
+ this.upsertOfflineDb();
12977
13237
  };
12978
13238
  this.handlePollUpdated = (event) => {
12979
13239
  if (event.poll?.id && event.poll.id !== this.id) return;
12980
13240
  if (!isPollUpdatedEvent(event)) return;
12981
13241
  const { id, ...pollData } = extractPollData(event.poll);
13242
+ if (pollData.is_closed) this.closeConfirmed = true;
12982
13243
  this.state.partialNext({ ...pollData, lastActivityAt: new Date(event.created_at) });
12983
13244
  this.upsertOfflineDb();
12984
13245
  };
12985
13246
  this.handlePollClosed = (event) => {
12986
13247
  if (event.poll?.id && event.poll.id !== this.id) return;
12987
13248
  if (!isPollClosedEventEvent(event)) return;
13249
+ this.closeConfirmed = true;
12988
13250
  this.state.partialNext({
12989
13251
  is_closed: true,
12990
13252
  lastActivityAt: new Date(event.created_at)
@@ -12998,8 +13260,7 @@ var Poll = class {
12998
13260
  const isOwnVote = event.poll_vote.user_id === this.client.userID;
12999
13261
  let latestAnswers = [...currentState.latest_answers];
13000
13262
  let ownAnswer = currentState.ownAnswer;
13001
- const ownVotesByOptionId = currentState.ownVotesByOptionId;
13002
- let maxVotedOptionIds = currentState.maxVotedOptionIds;
13263
+ const ownVotesByOptionId = { ...this.optimisticVotes.serverOwnVotes };
13003
13264
  if (isOwnVote) {
13004
13265
  if (isVoteAnswer(event.poll_vote)) {
13005
13266
  ownAnswer = event.poll_vote;
@@ -13009,19 +13270,11 @@ var Poll = class {
13009
13270
  }
13010
13271
  if (isVoteAnswer(event.poll_vote)) {
13011
13272
  latestAnswers = [event.poll_vote, ...latestAnswers];
13012
- } else {
13013
- maxVotedOptionIds = getMaxVotedOptionIds(event.poll.vote_counts_by_option);
13014
13273
  }
13015
- const pollEnrichData = extractPollEnrichedData(event.poll);
13016
- this.state.partialNext({
13017
- ...pollEnrichData,
13274
+ this.updateFromVoteEvent(event, ownVotesByOptionId, {
13018
13275
  latest_answers: latestAnswers,
13019
- lastActivityAt: new Date(event.created_at),
13020
- ownAnswer,
13021
- ownVotesByOptionId,
13022
- maxVotedOptionIds
13276
+ ownAnswer
13023
13277
  });
13024
- this.upsertOfflineDb();
13025
13278
  };
13026
13279
  this.handleVoteChanged = (event) => {
13027
13280
  if (event.poll?.id && event.poll.id !== this.id) return;
@@ -13030,8 +13283,7 @@ var Poll = class {
13030
13283
  const isOwnVote = event.poll_vote.user_id === this.client.userID;
13031
13284
  let latestAnswers = [...currentState.latest_answers];
13032
13285
  let ownAnswer = currentState.ownAnswer;
13033
- let ownVotesByOptionId = currentState.ownVotesByOptionId;
13034
- let maxVotedOptionIds = currentState.maxVotedOptionIds;
13286
+ let ownVotesByOptionId = this.optimisticVotes.serverOwnVotes;
13035
13287
  if (isOwnVote) {
13036
13288
  if (isVoteAnswer(event.poll_vote)) {
13037
13289
  latestAnswers = [
@@ -13055,23 +13307,14 @@ var Poll = class {
13055
13307
  if (ownAnswer?.id === event.poll_vote.id) {
13056
13308
  ownAnswer = void 0;
13057
13309
  }
13058
- maxVotedOptionIds = getMaxVotedOptionIds(event.poll.vote_counts_by_option);
13059
13310
  }
13060
13311
  } else if (isVoteAnswer(event.poll_vote)) {
13061
13312
  latestAnswers = [event.poll_vote, ...latestAnswers];
13062
- } else {
13063
- maxVotedOptionIds = getMaxVotedOptionIds(event.poll.vote_counts_by_option);
13064
13313
  }
13065
- const pollEnrichData = extractPollEnrichedData(event.poll);
13066
- this.state.partialNext({
13067
- ...pollEnrichData,
13314
+ this.updateFromVoteEvent(event, ownVotesByOptionId, {
13068
13315
  latest_answers: latestAnswers,
13069
- lastActivityAt: new Date(event.created_at),
13070
- ownAnswer,
13071
- ownVotesByOptionId,
13072
- maxVotedOptionIds
13316
+ ownAnswer
13073
13317
  });
13074
- this.upsertOfflineDb();
13075
13318
  };
13076
13319
  this.handleVoteRemoved = (event) => {
13077
13320
  if (event.poll?.id && event.poll.id !== this.id) return;
@@ -13080,29 +13323,19 @@ var Poll = class {
13080
13323
  const isOwnVote = event.poll_vote.user_id === this.client.userID;
13081
13324
  let latestAnswers = [...currentState.latest_answers];
13082
13325
  let ownAnswer = currentState.ownAnswer;
13083
- const ownVotesByOptionId = { ...currentState.ownVotesByOptionId };
13084
- let maxVotedOptionIds = currentState.maxVotedOptionIds;
13326
+ const ownVotesByOptionId = { ...this.optimisticVotes.serverOwnVotes };
13085
13327
  if (isVoteAnswer(event.poll_vote)) {
13086
13328
  latestAnswers = latestAnswers.filter((answer) => answer.id !== event.poll_vote.id);
13087
13329
  if (isOwnVote) {
13088
13330
  ownAnswer = void 0;
13089
13331
  }
13090
- } else {
13091
- maxVotedOptionIds = getMaxVotedOptionIds(event.poll.vote_counts_by_option);
13092
- if (isOwnVote && event.poll_vote.option_id) {
13093
- delete ownVotesByOptionId[event.poll_vote.option_id];
13094
- }
13332
+ } else if (isOwnVote && event.poll_vote.option_id) {
13333
+ delete ownVotesByOptionId[event.poll_vote.option_id];
13095
13334
  }
13096
- const pollEnrichData = extractPollEnrichedData(event.poll);
13097
- this.state.partialNext({
13098
- ...pollEnrichData,
13335
+ this.updateFromVoteEvent(event, ownVotesByOptionId, {
13099
13336
  latest_answers: latestAnswers,
13100
- lastActivityAt: new Date(event.created_at),
13101
- ownAnswer,
13102
- ownVotesByOptionId,
13103
- maxVotedOptionIds
13337
+ ownAnswer
13104
13338
  });
13105
- this.upsertOfflineDb();
13106
13339
  };
13107
13340
  this.query = async (id) => {
13108
13341
  const { poll } = await this.client.getPoll(id);
@@ -13111,11 +13344,33 @@ var Poll = class {
13111
13344
  };
13112
13345
  this.update = async (data) => await this.client.updatePoll({ ...data, id: this.id });
13113
13346
  this.partialUpdate = async (partialPollObject) => await this.client.partialUpdatePoll(this.id, partialPollObject);
13114
- this.close = async () => await this.client.closePoll(this.id);
13347
+ /**
13348
+ * Closes the poll. The poll is marked as closed optimistically and the change is
13349
+ * reverted if the request fails, unless the server confirmed the close meanwhile.
13350
+ * Does nothing if the poll is already closed.
13351
+ */
13352
+ this.close = async () => {
13353
+ if (this.data.is_closed) return;
13354
+ this.closeConfirmed = false;
13355
+ this.state.partialNext({ is_closed: true });
13356
+ try {
13357
+ return await this.client.closePoll(this.id);
13358
+ } catch (error) {
13359
+ if (!this.closeConfirmed) {
13360
+ this.state.partialNext({ is_closed: false });
13361
+ }
13362
+ throw error;
13363
+ }
13364
+ };
13115
13365
  this.delete = async () => await this.client.deletePoll(this.id);
13116
13366
  this.createOption = async (option) => await this.client.createPollOption(this.id, option);
13117
13367
  this.updateOption = async (option) => await this.client.updatePollOption(this.id, option);
13118
13368
  this.deleteOption = async (optionId) => await this.client.deletePollOption(this.id, optionId);
13369
+ /**
13370
+ * Casts a vote for an option. The vote is shown right away through a local placeholder vote,
13371
+ * which the server's vote replaces once the WS events of all pending vote changes arrived.
13372
+ * If the request fails, the change is reverted.
13373
+ */
13119
13374
  this.castVote = async (optionId, messageId) => {
13120
13375
  const { max_votes_allowed, ownVotesByOptionId } = this.data;
13121
13376
  const reachedVoteLimit = max_votes_allowed && max_votes_allowed === Object.keys(ownVotesByOptionId).length;
@@ -13132,11 +13387,14 @@ var Poll = class {
13132
13387
  });
13133
13388
  return;
13134
13389
  }
13135
- return await this.client.castPollVote(messageId, this.id, {
13136
- option_id: optionId
13137
- });
13390
+ return await this.optimisticVotes.castVote(optionId, messageId);
13138
13391
  };
13139
- this.removeVote = async (voteId, messageId) => await this.client.removePollVote(messageId, this.id, voteId);
13392
+ /**
13393
+ * Removes a vote. The vote is removed right away and restored if the request fails. Vote
13394
+ * requests are sent in order, so removing a vote whose cast is not confirmed yet uses the vote
13395
+ * id from the cast's response; if that cast failed, there is nothing to remove.
13396
+ */
13397
+ this.removeVote = async (voteId, messageId) => await this.optimisticVotes.removeVote(voteId, messageId);
13140
13398
  this.addAnswer = async (answerText, messageId) => await this.client.addPollAnswer(messageId, this.id, answerText);
13141
13399
  this.removeAnswer = async (answerId, messageId) => await this.client.removePollVote(messageId, this.id, answerId);
13142
13400
  this.queryAnswers = async (params) => await this.client.queryPollAnswers(
@@ -13154,24 +13412,12 @@ var Poll = class {
13154
13412
  this.client = client;
13155
13413
  this.id = poll.id;
13156
13414
  this.state = new StateStore(this.getInitialStateFromPollResponse(poll));
13415
+ this.optimisticVotes = new OptimisticPollVotes({ client: this.client, poll: this });
13157
13416
  }
13158
13417
  get data() {
13159
13418
  return this.state.getLatestValue();
13160
13419
  }
13161
13420
  };
13162
- function getMaxVotedOptionIds(voteCountsByOption) {
13163
- let maxVotes = 0;
13164
- let winningOptions = [];
13165
- for (const [id, count] of Object.entries(voteCountsByOption ?? {})) {
13166
- if (count > maxVotes) {
13167
- winningOptions = [id];
13168
- maxVotes = count;
13169
- } else if (count === maxVotes) {
13170
- winningOptions.push(id);
13171
- }
13172
- }
13173
- return winningOptions;
13174
- }
13175
13421
  function getOwnVotesByOptionId(ownVotes) {
13176
13422
  return !ownVotes ? {} : ownVotes.reduce((acc, vote) => {
13177
13423
  if (isVoteAnswer(vote) || !vote.option_id) return acc;
@@ -13193,7 +13439,7 @@ function extractPollData(pollResponse) {
13193
13439
  voting_visibility: pollResponse.voting_visibility
13194
13440
  };
13195
13441
  }
13196
- function mapPollStateToResponse(poll) {
13442
+ function mapPollStateToResponse(poll, state = poll.data) {
13197
13443
  const {
13198
13444
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
13199
13445
  lastActivityAt,
@@ -13202,7 +13448,7 @@ function mapPollStateToResponse(poll) {
13202
13448
  ownVotesByOptionId,
13203
13449
  ownAnswer,
13204
13450
  ...restState
13205
- } = poll.data;
13451
+ } = state;
13206
13452
  const ownVotes = [
13207
13453
  ...Object.values(ownVotesByOptionId),
13208
13454
  ...ownAnswer ? [ownAnswer] : []
@@ -14987,7 +15233,7 @@ var StreamChat = class _StreamChat {
14987
15233
  return this.userAgent;
14988
15234
  }
14989
15235
  if (!this.cachedUserAgent) {
14990
- const version = "9.53.0";
15236
+ const version = "9.54.0";
14991
15237
  const { name: sdkName, version: sdkVersion } = this.sdkIdentifier ?? {};
14992
15238
  const { name: appName, version: appVersion } = this.appIdentifier ?? {};
14993
15239
  const { os, model: deviceModel } = this.deviceIdentifier ?? {};