@stream-io/feeds-client 0.1.4 → 0.1.6

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 (62) hide show
  1. package/@react-bindings/hooks/client-state-hooks/index.ts +2 -0
  2. package/@react-bindings/hooks/feed-state-hooks/index.ts +7 -0
  3. package/@react-bindings/hooks/internal/index.ts +1 -0
  4. package/@react-bindings/hooks/util/index.ts +1 -0
  5. package/@react-bindings/index.ts +6 -3
  6. package/CHANGELOG.md +31 -0
  7. package/dist/@react-bindings/contexts/StreamFeedContext.d.ts +12 -0
  8. package/dist/@react-bindings/hooks/client-state-hooks/index.d.ts +2 -0
  9. package/dist/@react-bindings/hooks/client-state-hooks/useClientConnectedUser.d.ts +4 -0
  10. package/dist/@react-bindings/hooks/client-state-hooks/useWsConnectionState.d.ts +6 -0
  11. package/dist/@react-bindings/hooks/feed-state-hooks/index.d.ts +7 -0
  12. package/dist/@react-bindings/hooks/feed-state-hooks/useComments.d.ts +19 -0
  13. package/dist/@react-bindings/hooks/feed-state-hooks/useFeedActivities.d.ts +11 -0
  14. package/dist/@react-bindings/hooks/feed-state-hooks/useFeedMetadata.d.ts +12 -0
  15. package/dist/@react-bindings/hooks/feed-state-hooks/useFollowers.d.ts +16 -0
  16. package/dist/@react-bindings/hooks/feed-state-hooks/useFollowing.d.ts +16 -0
  17. package/dist/@react-bindings/hooks/feed-state-hooks/useOwnCapabilities.d.ts +33 -0
  18. package/dist/@react-bindings/hooks/feed-state-hooks/useOwnFollows.d.ts +8 -0
  19. package/dist/@react-bindings/hooks/internal/index.d.ts +1 -0
  20. package/dist/@react-bindings/hooks/internal/useStableCallback.d.ts +25 -0
  21. package/dist/@react-bindings/hooks/util/index.d.ts +1 -0
  22. package/dist/@react-bindings/hooks/util/useReactionActions.d.ts +17 -0
  23. package/dist/@react-bindings/index.d.ts +5 -3
  24. package/dist/@react-bindings/wrappers/StreamFeed.d.ts +12 -0
  25. package/dist/index-react-bindings.browser.cjs +521 -210
  26. package/dist/index-react-bindings.browser.cjs.map +1 -1
  27. package/dist/index-react-bindings.browser.js +514 -212
  28. package/dist/index-react-bindings.browser.js.map +1 -1
  29. package/dist/index-react-bindings.node.cjs +521 -210
  30. package/dist/index-react-bindings.node.cjs.map +1 -1
  31. package/dist/index-react-bindings.node.js +514 -212
  32. package/dist/index-react-bindings.node.js.map +1 -1
  33. package/dist/index.browser.cjs +212 -106
  34. package/dist/index.browser.cjs.map +1 -1
  35. package/dist/index.browser.js +209 -107
  36. package/dist/index.browser.js.map +1 -1
  37. package/dist/index.node.cjs +212 -106
  38. package/dist/index.node.cjs.map +1 -1
  39. package/dist/index.node.js +209 -107
  40. package/dist/index.node.js.map +1 -1
  41. package/dist/src/Feed.d.ts +7 -3
  42. package/dist/src/FeedsClient.d.ts +6 -5
  43. package/dist/src/types.d.ts +7 -0
  44. package/dist/src/utils.d.ts +9 -1
  45. package/dist/tsconfig.tsbuildinfo +1 -1
  46. package/package.json +6 -2
  47. package/src/Feed.ts +214 -97
  48. package/src/FeedsClient.ts +22 -12
  49. package/src/common/ActivitySearchSource.ts +5 -5
  50. package/src/common/ApiClient.ts +1 -1
  51. package/src/common/FeedSearchSource.ts +1 -1
  52. package/src/common/Poll.ts +35 -10
  53. package/src/common/TokenManager.ts +2 -3
  54. package/src/common/UserSearchSource.ts +1 -1
  55. package/src/common/real-time/StableWSConnection.ts +4 -1
  56. package/src/state-updates/bookmark-utils.test.ts +134 -8
  57. package/src/state-updates/bookmark-utils.ts +17 -7
  58. package/src/types.ts +12 -1
  59. package/src/utils.ts +25 -1
  60. package/dist/@react-bindings/hooks/clientStateHooks.d.ts +0 -10
  61. package/dist/@react-bindings/hooks/useComments.d.ts +0 -12
  62. package/dist/@react-bindings/hooks/useOwnCapabilities.d.ts +0 -33
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/feeds-client",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "packageManager": "yarn@3.2.4",
5
5
  "main": "./dist/index.node.js",
