@stream-io/feeds-client 0.2.17 → 0.2.19

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 (117) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/cjs/index.js +94 -25
  3. package/dist/cjs/index.js.map +1 -1
  4. package/dist/cjs/react-bindings.js +26 -55
  5. package/dist/cjs/react-bindings.js.map +1 -1
  6. package/dist/es/index.mjs +86 -17
  7. package/dist/es/index.mjs.map +1 -1
  8. package/dist/es/react-bindings.mjs +19 -48
  9. package/dist/es/react-bindings.mjs.map +1 -1
  10. package/dist/{index-nq6SDtbt.js → feeds-client-C09giTf1.js} +322 -133
  11. package/dist/feeds-client-C09giTf1.js.map +1 -0
  12. package/dist/{index-BZL77zNq.mjs → feeds-client-CFadXO-B.mjs} +335 -146
  13. package/dist/feeds-client-CFadXO-B.mjs.map +1 -0
  14. package/dist/tsconfig.tsbuildinfo +1 -1
  15. package/dist/types/bindings/react/hooks/feed-state-hooks/useOwnCapabilities.d.ts +2 -32
  16. package/dist/types/bindings/react/hooks/feed-state-hooks/useOwnCapabilities.d.ts.map +1 -1
  17. package/dist/types/common/real-time/StableWSConnection.d.ts +3 -3
  18. package/dist/types/common/real-time/event-models.d.ts +7 -2
  19. package/dist/types/common/real-time/event-models.d.ts.map +1 -1
  20. package/dist/types/common/types.d.ts +1 -0
  21. package/dist/types/common/types.d.ts.map +1 -1
  22. package/dist/types/feed/event-handlers/activity/handle-activity-added.d.ts +4 -3
  23. package/dist/types/feed/event-handlers/activity/handle-activity-added.d.ts.map +1 -1
  24. package/dist/types/feed/event-handlers/activity/handle-activity-updated.d.ts.map +1 -1
  25. package/dist/types/feed/event-handlers/activity-updater.d.ts +44 -0
  26. package/dist/types/feed/event-handlers/activity-updater.d.ts.map +1 -0
  27. package/dist/types/feed/event-handlers/add-aggregated-activities-to-state.d.ts +6 -0
  28. package/dist/types/feed/event-handlers/add-aggregated-activities-to-state.d.ts.map +1 -0
  29. package/dist/types/feed/event-handlers/index.d.ts +3 -1
  30. package/dist/types/feed/event-handlers/index.d.ts.map +1 -1
  31. package/dist/types/feed/event-handlers/{aggregated-feed/handle-aggregated-feed-updated.d.ts → notification-feed/handle-notification-feed-updated.d.ts} +2 -11
  32. package/dist/types/feed/event-handlers/notification-feed/handle-notification-feed-updated.d.ts.map +1 -0
  33. package/dist/types/feed/event-handlers/notification-feed/index.d.ts +2 -0
  34. package/dist/types/feed/event-handlers/notification-feed/index.d.ts.map +1 -0
  35. package/dist/types/feed/event-handlers/story-feeds/handle-story-feeds-updated.d.ts +15 -0
  36. package/dist/types/feed/event-handlers/story-feeds/handle-story-feeds-updated.d.ts.map +1 -0
  37. package/dist/types/feed/event-handlers/story-feeds/index.d.ts +2 -0
  38. package/dist/types/feed/event-handlers/story-feeds/index.d.ts.map +1 -0
  39. package/dist/types/feed/feed.d.ts +10 -4
  40. package/dist/types/feed/feed.d.ts.map +1 -1
  41. package/dist/types/feeds-client/feeds-client.d.ts +14 -4
  42. package/dist/types/feeds-client/feeds-client.d.ts.map +1 -1
  43. package/dist/types/gen/feeds/FeedsApi.d.ts.map +1 -1
  44. package/dist/types/gen/models/index.d.ts +42 -451
  45. package/dist/types/gen/models/index.d.ts.map +1 -1
  46. package/dist/types/utils/throttling/index.d.ts +3 -0
  47. package/dist/types/utils/throttling/index.d.ts.map +1 -0
  48. package/dist/types/utils/throttling/throttle.d.ts +34 -0
  49. package/dist/types/utils/throttling/throttle.d.ts.map +1 -0
  50. package/dist/types/utils/throttling/throttled-get-batched-own-capabilities.d.ts +14 -0
  51. package/dist/types/utils/throttling/throttled-get-batched-own-capabilities.d.ts.map +1 -0
  52. package/package.json +7 -3
  53. package/react-bindings.d.ts +11 -0
  54. package/react-bindings.js +7 -0
  55. package/react-bindings.mjs +11 -0
  56. package/src/bindings/react/hooks/feed-state-hooks/useOwnCapabilities.ts +21 -73
  57. package/src/common/real-time/event-models.ts +8 -2
  58. package/src/common/types.ts +1 -0
  59. package/src/feed/event-handlers/activity/handle-activity-added.ts +18 -12
  60. package/src/feed/event-handlers/activity/handle-activity-updated.ts +12 -16
  61. package/src/feed/event-handlers/activity-updater.ts +15 -0
  62. package/src/feed/event-handlers/add-aggregated-activities-to-state.ts +72 -0
  63. package/src/feed/event-handlers/index.ts +3 -1
  64. package/src/feed/event-handlers/{aggregated-feed/handle-aggregated-feed-updated.ts → notification-feed/handle-notification-feed-updated.ts} +2 -94
  65. package/src/feed/event-handlers/notification-feed/index.ts +1 -0
  66. package/src/feed/event-handlers/story-feeds/handle-story-feeds-updated.ts +122 -0
  67. package/src/feed/event-handlers/story-feeds/index.ts +1 -0
  68. package/src/feed/feed.ts +30 -3
  69. package/src/feeds-client/feeds-client.ts +127 -6
  70. package/src/gen/feeds/FeedsApi.ts +5 -0
  71. package/src/gen/model-decoders/decoders.ts +10 -4
  72. package/src/gen/models/index.ts +75 -834
  73. package/src/test-utils/response-generators.ts +37 -1
  74. package/src/utils/throttling/index.ts +2 -0
  75. package/src/utils/throttling/throttle.ts +123 -0
  76. package/src/utils/throttling/throttled-get-batched-own-capabilities.ts +42 -0
  77. package/dist/index-BZL77zNq.mjs.map +0 -1
  78. package/dist/index-nq6SDtbt.js.map +0 -1
  79. package/dist/types/feed/event-handlers/aggregated-feed/handle-aggregated-feed-updated.d.ts.map +0 -1
  80. package/dist/types/feed/event-handlers/aggregated-feed/index.d.ts +0 -2
  81. package/dist/types/feed/event-handlers/aggregated-feed/index.d.ts.map +0 -1
  82. package/src/feed/event-handlers/activity/activity-marked-utils.test.ts +0 -208
  83. package/src/feed/event-handlers/activity/activity-reaction-utils.test.ts +0 -371
  84. package/src/feed/event-handlers/activity/activity-utils.test.ts +0 -252
  85. package/src/feed/event-handlers/activity/handle-activity-added.test.ts +0 -86
  86. package/src/feed/event-handlers/activity/handle-activity-deleted.test.ts +0 -117
  87. package/src/feed/event-handlers/activity/handle-activity-pinned.test.ts +0 -60
  88. package/src/feed/event-handlers/activity/handle-activity-reaction-added.test.ts +0 -257
  89. package/src/feed/event-handlers/activity/handle-activity-reaction-deleted.test.ts +0 -317
  90. package/src/feed/event-handlers/activity/handle-activity-reaction-updated.test.ts +0 -282
  91. package/src/feed/event-handlers/activity/handle-activity-unpinned.test.ts +0 -95
  92. package/src/feed/event-handlers/activity/handle-activity-updated.test.ts +0 -245
  93. package/src/feed/event-handlers/aggregated-feed/handle-aggregated-feed-updated.test.ts +0 -644
  94. package/src/feed/event-handlers/aggregated-feed/index.ts +0 -1
  95. package/src/feed/event-handlers/bookmark/bookmark-utils.test.ts +0 -521
  96. package/src/feed/event-handlers/bookmark/handle-bookmark-added.test.ts +0 -178
  97. package/src/feed/event-handlers/bookmark/handle-bookmark-deleted.test.ts +0 -188
  98. package/src/feed/event-handlers/bookmark/handle-bookmark-updated.test.ts +0 -196
  99. package/src/feed/event-handlers/comment/handle-comment-added.test.ts +0 -271
  100. package/src/feed/event-handlers/comment/handle-comment-deleted.test.ts +0 -255
  101. package/src/feed/event-handlers/comment/handle-comment-reaction-added.test.ts +0 -329
  102. package/src/feed/event-handlers/comment/handle-comment-reaction-deleted.test.ts +0 -343
  103. package/src/feed/event-handlers/comment/handle-comment-reaction-updated.test.ts +0 -350
  104. package/src/feed/event-handlers/comment/handle-comment-updated.test.ts +0 -267
  105. package/src/feed/event-handlers/comment/utils/update-comment-count.test.ts +0 -322
  106. package/src/feed/event-handlers/feed-member/handle-feed-member-added.test.ts +0 -75
  107. package/src/feed/event-handlers/feed-member/handle-feed-member-removed.test.ts +0 -82
  108. package/src/feed/event-handlers/feed-member/handle-feed-member-updated.test.ts +0 -84
  109. package/src/feed/event-handlers/follow/follow-state-update-queue.test.ts +0 -219
  110. package/src/feed/event-handlers/follow/handle-follow-created.test.ts +0 -250
  111. package/src/feed/event-handlers/follow/handle-follow-deleted.test.ts +0 -268
  112. package/src/feed/event-handlers/follow/handle-follow-updated.test.ts +0 -131
  113. package/src/feed/feed.test.ts +0 -90
  114. package/src/feeds-client/event-handlers/user/handle-user-updated.test.ts +0 -53
  115. package/src/utils/event-triggered-by-connected-user.test.ts +0 -73
  116. package/src/utils/state-update-queue.test.ts +0 -129
  117. package/src/utils/unique-array-merge.test.ts +0 -179
