@stream-io/feeds-client 0.3.46 → 0.3.48

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/cjs/index.js +1 -1
  3. package/dist/cjs/index.js.map +1 -1
  4. package/dist/cjs/react-bindings.js +56 -23
  5. package/dist/cjs/react-bindings.js.map +1 -1
  6. package/dist/es/index.mjs +2 -2
  7. package/dist/es/index.mjs.map +1 -1
  8. package/dist/es/react-bindings.mjs +56 -23
  9. package/dist/es/react-bindings.mjs.map +1 -1
  10. package/dist/{feeds-client-CSg4hlZ4.mjs → feeds-client-D-EFo20w.mjs} +100 -23
  11. package/dist/feeds-client-D-EFo20w.mjs.map +1 -0
  12. package/dist/{feeds-client-CF1yEox0.js → feeds-client-DuJuJuEJ.js} +100 -23
  13. package/dist/feeds-client-DuJuJuEJ.js.map +1 -0
  14. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  15. package/dist/types/bindings/react/hooks/feed-state-hooks/index.d.ts +1 -0
  16. package/dist/types/bindings/react/hooks/feed-state-hooks/index.d.ts.map +1 -1
  17. package/dist/types/bindings/react/hooks/feed-state-hooks/useMembers.d.ts +17 -0
  18. package/dist/types/bindings/react/hooks/feed-state-hooks/useMembers.d.ts.map +1 -0
  19. package/dist/types/common/Poll.d.ts +9 -4
  20. package/dist/types/common/Poll.d.ts.map +1 -1
  21. package/dist/types/feed/event-handlers/activity-updater.d.ts +2 -2
  22. package/dist/types/feed/event-handlers/activity-updater.d.ts.map +1 -1
  23. package/dist/types/feeds-client/feeds-client.d.ts +11 -1
  24. package/dist/types/feeds-client/feeds-client.d.ts.map +1 -1
  25. package/dist/types/gen/feeds/FeedApi.d.ts +2 -1
  26. package/dist/types/gen/feeds/FeedApi.d.ts.map +1 -1
  27. package/dist/types/gen/feeds/FeedsApi.d.ts +5 -1
  28. package/dist/types/gen/feeds/FeedsApi.d.ts.map +1 -1
  29. package/dist/types/gen/models/index.d.ts +13 -0
  30. package/dist/types/gen/models/index.d.ts.map +1 -1
  31. package/package.json +1 -1
  32. package/src/bindings/react/hooks/feed-state-hooks/index.ts +1 -0
  33. package/src/bindings/react/hooks/feed-state-hooks/useMembers.ts +55 -0
  34. package/src/common/Poll.ts +47 -26
  35. package/src/feed/event-handlers/activity-updater.ts +2 -0
  36. package/src/feeds-client/feeds-client.ts +52 -11
  37. package/src/gen/feeds/FeedApi.ts +12 -0
  38. package/src/gen/feeds/FeedsApi.ts +36 -0
  39. package/src/gen/model-decoders/decoders.ts +7 -0
  40. package/src/gen/models/index.ts +22 -0
  41. package/dist/feeds-client-CF1yEox0.js.map +0 -1
  42. package/dist/feeds-client-CSg4hlZ4.mjs.map +0 -1
@@ -6,9 +6,6 @@ import type {
6
6
  PollUpdatedFeedEvent,
7
7
  WSEvent,
8
8
  PollClosedFeedEvent,
9
- PollVoteCastedFeedEvent,
10
- PollVoteChangedFeedEvent,
11
- PollVoteRemovedFeedEvent,
12
9
  PollResponseData,
13
10
  } from '../gen/models';
14
11
 
@@ -20,21 +17,14 @@ const isPollClosedEventEvent = (
20
17
  e: WSEvent,
21
18
  ): e is { type: 'feeds.poll.closed' } & PollClosedFeedEvent =>
22
19
  e.type === 'feeds.poll.closed';
23
- const isPollVoteCastedEvent = (
24
- e: WSEvent,
25
- ): e is { type: 'feeds.poll.vote_casted' } & PollVoteCastedFeedEvent =>
26
- e.type === 'feeds.poll.vote_casted';
27
- const isPollVoteChangedEvent = (
28
- e: WSEvent,
29
- ): e is { type: 'feeds.poll.vote_changed' } & PollVoteChangedFeedEvent =>
30
- e.type === 'feeds.poll.vote_changed';
31
- const isPollVoteRemovedEvent = (
32
- e: WSEvent,
33
- ): e is { type: 'feeds.poll.vote_removed' } & PollVoteRemovedFeedEvent =>
34
- e.type === 'feeds.poll.vote_removed';
35
20
 