6
6
  "exports": {
@@ -44,7 +44,9 @@
44
44
  "start": "rollup -w -c",
45
45
  "build": "yarn clean && rollup -c",
46
46
  "test": "vitest",
47
- "test-ci": "vitest --coverage"
47
+ "test:unit": "vitest --exclude '__integration-tests__/**'",
48
+ "test-ci": "vitest --exclude '__integration-tests__/docs-snippets/**' --coverage",
49
+ "test-docs-snippets": "vitest __integration-tests__/docs-snippets/**"
48
50
  },
49
51
  "files": [
50
52
  "dist",
@@ -61,7 +63,9 @@
61
63
  "devDependencies": {
62
64
  "@rollup/plugin-replace": "^6.0.1",
63
65
  "@rollup/plugin-typescript": "^12.1.0",
66
+ "@stream-io/node-sdk": "0.5.0",
64
67
  "@types/react": "^19.1.8",
68
+ "@vitest/coverage-v8": "3.2.4",
65
69
  "dotenv": "^16.4.5",
66
70
  "react": "19.0.0",
67
71
  "rimraf": "^6.0.1",
package/src/Feed.ts CHANGED
@@ -14,6 +14,8 @@ import {
14
14
  BookmarkAddedEvent,
15
15
  BookmarkDeletedEvent,
16
16
  BookmarkUpdatedEvent,
17
+ QueryFeedMembersRequest,
18
+ SortParamRequest,
17
19
  } from './gen/models';
18
20
  import { Patch, StateStore } from './common/StateStore';
19
21
  import { EventDispatcher } from './common/EventDispatcher';
@@ -43,6 +45,7 @@ import type {
43
45
  PagerResponseWithLoadingStates,
44
46
  } from './types';
45
47
  import type { FromArray } from './types-internal';
48
+ import { checkHasAnotherPage, Constants } from './utils';
46
49
 
47
50
  export type FeedState = Omit<
48
51
  Partial<GetOrCreateFeedResponse & FeedResponse>,
@@ -103,16 +106,15 @@ export type FeedState = Omit<
103
106
  | undefined
104
107
  >;
105
108
 
106
- followers_pagination?: LoadingStates & { sort?: string };
109
+ followers_pagination?: LoadingStates & { sort?: SortParamRequest[] };
107
110
 
108
- following_pagination?: LoadingStates & { sort?: string };
111
+ following_pagination?: LoadingStates & { sort?: SortParamRequest[] };
112
+
113
+ member_pagination?: LoadingStates & { sort?: SortParamRequest[] };
109
114
 
110
115
  last_get_or_create_request_config?: GetOrCreateFeedRequest;
111
116
  };
112
117
 
113
- const END_OF_LIST = 'eol' as const;
114
- const DEFAULT_COMMENT_PAGINATION = 'first' as const;
115
-
116
118
  type EventHandlerByEventType = {
117
119
  [Key in NonNullable<WSEvent['type']>]: Key extends Extract<
118
120
  WSEvent,
@@ -153,7 +155,7 @@ export class Feed extends FeedApi {
153
155
  },
154
156
  'feeds.activity.reaction.added': (event) => {
155
157
  const currentActivities = this.currentState.activities;
156
- const connectedUser = this.client.state.getLatestValue().connectedUser;
158
+ const connectedUser = this.client.state.getLatestValue().connected_user;
157
159
  const isCurrentUser = Boolean(
158
160
  connectedUser && event.reaction.user.id === connectedUser.id,
159
161
  );
@@ -169,7 +171,7 @@ export class Feed extends FeedApi {
169
171
  },
170
172
  'feeds.activity.reaction.deleted': (event) => {
171
173
  const currentActivities = this.currentState.activities;
172
- const connectedUser = this.client.state.getLatestValue().connectedUser;
174
+ const connectedUser = this.client.state.getLatestValue().connected_user;
173
175
  const isCurrentUser = Boolean(
174
176
  connectedUser && event.reaction.user.id === connectedUser.id,
175
177
  );
@@ -221,7 +223,10 @@ export class Feed extends FeedApi {
221
223
 
222
224
  if (
223
225
  entityState?.pagination?.sort === 'last' &&
224
- entityState?.pagination.next === END_OF_LIST
226
+ !checkHasAnotherPage(
227
+ entityState.comments,
228
+ entityState?.pagination.next,
229
+ )
225
230
  ) {
226
231
  newComments.unshift(comment);
227
232
  } else if (entityState?.pagination?.sort === 'first') {
@@ -320,7 +325,12 @@ export class Feed extends FeedApi {
320
325
  ...event.follow.source_feed,
321
326
  };
322
327
 
323
- if (currentState.following_pagination?.next === END_OF_LIST) {
328
+ if (
329
+ !checkHasAnotherPage(
330
+ currentState.following,
331
+ currentState.following_pagination?.next,
332
+ )
333
+ ) {
324
334
  // TODO: respect sort
325
335
  newState.following = currentState.following
326
336
  ? currentState.following.concat(event.follow)
@@ -334,7 +344,7 @@ export class Feed extends FeedApi {
334
344
  event.follow.target_feed.fid === this.fid
335
345
  ) {
336
346
  const source = event.follow.source_feed;
337
- const connectedUser = this.client.state.getLatestValue().connectedUser;
347
+ const connectedUser = this.client.state.getLatestValue().connected_user;
338
348
 
339
349
  this.state.next((currentState) => {
340
350
  const newState = { ...currentState, ...event.follow.target_feed };
@@ -345,7 +355,12 @@ export class Feed extends FeedApi {
345
355
  : [event.follow];
346
356
  }
347
357
 
348
- if (currentState.followers_pagination?.next === END_OF_LIST) {
358
+ if (
359
+ !checkHasAnotherPage(
360
+ currentState.followers,
361
+ currentState.followers_pagination?.next,
362
+ )
363
+ ) {
349
364
  // TODO: respect sort
350
365
  newState.followers = currentState.followers
351
366
  ? currentState.followers.concat(event.follow)
@@ -374,7 +389,7 @@ export class Feed extends FeedApi {
374
389
  event.follow.target_feed.fid === this.fid
375
390
  ) {
376
391
  const source = event.follow.source_feed;
377
- const connectedUser = this.client.state.getLatestValue().connectedUser;
392
+ const connectedUser = this.client.state.getLatestValue().connected_user;
378
393
 
379
394
  this.state.next((currentState) => {
380
395
  const newState = { ...currentState, ...event.follow.target_feed };
@@ -400,49 +415,90 @@ export class Feed extends FeedApi {
400
415
  this.handleCommentReactionEvent.bind(this),
401
416
  'feeds.comment.reaction.updated': Feed.noop,
402
417
  'feeds.feed_member.added': (event) => {
403
- const { member } = event;
404
-
405
- // do not add a member if the pagination has reached the end of the list
406
- if (this.currentState.member_pagination?.next !== END_OF_LIST) return;
418
+ const { connected_user: connectedUser } =
419
+ this.client.state.getLatestValue();
407
420
 
408
421
  this.state.next((currentState) => {
409
- return {
410
- ...currentState,
411
- // TODO: respect sort
412
- members: currentState.members
413
- ? currentState.members.concat(member)
414
- : [member],
415
- };
422
+ let newState: FeedState | undefined;
423
+
424
+ if (
425
+ !checkHasAnotherPage(
426
+ currentState.members,
427
+ currentState.member_pagination?.next,
428
+ )
429
+ ) {
430
+ newState ??= {
431
+ ...currentState,
432
+ };
433
+
434
+ newState.members = newState.members?.concat(event.member) ?? [
435
+ event.member,
436
+ ];
437
+ }
438
+
439
+ if (connectedUser?.id === event.member.user.id) {
440
+ newState ??= {
441
+ ...currentState,
442
+ };
443
+
444
+ newState.own_membership = event.member;
445
+ }
446
+
447
+ return newState ?? currentState;
416
448
  });
417
449
  },
418
450
  'feeds.feed_member.removed': (event) => {
451
+ const { connected_user: connectedUser } =
452
+ this.client.state.getLatestValue();
453
+
419
454
  this.state.next((currentState) => {
420
- return {
455
+ const newState = {
421
456
  ...currentState,
422
457
  members: currentState.members?.filter(
423
458
  (member) => member.user.id !== event.user?.id,
424
459
  ),
425
460
  };
461
+
462
+ if (connectedUser?.id === event.member_id) {
463
+ delete newState.own_membership;
464
+ }
465
+
466
+ return newState;
426
467
  });
427
468
  },
428
469
  'feeds.feed_member.updated': (event) => {
470
+ const { connected_user: connectedUser } =
471
+ this.client.state.getLatestValue();
472
+
429
473
  this.state.next((currentState) => {
430
474
  const memberIndex =
431
475
  currentState.members?.findIndex(
432
476
  (member) => member.user.id === event.member.user.id,
433
477
  ) ?? -1;
434
478
 
479
+ let newState: FeedState | undefined;
480
+
435
481
  if (memberIndex !== -1) {
482
+ // if there's an index, there's a member to update
436
483
  const newMembers = [...currentState.members!];
437
484
  newMembers[memberIndex] = event.member;
438
485
 
439
- return {
486
+ newState ??= {
487
+ ...currentState,
488
+ };
489
+
490
+ newState.members = newMembers;
491
+ }
492
+
493
+ if (connectedUser?.id === event.member.user.id) {
494
+ newState ??= {
440
495
  ...currentState,
441
- members: newMembers,
442
496
  };
497
+
498
+ newState.own_membership = event.member;
443
499
  }
444
500
 
445
- return currentState;
501
+ return newState ?? currentState;
446
502
  });
447
503
  },
448
504
  // the poll events should be removed from here
@@ -506,7 +562,7 @@ export class Feed extends FeedApi {
506
562
  },
507
563
  ) {
508
564
  const { comment, reaction } = event;
509
- const connectedUser = this.client.state.getLatestValue().connectedUser;
565
+ const connectedUser = this.client.state.getLatestValue().connected_user;
510
566
 
511
567
  this.state.next((currentState) => {
512
568
  const forId = comment.parent_id ?? comment.object_id;
@@ -614,38 +670,6 @@ export class Feed extends FeedApi {
614
670
  ...responseCopy,
615
671
  };
616
672
 
617
- // if there is no next cursor, set it to END_OF_LIST
618
- // request has to have a limit set for this to work
619
- if (
620
- (request?.followers_pagination?.limit ?? 0) > 0 &&
621
- typeof nextState.followers_pagination?.next === 'undefined'
622
- ) {
623
- nextState.followers_pagination = {
624
- ...nextState.followers_pagination,
625
- next: END_OF_LIST,
626
- };
627
- }
628
-
629
- if (
630
- (request?.following_pagination?.limit ?? 0) > 0 &&
631
- typeof nextState.following_pagination?.next === 'undefined'
632
- ) {
633
- nextState.following_pagination = {
634
- ...nextState.following_pagination,
635
- next: END_OF_LIST,
636
- };
637
- }
638
-
639
- if (
640
- (request?.member_pagination?.limit ?? 0) > 0 &&
641
- typeof nextState.member_pagination?.next === 'undefined'
642
- ) {
643
- nextState.member_pagination = {
644
- ...nextState.member_pagination,
645
- next: END_OF_LIST,
646
- };
647
- }
648
-
649
673
  if (!request?.followers_pagination?.limit) {
650
674
  delete nextState.followers;
651
675
  }
@@ -672,7 +696,8 @@ export class Feed extends FeedApi {
672
696
 
673
697
  private handleBookmarkAdded(event: BookmarkAddedEvent) {
674
698
  const currentActivities = this.currentState.activities;
675
- const { connectedUser } = this.client.state.getLatestValue();
699
+ const { connected_user: connectedUser } =
700
+ this.client.state.getLatestValue();
676
701
  const isCurrentUser = event.bookmark.user.id === connectedUser?.id;
677
702
 
678
703
  const result = addBookmarkToActivities(
@@ -687,7 +712,8 @@ export class Feed extends FeedApi {
687
712
 
688
713
  private handleBookmarkDeleted(event: BookmarkDeletedEvent) {
689
714
  const currentActivities = this.currentState.activities;
690
- const { connectedUser } = this.client.state.getLatestValue();
715
+ const { connected_user: connectedUser } =
716
+ this.client.state.getLatestValue();
691
717
  const isCurrentUser = event.bookmark.user.id === connectedUser?.id;
692
718
 
693
719
  const result = removeBookmarkFromActivities(
@@ -702,7 +728,8 @@ export class Feed extends FeedApi {
702
728
 
703
729
  private handleBookmarkUpdated(event: BookmarkUpdatedEvent) {
704
730
  const currentActivities = this.currentState.activities;
705
- const { connectedUser } = this.client.state.getLatestValue();
731
+ const { connected_user: connectedUser } =
732
+ this.client.state.getLatestValue();
706
733
  const isCurrentUser = event.bookmark.user.id === connectedUser?.id;
707
734
 
708
735
  const result = updateBookmarkInActivities(
@@ -796,6 +823,8 @@ export class Feed extends FeedApi {
796
823
  sort: string;
797
824
  base: () => Promise<PagerResponse & { comments: CommentResponse[] }>;
798
825
  }) {
826
+ let error: unknown;
827
+
799
828
  try {
800
829
  this.state.next((currentState) => ({
801
830
  ...currentState,
@@ -811,7 +840,7 @@ export class Feed extends FeedApi {
811
840
  },
812
841
  }));
813
842
 
814
- const { next: newNextCursor = END_OF_LIST, comments } = await base();
843
+ const { next: newNextCursor, comments } = await base();
815
844
 
816
845
  this.state.next((currentState) => {
817
846
  const newPagination = {
@@ -840,9 +869,8 @@ export class Feed extends FeedApi {
840
869
  },
841
870
  };
842
871
  });
843
- } catch (error) {
844
- console.error(error);
845
- // TODO: figure out how to handle errorss
872
+ } catch (e) {
873
+ error = e;
846
874
  } finally {
847
875
  this.state.next((currentState) => ({
848
876
  ...currentState,
@@ -858,6 +886,10 @@ export class Feed extends FeedApi {
858
886
  },
859
887
  }));
860
888
  }
889
+
890
+ if (error) {
891
+ throw error;
892
+ }
861
893
  }
862
894
 
863
895
  public async loadNextPageActivityComments(
@@ -866,15 +898,22 @@ export class Feed extends FeedApi {
866
898
  Omit<GetCommentsRequest, 'object_id' | 'object_type' | 'next'>
867
899
  >,
868
900
  ) {
869
- const pagination =
870
- this.currentState.comments_by_entity_id[activity.id]?.pagination;
871
- const currentNextCursor = pagination?.next;
872
- const currentSort = pagination?.sort;
873
- const isLoading = pagination?.loading_next_page;
874
-
875
- const sort = currentSort ?? request?.sort ?? DEFAULT_COMMENT_PAGINATION;
876
-
877
- if (isLoading || currentNextCursor === END_OF_LIST) return;
901
+ const currentEntityState =
902
+ this.currentState.comments_by_entity_id[activity.id];
903
+ const currentPagination = currentEntityState?.pagination;
904
+ const currentNextCursor = currentPagination?.next;
905
+ const currentSort = currentPagination?.sort;
906
+ const isLoading = currentPagination?.loading_next_page;
907
+
908
+ const sort =
909
+ currentSort ?? request?.sort ?? Constants.DEFAULT_COMMENT_PAGINATION;
910
+
911
+ if (
912
+ isLoading ||
913
+ !checkHasAnotherPage(currentEntityState?.comments, currentNextCursor)
914
+ ) {
915
+ return;
916
+ }
878
917
 
879
918
  await this.loadNextPageComments({
880
919
  forId: activity.id,
@@ -894,15 +933,22 @@ export class Feed extends FeedApi {
894
933
  comment: CommentResponse,
895
934
  request?: Partial<Omit<GetCommentsRepliesRequest, 'comment_id' | 'next'>>,
896
935
  ) {
897
- const pagination =
898
- this.currentState.comments_by_entity_id[comment.id]?.pagination;
899
- const currentNextCursor = pagination?.next;
900
- const currentSort = pagination?.sort;
901
- const isLoading = pagination?.loading_next_page;
902
-
903
- const sort = currentSort ?? request?.sort ?? DEFAULT_COMMENT_PAGINATION;
904
-
905
- if (isLoading || currentNextCursor === END_OF_LIST) return;
936
+ const currentEntityState =
937
+ this.currentState.comments_by_entity_id[comment.id];
938
+ const currentPagination = currentEntityState?.pagination;
939
+ const currentNextCursor = currentPagination?.next;
940
+ const currentSort = currentPagination?.sort;
941
+ const isLoading = currentPagination?.loading_next_page;
942
+
943
+ const sort =
944
+ currentSort ?? request?.sort ?? Constants.DEFAULT_COMMENT_PAGINATION;
945
+
946
+ if (
947
+ isLoading ||
948
+ !checkHasAnotherPage(currentEntityState?.comments, currentNextCursor)
949
+ ) {
950
+ return;
951
+ }
906
952
 
907
953
  await this.loadNextPageComments({
908
954
  forId: comment.id,
@@ -911,7 +957,10 @@ export class Feed extends FeedApi {
911
957
  ...request,
912
958
  comment_id: comment.id,
913
959
  // use known sort first (prevents broken pagination)
914
- sort: currentSort ?? request?.sort ?? DEFAULT_COMMENT_PAGINATION,
960
+ sort:
961
+ currentSort ??
962
+ request?.sort ??
963
+ Constants.DEFAULT_COMMENT_PAGINATION,
915
964
  next: currentNextCursor,
916
965
  }),
917
966
  parentId: comment.parent_id ?? comment.object_id,
@@ -921,15 +970,20 @@ export class Feed extends FeedApi {
921
970
 
922
971
  private async loadNextPageFollows(
923
972
  type: 'followers' | 'following',
924
- request: Pick<QueryFollowsRequest, 'limit'>,
973
+ request: Pick<QueryFollowsRequest, 'limit' | 'sort'>,
925
974
  ) {
926
975
  const paginationKey = `${type}_pagination` as const;
927
976
  const method = `query${capitalize(type)}` as const;
928
977
 
978
+ const currentFollows = this.currentState[type];
929
979
  const currentNextCursor = this.currentState[paginationKey]?.next;
930
980
  const isLoading = this.currentState[paginationKey]?.loading_next_page;
981
+ const sort = this.currentState[paginationKey]?.sort ?? request.sort;
982
+ let error: unknown;
931
983
 
932
- if (isLoading || currentNextCursor === END_OF_LIST) return;
984
+ if (isLoading || !checkHasAnotherPage(currentFollows, currentNextCursor)) {
985
+ return;
986
+ }
933
987
 
934
988
  try {
935
989
  this.state.next((currentState) => {
@@ -942,12 +996,11 @@ export class Feed extends FeedApi {
942
996
  };
943
997
  });
944
998
 
945
- const { next: newNextCursor = END_OF_LIST, follows } = await this[method](
946
- {
947
- ...request,
948
- next: currentNextCursor,
949
- },
950
- );
999
+ const { next: newNextCursor, follows } = await this[method]({
1000
+ ...request,
1001
+ next: currentNextCursor,
1002
+ sort,
1003
+ });
951
1004
 
952
1005
  this.state.next((currentState) => ({
953
1006
  ...currentState,
@@ -957,11 +1010,11 @@ export class Feed extends FeedApi {
957
1010
  [paginationKey]: {
958
1011
  ...currentState[paginationKey],
959
1012
  next: newNextCursor,
1013
+ sort,
960
1014
  },
961
1015
  }));
962
- } catch (error) {
963
- console.error(error);
964
- // TODO: figure out how to handle errorss
1016
+ } catch (e) {
1017
+ error = e;
965
1018
  } finally {
966
1019
  this.state.next((currentState) => {
967
1020
  return {
@@ -973,6 +1026,10 @@ export class Feed extends FeedApi {
973
1026
  };
974
1027
  });
975
1028
  }
1029
+
1030
+ if (error) {
1031
+ throw error;
1032
+ }
976
1033
  }
977
1034
 
978
1035
  async loadNextPageFollowers(request: Pick<QueryFollowsRequest, 'limit'>) {
@@ -983,6 +1040,66 @@ export class Feed extends FeedApi {
983
1040
  await this.loadNextPageFollows('following', request);
984
1041
  }
985
1042
 
1043
+ async loadNextPageMembers(
1044
+ request: Omit<QueryFeedMembersRequest, 'next' | 'prev'>,
1045
+ ) {
1046
+ const currentMembers = this.currentState.members;
1047
+ const currentNextCursor = this.currentState.member_pagination?.next;
1048
+ const isLoading = this.currentState.member_pagination?.loading_next_page;
1049
+ const sort = this.currentState.member_pagination?.sort ?? request.sort;
1050
+ let error: unknown;
1051
+
1052
+ if (isLoading || !checkHasAnotherPage(currentMembers, currentNextCursor)) {
1053
+ return;
1054
+ }
1055
+
1056
+ try {
1057
+ this.state.next((currentState) => ({
1058
+ ...currentState,
1059
+ member_pagination: {
1060
+ ...currentState.member_pagination,
1061
+ loading_next_page: true,
1062
+ },
1063
+ }));
1064
+
1065
+ const { next: newNextCursor, members } =
1066
+ await this.client.queryFeedMembers({
1067
+ ...request,
1068
+ sort,
1069
+ feed_id: this.id,
1070
+ feed_group_id: this.group,
1071
+ next: currentNextCursor,
1072
+ });
1073
+
1074
+ this.state.next((currentState) => ({
1075
+ ...currentState,
1076
+ members: currentState.members
1077
+ ? currentState.members.concat(members)
1078
+ : members,
1079
+ member_pagination: {
1080
+ ...currentState.member_pagination,
1081
+ next: newNextCursor,
1082
+ // set sort if not defined yet
1083
+ sort: currentState.member_pagination?.sort ?? request.sort,
1084
+ },
1085
+ }));
1086
+ } catch (e) {
1087
+ error = e;
1088
+ } finally {
1089
+ this.state.next((currentState) => ({
1090
+ ...currentState,
1091
+ member_pagination: {
1092
+ ...currentState.member_pagination,
1093
+ loading_next_page: false,
1094
+ },
1095
+ }));
1096
+ }
1097
+
1098
+ if (error) {
1099
+ throw error;
1100
+ }
1101
+ }
1102
+
986
1103
  /**
987
1104
  * Method which queries followers of this feed (feeds which target this feed).
988
1105
  *
@@ -12,7 +12,7 @@ import {
12
12
  UserRequest,
13
13
  WSEvent,
14
14
  } from './gen/models';
15
- import { FeedsEvent, TokenOrProvider } from './types';
15
+ import { FeedsEvent, StreamFile, TokenOrProvider } from './types';
16
16
  import { StateStore } from './common/StateStore';
17
17
  import { TokenManager } from './common/TokenManager';
18
18
  import { ConnectionIdManager } from './common/ConnectionIdManager';
@@ -22,6 +22,7 @@ import { ApiClient } from './common/ApiClient';
22
22
  import {
23
23
  addConnectionEventListeners,
24
24
  removeConnectionEventListeners,
25
+ streamDevToken,
25
26
  } from './common/utils';
26
27
  import { decodeWSEvent } from './gen/model-decoders/event-decoder-mapping';
27
28
  import { Feed } from './Feed';
@@ -34,8 +35,8 @@ import { ModerationClient } from './ModerationClient';
34
35
  import { StreamPoll } from './common/Poll';
35
36
 
36
37
  export type FeedsClientState = {
37
- connectedUser: OwnUser | undefined;
38
- isWsConnectionHealthy: boolean;
38
+ connected_user: OwnUser | undefined;
39
+ is_ws_connection_healthy: boolean;
39
40
  };
40
41
 
41
42
  type FID = string;
@@ -69,8 +70,8 @@ export class FeedsClient extends FeedsApi {
69
70
  );
70
71
  super(apiClient);
71
72
  this.state = new StateStore<FeedsClientState>({
72
- connectedUser: undefined,
73
- isWsConnectionHealthy: false,
73
+ connected_user: undefined,
74
+ is_ws_connection_healthy: false,
74
75
  });
75
76
  this.moderation = new ModerationClient(apiClient);
76
77
  this.tokenManager = tokenManager;
@@ -85,7 +86,7 @@ export class FeedsClient extends FeedsApi {
85
86
  switch (event.type) {
86
87
  case 'connection.changed': {
87
88
  const { online } = event;
88
- this.state.partialNext({ isWsConnectionHealthy: online });
89
+ this.state.partialNext({ is_ws_connection_healthy: online });
89
90
 
90
91
  if (online) {
91
92
  this.healthyConnectionChangedEventCount++;
@@ -210,7 +211,7 @@ export class FeedsClient extends FeedsApi {
210
211
 
211
212
  connectUser = async (user: UserRequest, tokenProvider: TokenOrProvider) => {
212
213
  if (
213
- this.state.getLatestValue().connectedUser !== undefined ||
214
+ this.state.getLatestValue().connected_user !== undefined ||
214
215
  this.wsConnection
215
216
  ) {
216
217
  throw new Error(`Can't connect a new user, call "disconnectUser" first`);
@@ -234,8 +235,8 @@ export class FeedsClient extends FeedsApi {
234
235
  );
235
236
  const connectedEvent = await this.wsConnection.connect();
236
237
  this.state.partialNext({
237
- connectedUser: connectedEvent?.me,
238
- isWsConnectionHealthy: this.wsConnection.isHealthy,
238
+ connected_user: connectedEvent?.me,
239
+ is_ws_connection_healthy: !!this.wsConnection?.isHealthy,
239
240
  });
240
241
  } catch (err) {
241
242
  await this.disconnectUser();
@@ -243,6 +244,10 @@ export class FeedsClient extends FeedsApi {
243
244
  }
244
245
  };
245
246
 
247
+ devToken = (userId: string) => {
248
+ return streamDevToken(userId);
249
+ };
250
+
246
251
  closePoll = async (request: {
247
252
  poll_id: string;
248
253
  }): Promise<StreamResponse<PollResponse>> => {
@@ -255,7 +260,9 @@ export class FeedsClient extends FeedsApi {
255
260
  };
256
261
 
257
262
  // @ts-expect-error API spec says file should be a string
258
- uploadFile = (request: Omit<FileUploadRequest, 'file'> & { file: File }) => {
263
+ uploadFile = (
264
+ request: Omit<FileUploadRequest, 'file'> & { file: StreamFile },
265
+ ) => {
259
266
  return super.uploadFile({
260
267
  // @ts-expect-error API spec says file should be a string
261
268
  file: request.file,
@@ -264,7 +271,7 @@ export class FeedsClient extends FeedsApi {
264
271
 
265
272
  // @ts-expect-error API spec says file should be a string
266
273
  uploadImage = (
267
- request: Omit<ImageUploadRequest, 'file'> & { file: File },
274
+ request: Omit<ImageUploadRequest, 'file'> & { file: StreamFile },
268
275
  ) => {
269
276
  return super.uploadImage({
270
277
  // @ts-expect-error API spec says file should be a string
@@ -311,7 +318,10 @@ export class FeedsClient extends FeedsApi {
311
318
 
312
319
  this.connectionIdManager.reset();
313
320
  this.tokenManager.reset();
314
- this.state.partialNext({ connectedUser: undefined, isWsConnectionHealthy: false });
321
+ this.state.partialNext({
322
+ connected_user: undefined,
323
+ is_ws_connection_healthy: false,
324
+ });
315
325
  };
316
326
 
317
327
  on = this.eventDispatcher.on;