@@ -1,84 +1,32 @@
1
- import { useMemo } from 'react';
2
- import { type Feed, type FeedState, FeedOwnCapability } from '@self';
1
+ import type { FeedOwnCapability, Feed, FeedsClientState } from '@self';
3
2
  import { useStateStore } from '@stream-io/state-store/react-bindings';
4
3
  import { useFeedContext } from '../../contexts/StreamFeedContext';
4
+ import { useFeedsClient } from '../../contexts/StreamFeedsContext';
5
+ import { useCallback } from 'react';
5
6
 
6
7
  const stableEmptyArray: readonly FeedOwnCapability[] = [];
7
8
 
8
- const selector = (currentState: FeedState) => ({
9
- oc: currentState.own_capabilities ?? stableEmptyArray,
10
- });
11
-
12
- type KebabToSnakeCase<S extends string> = S extends `${infer T}-${infer U}`
13
- ? `${T}_${KebabToSnakeCase<U>}`
14
- : S;
15
-
16
9
  export const useOwnCapabilities = (feedFromProps?: Feed) => {
10
+ const client = useFeedsClient();
17
11
  const feedFromContext = useFeedContext();
18
12
  const feed = feedFromProps ?? feedFromContext;
13
+ const fid = feed?.feed;
14
+
15
+ const selector = useCallback((currentState: FeedsClientState) => {
16
+ if (!fid) {
17
+ return { feedOwnCapabilities: stableEmptyArray };
18
+ }
19
+
20
+ return {
21
+ feedOwnCapabilities:
22
+ currentState.own_capabilities_by_fid[fid] ?? stableEmptyArray,
23
+ };
24
+ }, [fid]);
25
+
26
+ const { feedOwnCapabilities = stableEmptyArray } =
27
+ useStateStore(client?.state, selector) ?? {};
19
28
 
20
- const { oc = stableEmptyArray } = useStateStore(feed?.state, selector) ?? {};
29
+ // console.log('GETTING CAPA: ', feed?.feed, feedOwnCapabilities);
21
30
 
22
- return useMemo(
23
- () => {
24
- const capabilitiesSet = new Set(oc);
25
- return ({
26
- can_add_activity: capabilitiesSet.has(FeedOwnCapability.ADD_ACTIVITY),
27
- can_add_activity_bookmark:
28
- capabilitiesSet.has(FeedOwnCapability.ADD_ACTIVITY_BOOKMARK),
29
- can_add_activity_reaction:
30
- capabilitiesSet.has(FeedOwnCapability.ADD_ACTIVITY_REACTION),
31
- can_add_comment: capabilitiesSet.has(FeedOwnCapability.ADD_COMMENT),
32
- can_add_comment_reaction:
33
- capabilitiesSet.has(FeedOwnCapability.ADD_COMMENT_REACTION),
34
- can_create_feed: capabilitiesSet.has(FeedOwnCapability.CREATE_FEED),
35
- can_delete_any_activity:
36
- capabilitiesSet.has(FeedOwnCapability.DELETE_ANY_ACTIVITY),
37
- can_delete_any_comment:
38
- capabilitiesSet.has(FeedOwnCapability.DELETE_ANY_COMMENT),
39
- can_delete_feed: capabilitiesSet.has(FeedOwnCapability.DELETE_FEED),
40
- can_delete_own_activity:
41
- capabilitiesSet.has(FeedOwnCapability.DELETE_OWN_ACTIVITY),
42
- can_delete_own_activity_bookmark:
43
- capabilitiesSet.has(FeedOwnCapability.DELETE_OWN_ACTIVITY_BOOKMARK),
44
- can_delete_own_activity_reaction:
45
- capabilitiesSet.has(FeedOwnCapability.DELETE_OWN_ACTIVITY_REACTION),
46
- can_delete_own_comment:
47
- capabilitiesSet.has(FeedOwnCapability.DELETE_OWN_COMMENT),
48
- can_delete_own_comment_reaction:
49
- capabilitiesSet.has(FeedOwnCapability.DELETE_OWN_COMMENT_REACTION),
50
- can_follow: capabilitiesSet.has(FeedOwnCapability.FOLLOW),
51
- can_pin_activity: capabilitiesSet.has(FeedOwnCapability.PIN_ACTIVITY),
52
- can_query_feed_members:
53
- capabilitiesSet.has(FeedOwnCapability.QUERY_FEED_MEMBERS),
54
- can_query_follows:
55
- capabilitiesSet.has(FeedOwnCapability.QUERY_FOLLOWS),
56
- can_read_activities:
57
- capabilitiesSet.has(FeedOwnCapability.READ_ACTIVITIES),
58
- can_read_feed: capabilitiesSet.has(FeedOwnCapability.READ_FEED),
59
- can_unfollow: capabilitiesSet.has(FeedOwnCapability.UNFOLLOW),
60
- can_update_any_activity:
61
- capabilitiesSet.has(FeedOwnCapability.UPDATE_ANY_ACTIVITY),
62
- can_update_any_comment:
63
- capabilitiesSet.has(FeedOwnCapability.UPDATE_ANY_COMMENT),
64
- can_update_feed: capabilitiesSet.has(FeedOwnCapability.UPDATE_FEED),
65
- can_update_feed_followers:
66
- capabilitiesSet.has(FeedOwnCapability.UPDATE_FEED_FOLLOWERS),
67
- can_update_feed_members:
68
- capabilitiesSet.has(FeedOwnCapability.UPDATE_FEED_MEMBERS),
69
- can_update_own_activity:
70
- capabilitiesSet.has(FeedOwnCapability.UPDATE_OWN_ACTIVITY),
71
- can_update_own_activity_bookmark:
72
- capabilitiesSet.has(FeedOwnCapability.UPDATE_OWN_ACTIVITY_BOOKMARK),
73
- can_update_own_comment:
74
- capabilitiesSet.has(FeedOwnCapability.UPDATE_OWN_COMMENT),
75
- }) satisfies Record<
76
- `can_${KebabToSnakeCase<
77
- (typeof FeedOwnCapability)[keyof typeof FeedOwnCapability]
78
- >}`,
79
- boolean
80
- >;
81
- },
82
- [oc],
83
- );
31
+ return feedOwnCapabilities;
84
32
  };
