@stream-io/feeds-client 0.1.3 → 0.1.5

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 (54) hide show
  1. package/@react-bindings/hooks/client-state-hooks/index.ts +2 -0
  2. package/@react-bindings/hooks/feed-state-hooks/index.ts +5 -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 +21 -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 +5 -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/useFollowers.d.ts +16 -0
  15. package/dist/@react-bindings/hooks/feed-state-hooks/useFollowing.d.ts +16 -0
  16. package/dist/@react-bindings/hooks/{useOwnCapabilities.d.ts → feed-state-hooks/useOwnCapabilities.d.ts} +2 -2
  17. package/dist/@react-bindings/hooks/internal/index.d.ts +1 -0
  18. package/dist/@react-bindings/hooks/internal/useStableCallback.d.ts +25 -0
  19. package/dist/@react-bindings/hooks/util/index.d.ts +1 -0
  20. package/dist/@react-bindings/hooks/util/useReactionActions.d.ts +17 -0
  21. package/dist/@react-bindings/index.d.ts +5 -3
  22. package/dist/@react-bindings/wrappers/StreamFeed.d.ts +12 -0
  23. package/dist/index-react-bindings.browser.cjs +456 -183
  24. package/dist/index-react-bindings.browser.cjs.map +1 -1
  25. package/dist/index-react-bindings.browser.js +451 -185
  26. package/dist/index-react-bindings.browser.js.map +1 -1
  27. package/dist/index-react-bindings.node.cjs +456 -183
  28. package/dist/index-react-bindings.node.cjs.map +1 -1
  29. package/dist/index-react-bindings.node.js +451 -185
  30. package/dist/index-react-bindings.node.js.map +1 -1
  31. package/dist/index.browser.cjs +172 -72
  32. package/dist/index.browser.cjs.map +1 -1
  33. package/dist/index.browser.js +169 -73
  34. package/dist/index.browser.js.map +1 -1
  35. package/dist/index.node.cjs +172 -72
  36. package/dist/index.node.cjs.map +1 -1
  37. package/dist/index.node.js +169 -73
  38. package/dist/index.node.js.map +1 -1
  39. package/dist/src/Feed.d.ts +7 -3
  40. package/dist/src/FeedsClient.d.ts +4 -3
  41. package/dist/src/gen/models/index.d.ts +92 -15
  42. package/dist/src/types.d.ts +7 -0
  43. package/dist/src/utils.d.ts +9 -1
  44. package/dist/tsconfig.tsbuildinfo +1 -1
  45. package/package.json +2 -1
  46. package/src/Feed.ts +200 -89
  47. package/src/FeedsClient.ts +8 -3
  48. package/src/common/real-time/StableWSConnection.ts +4 -1
  49. package/src/gen/feeds/FeedsApi.ts +5 -0
  50. package/src/gen/models/index.ts +143 -17
  51. package/src/types.ts +12 -1
  52. package/src/utils.ts +25 -1
  53. package/dist/@react-bindings/hooks/clientStateHooks.d.ts +0 -10
  54. package/dist/@react-bindings/hooks/useComments.d.ts +0 -12
@@ -275,122 +275,6 @@ function useStateStore(store, selector) {
275
275
  return state;
276
276
  }
277
277
 