36
- export const isVoteAnswer = (vote: PollVoteResponseData) =>
37
- !!vote?.answer_text;
21
+ export type PollVotePayload = {
22
+ poll: PollResponseData;
23
+ poll_vote: PollVoteResponseData;
24
+ created_at: Date;
25
+ };
26
+
27
+ export const isVoteAnswer = (vote: PollVoteResponseData) => !!vote?.answer_text;
38
28
 
39
29
  export type PollAnswersQueryParams = QueryPollVotesRequest & {
40
30
  poll_id: string;
@@ -126,28 +116,43 @@ export class StreamPoll {
126
116
  });
127
117
  };
128
118
 
129
- public handleVoteCasted = (event: PollVoteCastedFeedEvent) => {
119
+ public handleVoteCasted = (event: PollVotePayload) => {
130
120
  if (event.poll?.id && event.poll.id !== this.id) return;
131
- if (!isPollVoteCastedEvent(event as WSEvent)) return;
132
121
  const currentState = this.data;
133
122
  const isOwnVote =
134
123
  event.poll_vote.user_id ===
135
124
  this.client.state.getLatestValue().connected_user?.id;
125
+
126
+ if (isOwnVote) {
127
+ const alreadyApplied = isVoteAnswer(event.poll_vote)
128
+ ? currentState.own_answer?.id === event.poll_vote.id
129
+ : !!event.poll_vote.option_id &&
130
+ currentState.own_votes_by_option_id[event.poll_vote.option_id]?.id ===
131
+ event.poll_vote.id;
132
+ if (alreadyApplied) return;
133
+ }
134
+
136
135
  let latestAnswers = [...currentState.latest_answers];
137
136
  let ownAnswer = currentState.own_answer;
138
- const ownVotesByOptionId = currentState.own_votes_by_option_id;
137
+ let ownVotesByOptionId = currentState.own_votes_by_option_id;
139
138
  let maxVotedOptionIds = currentState.max_voted_option_ids;
140
139
 
141
140
  if (isOwnVote) {
142
141
  if (isVoteAnswer(event.poll_vote)) {
143
142
  ownAnswer = event.poll_vote;
144
143
  } else if (event.poll_vote.option_id) {
145
- ownVotesByOptionId[event.poll_vote.option_id] = event.poll_vote;
144
+ ownVotesByOptionId = {
145
+ ...ownVotesByOptionId,
146
+ [event.poll_vote.option_id]: event.poll_vote,
147
+ };
146
148
  }
147
149
  }
148
150
 
149
151
  if (isVoteAnswer(event.poll_vote)) {
150
- latestAnswers = [event.poll_vote, ...latestAnswers];
152
+ latestAnswers = [
153
+ event.poll_vote,
154
+ ...latestAnswers.filter((a) => a.id !== event.poll_vote.id),
155
+ ];
151
156
  } else {
152
157
  maxVotedOptionIds = getMaxVotedOptionIds(
153
158
  event.poll.vote_counts_by_option,
@@ -173,14 +178,23 @@ export class StreamPoll {
173
178
  });
174
179
  };
175
180
 
176
- public handleVoteChanged = (event: PollVoteChangedFeedEvent) => {
181
+ public handleVoteChanged = (event: PollVotePayload) => {
177
182
  // this event is triggered only when event.poll.enforce_unique_vote === true
178
183
  if (event.poll?.id && event.poll.id !== this.id) return;
179
- if (!isPollVoteChangedEvent(event as WSEvent)) return;
180
184
  const currentState = this.data;
181
185
  const isOwnVote =
182
186
  event.poll_vote.user_id ===
183
187
  this.client.state.getLatestValue().connected_user?.id;
188
+
189
+ if (isOwnVote) {
190
+ const alreadyApplied = isVoteAnswer(event.poll_vote)
191
+ ? currentState.own_answer?.id === event.poll_vote.id
192
+ : !!event.poll_vote.option_id &&
193
+ currentState.own_votes_by_option_id[event.poll_vote.option_id]?.id ===
194
+ event.poll_vote.id;
195
+ if (alreadyApplied) return;
196
+ }
197
+
184
198
  let latestAnswers = [...currentState.latest_answers];
185
199
  let ownAnswer = currentState.own_answer;
186
200
  let ownVotesByOptionId = currentState.own_votes_by_option_id;
@@ -246,13 +260,20 @@ export class StreamPoll {
246
260
  });
247
261
  };
248
262
 
249
- public handleVoteRemoved = (event: PollVoteRemovedFeedEvent) => {
263
+ public handleVoteRemoved = (event: PollVotePayload) => {
250
264
  if (event.poll?.id && event.poll.id !== this.id) return;
251
- if (!isPollVoteRemovedEvent(event as WSEvent)) return;
252
265
  const currentState = this.data;
253
266
  const isOwnVote =
254
267
  event.poll_vote.user_id ===
255
268
  this.client.state.getLatestValue().connected_user?.id;
269
+
270
+ if (isOwnVote) {
271
+ const alreadyApplied = isVoteAnswer(event.poll_vote)
272
+ ? !currentState.own_answer
273
+ : !!event.poll_vote.option_id &&
274
+ !(event.poll_vote.option_id in currentState.own_votes_by_option_id);
275
+ if (alreadyApplied) return;
276
+ }
256
277
  let latestAnswers = [...currentState.latest_answers];
257
278
  let ownAnswer = currentState.own_answer;
258
279
  const ownVotesByOptionId = { ...currentState.own_votes_by_option_id };
@@ -11,5 +11,7 @@ export const updateActivity = ({
11
11
  ...newActivtiy,
12
12
  own_reactions: currentActivity.own_reactions,
13
13
  own_bookmarks: currentActivity.own_bookmarks,
14
+ friend_reactions: currentActivity.friend_reactions,
15
+ friend_reaction_count: currentActivity.friend_reaction_count,
14
16
  };
15
17
  };
@@ -7,6 +7,7 @@ import type {
7
7
  AddCommentRequest,
8
8
  AddCommentResponse,
9
9
  AddReactionRequest,
10
+ CastPollVoteRequest,
10
11
  CreateGuestResponse,
11
12
  DeleteActivityReactionResponse,
12
13
  DeleteCommentReactionResponse,
@@ -22,6 +23,7 @@ import type {
22
23
  ImageUploadRequest,
23
24
  OwnBatchRequest,
24
25
  PollResponse,
26
+ PollVoteResponse,
25
27
  PollVotesResponse,
26
28
  QueryFeedsRequest,
27
29
  QueryPollVotesRequest,
@@ -352,17 +354,16 @@ export class FeedsClient extends FeedsApi {
352
354
 
353
355
  public hydratePollCache(activities: ActivityResponse[]) {
354
356
  for (const activity of activities) {
355
- if (!activity.poll) {
356
- continue;
357
- }
358
- const pollResponse = activity.poll;
359
- const pollFromCache = this.pollFromState(pollResponse.id);
360
- if (!pollFromCache) {
361
- const poll = new StreamPoll({ client: this, poll: pollResponse });
362
- this.polls_by_id.set(poll.id, poll);
363
- } else {
364
- pollFromCache.reinitializeState(pollResponse);
365
- }
357
+ const polls = [activity.poll, activity.parent?.poll].filter((p) => !!p);
358
+ polls.forEach((pollResponse) => {
359
+ const pollFromCache = this.pollFromState(pollResponse.id);
360
+ if (!pollFromCache) {
361
+ const poll = new StreamPoll({ client: this, poll: pollResponse });
362
+ this.polls_by_id.set(poll.id, poll);
363
+ } else {
364
+ pollFromCache.reinitializeState(pollResponse);
365
+ }
366
+ });
366
367
  }
367
368
  }
368
369
 
@@ -462,6 +463,46 @@ export class FeedsClient extends FeedsApi {
462
463
  });
463
464
  };
464
465
 
466
+ castPollVote = async (
467
+ request: CastPollVoteRequest & { activity_id: string; poll_id: string },
468
+ ): Promise<StreamResponse<PollVoteResponse>> => {
469
+ const poll = this.pollFromState(request.poll_id);
470
+ const response = await super.castPollVote(request);
471
+ if (response.poll && response.vote && poll) {
472
+ const payload = {
473
+ poll: response.poll,
474
+ poll_vote: response.vote,
475
+ created_at: new Date(),
476
+ };
477
+ if (
478
+ poll.data.enforce_unique_vote &&
479
+ Object.keys(poll.data.own_votes_by_option_id).length > 0
480
+ ) {
481
+ poll.handleVoteChanged(payload);
482
+ } else {
483
+ poll.handleVoteCasted(payload);
484
+ }
485
+ }
486
+ return response;
487
+ };
488
+
489
+ deletePollVote = async (request: {
490
+ activity_id: string;
491
+ poll_id: string;
492
+ vote_id: string;
493
+ user_id?: string;
494
+ }): Promise<StreamResponse<PollVoteResponse>> => {
495
+ const response = await super.deletePollVote(request);
496
+ if (response.poll && response.vote) {
497
+ this.pollFromState(request.poll_id)?.handleVoteRemoved({
498
+ poll: response.poll,
499
+ poll_vote: response.vote,
500
+ created_at: new Date(),
501
+ });
502
+ }
503
+ return response;
504
+ };
505
+
465
506
  uploadFile = (
466
507
  request?: Omit<FileUploadRequest, 'file'> & { file?: StreamFile | string },
467
508
  ) => {
@@ -10,6 +10,8 @@ import type {
10
10
  PinActivityResponse,
11
11
  QueryFeedMembersRequest,
12
12
  QueryFeedMembersResponse,
13
+ QueryPinnedActivitiesRequest,
14
+ QueryPinnedActivitiesResponse,
13
15
  RejectFeedMemberInviteRequest,
14
16
  RejectFeedMemberInviteResponse,
15
17
  Response,
@@ -127,6 +129,16 @@ export class FeedApi {
127
129
  });
128
130
  }
129
131
 
132
+ queryPinnedActivities(
133
+ request?: QueryPinnedActivitiesRequest,
134
+ ): Promise<StreamResponse<QueryPinnedActivitiesResponse>> {
135
+ return this.feedsApi.queryPinnedActivities({
136
+ feed_id: this.id,
137
+ feed_group_id: this.group,
138
+ ...request,
139
+ });
140
+ }
141
+
130
142
  stopWatching(request?: {
131
143
  connection_id?: string;
132
144
  }): Promise<StreamResponse<Response>> {
@@ -88,6 +88,8 @@ import type {
88
88
  QueryFeedsResponse,
89
89
  QueryFollowsRequest,
90
90
  QueryFollowsResponse,
91
+ QueryPinnedActivitiesRequest,
92
+ QueryPinnedActivitiesResponse,
91
93
  QueryPollVotesRequest,
92
94
  QueryPollsRequest,
93
95
  QueryPollsResponse,
@@ -1611,6 +1613,40 @@ export class FeedsApi {
1611
1613
  return { ...response.body, metadata: response.metadata };
1612
1614
  }
1613
1615
 
1616
+ async queryPinnedActivities(
1617
+ request: QueryPinnedActivitiesRequest & {
1618
+ feed_group_id: string;
1619
+ feed_id: string;
1620
+ },
1621
+ ): Promise<StreamResponse<QueryPinnedActivitiesResponse>> {
1622
+ const pathParams = {
1623
+ feed_group_id: request?.feed_group_id,
1624
+ feed_id: request?.feed_id,
1625
+ };
1626
+ const body = {
1627
+ limit: request?.limit,
1628
+ next: request?.next,
1629
+ prev: request?.prev,
1630
+ sort: request?.sort,
1631
+ filter: request?.filter,
1632
+ };
1633
+
1634
+ const response = await this.apiClient.sendRequest<
1635
+ StreamResponse<QueryPinnedActivitiesResponse>
1636
+ >(
1637
+ 'POST',
1638
+ '/api/v2/feeds/feed_groups/{feed_group_id}/feeds/{feed_id}/pinned_activities/query',
1639
+ pathParams,
1640
+ undefined,
1641
+ body,
1642
+ 'application/json',
1643
+ );
1644
+
1645
+ decoders.QueryPinnedActivitiesResponse?.(response.body);
1646
+
1647
+ return { ...response.body, metadata: response.metadata };
1648
+ }
1649
+
1614
1650
  async stopWatchingFeed(request: {
1615
1651
  feed_group_id: string;
1616
1652
  feed_id: string;
@@ -1713,6 +1713,13 @@ decoders.QueryModerationConfigsResponse = (input?: Record<string, any>) => {
1713
1713
  return decode(typeMappings, input);
1714
1714
  };
1715
1715
 
1716
+ decoders.QueryPinnedActivitiesResponse = (input?: Record<string, any>) => {
1717
+ const typeMappings: TypeMapping = {
1718
+ pinned_activities: { type: 'ActivityPinResponse', isSingle: false },
1719
+ };
1720
+ return decode(typeMappings, input);
1721
+ };
1722
+
1716
1723
  decoders.QueryPollsResponse = (input?: Record<string, any>) => {
1717
1724
  const typeMappings: TypeMapping = {
1718
1725
  polls: { type: 'PollResponseData', isSingle: false },
@@ -4952,6 +4952,28 @@ export interface QueryModerationConfigsResponse {
4952
4952
  prev?: string;
4953
4953
  }
4954
4954
 
4955
+ export interface QueryPinnedActivitiesRequest {
4956
+ limit?: number;
4957
+
4958
+ next?: string;
4959
+
4960
+ prev?: string;
4961
+
4962
+ sort?: SortParamRequest[];
4963
+
4964
+ filter?: Record<string, any>;
4965
+ }
4966
+
4967
+ export interface QueryPinnedActivitiesResponse {
4968
+ duration: string;
4969
+
4970
+ pinned_activities: ActivityPinResponse[];
4971
+
4972
+ next?: string;
4973
+
4974
+ prev?: string;
4975
+ }
4976
+
4955
4977
  export interface QueryPollVotesRequest {
4956
4978
  limit?: number;
4957
4979