@stream-io/feeds-client 0.2.1 → 0.2.2

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/feed-state-hooks/index.ts +4 -0
  2. package/CHANGELOG.md +8 -0
  3. package/dist/@react-bindings/hooks/feed-state-hooks/index.d.ts +4 -0
  4. package/dist/@react-bindings/hooks/feed-state-hooks/useAggregatedActivities.d.ts +11 -0
  5. package/dist/@react-bindings/hooks/feed-state-hooks/useIsAggregatedActivityRead.d.ts +6 -0
  6. package/dist/@react-bindings/hooks/feed-state-hooks/useIsAggregatedActivitySeen.d.ts +6 -0
  7. package/dist/@react-bindings/hooks/feed-state-hooks/useNotificationStatus.d.ts +13 -0
  8. package/dist/@react-bindings/wrappers/StreamFeed.d.ts +1 -1
  9. package/dist/index-react-bindings.browser.cjs +154 -29
  10. package/dist/index-react-bindings.browser.cjs.map +1 -1
  11. package/dist/index-react-bindings.browser.js +151 -30
  12. package/dist/index-react-bindings.browser.js.map +1 -1
  13. package/dist/index-react-bindings.node.cjs +154 -29
  14. package/dist/index-react-bindings.node.cjs.map +1 -1
  15. package/dist/index-react-bindings.node.js +151 -30
  16. package/dist/index-react-bindings.node.js.map +1 -1
  17. package/dist/index.browser.cjs +88 -12
  18. package/dist/index.browser.cjs.map +1 -1
  19. package/dist/index.browser.js +88 -12
  20. package/dist/index.browser.js.map +1 -1
  21. package/dist/index.node.cjs +88 -12
  22. package/dist/index.node.cjs.map +1 -1
  23. package/dist/index.node.js +88 -12
  24. package/dist/index.node.js.map +1 -1
  25. package/dist/src/feed/event-handlers/activity/handle-activity-marked.d.ts +11 -0
  26. package/dist/src/feed/event-handlers/activity/index.d.ts +1 -0
  27. package/dist/src/feed/event-handlers/notification-feed/handle-notification-feed-updated.d.ts +8 -1
  28. package/dist/src/feed/feed.d.ts +2 -2
  29. package/dist/src/test-utils/response-generators.d.ts +21 -1
  30. package/dist/tsconfig.tsbuildinfo +1 -1
  31. package/package.json +1 -1
  32. package/src/feed/event-handlers/activity/activity-marked-utils.test.ts +208 -0
  33. package/src/feed/event-handlers/activity/handle-activity-marked.ts +68 -0
  34. package/src/feed/event-handlers/activity/handle-activity-reaction-added.test.ts +15 -15
  35. package/src/feed/event-handlers/activity/handle-activity-reaction-deleted.test.ts +14 -14
  36. package/src/feed/event-handlers/activity/handle-activity-unpinned.test.ts +4 -3
  37. package/src/feed/event-handlers/activity/handle-activity-updated.test.ts +4 -4
  38. package/src/feed/event-handlers/activity/index.ts +2 -1
  39. package/src/feed/event-handlers/bookmark/handle-bookmark-added.test.ts +14 -14
  40. package/src/feed/event-handlers/bookmark/handle-bookmark-deleted.test.ts +14 -14
  41. package/src/feed/event-handlers/bookmark/handle-bookmark-updated.test.ts +16 -16
  42. package/src/feed/event-handlers/comment/handle-comment-added.test.ts +147 -0
  43. package/src/feed/event-handlers/comment/handle-comment-deleted.test.ts +133 -0
  44. package/src/feed/event-handlers/comment/handle-comment-deleted.ts +24 -10
  45. package/src/feed/event-handlers/comment/handle-comment-reaction.test.ts +315 -0
  46. package/src/feed/event-handlers/comment/handle-comment-updated.test.ts +131 -0
  47. package/src/feed/event-handlers/follow/handle-follow-created.test.ts +7 -7
  48. package/src/feed/event-handlers/follow/handle-follow-deleted.test.ts +2 -2
  49. package/src/feed/event-handlers/follow/handle-follow-updated.test.ts +1 -1
  50. package/src/feed/event-handlers/notification-feed/handle-notification-feed-updated.test.ts +120 -0
  51. package/src/feed/event-handlers/notification-feed/handle-notification-feed-updated.ts +47 -3
  52. package/src/feed/feed.ts +4 -2
  53. package/src/gen/model-decoders/decoders.ts +1 -1
  54. package/src/test-utils/response-generators.ts +123 -0