@@ -1,4 +1,5 @@
1
- import type { OwnUser } from '../../gen/models';
1
+ import type { OwnUser } from '@self';
2
+ import type { StreamApiError } from '@self';
2
3
 
3
4
  export interface ConnectionChangedEvent {
4
5
  type: 'connection.changed';
@@ -39,9 +40,10 @@ export interface ConnectedEvent {
39
40
 
40
41
  export enum UnhandledErrorType {
41
42
  ReconnectionReconciliation = 'reconnection-reconciliation',
43
+ FetchingOwnCapabilitiesOnNewActivity = 'fetching-own-capabilities-on-new-activity',
42
44
  }
43
45
 
44
- export type SyncFailure = { feed: string, reason: unknown };
46
+ export type SyncFailure = { feed: string; reason: unknown };
45
47
 
46
48
  export type UnhandledErrorEvent = {
47
49
  type: 'errors.unhandled';
@@ -51,4 +53,8 @@ export type UnhandledErrorEvent = {
51
53
  error_type: UnhandledErrorType.ReconnectionReconciliation;
52
54
  failures: SyncFailure[];
53
55
  }
56
+ | {
57
+ error_type: UnhandledErrorType.FetchingOwnCapabilitiesOnNewActivity;
58
+ error: StreamApiError;
59
+ }
54
60
  );
@@ -6,6 +6,7 @@ export type FeedsClientOptions = {
6
6
  base_url?: string;
7
7
  timeout?: number;
8
8
  configure_loggers_options?: ConfigureLoggersOptions;
9
+ query_batch_own_capabilties_throttling_interval?: number;
9
10
  };
10
11
 
11
12
  export type RateLimit = {
@@ -1,6 +1,6 @@
1
1
  import type { Feed } from '../../feed';
2
2
  import type { ActivityResponse } from '../../../gen/models';
3
- import type { EventPayload, UpdateStateResult } from '../../../types-internal';
3
+ import type { EventPayload } from '../../../types-internal';
4
4
 
5
5
  export function addActivitiesToState(
6
6
  this: Feed,
@@ -8,20 +8,18 @@ export function addActivitiesToState(
8
8
  activities: ActivityResponse[] | undefined,
9
9
  position: 'start' | 'end',
10
10
  ) {
11
- let result: UpdateStateResult<{ activities: ActivityResponse[] }>;
12
11
  if (activities === undefined) {
13
- activities = [];
14
- result = {
15
- changed: true,
16
- activities,
17
- };
18
- } else {
19
- result = {
12
+ return {
20
13
  changed: false,
21
- activities,
14
+ activities: [],
22
15
  };
23
16
  }
24
17
 
18
+ let result = {
19
+ changed: false,
20
+ activities,
21
+ };
22
+
25
23
  const newActivitiesDeduplicated: ActivityResponse[] = [];
26
24
  newActivities.forEach((newActivityResponse) => {
27
25
  if (!this.hasActivity(newActivityResponse.id)) {
@@ -51,10 +49,18 @@ export function handleActivityAdded(
51
49
  const result = addActivitiesToState.bind(this)(
52
50
  [event.activity],
53
51
  currentActivities,
54
- 'start',
52
+ this.currentState.addNewActivitiesTo,
55
53
  );
56
54
  if (result.changed) {
57
- this.client.hydratePollCache([event.activity]);
55
+ const activity = event.activity;
56
+ this.client.hydratePollCache([activity]);
57
+
58
+ const currentFeed = activity.current_feed;
59
+
60
+ if (currentFeed) {
61
+ this.client.hydrateCapabilitiesCache([currentFeed]);
62
+ }
63
+
58
64
  this.state.partialNext({ activities: result.activities });
59
65
  }
60
66
  }
@@ -1,5 +1,8 @@
1
1
  import type { Feed } from '../../../feed';
2
- import type { ActivityPinResponse, ActivityResponse } from '../../../gen/models';
2
+ import type {
3
+ ActivityPinResponse,
4
+ ActivityResponse,
5
+ } from '../../../gen/models';
3
6
  import type { EventPayload, PartializeAllBut } from '../../../types-internal';
4
7
  import {
5
8
  getStateUpdateQueueId,
@@ -7,25 +10,14 @@ import {
7
10
  updateEntityInArray,
8
11
  } from '../../../utils';
9
12
  import { eventTriggeredByConnectedUser } from '../../../utils/event-triggered-by-connected-user';
13
+ import { updateActivity } from '../activity-updater';
10
14
 
11
15
  export type ActivityUpdatedPayload = PartializeAllBut<
12
16
  EventPayload<'feeds.activity.updated'>,
13
17
  'activity'
14
18
  >;
15
19
 
16
- const sharedUpdateActivity = ({
17
- currentActivity,
18
- event,
19
- }: {
20
- currentActivity: ActivityResponse;
21
- event: ActivityUpdatedPayload;
22
- }) => {
23
- return {
24
- ...event.activity,
25
- own_reactions: currentActivity.own_reactions,
26
- own_bookmarks: currentActivity.own_bookmarks,
27
- };
28
- };
20
+ const sharedUpdateActivity = updateActivity;
29
21
 
30
22
  export const updateActivityInState = (
31
23
  event: ActivityUpdatedPayload,
@@ -37,7 +29,7 @@ export const updateActivityInState = (
37
29
  updater: (matchedActivity) =>
38
30
  sharedUpdateActivity({
39
31
  currentActivity: matchedActivity,
40
- event,
32
+ newActivtiy: event.activity,
41
33
  }),
42
34
  });
43
35
 
@@ -52,7 +44,7 @@ export const updatePinnedActivityInState = (
52
44
  updater: (matchedPinnedActivity) => {
53
45
  const newActivity = sharedUpdateActivity({
54
46
  currentActivity: matchedPinnedActivity.activity,
55
- event,
47
+ newActivtiy: event.activity,
56
48
  });
57
49
 
58
50
  if (newActivity === matchedPinnedActivity.activity) {
@@ -100,6 +92,10 @@ export function handleActivityUpdated(
100
92
  if (result1?.changed || result2.changed) {
101
93
  this.client.hydratePollCache([payload.activity]);
102
94
 
95
+ if (payload.activity.current_feed) {
96
+ this.client.hydrateCapabilitiesCache([payload.activity.current_feed]);
97
+ }
98
+
103
99
  this.state.partialNext({
104
100
  activities: result1?.changed ? result1.entities : currentActivities,
105
101
  pinned_activities: result2.entities,
@@ -0,0 +1,15 @@
1
+ import type { ActivityResponse } from '../../gen/models';
2
+
3
+ export const updateActivity = ({
4
+ currentActivity,
5
+ newActivtiy,
6
+ }: {
7
+ currentActivity: ActivityResponse;
8
+ newActivtiy: ActivityResponse;
9
+ }) => {
10
+ return {
11
+ ...newActivtiy,
12
+ own_reactions: currentActivity.own_reactions,
13
+ own_bookmarks: currentActivity.own_bookmarks,
14
+ };
15
+ };
@@ -0,0 +1,72 @@
1
+ import type { AggregatedActivityResponse } from '../../gen/models';
2
+ import type { UpdateStateResult } from '../../types-internal';
3
+ import { replaceUniqueArrayMerge, uniqueArrayMerge } from '../../utils';
4
+ import { updateActivity } from './activity-updater';
5
+
6
+ export const addAggregatedActivitiesToState = (
7
+ newAggregatedActivities: AggregatedActivityResponse[],
8
+ aggregatedActivities: AggregatedActivityResponse[] | undefined,
9
+ position: 'start' | 'end' | 'replace',
10
+ ) => {
11
+ let result: UpdateStateResult<{
12
+ aggregated_activities: AggregatedActivityResponse[];
13
+ }>;
14
+ if (newAggregatedActivities.length === 0) {
15
+ result = {
16
+ changed: false,
17
+ aggregated_activities: aggregatedActivities ?? [],
18
+ };
19
+ } else {
20
+ result = {
21
+ changed: true,
22
+ aggregated_activities: [],
23
+ };
24
+
25
+ // Merge update activities in the group
26
+ newAggregatedActivities.forEach((newAggregatedActivity) => {
27
+ const existingAggregatedActivity = aggregatedActivities?.find(
28
+ (a) => a.group === newAggregatedActivity.group,
29
+ );
30
+ if (existingAggregatedActivity) {
31
+ for (let i = 0; i < newAggregatedActivity.activities.length; i++) {
32
+ const activity = newAggregatedActivity.activities[i];
33
+ const existingActivity = existingAggregatedActivity.activities.find(
34
+ (a) => a.id === activity.id,
35
+ );
36
+ if (existingActivity) {
37
+ newAggregatedActivity.activities[i] = updateActivity({
38
+ currentActivity: existingActivity,
39
+ newActivtiy: activity,
40
+ });
41
+ }
42
+ }
43
+ }
44
+ });
45
+
46
+ switch (position) {
47
+ case 'start':
48
+ result.aggregated_activities = uniqueArrayMerge(
49
+ newAggregatedActivities,
50
+ aggregatedActivities ?? [],
51
+ (a) => a.group,
52
+ );
53
+ break;
54
+ case 'end':
55
+ result.aggregated_activities = uniqueArrayMerge(
56
+ aggregatedActivities ?? [],
57
+ newAggregatedActivities,
58
+ (a) => a.group,
59
+ );
60
+ break;
61
+ case 'replace':
62
+ result.aggregated_activities = replaceUniqueArrayMerge(
63
+ aggregatedActivities ?? [],
64
+ newAggregatedActivities,
65
+ (a) => a.group,
66
+ );
67
+ break;
68
+ }
69
+ }
70
+
71
+ return result;
72
+ };
@@ -4,5 +4,7 @@ export * from './feed-member';
4
4
  export * from './bookmark';
5
5
  export * from './activity';
6
6
  export * from './feed';
7
- export * from './aggregated-feed';
7
+ export * from './notification-feed';
8
+ export * from './story-feeds';
8
9
  export * from './watch';
10
+ export * from './add-aggregated-activities-to-state';
@@ -3,57 +3,9 @@ import type {
3
3
  AggregatedActivityResponse,
4
4
  NotificationFeedUpdatedEvent,
5
5
  NotificationStatusResponse,
6
- StoriesFeedUpdatedEvent,
7
6
  } from '../../../gen/models';
8
7
  import type { EventPayload, UpdateStateResult } from '../../../types-internal';
9
- import { replaceUniqueArrayMerge, uniqueArrayMerge } from '../../../utils';
10
-
11
- export const addAggregatedActivitiesToState = (
12
- newAggregatedActivities: AggregatedActivityResponse[],
13
- aggregatedActivities: AggregatedActivityResponse[] | undefined,
14
- position: 'start' | 'end' | 'replace',
15
- ) => {
16
- let result: UpdateStateResult<{
17
- aggregated_activities: AggregatedActivityResponse[];
18
- }>;
19
- if (newAggregatedActivities.length === 0) {
20
- result = {
21
- changed: false,
22
- aggregated_activities: [],
23
- };
24
- } else {
25
- result = {
26
- changed: true,
27
- aggregated_activities: [],
28
- };
29
- }
30
-
31
- switch (position) {
32
- case 'start':
33
- result.aggregated_activities = uniqueArrayMerge(
34
- newAggregatedActivities,
35
- aggregatedActivities ?? [],
36
- (a) => a.group,
37
- );
38
- break;
39
- case 'end':
40
- result.aggregated_activities = uniqueArrayMerge(
41
- aggregatedActivities ?? [],
42
- newAggregatedActivities,
43
- (a) => a.group,
44
- );
45
- break;
46
- case 'replace':
47
- result.aggregated_activities = replaceUniqueArrayMerge(
48
- aggregatedActivities ?? [],
49
- newAggregatedActivities,
50
- (a) => a.group,
51
- );
52
- break;
53
- }
54
-
55
- return result;
56
- };
8
+ import { addAggregatedActivitiesToState } from '../add-aggregated-activities-to-state';
57
9
 
58
10
  export const updateNotificationStatus = (
59
11
  newNotificationStatus?: NotificationStatusResponse,
@@ -94,7 +46,7 @@ export const updateNotificationFeedFromEvent = (
94
46
  aggregated_activities?: AggregatedActivityResponse[];
95
47
  } = {};
96
48
 
97
- if (event.notification_status && currentNotificationStatus) {
49
+ if (event.notification_status) {
98
50
  const notificationStatusResult = updateNotificationStatus(
99
51
  event.notification_status,
100
52
  currentNotificationStatus,
@@ -147,47 +99,3 @@ export function handleNotificationFeedUpdated(
147
99
  });
148
100
  }
149
101
  }
150
-
151
- export function updateStoriesFeedFromEvent(
152
- aggregatedActivities: AggregatedActivityResponse[] | undefined,
153
- event: StoriesFeedUpdatedEvent,
154
- ): UpdateStateResult<{
155
- data?: {
156
- aggregated_activities?: AggregatedActivityResponse[];
157
- };
158
- }> {
159
- if (!aggregatedActivities) {
160
- return {
161
- changed: false,
162
- };
163
- }
164
-
165
- if (event.aggregated_activities) {
166
- const result = addAggregatedActivitiesToState(
167
- event.aggregated_activities,
168
- aggregatedActivities,
169
- 'replace',
170
- );
171
-
172
- return result;
173
- }
174
-
175
- return {
176
- changed: false,
177
- };
178
- }
179
-
180
- export function handleStoriesFeedUpdated(
181
- this: Feed,
182
- event: EventPayload<'feeds.stories_feed.updated'>,
183
- ) {
184
- const result = updateStoriesFeedFromEvent(
185
- this.currentState.aggregated_activities,
186
- event,
187
- );
188
- if (result.changed) {
189
- this.state.partialNext({
190
- aggregated_activities: result.data?.aggregated_activities,
191
- });
192
- }
193
- }
@@ -0,0 +1 @@
1
+ export * from './handle-notification-feed-updated';
@@ -0,0 +1,122 @@
1
+ import type {
2
+ ActivityResponse,
3
+ AggregatedActivityResponse,
4
+ StoriesFeedUpdatedEvent,
5
+ } from '../../../gen/models';
6
+ import type { UpdateStateResult, EventPayload } from '../../../types-internal';
7
+ import type { Feed } from '../../feed';
8
+ import { updateActivity } from '../activity-updater';
9
+ import { addAggregatedActivitiesToState } from '../add-aggregated-activities-to-state';
10
+
11
+ export const updateActivities = (
12
+ activitiesToUpsert: ActivityResponse[],
13
+ currentActivities: ActivityResponse[] | undefined,
14
+ ) => {
15
+ if (
16
+ !activitiesToUpsert ||
17
+ activitiesToUpsert.length === 0 ||
18
+ !currentActivities
19
+ ) {
20
+ return {
21
+ changed: false,
22
+ activities: currentActivities ?? [],
23
+ };
24
+ }
25
+
26
+ const result: ActivityResponse[] = [];
27
+ for (let i = 0; i < currentActivities.length; i++) {
28
+ const activity = currentActivities[i];
29
+ const updatedActivity = activitiesToUpsert.find(
30
+ (a) => a.id === activity.id,
31
+ );
32
+ if (updatedActivity) {
33
+ result.push(
34
+ updateActivity({
35
+ currentActivity: activity,
36
+ newActivtiy: updatedActivity,
37
+ }),
38
+ );
39
+ } else {
40
+ result.push(activity);
41
+ }
42
+ }
43
+
44
+ return {
45
+ changed: true,
46
+ activities: result,
47
+ };
48
+ };
49
+
50
+ export function updateStoriesFeedFromEvent(
51
+ aggregatedActivities: AggregatedActivityResponse[] | undefined,
52
+ activities: ActivityResponse[] | undefined,
53
+ event: StoriesFeedUpdatedEvent,
54
+ ): UpdateStateResult<{
55
+ data?: {
56
+ aggregated_activities?: AggregatedActivityResponse[];
57
+ activities?: ActivityResponse[];
58
+ };
59
+ }> {
60
+ if (
61
+ (!aggregatedActivities &&
62
+ event.aggregated_activities &&
63
+ event.aggregated_activities?.length > 0) ||
64
+ (!activities && event.activities && event.activities?.length > 0)
65
+ ) {
66
+ return {
67
+ changed: false,
68
+ };
69
+ }
70
+
71
+ const result = {
72
+ changed: true,
73
+ data: {
74
+ aggregated_activities: aggregatedActivities,
75
+ activities: activities,
76
+ },
77
+ };
78
+ if (event.aggregated_activities) {
79
+ const aggregatedActivitiesResult = addAggregatedActivitiesToState(
80
+ event.aggregated_activities,
81
+ aggregatedActivities,
82
+ 'replace',
83
+ );
84
+
85
+ if (aggregatedActivitiesResult.changed) {
86
+ result.data.aggregated_activities =
87
+ aggregatedActivitiesResult.aggregated_activities;
88
+ }
89
+ }
90
+
91
+ if (event.activities) {
92
+ const activitiesResult = updateActivities(event.activities, activities);
93
+ if (activitiesResult.changed) {
94
+ result.data.activities = activitiesResult.activities;
95
+ }
96
+ }
97
+
98
+ if (event.aggregated_activities || event.activities) {
99
+ return result;
100
+ }
101
+
102
+ return {
103
+ changed: false,
104
+ };
105
+ }
106
+
107
+ export function handleStoriesFeedUpdated(
108
+ this: Feed,
109
+ event: EventPayload<'feeds.stories_feed.updated'>,
110
+ ) {
111
+ const result = updateStoriesFeedFromEvent(
112
+ this.currentState.aggregated_activities,
113
+ this.currentState.activities,
114
+ event,
115
+ );
116
+ if (result.changed) {
117
+ this.state.partialNext({
118
+ aggregated_activities: result.data?.aggregated_activities,
119
+ activities: result.data?.activities,
120
+ });
121
+ }
122
+ }
@@ -0,0 +1 @@
1
+ export * from './handle-story-feeds-updated';
package/src/feed/feed.ts CHANGED
@@ -62,7 +62,7 @@ import { checkHasAnotherPage, Constants, uniqueArrayMerge } from '../utils';
62
62
 
63
63
  export type FeedState = Omit<
64
64
  Partial<GetOrCreateFeedResponse & FeedResponse>,
65
- 'feed' | 'duration'
65
+ 'feed' | 'own_capabilities' | 'duration'
66
66
  > & {
67
67
  /**
68
68
  * True when loading state using `getOrCreate`
@@ -131,6 +131,12 @@ export type FeedState = Omit<
131
131
  * `true` if the feed is receiving real-time updates via WebSocket
132
132
  */
133
133
  watch: boolean;
134
+
135
+ /**
136
+ * When a new activity is received from a WebSocket event by default it's added to the start of the list. You can change this to `end` to add it to the end of the list.
137
+ * Useful for story feeds.
138
+ */
139
+ addNewActivitiesTo: 'start' | 'end';
134
140
  };
135
141
 
136
142
  type EventHandlerByEventType = {
@@ -212,6 +218,7 @@ export class Feed extends FeedApi {
212
218
  id: string,
213
219
  data?: FeedResponse,
214
220
  watch = false,
221
+ addNewActivitiesTo: 'start' | 'end' = 'start',
215
222
  ) {
216
223
  super(client, groupId, id);
217
224
  this.state = new StateStore<FeedState>({
@@ -223,6 +230,7 @@ export class Feed extends FeedApi {
223
230
  is_loading_activities: false,
224
231
  comments_by_entity_id: {},
225
232
  watch,
233
+ addNewActivitiesTo,
226
234
  });
227
235
  this.client = client;
228
236
 
@@ -250,6 +258,10 @@ export class Feed extends FeedApi {
250
258
  return this.state.getLatestValue();
251
259
  }
252
260
 
261
+ set addNewActivitiesTo(value: 'start' | 'end') {
262
+ this.state.partialNext({ addNewActivitiesTo: value });
263
+ }
264
+
253
265
  hasActivity(activityId: string) {
254
266
  return this.indexedActivityIds.has(activityId);
255
267
  }
@@ -276,6 +288,16 @@ export class Feed extends FeedApi {
276
288
 
277
289
  try {
278
290
  const response = await super.getOrCreate(request);
291
+
292
+ const currentActivityFeeds = [];
293
+ for (const activity of response.activities) {
294
+ if (activity.current_feed) {
295
+ currentActivityFeeds.push(activity.current_feed);
296
+ }
297
+ }
298
+
299
+ this.client.hydrateCapabilitiesCache([response.feed, ...currentActivityFeeds]);
300
+
279
301
  if (request?.next) {
280
302
  const { activities: currentActivities = [] } = this.currentState;
281
303
 
@@ -837,11 +859,16 @@ export class Feed extends FeedApi {
837
859
  });
838
860
  }
839
861
 
840
- addActivity(request: Omit<ActivityRequest, 'feeds'>) {
841
- return this.feedsApi.addActivity({
862
+ async addActivity(request: Omit<ActivityRequest, 'feeds'>) {
863
+ const response = await this.client.addActivity({
842
864
  ...request,
843
865
  feeds: [this.feed],
844
866
  });
867
+ const currentFeed = response.activity.current_feed;
868
+ if (currentFeed) {
869
+ this.client.hydrateCapabilitiesCache([currentFeed]);
870
+ }
871
+ return response;
845
872
  }
846
873
 
847
874
  on = this.eventDispatcher.on;