278
- const useComments = (feed,
279
- /**
280
- * The parent (activity or comment) for which to fetch comments.
281
- */
282
- parent) => {
283
- const selector = react.useCallback((state) => ({
284
- comments: state.comments_by_entity_id?.[parent.id]?.comments ?? [],
285
- comment_pagination: state.comments_by_entity_id?.[parent.id]?.pagination,
286
- }), [parent.id]);
287
- return useStateStore(feed.state, selector);
288
- };
289
-
290
- const FeedOwnCapability = {
291
- ADD_ACTIVITY: 'add-activity',
292
- ADD_ACTIVITY_REACTION: 'add-activity-reaction',
293
- ADD_COMMENT: 'add-comment',
294
- ADD_COMMENT_REACTION: 'add-comment-reaction',
295
- BOOKMARK_ACTIVITY: 'bookmark-activity',
296
- CREATE_FEED: 'create-feed',
297
- DELETE_BOOKMARK: 'delete-bookmark',
298
- DELETE_COMMENT: 'delete-comment',
299
- DELETE_FEED: 'delete-feed',
300
- EDIT_BOOKMARK: 'edit-bookmark',
301
- FOLLOW: 'follow',
302
- INVITE_FEED: 'invite-feed',
303
- JOIN_FEED: 'join-feed',
304
- LEAVE_FEED: 'leave-feed',
305
- MANAGE_FEED_GROUP: 'manage-feed-group',
306
- MARK_ACTIVITY: 'mark-activity',
307
- PIN_ACTIVITY: 'pin-activity',
308
- QUERY_FEED_MEMBERS: 'query-feed-members',
309
- QUERY_FOLLOWS: 'query-follows',
310
- READ_ACTIVITIES: 'read-activities',
311
- READ_FEED: 'read-feed',
312
- REMOVE_ACTIVITY: 'remove-activity',
313
- REMOVE_ACTIVITY_REACTION: 'remove-activity-reaction',
314
- REMOVE_COMMENT_REACTION: 'remove-comment-reaction',
315
- UNFOLLOW: 'unfollow',
316
- UPDATE_ACTIVITY: 'update-activity',
317
- UPDATE_COMMENT: 'update-comment',
318
- UPDATE_FEED: 'update-feed',
319
- UPDATE_FEED_FOLLOWERS: 'update-feed-followers',
320
- UPDATE_FEED_MEMBERS: 'update-feed-members',
321
- };
322
-
323
- const stableEmptyArray = [];
324
- const selector = (currentState) => ({
325
- oc: currentState.own_capabilities ?? stableEmptyArray,
326
- });
327
- const useOwnCapabilities = (feed) => {
328
- const { oc = stableEmptyArray } = useStateStore(feed?.state, selector) ?? {};
329
- return react.useMemo(() => ({
330
- canAddActivity: oc.indexOf(FeedOwnCapability.ADD_ACTIVITY) > -1,
331
- canAddActivityReaction: oc.indexOf(FeedOwnCapability.ADD_ACTIVITY_REACTION) > -1,
332
- canAddComment: oc.indexOf(FeedOwnCapability.ADD_COMMENT) > -1,
333
- canAddCommentReaction: oc.indexOf(FeedOwnCapability.ADD_COMMENT_REACTION) > -1,
334
- canBookmarkActivity: oc.indexOf(FeedOwnCapability.BOOKMARK_ACTIVITY) > -1,
335
- canCreateFeed: oc.indexOf(FeedOwnCapability.CREATE_FEED) > -1,
336
- canDeleteBookmark: oc.indexOf(FeedOwnCapability.DELETE_BOOKMARK) > -1,
337
- canDeleteComment: oc.indexOf(FeedOwnCapability.DELETE_COMMENT) > -1,
338
- canDeleteFeed: oc.indexOf(FeedOwnCapability.DELETE_FEED) > -1,
339
- canEditBookmark: oc.indexOf(FeedOwnCapability.EDIT_BOOKMARK) > -1,
340
- canFollow: oc.indexOf(FeedOwnCapability.FOLLOW) > -1,
341
- canRemoveActivity: oc.indexOf(FeedOwnCapability.REMOVE_ACTIVITY) > -1,
342
- canRemoveActivityReaction: oc.indexOf(FeedOwnCapability.REMOVE_ACTIVITY_REACTION) > -1,
343
- canRemoveCommentReaction: oc.indexOf(FeedOwnCapability.REMOVE_COMMENT_REACTION) > -1,
344
- canUnfollow: oc.indexOf(FeedOwnCapability.UNFOLLOW) > -1,
345
- canUpdateFeed: oc.indexOf(FeedOwnCapability.UPDATE_FEED) > -1,
346
- canInviteFeed: oc.indexOf(FeedOwnCapability.INVITE_FEED) > -1,
347
- canJoinFeed: oc.indexOf(FeedOwnCapability.JOIN_FEED) > -1,
348
- canLeaveFeed: oc.indexOf(FeedOwnCapability.LEAVE_FEED) > -1,
349
- canManageFeedGroup: oc.indexOf(FeedOwnCapability.MANAGE_FEED_GROUP) > -1,
350
- canMarkActivity: oc.indexOf(FeedOwnCapability.MARK_ACTIVITY) > -1,
351
- canPinActivity: oc.indexOf(FeedOwnCapability.PIN_ACTIVITY) > -1,
352
- canQueryFeedMembers: oc.indexOf(FeedOwnCapability.QUERY_FEED_MEMBERS) > -1,
353
- canQueryFollows: oc.indexOf(FeedOwnCapability.QUERY_FOLLOWS) > -1,
354
- canReadActivities: oc.indexOf(FeedOwnCapability.READ_ACTIVITIES) > -1,
355
- canReadFeed: oc.indexOf(FeedOwnCapability.READ_FEED) > -1,
356
- canUpdateActivity: oc.indexOf(FeedOwnCapability.UPDATE_ACTIVITY) > -1,
357
- canUpdateComment: oc.indexOf(FeedOwnCapability.UPDATE_COMMENT) > -1,
358
- canUpdateFeedFollowers: oc.indexOf(FeedOwnCapability.UPDATE_FEED_FOLLOWERS) > -1,
359
- canUpdateFeedMembers: oc.indexOf(FeedOwnCapability.UPDATE_FEED_MEMBERS) > -1,
360
- }), [oc]);
361
- };
362
-
363
- const StreamFeedsContext = react.createContext(undefined);
364
- /**
365
- * Hook to access the nearest FeedsClient instance.
366
- */
367
- const useFeedsClient = () => {
368
- return react.useContext(StreamFeedsContext);
369
- };
370
-
371
- /**
372
- * A React hook that returns the currently connected user on a `FeedsClient` instance and null otherwise.
373
- */
374
- const useClientConnectedUser = () => {
375
- const client = useFeedsClient();
376
- const { user } = useStateStore(client?.state, clientConnectedUserSelector) ?? {};
377
- return user;
378
- };
379
- /**
380
- * A React hook that returns the websocket connection state of `FeedsClient`.
381
- */
382
- const useWsConnectionState = () => {
383
- const client = useFeedsClient();
384
- const { isHealthy } = useStateStore(client?.state, wsConnectionStateSelector) ?? {};
385
- return { isHealthy };
386
- };
387
- const clientConnectedUserSelector = (nextState) => ({
388
- user: nextState.connectedUser,
389
- });
390
- const wsConnectionStateSelector = (nextState) => ({
391
- isHealthy: nextState.isWsConnectionHealthy,
392
- });
393
-
394
278
  const decoders = {};
395
279
  const decodeDatetimeType = (input) => typeof input === 'number'
396
280
  ? new Date(Math.floor(input / 1000000))
@@ -2016,6 +1900,7 @@ class FeedsApi {
2016
1900
  };
2017
1901
  const body = {
2018
1902
  type: request?.type,
1903
+ create_notification_activity: request?.create_notification_activity,
2019
1904
  custom: request?.custom,
2020
1905
  };
2021
1906
  const response = await this.apiClient.sendRequest('POST', '/api/v2/feeds/activities/{activity_id}/reactions', pathParams, undefined, body, 'application/json');