@@ -277,7 +277,7 @@ function useStateStore(store, selector) {
277
277
 
278
278
  const decoders = {};
279
279
  const decodeDatetimeType = (input) => typeof input === 'number'
280
- ? new Date(Math.floor(input / 1000000))
280
+ ? new Date(Math.floor(input / 1e6))
281
281
  : new Date(input);
282
282
  decoders.DatetimeType = decodeDatetimeType;
283
283
  const decode = (typeMappings, input) => {
@@ -4505,20 +4505,29 @@ function handleCommentAdded(event) {
4505
4505
  function handleCommentDeleted({ comment }) {
4506
4506
  const entityId = comment.parent_id ?? comment.object_id;
4507
4507
  this.state.next((currentState) => {
4508
- const newCommentsByEntityId = {
4509
- ...currentState.comments_by_entity_id,
4510
- [entityId]: {
4511
- ...currentState.comments_by_entity_id[entityId],
4512
- },
4513
- };
4508
+ let newCommentsByEntityId;
4514
4509
  const index = this.getCommentIndex(comment, currentState);
4515
- if (newCommentsByEntityId?.[entityId]?.comments?.length && index !== -1) {
4510
+ if (index !== -1) {
4511
+ newCommentsByEntityId ?? (newCommentsByEntityId = {
4512
+ ...currentState.comments_by_entity_id,
4513
+ [entityId]: {
4514
+ ...currentState.comments_by_entity_id[entityId],
4515
+ },
4516
+ });
4516
4517
  newCommentsByEntityId[entityId].comments = [
4517
4518
  ...newCommentsByEntityId[entityId].comments,
4518
4519
  ];
4519
4520
  newCommentsByEntityId[entityId]?.comments?.splice(index, 1);
4520
4521
  }
4521
- delete newCommentsByEntityId[comment.id];
4522
+ if (typeof currentState.comments_by_entity_id[comment.id] !== 'undefined') {
4523
+ newCommentsByEntityId ?? (newCommentsByEntityId = {
4524
+ ...currentState.comments_by_entity_id,
4525
+ });
4526
+ delete newCommentsByEntityId[comment.id];
4527
+ }
4528
+ if (!newCommentsByEntityId) {
4529
+ return currentState;
4530
+ }
4522
4531
  return {
4523
4532
  ...currentState,
4524
4533
  comments_by_entity_id: newCommentsByEntityId,
@@ -5073,13 +5082,80 @@ function handleActivityReactionDeleted(event) {
5073
5082
  }
5074
5083
  }
5075
5084
 
5085
+ const updateNotificationStatusFromActivityMarked = (event, currentNotificationStatus, aggregatedActivities = []) => {
5086
+ if (!currentNotificationStatus) {
5087
+ return {
5088
+ changed: false,
5089
+ };
5090
+ }
5091
+ const newState = {
5092
+ ...currentNotificationStatus,
5093
+ };
5094
+ if (event.mark_all_read) {
5095
+ const allGroupIds = aggregatedActivities.map((activity) => activity.group);
5096
+ newState.read_activities = [
5097
+ ...new Set([
5098
+ ...(currentNotificationStatus.read_activities ?? []),
5099
+ ...allGroupIds,
5100
+ ]),
5101
+ ];
5102
+ }
5103
+ if (event.mark_read && event.mark_read.length > 0) {
5104
+ newState.read_activities = [
5105
+ ...new Set([
5106
+ ...(currentNotificationStatus?.read_activities ?? []),
5107
+ ...event.mark_read,
5108
+ ]),
5109
+ ];
5110
+ }
5111
+ if (event.mark_all_seen) {
5112
+ newState.last_seen_at = new Date();
5113
+ }
5114
+ return {
5115
+ changed: true,
5116
+ data: { notification_status: newState },
5117
+ };
5118
+ };
5119
+ function handleActivityMarked(event) {
5120
+ const result = updateNotificationStatusFromActivityMarked(event, this.currentState.notification_status, this.currentState.aggregated_activities);
5121
+ if (result.changed) {
5122
+ this.state.partialNext({
5123
+ notification_status: result.data?.notification_status,
5124
+ });
5125
+ }
5126
+ }
5127
+
5076
5128
  function handleFeedUpdated(event) {
5077
5129
  this.state.partialNext({ ...event.feed });
5078
5130
  }
5079
5131
 
5132
+ const updateNotificationFeedFromEvent = (event) => {
5133
+ const updates = {};
5134
+ if (event.notification_status) {
5135
+ updates.notification_status = event.notification_status;
5136
+ }
5137
+ if (event.aggregated_activities) {
5138
+ updates.aggregated_activities = event.aggregated_activities;
5139
+ }
5140
+ // Only return changed if we have actual updates
5141
+ if (Object.keys(updates).length > 0) {
5142
+ return {
5143
+ changed: true,
5144
+ data: updates,
5145
+ };
5146
+ }
5147
+ return {
5148
+ changed: false,
5149
+ };
5150
+ };
5080
5151
  function handleNotificationFeedUpdated(event) {
5081
- console.info('notification feed updated', event);
5082
- // TODO: handle notification feed updates
5152
+ const result = updateNotificationFeedFromEvent(event);
5153
+ if (result.changed) {
5154
+ this.state.partialNext({
5155
+ notification_status: result.data?.notification_status,
5156
+ aggregated_activities: result.data?.aggregated_activities,
5157
+ });
5158
+ }
5083
5159
  }
5084
5160
 
5085
5161
  function handleWatchStarted() {
@@ -5134,7 +5210,7 @@ class Feed extends FeedApi {
5134
5210
  'feeds.poll.vote_removed': Feed.noop,
5135
5211
  'feeds.activity.pinned': Feed.noop,
5136
5212
  'feeds.activity.unpinned': Feed.noop,
5137
- 'feeds.activity.marked': Feed.noop,
5213
+ 'feeds.activity.marked': handleActivityMarked.bind(this),
5138
5214
  'moderation.custom_action': Feed.noop,
5139
5215
  'moderation.flagged': Feed.noop,
5140
5216
  'moderation.mark_reviewed': Feed.noop,
@@ -6006,10 +6082,10 @@ const useFeedsClient = () => {
6006
6082
  */
6007
6083
  const useClientConnectedUser = () => {
6008
6084
  const client = useFeedsClient();
6009
- const { user } = useStateStore(client?.state, selector$a) ?? {};
6085
+ const { user } = useStateStore(client?.state, selector$c) ?? {};
6010
6086
  return user;
6011
6087
  };
6012
- const selector$a = (nextState) => ({
6088
+ const selector$c = (nextState) => ({
6013
6089
  user: nextState.connected_user,
6014
6090
  });
6015
6091
 
@@ -6018,10 +6094,10 @@ const selector$a = (nextState) => ({
6018
6094
  */
6019
6095
  const useWsConnectionState = () => {
6020
6096
  const client = useFeedsClient();
6021
- const { is_healthy } = useStateStore(client?.state, selector$9) ?? {};
6097
+ const { is_healthy } = useStateStore(client?.state, selector$b) ?? {};
6022
6098
  return { is_healthy };
6023
6099
  };
6024
- const selector$9 = (nextState) => ({
6100
+ const selector$b = (nextState) => ({
6025
6101
  is_healthy: nextState.is_ws_connection_healthy,
6026
6102
  });
6027
6103
 
@@ -6071,7 +6147,7 @@ const useStableCallback = (callback) => {
6071
6147
  const useFeedActivities = (feedFromProps) => {
6072
6148
  const feedFromContext = useFeedContext();
6073
6149
  const feed = feedFromProps ?? feedFromContext;
6074
- const data = useStateStore(feed?.state, selector$8);
6150
+ const data = useStateStore(feed?.state, selector$a);
6075
6151
  const loadNextPage = useStableCallback(async () => {
6076
6152
  if (!feed || !data?.has_next_page || data?.is_loading) {
6077
6153
  return;
@@ -6080,7 +6156,7 @@ const useFeedActivities = (feedFromProps) => {
6080
6156
  });
6081
6157
  return react.useMemo(() => ({ ...data, loadNextPage }), [data, loadNextPage]);
6082
6158
  };
6083
- const selector$8 = ({ is_loading_activities, next, activities = [] }) => ({
6159
+ const selector$a = ({ is_loading_activities, next, activities = [] }) => ({
6084
6160
  is_loading: is_loading_activities,
6085
6161
  has_next_page: typeof next !== 'undefined',
6086
6162
  activities,
@@ -6153,13 +6229,13 @@ const FeedOwnCapability = {
6153
6229
  };
6154
6230
 
6155
6231
  const stableEmptyArray = [];
6156
- const selector$7 = (currentState) => ({
6232
+ const selector$9 = (currentState) => ({
6157
6233
  oc: currentState.own_capabilities ?? stableEmptyArray,
6158
6234
  });
6159
6235
  const useOwnCapabilities = (feedFromProps) => {
6160
6236
  const feedFromContext = useFeedContext();
6161
6237
  const feed = feedFromProps ?? feedFromContext;
6162
- const { oc = stableEmptyArray } = useStateStore(feed?.state, selector$7) ?? {};
6238
+ const { oc = stableEmptyArray } = useStateStore(feed?.state, selector$9) ?? {};
6163
6239
  return react.useMemo(() => ({
6164
6240
  can_add_activity: oc.indexOf(FeedOwnCapability.ADD_ACTIVITY) > -1,
6165
6241
  can_add_activity_reaction: oc.indexOf(FeedOwnCapability.ADD_ACTIVITY_REACTION) > -1,
@@ -6194,7 +6270,7 @@ const useOwnCapabilities = (feedFromProps) => {
6194
6270
  }), [oc]);
6195
6271
  };
6196
6272
 
6197
- const selector$6 = ({ follower_count, followers, followers_pagination, }) => ({
6273
+ const selector$8 = ({ follower_count, followers, followers_pagination, }) => ({
6198
6274
  follower_count,
6199
6275
  followers,
6200
6276
  followers_pagination,
@@ -6202,7 +6278,7 @@ const selector$6 = ({ follower_count, followers, followers_pagination, }) => ({
6202
6278
  function useFollowers(feedFromProps) {
6203
6279
  const feedFromContext = useFeedContext();
6204
6280
  const feed = feedFromProps ?? feedFromContext;
6205
- const data = useStateStore(feed?.state, selector$6);
6281
+ const data = useStateStore(feed?.state, selector$8);
6206
6282
  const loadNextPage = react.useCallback((...options) => feed?.loadNextPageFollowers(...options), [feed]);
6207
6283
  return react.useMemo(() => {
6208
6284
  if (!data) {
@@ -6217,7 +6293,7 @@ function useFollowers(feedFromProps) {
6217
6293
  }, [data, loadNextPage]);
6218
6294
  }
6219
6295
 
6220
- const selector$5 = ({ following_count, following, following_pagination, }) => ({
6296
+ const selector$7 = ({ following_count, following, following_pagination, }) => ({
6221
6297
  following_count,
6222
6298
  following,
6223
6299
  following_pagination,
@@ -6225,7 +6301,7 @@ const selector$5 = ({ following_count, following, following_pagination, }) => ({
6225
6301
  function useFollowing(feedFromProps) {
6226
6302
  const feedFromContext = useFeedContext();
6227
6303
  const feed = feedFromProps ?? feedFromContext;
6228
- const data = useStateStore(feed?.state, selector$5);
6304
+ const data = useStateStore(feed?.state, selector$7);
6229
6305
  const loadNextPage = react.useCallback((...options) => feed?.loadNextPageFollowing(...options), [feed]);
6230
6306
  return react.useMemo(() => {
6231
6307
  if (!data) {
@@ -6247,9 +6323,9 @@ function useFollowing(feedFromProps) {
6247
6323
  const useFeedMetadata = (feedFromProps) => {
6248
6324
  const feedFromContext = useFeedContext();
6249
6325
  const feed = feedFromProps ?? feedFromContext;
6250
- return useStateStore(feed?.state, selector$4);
6326
+ return useStateStore(feed?.state, selector$6);
6251
6327
  };
6252
- const selector$4 = ({ follower_count = 0, following_count = 0, created_by, created_at, updated_at, }) => ({
6328
+ const selector$6 = ({ follower_count = 0, following_count = 0, created_by, created_at, updated_at, }) => ({
6253
6329
  created_by,
6254
6330
  follower_count,
6255
6331
  following_count,
@@ -6264,12 +6340,57 @@ const selector$4 = ({ follower_count = 0, following_count = 0, created_by, creat
6264
6340
  const useOwnFollows = (feedFromProps) => {
6265
6341
  const feedFromContext = useFeedContext();
6266
6342
  const feed = feedFromProps ?? feedFromContext;
6267
- return useStateStore(feed?.state, selector$3);
6343
+ return useStateStore(feed?.state, selector$5);
6268
6344
  };
6269
- const selector$3 = ({ own_follows }) => ({
6345
+ const selector$5 = ({ own_follows }) => ({
6270
6346
  own_follows,
6271
6347
  });
6272
6348
 
6349
+ const selector$4 = ({ notification_status }) => ({
6350
+ unread: notification_status?.unread ?? 0,
6351
+ unseen: notification_status?.unseen ?? 0,
6352
+ last_read_at: notification_status?.last_read_at,
6353
+ last_seen_at: notification_status?.last_seen_at,
6354
+ read_activities: notification_status?.read_activities,
6355
+ seen_activities: notification_status?.seen_activities,
6356
+ });
6357
+ function useNotificationStatus(feedFromProps) {
6358
+ const feedFromContext = useFeedContext();
6359
+ const feed = feedFromProps ?? feedFromContext;
6360
+ return useStateStore(feed?.state, selector$4);
6361
+ }
6362
+
6363
+ const selector$3 = ({ aggregated_activities }) => ({
6364
+ aggregated_activities,
6365
+ });
6366
+ function useAggregatedActivities(feedFromProps) {
6367
+ const feedFromContext = useFeedContext();
6368
+ const feed = feedFromProps ?? feedFromContext;
6369
+ return useStateStore(feed?.state, selector$3);
6370
+ }
6371
+
6372
+ const useIsAggregatedActivityRead = ({ feed: feedFromProps, aggregatedActivity, }) => {
6373
+ const feedFromContext = useFeedContext();
6374
+ const feed = feedFromProps ?? feedFromContext;
6375
+ const { read_activities: readActivities, /* last_read_at: lastReadAt */ } = useNotificationStatus(feed) ?? {};
6376
+ const group = aggregatedActivity.group;
6377
+ return react.useMemo(() =>
6378
+ // FIXME: This part of the condition does not work as marking individual groups as read also updates the last_read_at. Should be uncommented once it's fixed on the backend.
6379
+ // (lastReadAt &&
6380
+ // aggregatedActivity.updated_at.getTime() <= lastReadAt.getTime()) ||
6381
+ (readActivities ?? []).includes(group), [readActivities, group]);
6382
+ };
6383
+
6384
+ const useIsAggregatedActivitySeen = ({ feed: feedFromProps, aggregatedActivity, }) => {
6385
+ const feedFromContext = useFeedContext();
6386
+ const feed = feedFromProps ?? feedFromContext;
6387
+ const { seen_activities: seenActivities, last_seen_at: lastSeenAt } = useNotificationStatus(feed) ?? {};
6388
+ const group = aggregatedActivity.group;
6389
+ return react.useMemo(() => (lastSeenAt &&
6390
+ aggregatedActivity.updated_at.getTime() < lastSeenAt.getTime()) ||
6391
+ (seenActivities ?? []).includes(group), [lastSeenAt, aggregatedActivity.updated_at, seenActivities, group]);
6392
+ };
6393
+
6273
6394
  const StreamSearchResultsContext = react.createContext(undefined);
6274
6395
  /**
6275
6396
  * Hook to access the nearest SearchSource instance.
@@ -6386,7 +6507,7 @@ const StreamFeeds = ({ client, children }) => {
6386
6507
  };
6387
6508
  StreamFeeds.displayName = 'StreamFeeds';
6388
6509
 
6389
- const StreamFeed = ({ feed, children }) => {
6510
+ const StreamFeed = ({ feed, children, }) => {
6390
6511
  return (jsxRuntime.jsx(StreamFeedContext.Provider, { value: feed, children: children }));
6391
6512
  };
6392
6513
  StreamFeed.displayName = 'StreamFeed';
@@ -6409,6 +6530,7 @@ exports.StreamSearch = StreamSearch;
6409
6530
  exports.StreamSearchContext = StreamSearchContext;
6410
6531
  exports.StreamSearchResults = StreamSearchResults;
6411
6532
  exports.StreamSearchResultsContext = StreamSearchResultsContext;
6533
+ exports.useAggregatedActivities = useAggregatedActivities;
6412
6534
  exports.useBookmarkActions = useBookmarkActions;
6413
6535
  exports.useClientConnectedUser = useClientConnectedUser;
6414
6536
  exports.useComments = useComments;
@@ -6419,6 +6541,9 @@ exports.useFeedMetadata = useFeedMetadata;
6419
6541
  exports.useFeedsClient = useFeedsClient;
6420
6542
  exports.useFollowers = useFollowers;
6421
6543
  exports.useFollowing = useFollowing;
6544
+ exports.useIsAggregatedActivityRead = useIsAggregatedActivityRead;
6545
+ exports.useIsAggregatedActivitySeen = useIsAggregatedActivitySeen;
6546
+ exports.useNotificationStatus = useNotificationStatus;
6422
6547
  exports.useOwnCapabilities = useOwnCapabilities;
6423
6548
  exports.useOwnFollows = useOwnFollows;
6424
6549
  exports.useReactionActions = useReactionActions;