@@ -2110,6 +1995,7 @@ class FeedsApi {
2110
1995
  comment: request?.comment,
2111
1996
  object_id: request?.object_id,
2112
1997
  object_type: request?.object_type,
1998
+ create_notification_activity: request?.create_notification_activity,
2113
1999
  parent_id: request?.parent_id,
2114
2000
  attachments: request?.attachments,
2115
2001
  mentioned_user_ids: request?.mentioned_user_ids,
@@ -2173,6 +2059,7 @@ class FeedsApi {
2173
2059
  };
2174
2060
  const body = {
2175
2061
  type: request?.type,
2062
+ create_notification_activity: request?.create_notification_activity,
2176
2063
  custom: request?.custom,
2177
2064
  };
2178
2065
  const response = await this.apiClient.sendRequest('POST', '/api/v2/feeds/comments/{comment_id}/reactions', pathParams, undefined, body, 'application/json');
@@ -2398,6 +2285,7 @@ class FeedsApi {
2398
2285
  const body = {
2399
2286
  source: request?.source,
2400
2287
  target: request?.target,
2288
+ create_notification_activity: request?.create_notification_activity,
2401
2289
  follower_role: request?.follower_role,
2402
2290
  push_preference: request?.push_preference,
2403
2291
  custom: request?.custom,
@@ -2410,6 +2298,7 @@ class FeedsApi {
2410
2298
  const body = {
2411
2299
  source: request?.source,
2412
2300
  target: request?.target,
2301
+ create_notification_activity: request?.create_notification_activity,
2413
2302
  push_preference: request?.push_preference,
2414
2303
  custom: request?.custom,
2415
2304
  };
@@ -2830,6 +2719,13 @@ function removeConnectionEventListeners(cb) {
2830
2719
  window.removeEventListener('online', cb);
2831
2720
  }
2832
2721
  }
2722
+ const streamDevToken = (userId) => {
2723
+ return [
2724
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9', // {"alg": "HS256", "typ": "JWT"}
2725
+ window.btoa(JSON.stringify({ user_id: userId })),
2726
+ 'devtoken', // hardcoded signature
2727
+ ].join('.');
2728
+ };
2833
2729
  const capitalize = (s) => {
2834
2730
  return `${s.charAt(0).toLocaleUpperCase()}${s.slice(1)}`;
2835
2731
  };
@@ -3100,7 +2996,10 @@ class StableWSConnection {
3100
2996
  this.onmessage = (wsID, event) => {
3101
2997
  if (this.wsID !== wsID)
3102
2998
  return;
3103
- this._log('onmessage() - onmessage callback', { event, wsID });
2999
+ this._log('onmessage() - onmessage callback', {
3000
+ event: { ...event, data: JSON.parse(event.data) },
3001
+ wsID,
3002
+ });
3104
3003
  let data = typeof event.data === 'string' ? JSON.parse(event.data) : null;
3105
3004
  this.decoders.forEach((decode) => {
3106
3005
  data = decode(data);
@@ -4115,8 +4014,15 @@ const updateBookmarkInActivities = (event, activities, isCurrentUser) => {
4115
4014
  return updateActivityInActivities(updatedActivity, activities);
4116
4015
  };
4117
4016
 
4118
- const END_OF_LIST = 'eol';
4119
- const DEFAULT_COMMENT_PAGINATION = 'first';
4017
+ const checkHasAnotherPage = (v, cursor) => (typeof v === 'undefined' && typeof cursor === 'undefined') ||
4018
+ typeof cursor === 'string';
4019
+ const isCommentResponse = (entity) => {
4020
+ return typeof entity?.object_id === 'string';
4021
+ };
4022
+ const Constants = {
4023
+ DEFAULT_COMMENT_PAGINATION: 'first',
4024
+ };
4025
+
4120
4026
  class Feed extends FeedApi {
4121
4027
  constructor(client, groupId, id, data) {
4122
4028
  // Need this ugly cast because fileUpload endpoints :(
@@ -4189,7 +4095,7 @@ class Feed extends FeedApi {
4189
4095
  const entityState = currentState.comments_by_entity_id[forId];
4190
4096
  const newComments = entityState?.comments?.concat([]) ?? [];
4191
4097
  if (entityState?.pagination?.sort === 'last' &&
4192
- entityState?.pagination.next === END_OF_LIST) {
4098
+ !checkHasAnotherPage(entityState.comments, entityState?.pagination.next)) {
4193
4099
  newComments.unshift(comment);
4194
4100
  }
4195
4101
  else if (entityState?.pagination?.sort === 'first') {
@@ -4276,7 +4182,7 @@ class Feed extends FeedApi {
4276
4182
  ...currentState,
4277
4183
  ...event.follow.source_feed,
4278
4184
  };
4279
- if (currentState.following_pagination?.next === END_OF_LIST) {
4185
+ if (!checkHasAnotherPage(currentState.following, currentState.following_pagination?.next)) {
4280
4186
  // TODO: respect sort
4281
4187
  newState.following = currentState.following
4282
4188
  ? currentState.following.concat(event.follow)
@@ -4297,7 +4203,7 @@ class Feed extends FeedApi {
4297
4203
  ? currentState.own_follows.concat(event.follow)
4298
4204
  : [event.follow];
4299
4205
  }
4300
- if (currentState.followers_pagination?.next === END_OF_LIST) {
4206
+ if (!checkHasAnotherPage(currentState.followers, currentState.followers_pagination?.next)) {
4301
4207
  // TODO: respect sort
4302
4208
  newState.followers = currentState.followers
4303
4209
  ? currentState.followers.concat(event.follow)
@@ -4338,40 +4244,60 @@ class Feed extends FeedApi {
4338
4244
  'feeds.comment.reaction.deleted': this.handleCommentReactionEvent.bind(this),
4339
4245
  'feeds.comment.reaction.updated': Feed.noop,
4340
4246
  'feeds.feed_member.added': (event) => {
4341
- const { member } = event;
4342
- // do not add a member if the pagination has reached the end of the list
4343
- if (this.currentState.member_pagination?.next !== END_OF_LIST)
4344
- return;
4247
+ const { connectedUser } = this.client.state.getLatestValue();
4345
4248
  this.state.next((currentState) => {
4346
- return {
4347
- ...currentState,
4348
- // TODO: respect sort
4349
- members: currentState.members
4350
- ? currentState.members.concat(member)
4351
- : [member],
4352
- };
4249
+ let newState;
4250
+ if (!checkHasAnotherPage(currentState.members, currentState.member_pagination?.next)) {
4251
+ newState ?? (newState = {
4252
+ ...currentState,
4253
+ });
4254
+ newState.members = newState.members?.concat(event.member) ?? [
4255
+ event.member,
4256
+ ];
4257
+ }
4258
+ if (connectedUser?.id === event.member.user.id) {
4259
+ newState ?? (newState = {
4260
+ ...currentState,
4261
+ });
4262
+ newState.own_membership = event.member;
4263
+ }
4264
+ return newState ?? currentState;
4353
4265
  });
4354
4266
  },
4355
4267
  'feeds.feed_member.removed': (event) => {
4268
+ const { connectedUser } = this.client.state.getLatestValue();
4356
4269
  this.state.next((currentState) => {
4357
- return {
4270
+ const newState = {
4358
4271
  ...currentState,
4359
4272
  members: currentState.members?.filter((member) => member.user.id !== event.user?.id),
4360
4273
  };
4274
+ if (connectedUser?.id === event.member_id) {
4275
+ delete newState.own_membership;
4276
+ }
4277
+ return newState;
4361
4278
  });
4362
4279
  },
4363
4280
  'feeds.feed_member.updated': (event) => {
4281
+ const { connectedUser } = this.client.state.getLatestValue();
4364
4282
  this.state.next((currentState) => {
4365
4283
  const memberIndex = currentState.members?.findIndex((member) => member.user.id === event.member.user.id) ?? -1;
4284
+ let newState;
4366
4285
  if (memberIndex !== -1) {
4286
+ // if there's an index, there's a member to update
4367
4287
  const newMembers = [...currentState.members];
4368
4288
  newMembers[memberIndex] = event.member;
4369
- return {
4289
+ newState ?? (newState = {
4370
4290
  ...currentState,
4371
- members: newMembers,
4372
- };
4291
+ });
4292
+ newState.members = newMembers;
4293
+ }
4294
+ if (connectedUser?.id === event.member.user.id) {
4295
+ newState ?? (newState = {
4296
+ ...currentState,
4297
+ });
4298
+ newState.own_membership = event.member;
4373
4299
  }
4374
- return currentState;
4300
+ return newState ?? currentState;
4375
4301
  });
4376
4302
  },
4377
4303
  // the poll events should be removed from here
@@ -4497,29 +4423,6 @@ class Feed extends FeedApi {
4497
4423
  ...currentState,
4498
4424
  ...responseCopy,
4499
4425
  };
4500
- // if there is no next cursor, set it to END_OF_LIST
4501
- // request has to have a limit set for this to work
4502
- if ((request?.followers_pagination?.limit ?? 0) > 0 &&
4503
- typeof nextState.followers_pagination?.next === 'undefined') {
4504
- nextState.followers_pagination = {
4505
- ...nextState.followers_pagination,
4506
- next: END_OF_LIST,
4507
- };
4508
- }
4509
- if ((request?.following_pagination?.limit ?? 0) > 0 &&
4510
- typeof nextState.following_pagination?.next === 'undefined') {
4511
- nextState.following_pagination = {
4512
- ...nextState.following_pagination,
4513
- next: END_OF_LIST,
4514
- };
4515
- }
4516
- if ((request?.member_pagination?.limit ?? 0) > 0 &&
4517
- typeof nextState.member_pagination?.next === 'undefined') {
4518
- nextState.member_pagination = {
4519
- ...nextState.member_pagination,
4520
- next: END_OF_LIST,
4521
- };
4522
- }
4523
4426
  if (!request?.followers_pagination?.limit) {
4524
4427
  delete nextState.followers;
4525
4428
  }
@@ -4610,6 +4513,7 @@ class Feed extends FeedApi {
4610
4513
  });
4611
4514
  }
4612
4515
  async loadNextPageComments({ forId, base, sort, parentId, }) {
4516
+ let error;
4613
4517
  try {
4614
4518
  this.state.next((currentState) => ({
4615
4519
  ...currentState,
@@ -4624,7 +4528,7 @@ class Feed extends FeedApi {
4624
4528
  },
4625
4529
  },
4626
4530
  }));
4627
- const { next: newNextCursor = END_OF_LIST, comments } = await base();
4531
+ const { next: newNextCursor, comments } = await base();
4628
4532
  this.state.next((currentState) => {
4629
4533
  const newPagination = {
4630
4534
  ...currentState.comments_by_entity_id[forId]?.pagination,
@@ -4649,9 +4553,8 @@ class Feed extends FeedApi {
4649
4553
  };
4650
4554
  });
4651
4555
  }
4652
- catch (error) {
4653
- console.error(error);
4654
- // TODO: figure out how to handle errorss
4556
+ catch (e) {
4557
+ error = e;
4655
4558
  }
4656
4559
  finally {
4657
4560
  this.state.next((currentState) => ({
@@ -4668,15 +4571,21 @@ class Feed extends FeedApi {
4668
4571
  },
4669
4572
  }));
4670
4573
  }
4574
+ if (error) {
4575
+ throw error;
4576
+ }
4671
4577
  }
4672
4578
  async loadNextPageActivityComments(activity, request) {
4673
- const pagination = this.currentState.comments_by_entity_id[activity.id]?.pagination;
4674
- const currentNextCursor = pagination?.next;
4675
- const currentSort = pagination?.sort;
4676
- const isLoading = pagination?.loading_next_page;
4677
- const sort = currentSort ?? request?.sort ?? DEFAULT_COMMENT_PAGINATION;
4678
- if (isLoading || currentNextCursor === END_OF_LIST)
4579
+ const currentEntityState = this.currentState.comments_by_entity_id[activity.id];
4580
+ const currentPagination = currentEntityState?.pagination;
4581
+ const currentNextCursor = currentPagination?.next;
4582
+ const currentSort = currentPagination?.sort;
4583
+ const isLoading = currentPagination?.loading_next_page;
4584
+ const sort = currentSort ?? request?.sort ?? Constants.DEFAULT_COMMENT_PAGINATION;
4585
+ if (isLoading ||
4586
+ !checkHasAnotherPage(currentEntityState?.comments, currentNextCursor)) {
4679
4587
  return;
4588
+ }
4680
4589
  await this.loadNextPageComments({
4681
4590
  forId: activity.id,
4682
4591
  base: () => this.client.getComments({
@@ -4690,20 +4599,25 @@ class Feed extends FeedApi {
4690
4599
  });
4691
4600
  }
4692
4601
  async loadNextPageCommentReplies(comment, request) {
4693
- const pagination = this.currentState.comments_by_entity_id[comment.id]?.pagination;
4694
- const currentNextCursor = pagination?.next;
4695
- const currentSort = pagination?.sort;
4696
- const isLoading = pagination?.loading_next_page;
4697
- const sort = currentSort ?? request?.sort ?? DEFAULT_COMMENT_PAGINATION;
4698
- if (isLoading || currentNextCursor === END_OF_LIST)
4602
+ const currentEntityState = this.currentState.comments_by_entity_id[comment.id];
4603
+ const currentPagination = currentEntityState?.pagination;
4604
+ const currentNextCursor = currentPagination?.next;
4605
+ const currentSort = currentPagination?.sort;
4606
+ const isLoading = currentPagination?.loading_next_page;
4607
+ const sort = currentSort ?? request?.sort ?? Constants.DEFAULT_COMMENT_PAGINATION;
4608
+ if (isLoading ||
4609
+ !checkHasAnotherPage(currentEntityState?.comments, currentNextCursor)) {
4699
4610
  return;
4611
+ }
4700
4612
  await this.loadNextPageComments({
4701
4613
  forId: comment.id,
4702
4614
  base: () => this.client.getCommentReplies({
4703
4615
  ...request,
4704
4616
  comment_id: comment.id,
4705
4617
  // use known sort first (prevents broken pagination)
4706
- sort: currentSort ?? request?.sort ?? DEFAULT_COMMENT_PAGINATION,
4618
+ sort: currentSort ??
4619
+ request?.sort ??
4620
+ Constants.DEFAULT_COMMENT_PAGINATION,
4707
4621
  next: currentNextCursor,
4708
4622
  }),
4709
4623
  parentId: comment.parent_id ?? comment.object_id,
@@ -4713,10 +4627,14 @@ class Feed extends FeedApi {
4713
4627
  async loadNextPageFollows(type, request) {
4714
4628
  const paginationKey = `${type}_pagination`;
4715
4629
  const method = `query${capitalize(type)}`;
4630
+ const currentFollows = this.currentState[type];
4716
4631
  const currentNextCursor = this.currentState[paginationKey]?.next;
4717
4632
  const isLoading = this.currentState[paginationKey]?.loading_next_page;
4718
- if (isLoading || currentNextCursor === END_OF_LIST)
4633
+ const sort = this.currentState[paginationKey]?.sort ?? request.sort;
4634
+ let error;
4635
+ if (isLoading || !checkHasAnotherPage(currentFollows, currentNextCursor)) {
4719
4636
  return;
4637
+ }
4720
4638
  try {
4721
4639
  this.state.next((currentState) => {
4722
4640
  return {
@@ -4727,9 +4645,10 @@ class Feed extends FeedApi {
4727
4645
  },
4728
4646
  };
4729
4647
  });
4730
- const { next: newNextCursor = END_OF_LIST, follows } = await this[method]({
4648
+ const { next: newNextCursor, follows } = await this[method]({
4731
4649
  ...request,
4732
4650
  next: currentNextCursor,
4651
+ sort,
4733
4652
  });
4734
4653
  this.state.next((currentState) => ({
4735
4654
  ...currentState,
@@ -4739,12 +4658,12 @@ class Feed extends FeedApi {
4739
4658
  [paginationKey]: {
4740
4659
  ...currentState[paginationKey],
4741
4660
  next: newNextCursor,
4661
+ sort,
4742
4662
  },
4743
4663
  }));
4744
4664
  }
4745
- catch (error) {
4746
- console.error(error);
4747
- // TODO: figure out how to handle errorss
4665
+ catch (e) {
4666
+ error = e;
4748
4667
  }
4749
4668
  finally {
4750
4669
  this.state.next((currentState) => {
@@ -4757,6 +4676,9 @@ class Feed extends FeedApi {
4757
4676
  };
4758
4677
  });
4759
4678
  }
4679
+ if (error) {
4680
+ throw error;
4681
+ }
4760
4682
  }
4761
4683
  async loadNextPageFollowers(request) {
4762
4684
  await this.loadNextPageFollows('followers', request);
@@ -4764,6 +4686,59 @@ class Feed extends FeedApi {
4764
4686
  async loadNextPageFollowing(request) {
4765
4687
  await this.loadNextPageFollows('following', request);
4766
4688
  }
4689
+ async loadNextPageMembers(request) {
4690
+ const currentMembers = this.currentState.members;
4691
+ const currentNextCursor = this.currentState.member_pagination?.next;
4692
+ const isLoading = this.currentState.member_pagination?.loading_next_page;
4693
+ const sort = this.currentState.member_pagination?.sort ?? request.sort;
4694
+ let error;
4695
+ if (isLoading || !checkHasAnotherPage(currentMembers, currentNextCursor)) {
4696
+ return;
4697
+ }
4698
+ try {
4699
+ this.state.next((currentState) => ({
4700
+ ...currentState,
4701
+ member_pagination: {
4702
+ ...currentState.member_pagination,
4703
+ loading_next_page: true,
4704
+ },
4705
+ }));
4706
+ const { next: newNextCursor, members } = await this.client.queryFeedMembers({
4707
+ ...request,
4708
+ sort,
4709
+ feed_id: this.id,
4710
+ feed_group_id: this.group,
4711
+ next: currentNextCursor,
4712
+ });
4713
+ this.state.next((currentState) => ({
4714
+ ...currentState,
4715
+ members: currentState.members
4716
+ ? currentState.members.concat(members)
4717
+ : members,
4718
+ member_pagination: {
4719
+ ...currentState.member_pagination,
4720
+ next: newNextCursor,
4721
+ // set sort if not defined yet
4722
+ sort: currentState.member_pagination?.sort ?? request.sort,
4723
+ },
4724
+ }));
4725
+ }
4726
+ catch (e) {
4727
+ error = e;
4728
+ }
4729
+ finally {
4730
+ this.state.next((currentState) => ({
4731
+ ...currentState,
4732
+ member_pagination: {
4733
+ ...currentState.member_pagination,
4734
+ loading_next_page: false,
4735
+ },
4736
+ }));
4737
+ }
4738
+ if (error) {
4739
+ throw error;
4740
+ }
4741
+ }
4767
4742
  /**
4768
4743
  * Method which queries followers of this feed (feeds which target this feed).
4769
4744
  *
@@ -5254,6 +5229,9 @@ class FeedsClient extends FeedsApi {
5254
5229
  throw err;
5255
5230
  }
5256
5231
  };
5232
+ this.devToken = (userId) => {
5233
+ return streamDevToken(userId);
5234
+ };
5257
5235
  this.closePoll = async (request) => {
5258
5236
  return await this.updatePollPartial({
5259
5237
  poll_id: request.poll_id,
@@ -5512,18 +5490,313 @@ const useCreateFeedsClient = ({ apiKey, tokenOrProvider, userData, options, }) =
5512
5490
  return client;
5513
5491
  };
5514
5492
 
5493
+ const StreamFeedsContext = react.createContext(undefined);
5494
+ /**
5495
+ * Hook to access the nearest FeedsClient instance.
5496
+ */
5497
+ const useFeedsClient = () => {
5498
+ return react.useContext(StreamFeedsContext);
5499
+ };
5500
+
5501
+ /**
5502
+ * A React hook that returns the currently connected user on a `FeedsClient` instance and null otherwise.
5503
+ */
5504
+ const useClientConnectedUser = () => {
5505
+ const client = useFeedsClient();
5506
+ const { user } = useStateStore(client?.state, selector$5) ?? {};
5507
+ return user;
5508
+ };
5509
+ const selector$5 = (nextState) => ({
5510
+ user: nextState.connectedUser,
5511
+ });
5512
+
5513
+ /**
5514
+ * A React hook that returns the websocket connection state of `FeedsClient`.
5515
+ */
5516
+ const useWsConnectionState = () => {
5517
+ const client = useFeedsClient();
5518
+ const { isHealthy } = useStateStore(client?.state, selector$4) ?? {};
5519
+ return { isHealthy };
5520
+ };
5521
+ const selector$4 = (nextState) => ({
5522
+ isHealthy: nextState.isWsConnectionHealthy,
5523
+ });
5524
+
5525
+ const StreamFeedContext = react.createContext(undefined);
5526
+ /**
5527
+ * Hook to access the nearest Feed instance.
5528
+ */
5529
+ const useFeedContext = () => {
5530
+ return react.useContext(StreamFeedContext);
5531
+ };
5532
+
5533
+ /**
5534
+ * A utility hook implementing a stable callback. It takes in an unstable method that
5535
+ * is supposed to be invoked somewhere deeper in the DOM tree without making it
5536
+ * change its reference every time the parent component rerenders. It will also return
5537
+ * the value of the callback if it does return one.
5538
+ * A common use-case would be having a function whose invocation depends on state
5539
+ * somewhere high up in the DOM tree and wanting to use the same function deeper
5540
+ * down, for example in a leaf node and simply using useCallback results in
5541
+ * cascading dependency hell. If we wrap it in useStableCallback, we would be able
5542
+ * to:
5543
+ * - Use the same function as a dependency of another hook (since it is stable)
5544
+ * - Still invoke it and get the latest state
5545
+ *
5546
+ * **Caveats:**
5547
+ * - Never wrap a function that is supposed to return a React.ReactElement in
5548
+ * useStableCallback, since React will not know that the DOM needs to be updated
5549
+ * whenever the callback value changes (for example, renderItem from FlatList must
5550
+ * never be wrapped in this hook)
5551
+ * - Always prefer using a standard useCallback/stable function wherever possible
5552
+ * (the purpose of useStableCallback is to bridge the gap between top level contexts
5553
+ * and cascading rereders in downstream components - **not** as an escape hatch)
5554
+ * @param callback - the callback we want to stabilize
5555
+ */
5556
+ const useStableCallback = (callback) => {
5557
+ const ref = react.useRef(callback);
5558
+ ref.current = callback;
5559
+ return react.useCallback((...args) => {
5560
+ return ref.current(...args);
5561
+ }, []);
5562
+ };
5563
+
5564
+ /**
5565
+ * A React hook that returns a reactive object containing the current activities,
5566
+ * loading state and whether there is a next page to paginate to or not.
5567
+ */
5568
+ const useFeedActivities = (feedFromProps) => {
5569
+ const feedFromContext = useFeedContext();
5570
+ const feed = feedFromProps ?? feedFromContext;
5571
+ const data = useStateStore(feed?.state, selector$3);
5572
+ const loadNextPage = useStableCallback(async () => {
5573
+ if (!feed || !data?.hasNextPage || data?.isLoading) {
5574
+ return;
5575
+ }
5576
+ await feed.getNextPage();
5577
+ });
5578
+ return react.useMemo(() => ({ ...data, loadNextPage }), [data, loadNextPage]);
5579
+ };
5580
+ const selector$3 = (nextState) => ({
5581
+ isLoading: nextState.is_loading_activities,
5582
+ hasNextPage: typeof nextState.next !== 'undefined',
5583
+ activities: nextState.activities ?? [],
5584
+ });
5585
+
5586
+ function useComments({ feed: feedFromProps, parent, }) {
5587
+ const feedFromContext = useFeedContext();
5588
+ const feed = feedFromProps ?? feedFromContext;
5589
+ const selector = react.useCallback((state) => ({
5590
+ comments: state.comments_by_entity_id?.[parent.id]?.comments,
5591
+ comments_pagination: state.comments_by_entity_id?.[parent.id]?.pagination,
5592
+ }), [parent.id]);
5593
+ const data = useStateStore(feed?.state, selector);
5594
+ const loadNextPage = react.useMemo(() => {
5595
+ if (!feed)
5596
+ return undefined;
5597
+ return (request) => {
5598
+ if (isCommentResponse(parent)) {
5599
+ return feed.loadNextPageCommentReplies(parent, request);
5600
+ }
5601
+ else {
5602
+ return feed.loadNextPageActivityComments(parent, request);
5603
+ }
5604
+ };
5605
+ }, [feed, parent]);
5606
+ return react.useMemo(() => {
5607
+ if (!data) {
5608
+ return undefined;
5609
+ }
5610
+ return {
5611
+ ...data,
5612
+ hasNextPage: checkHasAnotherPage(data.comments, data.comments_pagination?.next),
5613
+ isLoadingNextPage: data?.comments_pagination?.loading_next_page ?? false,
5614
+ loadNextPage,
5615
+ };
5616
+ }, [data, loadNextPage]);
5617
+ }
5618
+
5619
+ const FeedOwnCapability = {
5620
+ ADD_ACTIVITY: 'add-activity',
5621
+ ADD_ACTIVITY_REACTION: 'add-activity-reaction',
5622
+ ADD_COMMENT: 'add-comment',
5623
+ ADD_COMMENT_REACTION: 'add-comment-reaction',
5624
+ BOOKMARK_ACTIVITY: 'bookmark-activity',
5625
+ CREATE_FEED: 'create-feed',
5626
+ DELETE_BOOKMARK: 'delete-bookmark',
5627
+ DELETE_COMMENT: 'delete-comment',
5628
+ DELETE_FEED: 'delete-feed',
5629
+ EDIT_BOOKMARK: 'edit-bookmark',
5630
+ FOLLOW: 'follow',
5631
+ INVITE_FEED: 'invite-feed',
5632
+ JOIN_FEED: 'join-feed',
5633
+ LEAVE_FEED: 'leave-feed',
5634
+ MANAGE_FEED_GROUP: 'manage-feed-group',
5635
+ MARK_ACTIVITY: 'mark-activity',
5636
+ PIN_ACTIVITY: 'pin-activity',
5637
+ QUERY_FEED_MEMBERS: 'query-feed-members',
5638
+ QUERY_FOLLOWS: 'query-follows',
5639
+ READ_ACTIVITIES: 'read-activities',
5640
+ READ_FEED: 'read-feed',
5641
+ REMOVE_ACTIVITY: 'remove-activity',
5642
+ REMOVE_ACTIVITY_REACTION: 'remove-activity-reaction',
5643
+ REMOVE_COMMENT_REACTION: 'remove-comment-reaction',
5644
+ UNFOLLOW: 'unfollow',
5645
+ UPDATE_ACTIVITY: 'update-activity',
5646
+ UPDATE_COMMENT: 'update-comment',
5647
+ UPDATE_FEED: 'update-feed',
5648
+ UPDATE_FEED_FOLLOWERS: 'update-feed-followers',
5649
+ UPDATE_FEED_MEMBERS: 'update-feed-members',
5650
+ };
5651
+
5652
+ const stableEmptyArray = [];
5653
+ const selector$2 = (currentState) => ({
5654
+ oc: currentState.own_capabilities ?? stableEmptyArray,
5655
+ });
5656
+ const useOwnCapabilities = (feedFromProps) => {
5657
+ const feedFromContext = useFeedContext();
5658
+ const feed = feedFromProps ?? feedFromContext;
5659
+ const { oc = stableEmptyArray } = useStateStore(feed?.state, selector$2) ?? {};
5660
+ return react.useMemo(() => ({
5661
+ canAddActivity: oc.indexOf(FeedOwnCapability.ADD_ACTIVITY) > -1,
5662
+ canAddActivityReaction: oc.indexOf(FeedOwnCapability.ADD_ACTIVITY_REACTION) > -1,
5663
+ canAddComment: oc.indexOf(FeedOwnCapability.ADD_COMMENT) > -1,
5664
+ canAddCommentReaction: oc.indexOf(FeedOwnCapability.ADD_COMMENT_REACTION) > -1,
5665
+ canBookmarkActivity: oc.indexOf(FeedOwnCapability.BOOKMARK_ACTIVITY) > -1,
5666
+ canCreateFeed: oc.indexOf(FeedOwnCapability.CREATE_FEED) > -1,
5667
+ canDeleteBookmark: oc.indexOf(FeedOwnCapability.DELETE_BOOKMARK) > -1,
5668
+ canDeleteComment: oc.indexOf(FeedOwnCapability.DELETE_COMMENT) > -1,
5669
+ canDeleteFeed: oc.indexOf(FeedOwnCapability.DELETE_FEED) > -1,
5670
+ canEditBookmark: oc.indexOf(FeedOwnCapability.EDIT_BOOKMARK) > -1,
5671
+ canFollow: oc.indexOf(FeedOwnCapability.FOLLOW) > -1,
5672
+ canRemoveActivity: oc.indexOf(FeedOwnCapability.REMOVE_ACTIVITY) > -1,
5673
+ canRemoveActivityReaction: oc.indexOf(FeedOwnCapability.REMOVE_ACTIVITY_REACTION) > -1,
5674
+ canRemoveCommentReaction: oc.indexOf(FeedOwnCapability.REMOVE_COMMENT_REACTION) > -1,
5675
+ canUnfollow: oc.indexOf(FeedOwnCapability.UNFOLLOW) > -1,
5676
+ canUpdateFeed: oc.indexOf(FeedOwnCapability.UPDATE_FEED) > -1,
5677
+ canInviteFeed: oc.indexOf(FeedOwnCapability.INVITE_FEED) > -1,
5678
+ canJoinFeed: oc.indexOf(FeedOwnCapability.JOIN_FEED) > -1,
5679
+ canLeaveFeed: oc.indexOf(FeedOwnCapability.LEAVE_FEED) > -1,
5680
+ canManageFeedGroup: oc.indexOf(FeedOwnCapability.MANAGE_FEED_GROUP) > -1,
5681
+ canMarkActivity: oc.indexOf(FeedOwnCapability.MARK_ACTIVITY) > -1,
5682
+ canPinActivity: oc.indexOf(FeedOwnCapability.PIN_ACTIVITY) > -1,
5683
+ canQueryFeedMembers: oc.indexOf(FeedOwnCapability.QUERY_FEED_MEMBERS) > -1,
5684
+ canQueryFollows: oc.indexOf(FeedOwnCapability.QUERY_FOLLOWS) > -1,
5685
+ canReadActivities: oc.indexOf(FeedOwnCapability.READ_ACTIVITIES) > -1,
5686
+ canReadFeed: oc.indexOf(FeedOwnCapability.READ_FEED) > -1,
5687
+ canUpdateActivity: oc.indexOf(FeedOwnCapability.UPDATE_ACTIVITY) > -1,
5688
+ canUpdateComment: oc.indexOf(FeedOwnCapability.UPDATE_COMMENT) > -1,
5689
+ canUpdateFeedFollowers: oc.indexOf(FeedOwnCapability.UPDATE_FEED_FOLLOWERS) > -1,
5690
+ canUpdateFeedMembers: oc.indexOf(FeedOwnCapability.UPDATE_FEED_MEMBERS) > -1,
5691
+ }), [oc]);
5692
+ };
5693
+
5694
+ const selector$1 = ({ follower_count, followers, followers_pagination, }) => ({
5695
+ follower_count,
5696
+ followers,
5697
+ followers_pagination,
5698
+ });
5699
+ function useFollowers(feedFromProps) {
5700
+ const feedFromContext = useFeedContext();
5701
+ const feed = feedFromProps ?? feedFromContext;
5702
+ const data = useStateStore(feed?.state, selector$1);
5703
+ const loadNextPage = react.useCallback((...options) => feed?.loadNextPageFollowers(...options), [feed]);
5704
+ return react.useMemo(() => {
5705
+ if (!data) {
5706
+ return undefined;
5707
+ }
5708
+ return {
5709
+ ...data,
5710
+ isLoadingNextPage: data.followers_pagination?.loading_next_page ?? false,
5711
+ hasNextPage: checkHasAnotherPage(data.followers, data.followers_pagination?.next),
5712
+ loadNextPage,
5713
+ };
5714
+ }, [data, loadNextPage]);
5715
+ }
5716
+
5717
+ const selector = ({ following_count, following, following_pagination, }) => ({
5718
+ following_count,
5719
+ following,
5720
+ following_pagination,
5721
+ });
5722
+ function useFollowing(feedFromProps) {
5723
+ const feedFromContext = useFeedContext();
5724
+ const feed = feedFromProps ?? feedFromContext;
5725
+ const data = useStateStore(feed?.state, selector);
5726
+ const loadNextPage = react.useCallback((...options) => feed?.loadNextPageFollowing(...options), [feed]);
5727
+ return react.useMemo(() => {
5728
+ if (!data) {
5729
+ return undefined;
5730
+ }
5731
+ return {
5732
+ ...data,
5733
+ isLoadingNextPage: data.following_pagination?.loading_next_page ?? false,
5734
+ hasNextPage: checkHasAnotherPage(data.following, data.following_pagination?.next),
5735
+ loadNextPage,
5736
+ };
5737
+ }, [data, loadNextPage]);
5738
+ }
5739
+
5740
+ /**
5741
+ * A utility hook that takes in an entity and a reaction type, and creates reaction actions
5742
+ * that can then be used on the UI. The entity can be either an ActivityResponse or a CommentResponse
5743
+ * as the hook determines internally which APIs it is supposed to use, while taking the
5744
+ * correct ownCapabilities into account.
5745
+ * @param entity - The entity to which we want to add a reaction, can be either ActivityResponse or CommentResponse.
5746
+ * @param type - The type of reaction we want to add or remove.
5747
+ */
5748
+ const useReactionActions = ({ entity, type, }) => {
5749
+ const client = useFeedsClient();
5750
+ const isComment = isCommentResponse(entity);
5751
+ const hasOwnReaction = react.useMemo(() => !!entity.own_reactions?.find((r) => r.type === type), [entity.own_reactions, type]);
5752
+ const addReaction = useStableCallback(async () => {
5753
+ await (isComment
5754
+ ? client?.addCommentReaction({ comment_id: entity.id, type })
5755
+ : client?.addReaction({ activity_id: entity.id, type }));
5756
+ });
5757
+ const removeReaction = useStableCallback(async () => {
5758
+ await (isComment
5759
+ ? client?.deleteCommentReaction({ comment_id: entity.id, type })
5760
+ : client?.deleteActivityReaction({
5761
+ activity_id: entity.id,
5762
+ type,
5763
+ }));
5764
+ });
5765
+ const toggleReaction = useStableCallback(async () => {
5766
+ if (hasOwnReaction) {
5767
+ await removeReaction();
5768
+ }
5769
+ else {
5770
+ await addReaction();
5771
+ }
5772
+ });
5773
+ return react.useMemo(() => ({ addReaction, removeReaction, toggleReaction }), [addReaction, removeReaction, toggleReaction]);
5774
+ };
5775
+
5515
5776
  const StreamFeeds = ({ client, children }) => {
5516
5777
  return (jsxRuntime.jsx(StreamFeedsContext.Provider, { value: client, children: children }));
5517
5778
  };
5518
5779
  StreamFeeds.displayName = 'StreamFeeds';
5519
5780
 
5781
+ const StreamFeed = ({ feed, children }) => {
5782
+ return (jsxRuntime.jsx(StreamFeedContext.Provider, { value: feed, children: children }));
5783
+ };
5784
+ StreamFeed.displayName = 'StreamFeed';
5785
+
5786
+ exports.StreamFeed = StreamFeed;
5787
+ exports.StreamFeedContext = StreamFeedContext;
5520
5788
  exports.StreamFeeds = StreamFeeds;
5521
5789
  exports.StreamFeedsContext = StreamFeedsContext;
5522
5790
  exports.useClientConnectedUser = useClientConnectedUser;
5523
5791
  exports.useComments = useComments;
5524
5792
  exports.useCreateFeedsClient = useCreateFeedsClient;
5793
+ exports.useFeedActivities = useFeedActivities;
5794
+ exports.useFeedContext = useFeedContext;
5525
5795
  exports.useFeedsClient = useFeedsClient;
5796
+ exports.useFollowers = useFollowers;
5797
+ exports.useFollowing = useFollowing;
5526
5798
  exports.useOwnCapabilities = useOwnCapabilities;
5799
+ exports.useReactionActions = useReactionActions;
5527
5800
  exports.useStateStore = useStateStore;
5528
5801
  exports.useWsConnectionState = useWsConnectionState;
5529
5802
  //# sourceMappingURL=index-react-bindings.node.cjs.map