@stream-io/feeds-client 0.1.10 → 0.2.0

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 (168) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/@react-bindings/contexts/StreamFeedContext.d.ts +1 -1
  3. package/dist/@react-bindings/contexts/StreamFeedsContext.d.ts +1 -1
  4. package/dist/@react-bindings/hooks/feed-state-hooks/useComments.d.ts +1 -1
  5. package/dist/@react-bindings/hooks/feed-state-hooks/useFeedActivities.d.ts +1 -1
  6. package/dist/@react-bindings/hooks/feed-state-hooks/useFeedMetadata.d.ts +1 -1
  7. package/dist/@react-bindings/hooks/feed-state-hooks/useFollowers.d.ts +1 -1
  8. package/dist/@react-bindings/hooks/feed-state-hooks/useFollowing.d.ts +1 -1
  9. package/dist/@react-bindings/hooks/feed-state-hooks/useOwnCapabilities.d.ts +1 -1
  10. package/dist/@react-bindings/hooks/feed-state-hooks/useOwnFollows.d.ts +1 -1
  11. package/dist/@react-bindings/hooks/useCreateFeedsClient.d.ts +1 -1
  12. package/dist/@react-bindings/wrappers/StreamFeed.d.ts +1 -1
  13. package/dist/index-react-bindings.browser.cjs +1720 -1601
  14. package/dist/index-react-bindings.browser.cjs.map +1 -1
  15. package/dist/index-react-bindings.browser.js +1720 -1601
  16. package/dist/index-react-bindings.browser.js.map +1 -1
  17. package/dist/index-react-bindings.node.cjs +1720 -1601
  18. package/dist/index-react-bindings.node.cjs.map +1 -1
  19. package/dist/index-react-bindings.node.js +1720 -1601
  20. package/dist/index-react-bindings.node.js.map +1 -1
  21. package/dist/index.browser.cjs +1724 -1602
  22. package/dist/index.browser.cjs.map +1 -1
  23. package/dist/index.browser.js +1722 -1603
  24. package/dist/index.browser.js.map +1 -1
  25. package/dist/index.d.ts +2 -2
  26. package/dist/index.node.cjs +1724 -1602
  27. package/dist/index.node.cjs.map +1 -1
  28. package/dist/index.node.js +1722 -1603
  29. package/dist/index.node.js.map +1 -1
  30. package/dist/src/common/ActivitySearchSource.d.ts +1 -1
  31. package/dist/src/common/FeedSearchSource.d.ts +2 -2
  32. package/dist/src/common/Poll.d.ts +1 -1
  33. package/dist/src/common/UserSearchSource.d.ts +1 -1
  34. package/dist/src/common/real-time/StableWSConnection.d.ts +3 -3
  35. package/dist/src/feed/event-handlers/activity/handle-activity-added.d.ts +7 -0
  36. package/dist/src/feed/event-handlers/activity/handle-activity-deleted.d.ts +8 -0
  37. package/dist/src/feed/event-handlers/activity/handle-activity-reaction-added.d.ts +8 -0
  38. package/dist/src/feed/event-handlers/activity/handle-activity-reaction-deleted.d.ts +8 -0
  39. package/dist/src/feed/event-handlers/activity/handle-activity-removed-from-feed.d.ts +3 -0
  40. package/dist/src/feed/event-handlers/activity/handle-activity-updated.d.ts +8 -0
  41. package/dist/src/feed/event-handlers/activity/index.d.ts +6 -0
  42. package/dist/src/feed/event-handlers/bookmark/handle-bookmark-added.d.ts +8 -0
  43. package/dist/src/feed/event-handlers/bookmark/handle-bookmark-deleted.d.ts +9 -0
  44. package/dist/src/feed/event-handlers/bookmark/handle-bookmark-updated.d.ts +8 -0
  45. package/dist/src/feed/event-handlers/bookmark/index.d.ts +3 -0
  46. package/dist/src/feed/event-handlers/comment/handle-comment-added.d.ts +3 -0
  47. package/dist/src/feed/event-handlers/comment/handle-comment-deleted.d.ts +3 -0
  48. package/dist/src/feed/event-handlers/comment/handle-comment-reaction.d.ts +3 -0
  49. package/dist/src/feed/event-handlers/comment/handle-comment-updated.d.ts +3 -0
  50. package/dist/src/feed/event-handlers/comment/index.d.ts +4 -0
  51. package/dist/src/feed/event-handlers/feed/handle-feed-updated.d.ts +3 -0
  52. package/dist/src/feed/event-handlers/feed/index.d.ts +1 -0
  53. package/dist/src/feed/event-handlers/feed-member/handle-feed-member-added.d.ts +3 -0
  54. package/dist/src/feed/event-handlers/feed-member/handle-feed-member-removed.d.ts +3 -0
  55. package/dist/src/feed/event-handlers/feed-member/handle-feed-member-updated.d.ts +3 -0
  56. package/dist/src/feed/event-handlers/feed-member/index.d.ts +3 -0
  57. package/dist/src/feed/event-handlers/follow/handle-follow-created.d.ts +7 -0
  58. package/dist/src/feed/event-handlers/follow/handle-follow-deleted.d.ts +7 -0
  59. package/dist/src/feed/event-handlers/follow/handle-follow-updated.d.ts +3 -0
  60. package/dist/src/feed/event-handlers/follow/index.d.ts +3 -0
  61. package/dist/src/feed/event-handlers/index.d.ts +8 -0
  62. package/dist/src/feed/event-handlers/notification-feed/handle-notification-feed-updated.d.ts +3 -0
  63. package/dist/src/feed/event-handlers/notification-feed/index.d.ts +1 -0
  64. package/dist/src/feed/event-handlers/watch/handle-watch-started.d.ts +2 -0
  65. package/dist/src/feed/event-handlers/watch/handle-watch-stopped.d.ts +2 -0
  66. package/dist/src/feed/event-handlers/watch/index.d.ts +2 -0
  67. package/dist/src/{Feed.d.ts → feed/feed.d.ts} +16 -43
  68. package/dist/src/feed/index.d.ts +2 -0
  69. package/dist/src/feeds-client/event-handlers/index.d.ts +1 -0
  70. package/dist/src/feeds-client/event-handlers/user/handle-user-updated.d.ts +3 -0
  71. package/dist/src/feeds-client/feeds-client.d.ts +76 -0
  72. package/dist/src/feeds-client/index.d.ts +2 -0
  73. package/dist/src/gen/feeds/FeedsApi.d.ts +27 -23
  74. package/dist/src/gen/models/index.d.ts +168 -23
  75. package/dist/src/gen-imports.d.ts +1 -1
  76. package/dist/src/test-utils/index.d.ts +1 -0
  77. package/dist/src/test-utils/response-generators.d.ts +9 -0
  78. package/dist/src/types-internal.d.ts +7 -0
  79. package/dist/src/types.d.ts +1 -1
  80. package/dist/src/utils/check-has-another-page.d.ts +1 -0
  81. package/dist/src/utils/constants.d.ts +3 -0
  82. package/dist/src/utils/index.d.ts +5 -0
  83. package/dist/src/utils/state-update-queue.d.ts +6 -0
  84. package/dist/src/utils/type-assertions.d.ts +7 -0
  85. package/dist/src/utils/unique-array-merge.d.ts +1 -0
  86. package/dist/tsconfig.tsbuildinfo +1 -1
  87. package/index.ts +2 -2
  88. package/package.json +3 -2
  89. package/src/common/ActivitySearchSource.ts +1 -1
  90. package/src/common/FeedSearchSource.ts +2 -2
  91. package/src/common/Poll.ts +1 -1
  92. package/src/common/UserSearchSource.ts +1 -1
  93. package/src/{state-updates → feed/event-handlers/activity}/activity-reaction-utils.test.ts +12 -2
  94. package/src/{state-updates → feed/event-handlers/activity}/activity-utils.test.ts +3 -2
  95. package/src/{state-updates/activity-utils.ts → feed/event-handlers/activity/handle-activity-added.ts} +16 -36
  96. package/src/feed/event-handlers/activity/handle-activity-deleted.ts +30 -0
  97. package/src/feed/event-handlers/activity/handle-activity-reaction-added.ts +67 -0
  98. package/src/feed/event-handlers/activity/handle-activity-reaction-deleted.ts +75 -0
  99. package/src/feed/event-handlers/activity/handle-activity-removed-from-feed.ts +16 -0
  100. package/src/feed/event-handlers/activity/handle-activity-updated.ts +47 -0
  101. package/src/feed/event-handlers/activity/index.ts +6 -0
  102. package/src/{state-updates → feed/event-handlers/bookmark}/bookmark-utils.test.ts +2 -2
  103. package/src/feed/event-handlers/bookmark/handle-bookmark-added.ts +63 -0
  104. package/src/feed/event-handlers/bookmark/handle-bookmark-deleted.ts +84 -0
  105. package/src/feed/event-handlers/bookmark/handle-bookmark-updated.ts +76 -0
  106. package/src/feed/event-handlers/bookmark/index.ts +3 -0
  107. package/src/feed/event-handlers/comment/handle-comment-added.ts +38 -0
  108. package/src/feed/event-handlers/comment/handle-comment-deleted.ts +35 -0
  109. package/src/feed/event-handlers/comment/handle-comment-reaction.ts +61 -0
  110. package/src/feed/event-handlers/comment/handle-comment-updated.ts +35 -0
  111. package/src/feed/event-handlers/comment/index.ts +4 -0
  112. package/src/feed/event-handlers/feed/handle-feed-updated.ts +9 -0
  113. package/src/feed/event-handlers/feed/index.ts +1 -0
  114. package/src/feed/event-handlers/feed-member/handle-feed-member-added.ts +31 -0
  115. package/src/feed/event-handlers/feed-member/handle-feed-member-removed.ts +24 -0
  116. package/src/feed/event-handlers/feed-member/handle-feed-member-updated.ts +40 -0
  117. package/src/feed/event-handlers/feed-member/index.ts +3 -0
  118. package/src/feed/event-handlers/follow/handle-follow-created.test.ts +250 -0
  119. package/src/feed/event-handlers/follow/handle-follow-created.ts +90 -0
  120. package/src/feed/event-handlers/follow/handle-follow-deleted.test.ts +268 -0
  121. package/src/feed/event-handlers/follow/handle-follow-deleted.ts +95 -0
  122. package/src/feed/event-handlers/follow/handle-follow-updated.test.ts +174 -0
  123. package/src/feed/event-handlers/follow/handle-follow-updated.ts +85 -0
  124. package/src/feed/event-handlers/follow/index.ts +3 -0
  125. package/src/feed/event-handlers/index.ts +8 -0
  126. package/src/feed/event-handlers/notification-feed/handle-notification-feed-updated.ts +10 -0
  127. package/src/feed/event-handlers/notification-feed/index.ts +1 -0
  128. package/src/feed/event-handlers/watch/handle-watch-started.ts +5 -0
  129. package/src/feed/event-handlers/watch/handle-watch-stopped.ts +5 -0
  130. package/src/feed/event-handlers/watch/index.ts +2 -0
  131. package/src/{Feed.ts → feed/feed.ts} +87 -516
  132. package/src/feed/index.ts +2 -0
  133. package/src/feeds-client/event-handlers/index.ts +1 -0
  134. package/src/feeds-client/event-handlers/user/handle-user-updated.test.ts +53 -0
  135. package/src/feeds-client/event-handlers/user/handle-user-updated.ts +28 -0
  136. package/src/{FeedsClient.ts → feeds-client/feeds-client.ts} +63 -36
  137. package/src/feeds-client/index.ts +2 -0
  138. package/src/gen/feeds/FeedsApi.ts +164 -138
  139. package/src/gen/model-decoders/decoders.ts +22 -0
  140. package/src/gen/models/index.ts +288 -29
  141. package/src/gen-imports.ts +1 -1
  142. package/src/test-utils/index.ts +1 -0
  143. package/src/test-utils/response-generators.ts +101 -0
  144. package/src/types-internal.ts +11 -0
  145. package/src/types.ts +1 -1
  146. package/src/utils/check-has-another-page.ts +6 -0
  147. package/src/utils/constants.ts +3 -0
  148. package/src/utils/index.ts +5 -0
  149. package/src/{state-updates → utils}/state-update-queue.test.ts +6 -6
  150. package/src/utils/state-update-queue.ts +42 -0
  151. package/src/utils/type-assertions.ts +22 -0
  152. package/src/{utils.test.ts → utils/unique-array-merge.test.ts} +7 -3
  153. package/src/utils/unique-array-merge.ts +19 -0
  154. package/dist/src/FeedsClient.d.ts +0 -75
  155. package/dist/src/state-updates/activity-reaction-utils.d.ts +0 -10
  156. package/dist/src/state-updates/activity-utils.d.ts +0 -13
  157. package/dist/src/state-updates/bookmark-utils.d.ts +0 -14
  158. package/dist/src/state-updates/follow-utils.d.ts +0 -19
  159. package/dist/src/state-updates/state-update-queue.d.ts +0 -15
  160. package/dist/src/utils.d.ts +0 -10
  161. package/src/state-updates/activity-reaction-utils.ts +0 -107
  162. package/src/state-updates/bookmark-utils.ts +0 -167
  163. package/src/state-updates/follow-utils.test.ts +0 -552
  164. package/src/state-updates/follow-utils.ts +0 -126
  165. package/src/state-updates/state-update-queue.ts +0 -35
  166. package/src/utils.ts +0 -48
  167. /package/dist/src/{ModerationClient.d.ts → moderation-client.d.ts} +0 -0
  168. /package/src/{ModerationClient.ts → moderation-client.ts} +0 -0
@@ -634,6 +634,7 @@ decoders.FeedResponse = (input) => {
634
634
  updated_at: { type: 'DatetimeType', isSingle: true },
635
635
  created_by: { type: 'UserResponse', isSingle: true },
636
636
  deleted_at: { type: 'DatetimeType', isSingle: true },
637
+ own_follows: { type: 'FollowResponse', isSingle: false },
637
638
  };
638
639
  return decode(typeMappings, input);
639
640
  };
@@ -852,6 +853,13 @@ decoders.ModerationCustomActionEvent = (input) => {
852
853
  };
853
854
  return decode(typeMappings, input);
854
855
  };
856
+ decoders.ModerationFlagResponse = (input) => {
857
+ const typeMappings = {
858
+ review_queue_item: { type: 'ReviewQueueItemResponse', isSingle: true },
859
+ user: { type: 'UserResponse', isSingle: true },
860
+ };
861
+ return decode(typeMappings, input);
862
+ };
855
863
  decoders.ModerationFlaggedEvent = (input) => {
856
864
  const typeMappings = {
857
865
  created_at: { type: 'DatetimeType', isSingle: true },
@@ -888,6 +896,7 @@ decoders.NotificationFeedUpdatedEvent = (input) => {
888
896
  };
889
897
  decoders.NotificationStatusResponse = (input) => {
890
898
  const typeMappings = {
899
+ last_read_at: { type: 'DatetimeType', isSingle: true },
891
900
  last_seen_at: { type: 'DatetimeType', isSingle: true },
892
901
  };
893
902
  return decode(typeMappings, input);
@@ -1155,6 +1164,7 @@ decoders.ReviewQueueItemResponse = (input) => {
1155
1164
  updated_at: { type: 'DatetimeType', isSingle: true },
1156
1165
  actions: { type: 'ActionLogResponse', isSingle: false },
1157
1166
  bans: { type: 'Ban', isSingle: false },
1167
+ flags: { type: 'ModerationFlagResponse', isSingle: false },
1158
1168
  completed_at: { type: 'DatetimeType', isSingle: true },
1159
1169
  reviewed_at: { type: 'DatetimeType', isSingle: true },
1160
1170
  assigned_to: { type: 'UserResponse', isSingle: true },
@@ -1231,6 +1241,12 @@ decoders.ThreadedCommentResponse = (input) => {
1231
1241
  };
1232
1242
  return decode(typeMappings, input);
1233
1243
  };
1244
+ decoders.UnfollowResponse = (input) => {
1245
+ const typeMappings = {
1246
+ follow: { type: 'FollowResponse', isSingle: true },
1247
+ };
1248
+ return decode(typeMappings, input);
1249
+ };
1234
1250
  decoders.UnpinActivityResponse = (input) => {
1235
1251
  const typeMappings = {
1236
1252
  activity: { type: 'ActivityResponse', isSingle: true },
@@ -1471,7 +1487,7 @@ class FeedsApi {
1471
1487
  async addActivity(request) {
1472
1488
  const body = {
1473
1489
  type: request?.type,
1474
- fids: request?.fids,
1490
+ feeds: request?.feeds,
1475
1491
  expires_at: request?.expires_at,
1476
1492
  id: request?.id,
1477
1493
  parent_id: request?.parent_id,
@@ -1501,7 +1517,7 @@ class FeedsApi {
1501
1517
  }
1502
1518
  async deleteActivities(request) {
1503
1519
  const body = {
1504
- activity_ids: request?.activity_ids,
1520
+ ids: request?.ids,
1505
1521
  hard_delete: request?.hard_delete,
1506
1522
  };
1507
1523
  const response = await this.apiClient.sendRequest('POST', '/api/v2/feeds/activities/delete', undefined, undefined, body, 'application/json');
@@ -1520,56 +1536,6 @@ class FeedsApi {
1520
1536
  decoders.QueryActivitiesResponse?.(response.body);
1521
1537
  return { ...response.body, metadata: response.metadata };
1522
1538
  }
1523
- async deleteActivity(request) {
1524
- const queryParams = {
1525
- hard_delete: request?.hard_delete,
1526
- };
1527
- const pathParams = {
1528
- activity_id: request?.activity_id,
1529
- };
1530
- const response = await this.apiClient.sendRequest('DELETE', '/api/v2/feeds/activities/{activity_id}', pathParams, queryParams);
1531
- decoders.DeleteActivityResponse?.(response.body);
1532
- return { ...response.body, metadata: response.metadata };
1533
- }
1534
- async getActivity(request) {
1535
- const pathParams = {
1536
- activity_id: request?.activity_id,
1537
- };
1538
- const response = await this.apiClient.sendRequest('GET', '/api/v2/feeds/activities/{activity_id}', pathParams, undefined);
1539
- decoders.GetActivityResponse?.(response.body);
1540
- return { ...response.body, metadata: response.metadata };
1541
- }
1542
- async updateActivityPartial(request) {
1543
- const pathParams = {
1544
- activity_id: request?.activity_id,
1545
- };
1546
- const body = {
1547
- unset: request?.unset,
1548
- set: request?.set,
1549
- };
1550
- const response = await this.apiClient.sendRequest('PATCH', '/api/v2/feeds/activities/{activity_id}', pathParams, undefined, body, 'application/json');
1551
- decoders.UpdateActivityPartialResponse?.(response.body);
1552
- return { ...response.body, metadata: response.metadata };
1553
- }
1554
- async updateActivity(request) {
1555
- const pathParams = {
1556
- activity_id: request?.activity_id,
1557
- };
1558
- const body = {
1559
- expires_at: request?.expires_at,
1560
- poll_id: request?.poll_id,
1561
- text: request?.text,
1562
- visibility: request?.visibility,
1563
- attachments: request?.attachments,
1564
- filter_tags: request?.filter_tags,
1565
- interest_tags: request?.interest_tags,
1566
- custom: request?.custom,
1567
- location: request?.location,
1568
- };
1569
- const response = await this.apiClient.sendRequest('PUT', '/api/v2/feeds/activities/{activity_id}', pathParams, undefined, body, 'application/json');
1570
- decoders.UpdateActivityResponse?.(response.body);
1571
- return { ...response.body, metadata: response.metadata };
1572
- }
1573
1539
  async deleteBookmark(request) {
1574
1540
  const queryParams = {
1575
1541
  folder_id: request?.folder_id,
@@ -1608,6 +1574,21 @@ class FeedsApi {
1608
1574
  decoders.AddBookmarkResponse?.(response.body);
1609
1575
  return { ...response.body, metadata: response.metadata };
1610
1576
  }
1577
+ async activityFeedback(request) {
1578
+ const pathParams = {
1579
+ activity_id: request?.activity_id,
1580
+ };
1581
+ const body = {
1582
+ hide: request?.hide,
1583
+ mute_user: request?.mute_user,
1584
+ reason: request?.reason,
1585
+ report: request?.report,
1586
+ show_less: request?.show_less,
1587
+ };
1588
+ const response = await this.apiClient.sendRequest('POST', '/api/v2/feeds/activities/{activity_id}/feedback', pathParams, undefined, body, 'application/json');
1589
+ decoders.ActivityFeedbackResponse?.(response.body);
1590
+ return { ...response.body, metadata: response.metadata };
1591
+ }
1611
1592
  async castPollVote(request) {
1612
1593
  const pathParams = {
1613
1594
  activity_id: request?.activity_id,
@@ -1670,6 +1651,56 @@ class FeedsApi {
1670
1651
  decoders.DeleteActivityReactionResponse?.(response.body);
1671
1652
  return { ...response.body, metadata: response.metadata };
1672
1653
  }
1654
+ async deleteActivity(request) {
1655
+ const queryParams = {
1656
+ hard_delete: request?.hard_delete,
1657
+ };
1658
+ const pathParams = {
1659
+ id: request?.id,
1660
+ };
1661
+ const response = await this.apiClient.sendRequest('DELETE', '/api/v2/feeds/activities/{id}', pathParams, queryParams);
1662
+ decoders.DeleteActivityResponse?.(response.body);
1663
+ return { ...response.body, metadata: response.metadata };
1664
+ }
1665
+ async getActivity(request) {
1666
+ const pathParams = {
1667
+ id: request?.id,
1668
+ };
1669
+ const response = await this.apiClient.sendRequest('GET', '/api/v2/feeds/activities/{id}', pathParams, undefined);
1670
+ decoders.GetActivityResponse?.(response.body);
1671
+ return { ...response.body, metadata: response.metadata };
1672
+ }
1673
+ async updateActivityPartial(request) {
1674
+ const pathParams = {
1675
+ id: request?.id,
1676
+ };
1677
+ const body = {
1678
+ unset: request?.unset,
1679
+ set: request?.set,
1680
+ };
1681
+ const response = await this.apiClient.sendRequest('PATCH', '/api/v2/feeds/activities/{id}', pathParams, undefined, body, 'application/json');
1682
+ decoders.UpdateActivityPartialResponse?.(response.body);
1683
+ return { ...response.body, metadata: response.metadata };
1684
+ }
1685
+ async updateActivity(request) {
1686
+ const pathParams = {
1687
+ id: request?.id,
1688
+ };
1689
+ const body = {
1690
+ expires_at: request?.expires_at,
1691
+ poll_id: request?.poll_id,
1692
+ text: request?.text,
1693
+ visibility: request?.visibility,
1694
+ attachments: request?.attachments,
1695
+ filter_tags: request?.filter_tags,
1696
+ interest_tags: request?.interest_tags,
1697
+ custom: request?.custom,
1698
+ location: request?.location,
1699
+ };
1700
+ const response = await this.apiClient.sendRequest('PUT', '/api/v2/feeds/activities/{id}', pathParams, undefined, body, 'application/json');
1701
+ decoders.UpdateActivityResponse?.(response.body);
1702
+ return { ...response.body, metadata: response.metadata };
1703
+ }
1673
1704
  async queryBookmarkFolders(request) {
1674
1705
  const body = {
1675
1706
  limit: request?.limit,
@@ -1765,49 +1796,52 @@ class FeedsApi {
1765
1796
  return { ...response.body, metadata: response.metadata };
1766
1797
  }
1767
1798
  async deleteComment(request) {
1799
+ const queryParams = {
1800
+ hard_delete: request?.hard_delete,
1801
+ };
1768
1802
  const pathParams = {
1769
- comment_id: request?.comment_id,
1803
+ id: request?.id,
1770
1804
  };
1771
- const response = await this.apiClient.sendRequest('DELETE', '/api/v2/feeds/comments/{comment_id}', pathParams, undefined);
1805
+ const response = await this.apiClient.sendRequest('DELETE', '/api/v2/feeds/comments/{id}', pathParams, queryParams);
1772
1806
  decoders.DeleteCommentResponse?.(response.body);
1773
1807
  return { ...response.body, metadata: response.metadata };
1774
1808
  }
1775
1809
  async getComment(request) {
1776
1810
  const pathParams = {
1777
- comment_id: request?.comment_id,
1811
+ id: request?.id,
1778
1812
  };
1779
- const response = await this.apiClient.sendRequest('GET', '/api/v2/feeds/comments/{comment_id}', pathParams, undefined);
1813
+ const response = await this.apiClient.sendRequest('GET', '/api/v2/feeds/comments/{id}', pathParams, undefined);
1780
1814
  decoders.GetCommentResponse?.(response.body);
1781
1815
  return { ...response.body, metadata: response.metadata };
1782
1816
  }
1783
1817
  async updateComment(request) {
1784
1818
  const pathParams = {
1785
- comment_id: request?.comment_id,
1819
+ id: request?.id,
1786
1820
  };
1787
1821
  const body = {
1788
1822
  comment: request?.comment,
1789
1823
  custom: request?.custom,
1790
1824
  };
1791
- const response = await this.apiClient.sendRequest('PATCH', '/api/v2/feeds/comments/{comment_id}', pathParams, undefined, body, 'application/json');
1825
+ const response = await this.apiClient.sendRequest('PATCH', '/api/v2/feeds/comments/{id}', pathParams, undefined, body, 'application/json');
1792
1826
  decoders.UpdateCommentResponse?.(response.body);
1793
1827
  return { ...response.body, metadata: response.metadata };
1794
1828
  }
1795
1829
  async addCommentReaction(request) {
1796
1830
  const pathParams = {
1797
- comment_id: request?.comment_id,
1831
+ id: request?.id,
1798
1832
  };
1799
1833
  const body = {
1800
1834
  type: request?.type,
1801
1835
  create_notification_activity: request?.create_notification_activity,
1802
1836
  custom: request?.custom,
1803
1837
  };
1804
- const response = await this.apiClient.sendRequest('POST', '/api/v2/feeds/comments/{comment_id}/reactions', pathParams, undefined, body, 'application/json');
1838
+ const response = await this.apiClient.sendRequest('POST', '/api/v2/feeds/comments/{id}/reactions', pathParams, undefined, body, 'application/json');
1805
1839
  decoders.AddCommentReactionResponse?.(response.body);
1806
1840
  return { ...response.body, metadata: response.metadata };
1807
1841
  }
1808
1842
  async queryCommentReactions(request) {
1809
1843
  const pathParams = {
1810
- comment_id: request?.comment_id,
1844
+ id: request?.id,
1811
1845
  };
1812
1846
  const body = {
1813
1847
  limit: request?.limit,
@@ -1816,16 +1850,16 @@ class FeedsApi {
1816
1850
  sort: request?.sort,
1817
1851
  filter: request?.filter,
1818
1852
  };
1819
- const response = await this.apiClient.sendRequest('POST', '/api/v2/feeds/comments/{comment_id}/reactions/query', pathParams, undefined, body, 'application/json');
1853
+ const response = await this.apiClient.sendRequest('POST', '/api/v2/feeds/comments/{id}/reactions/query', pathParams, undefined, body, 'application/json');
1820
1854
  decoders.QueryCommentReactionsResponse?.(response.body);
1821
1855
  return { ...response.body, metadata: response.metadata };
1822
1856
  }
1823
1857
  async deleteCommentReaction(request) {
1824
1858
  const pathParams = {
1825
- comment_id: request?.comment_id,
1859
+ id: request?.id,
1826
1860
  type: request?.type,
1827
1861
  };
1828
- const response = await this.apiClient.sendRequest('DELETE', '/api/v2/feeds/comments/{comment_id}/reactions/{type}', pathParams, undefined);
1862
+ const response = await this.apiClient.sendRequest('DELETE', '/api/v2/feeds/comments/{id}/reactions/{type}', pathParams, undefined);
1829
1863
  decoders.DeleteCommentReactionResponse?.(response.body);
1830
1864
  return { ...response.body, metadata: response.metadata };
1831
1865
  }
@@ -1839,9 +1873,9 @@ class FeedsApi {
1839
1873
  next: request?.next,
1840
1874
  };
1841
1875
  const pathParams = {
1842
- comment_id: request?.comment_id,
1876
+ id: request?.id,
1843
1877
  };
1844
- const response = await this.apiClient.sendRequest('GET', '/api/v2/feeds/comments/{comment_id}/replies', pathParams, queryParams);
1878
+ const response = await this.apiClient.sendRequest('GET', '/api/v2/feeds/comments/{id}/replies', pathParams, queryParams);
1845
1879
  decoders.GetCommentRepliesResponse?.(response.body);
1846
1880
  return { ...response.body, metadata: response.metadata };
1847
1881
  }
@@ -1890,7 +1924,6 @@ class FeedsApi {
1890
1924
  feed_id: request?.feed_id,
1891
1925
  };
1892
1926
  const body = {
1893
- created_by_id: request?.created_by_id,
1894
1927
  custom: request?.custom,
1895
1928
  };
1896
1929
  const response = await this.apiClient.sendRequest('PUT', '/api/v2/feeds/feed_groups/{feed_group_id}/feeds/{feed_id}', pathParams, undefined, body, 'application/json');
@@ -1906,6 +1939,7 @@ class FeedsApi {
1906
1939
  mark_all_read: request?.mark_all_read,
1907
1940
  mark_all_seen: request?.mark_all_seen,
1908
1941
  mark_read: request?.mark_read,
1942
+ mark_seen: request?.mark_seen,
1909
1943
  mark_watched: request?.mark_watched,
1910
1944
  };
1911
1945
  const response = await this.apiClient.sendRequest('POST', '/api/v2/feeds/feed_groups/{feed_group_id}/feeds/{feed_id}/activities/mark/batch', pathParams, undefined, body, 'application/json');
@@ -2016,7 +2050,7 @@ class FeedsApi {
2016
2050
  decoders.CreateFeedsBatchResponse?.(response.body);
2017
2051
  return { ...response.body, metadata: response.metadata };
2018
2052
  }
2019
- async feedsQueryFeeds(request) {
2053
+ async _queryFeeds(request) {
2020
2054
  const queryParams = {
2021
2055
  connection_id: request?.connection_id,
2022
2056
  };
@@ -2059,8 +2093,8 @@ class FeedsApi {
2059
2093
  }
2060
2094
  async acceptFollow(request) {
2061
2095
  const body = {
2062
- source_fid: request?.source_fid,
2063
- target_fid: request?.target_fid,
2096
+ source: request?.source,
2097
+ target: request?.target,
2064
2098
  follower_role: request?.follower_role,
2065
2099
  };
2066
2100
  const response = await this.apiClient.sendRequest('POST', '/api/v2/feeds/follows/accept', undefined, undefined, body, 'application/json');
@@ -2089,8 +2123,8 @@ class FeedsApi {
2089
2123
  }
2090
2124
  async rejectFollow(request) {
2091
2125
  const body = {
2092
- source_fid: request?.source_fid,
2093
- target_fid: request?.target_fid,
2126
+ source: request?.source,
2127
+ target: request?.target,
2094
2128
  };
2095
2129
  const response = await this.apiClient.sendRequest('POST', '/api/v2/feeds/follows/reject', undefined, undefined, body, 'application/json');
2096
2130
  decoders.RejectFollowResponse?.(response.body);
@@ -3751,1631 +3785,1699 @@ const decodeWSEvent = (data) => {
3751
3785
  }
3752
3786
  };
3753
3787
 
3754
- class FeedApi {
3755
- constructor(feedsApi, group, id) {
3756
- this.feedsApi = feedsApi;
3757
- this.group = group;
3758
- this.id = id;
3788
+ class ModerationApi {
3789
+ constructor(apiClient) {
3790
+ this.apiClient = apiClient;
3759
3791
  }
3760
- delete(request) {
3761
- return this.feedsApi.deleteFeed({
3762
- feed_id: this.id,
3763
- feed_group_id: this.group,
3764
- ...request,
3765
- });
3792
+ async ban(request) {
3793
+ const body = {
3794
+ target_user_id: request?.target_user_id,
3795
+ banned_by_id: request?.banned_by_id,
3796
+ channel_cid: request?.channel_cid,
3797
+ delete_messages: request?.delete_messages,
3798
+ ip_ban: request?.ip_ban,
3799
+ reason: request?.reason,
3800
+ shadow: request?.shadow,
3801
+ timeout: request?.timeout,
3802
+ banned_by: request?.banned_by,
3803
+ };
3804
+ const response = await this.apiClient.sendRequest('POST', '/api/v2/moderation/ban', undefined, undefined, body, 'application/json');
3805
+ decoders.BanResponse?.(response.body);
3806
+ return { ...response.body, metadata: response.metadata };
3766
3807
  }
3767
- getOrCreate(request) {
3768
- return this.feedsApi.getOrCreateFeed({
3769
- feed_id: this.id,
3770
- feed_group_id: this.group,
3771
- ...request,
3772
- });
3808
+ async upsertConfig(request) {
3809
+ const body = {
3810
+ key: request?.key,
3811
+ async: request?.async,
3812
+ team: request?.team,
3813
+ ai_image_config: request?.ai_image_config,
3814
+ ai_text_config: request?.ai_text_config,
3815
+ ai_video_config: request?.ai_video_config,
3816
+ automod_platform_circumvention_config: request?.automod_platform_circumvention_config,
3817
+ automod_semantic_filters_config: request?.automod_semantic_filters_config,
3818
+ automod_toxicity_config: request?.automod_toxicity_config,
3819
+ aws_rekognition_config: request?.aws_rekognition_config,
3820
+ block_list_config: request?.block_list_config,
3821
+ bodyguard_config: request?.bodyguard_config,
3822
+ google_vision_config: request?.google_vision_config,
3823
+ rule_builder_config: request?.rule_builder_config,
3824
+ velocity_filter_config: request?.velocity_filter_config,
3825
+ video_call_rule_config: request?.video_call_rule_config,
3826
+ };
3827
+ const response = await this.apiClient.sendRequest('POST', '/api/v2/moderation/config', undefined, undefined, body, 'application/json');
3828
+ decoders.UpsertConfigResponse?.(response.body);
3829
+ return { ...response.body, metadata: response.metadata };
3773
3830
  }
3774
- update(request) {
3775
- return this.feedsApi.updateFeed({
3776
- feed_id: this.id,
3777
- feed_group_id: this.group,
3778
- ...request,
3779
- });
3831
+ async deleteConfig(request) {
3832
+ const queryParams = {
3833
+ team: request?.team,
3834
+ };
3835
+ const pathParams = {
3836
+ key: request?.key,
3837
+ };
3838
+ const response = await this.apiClient.sendRequest('DELETE', '/api/v2/moderation/config/{key}', pathParams, queryParams);
3839
+ decoders.DeleteModerationConfigResponse?.(response.body);
3840
+ return { ...response.body, metadata: response.metadata };
3780
3841
  }
3781
- markActivity(request) {
3782
- return this.feedsApi.markActivity({
3783
- feed_id: this.id,
3784
- feed_group_id: this.group,
3785
- ...request,
3786
- });
3842
+ async getConfig(request) {
3843
+ const queryParams = {
3844
+ team: request?.team,
3845
+ };
3846
+ const pathParams = {
3847
+ key: request?.key,
3848
+ };
3849
+ const response = await this.apiClient.sendRequest('GET', '/api/v2/moderation/config/{key}', pathParams, queryParams);
3850
+ decoders.GetConfigResponse?.(response.body);
3851
+ return { ...response.body, metadata: response.metadata };
3787
3852
  }
3788
- unpinActivity(request) {
3789
- return this.feedsApi.unpinActivity({
3790
- feed_id: this.id,
3791
- feed_group_id: this.group,
3792
- ...request,
3793
- });
3853
+ async queryModerationConfigs(request) {
3854
+ const body = {
3855
+ limit: request?.limit,
3856
+ next: request?.next,
3857
+ prev: request?.prev,
3858
+ sort: request?.sort,
3859
+ filter: request?.filter,
3860
+ };
3861
+ const response = await this.apiClient.sendRequest('POST', '/api/v2/moderation/configs', undefined, undefined, body, 'application/json');
3862
+ decoders.QueryModerationConfigsResponse?.(response.body);
3863
+ return { ...response.body, metadata: response.metadata };
3794
3864
  }
3795
- pinActivity(request) {
3796
- return this.feedsApi.pinActivity({
3797
- feed_id: this.id,
3798
- feed_group_id: this.group,
3799
- ...request,
3800
- });
3865
+ async flag(request) {
3866
+ const body = {
3867
+ entity_id: request?.entity_id,
3868
+ entity_type: request?.entity_type,
3869
+ entity_creator_id: request?.entity_creator_id,
3870
+ reason: request?.reason,
3871
+ custom: request?.custom,
3872
+ moderation_payload: request?.moderation_payload,
3873
+ };
3874
+ const response = await this.apiClient.sendRequest('POST', '/api/v2/moderation/flag', undefined, undefined, body, 'application/json');
3875
+ decoders.FlagResponse?.(response.body);
3876
+ return { ...response.body, metadata: response.metadata };
3801
3877
  }
3802
- updateFeedMembers(request) {
3803
- return this.feedsApi.updateFeedMembers({
3804
- feed_id: this.id,
3805
- feed_group_id: this.group,
3806
- ...request,
3807
- });
3808
- }
3809
- acceptFeedMemberInvite(request) {
3810
- return this.feedsApi.acceptFeedMemberInvite({
3811
- feed_id: this.id,
3812
- feed_group_id: this.group,
3813
- ...request,
3814
- });
3815
- }
3816
- queryFeedMembers(request) {
3817
- return this.feedsApi.queryFeedMembers({
3818
- feed_id: this.id,
3819
- feed_group_id: this.group,
3820
- ...request,
3821
- });
3822
- }
3823
- rejectFeedMemberInvite(request) {
3824
- return this.feedsApi.rejectFeedMemberInvite({
3825
- feed_id: this.id,
3826
- feed_group_id: this.group,
3827
- ...request,
3828
- });
3829
- }
3830
- stopWatching(request) {
3831
- return this.feedsApi.stopWatchingFeed({
3832
- feed_id: this.id,
3833
- feed_group_id: this.group,
3834
- ...request,
3835
- });
3836
- }
3837
- }
3838
-
3839
- const addActivitiesToState = (newActivities, activities, position) => {
3840
- let result;
3841
- if (activities === undefined) {
3842
- activities = [];
3843
- result = {
3844
- changed: true,
3845
- activities,
3878
+ async mute(request) {
3879
+ const body = {
3880
+ target_ids: request?.target_ids,
3881
+ timeout: request?.timeout,
3846
3882
  };
3883
+ const response = await this.apiClient.sendRequest('POST', '/api/v2/moderation/mute', undefined, undefined, body, 'application/json');
3884
+ decoders.MuteResponse?.(response.body);
3885
+ return { ...response.body, metadata: response.metadata };
3847
3886
  }
3848
- else {
3849
- result = {
3850
- changed: false,
3851
- activities,
3887
+ async queryReviewQueue(request) {
3888
+ const body = {
3889
+ limit: request?.limit,
3890
+ lock_count: request?.lock_count,
3891
+ lock_duration: request?.lock_duration,
3892
+ lock_items: request?.lock_items,
3893
+ next: request?.next,
3894
+ prev: request?.prev,
3895
+ stats_only: request?.stats_only,
3896
+ sort: request?.sort,
3897
+ filter: request?.filter,
3852
3898
  };
3899
+ const response = await this.apiClient.sendRequest('POST', '/api/v2/moderation/review_queue', undefined, undefined, body, 'application/json');
3900
+ decoders.QueryReviewQueueResponse?.(response.body);
3901
+ return { ...response.body, metadata: response.metadata };
3853
3902
  }
3854
- const newActivitiesDeduplicated = [];
3855
- newActivities.forEach((newActivityResponse) => {
3856
- const index = activities.findIndex((a) => a.id === newActivityResponse.id);
3857
- if (index === -1) {
3858
- newActivitiesDeduplicated.push(newActivityResponse);
3859
- }
3860
- });
3861
- if (newActivitiesDeduplicated.length > 0) {
3862
- // TODO: since feed activities are not necessarily ordered by created_at (personalization) we don't order by created_at
3863
- // Maybe we can add a flag to the JS client to support order by created_at
3864
- const updatedActivities = [
3865
- ...(position === 'start' ? newActivitiesDeduplicated : []),
3866
- ...activities,
3867
- ...(position === 'end' ? newActivitiesDeduplicated : []),
3868
- ];
3869
- result = { changed: true, activities: updatedActivities };
3870
- }
3871
- return result;
3872
- };
3873
- const updateActivityInState = (updatedActivityResponse, activities) => {
3874
- const index = activities.findIndex((a) => a.id === updatedActivityResponse.id);
3875
- if (index !== -1) {
3876
- const newActivities = [...activities];
3877
- const activity = activities[index];
3878
- newActivities[index] = {
3879
- ...updatedActivityResponse,
3880
- own_reactions: activity.own_reactions,
3881
- own_bookmarks: activity.own_bookmarks,
3882
- latest_reactions: activity.latest_reactions,
3883
- reaction_groups: activity.reaction_groups,
3903
+ async submitAction(request) {
3904
+ const body = {
3905
+ action_type: request?.action_type,
3906
+ item_id: request?.item_id,
3907
+ ban: request?.ban,
3908
+ custom: request?.custom,
3909
+ delete_activity: request?.delete_activity,
3910
+ delete_message: request?.delete_message,
3911
+ delete_reaction: request?.delete_reaction,
3912
+ delete_user: request?.delete_user,
3913
+ mark_reviewed: request?.mark_reviewed,
3914
+ unban: request?.unban,
3884
3915
  };
3885
- return { changed: true, activities: newActivities };
3886
- }
3887
- else {
3888
- return { changed: false, activities };
3889
- }
3890
- };
3891
- const removeActivityFromState = (activityResponse, activities) => {
3892
- const index = activities.findIndex((a) => a.id === activityResponse.id);
3893
- if (index !== -1) {
3894
- const newActivities = [...activities];
3895
- newActivities.splice(index, 1);
3896
- return { changed: true, activities: newActivities };
3897
- }
3898
- else {
3899
- return { changed: false, activities };
3916
+ const response = await this.apiClient.sendRequest('POST', '/api/v2/moderation/submit_action', undefined, undefined, body, 'application/json');
3917
+ decoders.SubmitActionResponse?.(response.body);
3918
+ return { ...response.body, metadata: response.metadata };
3900
3919
  }
3901
- };
3920
+ }
3902
3921
 
3903
- const updateActivityInActivities$1 = (updatedActivity, activities) => {
3904
- const index = activities.findIndex((a) => a.id === updatedActivity.id);
3905
- if (index !== -1) {
3906
- const newActivities = [...activities];
3907
- newActivities[index] = updatedActivity;
3908
- return { changed: true, activities: newActivities };
3909
- }
3910
- else {
3911
- return { changed: false, activities };
3912
- }
3913
- };
3914
- const addReactionToActivity = (event, activity, isCurrentUser) => {
3915
- // Update own_reactions if the reaction is from the current user
3916
- const ownReactions = [...(activity.own_reactions || [])];
3917
- if (isCurrentUser) {
3918
- ownReactions.push(event.reaction);
3919
- }
3920
- return {
3921
- ...activity,
3922
- own_reactions: ownReactions,
3923
- latest_reactions: event.activity.latest_reactions,
3924
- reaction_groups: event.activity.reaction_groups,
3925
- changed: true,
3926
- };
3927
- };
3928
- const removeReactionFromActivity = (event, activity, isCurrentUser) => {
3929
- // Update own_reactions if the reaction is from the current user
3930
- const ownReactions = isCurrentUser
3931
- ? (activity.own_reactions || []).filter((r) => !(r.type === event.reaction.type &&
3932
- r.user.id === event.reaction.user.id))
3933
- : activity.own_reactions;
3934
- return {
3935
- ...activity,
3936
- own_reactions: ownReactions,
3937
- latest_reactions: event.activity.latest_reactions,
3938
- reaction_groups: event.activity.reaction_groups,
3939
- changed: true,
3940
- };
3941
- };
3942
- const addReactionToActivities = (event, activities, isCurrentUser) => {
3943
- if (!activities) {
3944
- return { changed: false, activities: [] };
3945
- }
3946
- const activityIndex = activities.findIndex((a) => a.id === event.activity.id);
3947
- if (activityIndex === -1) {
3948
- return { changed: false, activities };
3949
- }
3950
- const activity = activities[activityIndex];
3951
- const updatedActivity = addReactionToActivity(event, activity, isCurrentUser);
3952
- return updateActivityInActivities$1(updatedActivity, activities);
3953
- };
3954
- const removeReactionFromActivities = (event, activities, isCurrentUser) => {
3955
- if (!activities) {
3956
- return { changed: false, activities: [] };
3957
- }
3958
- const activityIndex = activities.findIndex((a) => a.id === event.activity.id);
3959
- if (activityIndex === -1) {
3960
- return { changed: false, activities };
3961
- }
3962
- const activity = activities[activityIndex];
3963
- const updatedActivity = removeReactionFromActivity(event, activity, isCurrentUser);
3964
- return updateActivityInActivities$1(updatedActivity, activities);
3965
- };
3922
+ class ModerationClient extends ModerationApi {
3923
+ }
3966
3924
 
3967
- // Helper function to check if two bookmarks are the same
3968
- // A bookmark is identified by activity_id + folder_id + user_id
3969
- const isSameBookmark = (bookmark1, bookmark2) => {
3970
- return (bookmark1.user.id === bookmark2.user.id &&
3971
- bookmark1.activity.id === bookmark2.activity.id &&
3972
- bookmark1.folder?.id === bookmark2.folder?.id);
3973
- };
3974
- const updateActivityInActivities = (updatedActivity, activities) => {
3975
- const index = activities.findIndex((a) => a.id === updatedActivity.id);
3976
- if (index !== -1) {
3977
- const newActivities = [...activities];
3978
- newActivities[index] = updatedActivity;
3979
- return { changed: true, activities: newActivities };
3980
- }
3981
- else {
3982
- return { changed: false, activities };
3983
- }
3984
- };
3985
- const addBookmarkToActivity = (event, activity, isCurrentUser) => {
3986
- // Update own_bookmarks if the bookmark is from the current user
3987
- const ownBookmarks = [...(activity.own_bookmarks || [])];
3988
- if (isCurrentUser) {
3989
- ownBookmarks.push(event.bookmark);
3990
- }
3991
- return {
3992
- ...activity,
3993
- own_bookmarks: ownBookmarks,
3994
- changed: true,
3995
- };
3996
- };
3997
- const removeBookmarkFromActivity = (event, activity, isCurrentUser) => {
3998
- // Update own_bookmarks if the bookmark is from the current user
3999
- const ownBookmarks = isCurrentUser
4000
- ? (activity.own_bookmarks || []).filter((bookmark) => !isSameBookmark(bookmark, event.bookmark))
4001
- : activity.own_bookmarks;
4002
- return {
4003
- ...activity,
4004
- own_bookmarks: ownBookmarks,
4005
- changed: true,
4006
- };
4007
- };
4008
- const updateBookmarkInActivity = (event, activity, isCurrentUser) => {
4009
- // Update own_bookmarks if the bookmark is from the current user
4010
- let ownBookmarks = activity.own_bookmarks || [];
4011
- if (isCurrentUser) {
4012
- const bookmarkIndex = ownBookmarks.findIndex((bookmark) => isSameBookmark(bookmark, event.bookmark));
4013
- if (bookmarkIndex !== -1) {
4014
- ownBookmarks = [...ownBookmarks];
4015
- ownBookmarks[bookmarkIndex] = event.bookmark;
4016
- }
4017
- }
4018
- return {
4019
- ...activity,
4020
- own_bookmarks: ownBookmarks,
4021
- changed: true,
4022
- };
4023
- };
4024
- const addBookmarkToActivities = (event, activities, isCurrentUser) => {
4025
- if (!activities) {
4026
- return { changed: false, activities: [] };
4027
- }
4028
- const activityIndex = activities.findIndex((a) => a.id === event.bookmark.activity.id);
4029
- if (activityIndex === -1) {
4030
- return { changed: false, activities };
4031
- }
4032
- const activity = activities[activityIndex];
4033
- const updatedActivity = addBookmarkToActivity(event, activity, isCurrentUser);
4034
- return updateActivityInActivities(updatedActivity, activities);
4035
- };
4036
- const removeBookmarkFromActivities = (event, activities, isCurrentUser) => {
4037
- if (!activities) {
4038
- return { changed: false, activities: [] };
4039
- }
4040
- const activityIndex = activities.findIndex((a) => a.id === event.bookmark.activity.id);
4041
- if (activityIndex === -1) {
4042
- return { changed: false, activities };
4043
- }
4044
- const activity = activities[activityIndex];
4045
- const updatedActivity = removeBookmarkFromActivity(event, activity, isCurrentUser);
4046
- return updateActivityInActivities(updatedActivity, activities);
4047
- };
4048
- const updateBookmarkInActivities = (event, activities, isCurrentUser) => {
4049
- if (!activities) {
4050
- return { changed: false, activities: [] };
4051
- }
4052
- const activityIndex = activities.findIndex((a) => a.id === event.bookmark.activity.id);
4053
- if (activityIndex === -1) {
4054
- return { changed: false, activities };
4055
- }
4056
- const activity = activities[activityIndex];
4057
- const updatedActivity = updateBookmarkInActivity(event, activity, isCurrentUser);
4058
- return updateActivityInActivities(updatedActivity, activities);
4059
- };
4060
-
4061
- const isFeedResponse = (follow) => {
4062
- return 'created_by' in follow;
4063
- };
4064
- const handleFollowCreated = (follow, currentState, currentFeedId, connectedUserId) => {
4065
- // filter non-accepted follows (the way getOrCreate does by default)
4066
- if (follow.status !== 'accepted') {
4067
- return { changed: false, data: currentState };
4068
- }
4069
- let newState = { ...currentState };
4070
- // this feed followed someone
4071
- if (follow.source_feed.fid === currentFeedId) {
4072
- newState = {
4073
- ...newState,
4074
- // Update FeedResponse fields, that has the new follower/following count
4075
- ...follow.source_feed,
3925
+ const isPollUpdatedEvent = (e) => e.type === 'feeds.poll.updated';
3926
+ const isPollClosedEventEvent = (e) => e.type === 'feeds.poll.closed';
3927
+ const isPollVoteCastedEvent = (e) => e.type === 'feeds.poll.vote_casted';
3928
+ const isPollVoteChangedEvent = (e) => e.type === 'feeds.poll.vote_changed';
3929
+ const isPollVoteRemovedEvent = (e) => e.type === 'feeds.poll.vote_removed';
3930
+ const isVoteAnswer = (vote) => !!vote?.answer_text;
3931
+ class StreamPoll {
3932
+ constructor({ client, poll }) {
3933
+ this.getInitialStateFromPollResponse = (poll) => {
3934
+ const { own_votes, id, ...pollResponseForState } = poll;
3935
+ const { ownAnswer, ownVotes } = own_votes?.reduce((acc, voteOrAnswer) => {
3936
+ if (isVoteAnswer(voteOrAnswer)) {
3937
+ acc.ownAnswer = voteOrAnswer;
3938
+ }
3939
+ else {
3940
+ acc.ownVotes.push(voteOrAnswer);
3941
+ }
3942
+ return acc;
3943
+ }, { ownVotes: [] }) ?? { ownVotes: [] };
3944
+ return {
3945
+ ...pollResponseForState,
3946
+ last_activity_at: new Date(),
3947
+ max_voted_option_ids: getMaxVotedOptionIds(pollResponseForState.vote_counts_by_option),
3948
+ own_answer: ownAnswer,
3949
+ own_votes_by_option_id: getOwnVotesByOptionId(ownVotes),
3950
+ };
4076
3951
  };
4077
- // Only update if following array already exists
4078
- if (currentState.following !== undefined) {
4079
- newState.following = [follow, ...currentState.following];
4080
- }
4081
- }
4082
- else if (
4083
- // someone followed this feed
4084
- follow.target_feed.fid === currentFeedId) {
4085
- const source = follow.source_feed;
4086
- newState = {
4087
- ...newState,
4088
- // Update FeedResponse fields, that has the new follower/following count
4089
- ...follow.target_feed,
3952
+ this.reinitializeState = (poll) => {
3953
+ this.state.partialNext(this.getInitialStateFromPollResponse(poll));
4090
3954
  };
4091
- if (source.created_by.id === connectedUserId) {
4092
- newState.own_follows = currentState.own_follows
4093
- ? currentState.own_follows.concat(follow)
4094
- : [follow];
4095
- }
4096
- // Only update if followers array already exists
4097
- if (currentState.followers !== undefined) {
4098
- newState.followers = [follow, ...currentState.followers];
4099
- }
4100
- }
4101
- return { changed: true, data: newState };
4102
- };
4103
- const handleFollowDeleted = (follow, currentState, currentFeedId, connectedUserId) => {
4104
- let newState = { ...currentState };
4105
- // this feed unfollowed someone
4106
- if (follow.source_feed.fid === currentFeedId) {
4107
- newState = {
4108
- ...newState,
4109
- // Update FeedResponse fields, that has the new follower/following count
4110
- ...follow.source_feed,
3955
+ this.handlePollUpdated = (event) => {
3956
+ if (event.poll?.id && event.poll.id !== this.id)
3957
+ return;
3958
+ if (!isPollUpdatedEvent(event))
3959
+ return;
3960
+ const { id, ...pollData } = event.poll;
3961
+ // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
3962
+ this.state.partialNext({
3963
+ ...pollData,
3964
+ last_activity_at: new Date(event.created_at),
3965
+ });
4111
3966
  };
4112
- // Only update if following array already exists
4113
- if (currentState.following !== undefined) {
4114
- newState.following = currentState.following.filter((followItem) => followItem.target_feed.fid !== follow.target_feed.fid);
4115
- }
4116
- }
4117
- else if (
4118
- // someone unfollowed this feed
4119
- follow.target_feed.fid === currentFeedId) {
4120
- const source = follow.source_feed;
4121
- newState = {
4122
- ...newState,
4123
- // Update FeedResponse fields, that has the new follower/following count
4124
- ...follow.target_feed,
3967
+ this.handlePollClosed = (event) => {
3968
+ if (event.poll?.id && event.poll.id !== this.id)
3969
+ return;
3970
+ if (!isPollClosedEventEvent(event))
3971
+ return;
3972
+ this.state.partialNext({
3973
+ is_closed: true,
3974
+ last_activity_at: new Date(event.created_at),
3975
+ });
4125
3976
  };
4126
- if (isFeedResponse(source) &&
4127
- source.created_by.id === connectedUserId &&
4128
- currentState.own_follows !== undefined) {
4129
- newState.own_follows = currentState.own_follows.filter((followItem) => followItem.source_feed.fid !== follow.source_feed.fid);
4130
- }
4131
- // Only update if followers array already exists
4132
- if (currentState.followers !== undefined) {
4133
- newState.followers = currentState.followers.filter((followItem) => followItem.source_feed.fid !== follow.source_feed.fid);
4134
- }
4135
- }
4136
- return { changed: true, data: newState };
4137
- };
4138
- const handleFollowUpdated = (currentState) => {
4139
- // For now, we'll treat follow updates as no-ops since the current implementation does
4140
- // This can be enhanced later if needed
4141
- return { changed: false, data: currentState };
4142
- };
4143
-
4144
- const isImageFile = (file) => {
4145
- // photoshop files begin with 'image/'
4146
- return file.type.startsWith('image/') && !file.type.endsWith('.photoshop');
4147
- };
4148
- const isVideoFile = (file) => {
4149
- return file.type.startsWith('video/');
4150
- };
4151
- const checkHasAnotherPage = (v, cursor) => (typeof v === 'undefined' && typeof cursor === 'undefined') ||
4152
- typeof cursor === 'string';
4153
- const isCommentResponse = (entity) => {
4154
- return typeof entity?.object_id === 'string';
4155
- };
4156
- const Constants = {
4157
- DEFAULT_COMMENT_PAGINATION: 'first',
4158
- };
4159
- const uniqueArrayMerge = (existingArray, arrayToMerge, getKey) => {
4160
- const existing = new Set();
4161
- existingArray.forEach((value) => {
4162
- const key = getKey(value);
4163
- existing.add(key);
4164
- });
4165
- const filteredArrayToMerge = arrayToMerge.filter((value) => {
4166
- const key = getKey(value);
4167
- return !existing.has(key);
4168
- });
4169
- return existingArray.concat(filteredArrayToMerge);
4170
- };
4171
-
4172
- const shouldUpdateState = ({ stateUpdateId, stateUpdateQueue, watch, }) => {
4173
- if (!watch) {
4174
- return true;
4175
- }
4176
- if (watch && stateUpdateQueue.has(stateUpdateId)) {
4177
- stateUpdateQueue.delete(stateUpdateId);
4178
- return false;
4179
- }
4180
- stateUpdateQueue.add(stateUpdateId);
4181
- return true;
4182
- };
4183
- const getStateUpdateQueueIdForFollow = (follow) => {
4184
- return `follow${follow.source_feed.fid}-${follow.target_feed.fid}`;
4185
- };
4186
- const getStateUpdateQueueIdForUnfollow = (follow) => {
4187
- return `unfollow${follow.source_feed.fid}-${follow.target_feed.fid}`;
4188
- };
4189
-
4190
- class Feed extends FeedApi {
4191
- constructor(client, groupId, id, data, watch = false) {
4192
- super(client, groupId, id);
4193
- this.stateUpdateQueue = new Set();
4194
- this.eventHandlers = {
4195
- 'feeds.activity.added': (event) => {
4196
- const currentActivities = this.currentState.activities;
4197
- const result = addActivitiesToState([event.activity], currentActivities, 'start');
4198
- if (result.changed) {
4199
- this.client.hydratePollCache([event.activity]);
4200
- this.state.partialNext({ activities: result.activities });
4201
- }
4202
- },
4203
- 'feeds.activity.deleted': (event) => {
4204
- const currentActivities = this.currentState.activities;
4205
- if (currentActivities) {
4206
- const result = removeActivityFromState(event.activity, currentActivities);
4207
- if (result.changed) {
4208
- this.state.partialNext({ activities: result.activities });
4209
- }
4210
- }
4211
- },
4212
- 'feeds.activity.reaction.added': (event) => {
4213
- const currentActivities = this.currentState.activities;
4214
- const connectedUser = this.client.state.getLatestValue().connected_user;
4215
- const isCurrentUser = Boolean(connectedUser && event.reaction.user.id === connectedUser.id);
4216
- const result = addReactionToActivities(event, currentActivities, isCurrentUser);
4217
- if (result.changed) {
4218
- this.state.partialNext({ activities: result.activities });
4219
- }
4220
- },
4221
- 'feeds.activity.reaction.deleted': (event) => {
4222
- const currentActivities = this.currentState.activities;
4223
- const connectedUser = this.client.state.getLatestValue().connected_user;
4224
- const isCurrentUser = Boolean(connectedUser && event.reaction.user.id === connectedUser.id);
4225
- const result = removeReactionFromActivities(event, currentActivities, isCurrentUser);
4226
- if (result.changed) {
4227
- this.state.partialNext({ activities: result.activities });
3977
+ this.handleVoteCasted = (event) => {
3978
+ if (event.poll?.id && event.poll.id !== this.id)
3979
+ return;
3980
+ if (!isPollVoteCastedEvent(event))
3981
+ return;
3982
+ const currentState = this.data;
3983
+ const isOwnVote = event.poll_vote.user_id ===
3984
+ this.client.state.getLatestValue().connected_user?.id;
3985
+ let latestAnswers = [...currentState.latest_answers];
3986
+ let ownAnswer = currentState.own_answer;
3987
+ const ownVotesByOptionId = currentState.own_votes_by_option_id;
3988
+ let maxVotedOptionIds = currentState.max_voted_option_ids;
3989
+ if (isOwnVote) {
3990
+ // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
3991
+ if (isVoteAnswer(event.poll_vote)) {
3992
+ // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
3993
+ ownAnswer = event.poll_vote;
4228
3994
  }
4229
- },
4230
- 'feeds.activity.reaction.updated': Feed.noop,
4231
- 'feeds.activity.removed_from_feed': (event) => {
4232
- const currentActivities = this.currentState.activities;
4233
- if (currentActivities) {
4234
- const result = removeActivityFromState(event.activity, currentActivities);
4235
- if (result.changed) {
4236
- this.state.partialNext({ activities: result.activities });
4237
- }
3995
+ else if (event.poll_vote.option_id) {
3996
+ // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
3997
+ ownVotesByOptionId[event.poll_vote.option_id] = event.poll_vote;
4238
3998
  }
4239
- },
4240
- 'feeds.activity.updated': (event) => {
4241
- const currentActivities = this.currentState.activities;
4242
- if (currentActivities) {
4243
- const result = updateActivityInState(event.activity, currentActivities);
4244
- if (result.changed) {
4245
- this.client.hydratePollCache([event.activity]);
4246
- this.state.partialNext({ activities: result.activities });
4247
- }
3999
+ }
4000
+ // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
4001
+ if (isVoteAnswer(event.poll_vote)) {
4002
+ // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
4003
+ latestAnswers = [event.poll_vote, ...latestAnswers];
4004
+ }
4005
+ else {
4006
+ maxVotedOptionIds = getMaxVotedOptionIds(event.poll.vote_counts_by_option);
4007
+ }
4008
+ const { answers_count, latest_votes_by_option, vote_count, vote_counts_by_option, } = event.poll;
4009
+ this.state.partialNext({
4010
+ answers_count,
4011
+ // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
4012
+ latest_votes_by_option,
4013
+ vote_count,
4014
+ vote_counts_by_option,
4015
+ latest_answers: latestAnswers,
4016
+ last_activity_at: new Date(event.created_at),
4017
+ own_answer: ownAnswer,
4018
+ own_votes_by_option_id: ownVotesByOptionId,
4019
+ max_voted_option_ids: maxVotedOptionIds,
4020
+ });
4021
+ };
4022
+ this.handleVoteChanged = (event) => {
4023
+ // this event is triggered only when event.poll.enforce_unique_vote === true
4024
+ if (event.poll?.id && event.poll.id !== this.id)
4025
+ return;
4026
+ if (!isPollVoteChangedEvent(event))
4027
+ return;
4028
+ const currentState = this.data;
4029
+ const isOwnVote = event.poll_vote.user_id ===
4030
+ this.client.state.getLatestValue().connected_user?.id;
4031
+ let latestAnswers = [...currentState.latest_answers];
4032
+ let ownAnswer = currentState.own_answer;
4033
+ let ownVotesByOptionId = currentState.own_votes_by_option_id;
4034
+ let maxVotedOptionIds = currentState.max_voted_option_ids;
4035
+ if (isOwnVote) {
4036
+ // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
4037
+ if (isVoteAnswer(event.poll_vote)) {
4038
+ latestAnswers = [
4039
+ // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
4040
+ event.poll_vote,
4041
+ ...latestAnswers.filter((answer) => answer.id !== event.poll_vote.id),
4042
+ ];
4043
+ // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
4044
+ ownAnswer = event.poll_vote;
4248
4045
  }
4249
- },
4250
- 'feeds.bookmark.added': this.handleBookmarkAdded.bind(this),
4251
- 'feeds.bookmark.deleted': this.handleBookmarkDeleted.bind(this),
4252
- 'feeds.bookmark.updated': this.handleBookmarkUpdated.bind(this),
4253
- 'feeds.bookmark_folder.deleted': Feed.noop,
4254
- 'feeds.bookmark_folder.updated': Feed.noop,
4255
- 'feeds.comment.added': (event) => {
4256
- const { comment } = event;
4257
- const forId = comment.parent_id ?? comment.object_id;
4258
- this.state.next((currentState) => {
4259
- const entityState = currentState.comments_by_entity_id[forId];
4260
- const newComments = entityState?.comments?.concat([]) ?? [];
4261
- if (entityState?.pagination?.sort === 'last' &&
4262
- !checkHasAnotherPage(entityState.comments, entityState?.pagination.next)) {
4263
- newComments.unshift(comment);
4264
- }
4265
- else if (entityState?.pagination?.sort === 'first') {
4266
- newComments.push(comment);
4046
+ else if (event.poll_vote.option_id) {
4047
+ if (event.poll.enforce_unique_vote) {
4048
+ // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
4049
+ ownVotesByOptionId = { [event.poll_vote.option_id]: event.poll_vote };
4267
4050
  }
4268
4051
  else {
4269
- // no other sorting option is supported yet
4270
- return currentState;
4271
- }
4272
- return {
4273
- ...currentState,
4274
- comments_by_entity_id: {
4275
- ...currentState.comments_by_entity_id,
4276
- [forId]: {
4277
- ...currentState.comments_by_entity_id[forId],
4278
- comments: newComments,
4279
- },
4280
- },
4281
- };
4282
- });
4283
- },
4284
- 'feeds.comment.deleted': ({ comment }) => {
4285
- const forId = comment.parent_id ?? comment.object_id;
4286
- this.state.next((currentState) => {
4287
- const newCommentsByEntityId = {
4288
- ...currentState.comments_by_entity_id,
4289
- [forId]: {
4290
- ...currentState.comments_by_entity_id[forId],
4291
- },
4292
- };
4293
- const index = this.getCommentIndex(comment, currentState);
4294
- if (newCommentsByEntityId?.[forId]?.comments?.length && index !== -1) {
4295
- newCommentsByEntityId[forId].comments = [
4296
- ...newCommentsByEntityId[forId].comments,
4297
- ];
4298
- newCommentsByEntityId[forId]?.comments?.splice(index, 1);
4299
- }
4300
- delete newCommentsByEntityId[comment.id];
4301
- return {
4302
- ...currentState,
4303
- comments_by_entity_id: newCommentsByEntityId,
4304
- };
4305
- });
4306
- },
4307
- 'feeds.comment.updated': (event) => {
4308
- const { comment } = event;
4309
- const forId = comment.parent_id ?? comment.object_id;
4310
- this.state.next((currentState) => {
4311
- const entityState = currentState.comments_by_entity_id[forId];
4312
- if (!entityState?.comments?.length)
4313
- return currentState;
4314
- const index = this.getCommentIndex(comment, currentState);
4315
- if (index === -1)
4316
- return currentState;
4317
- const newComments = [...entityState.comments];
4318
- newComments[index] = comment;
4319
- return {
4320
- ...currentState,
4321
- comments_by_entity_id: {
4322
- ...currentState.comments_by_entity_id,
4323
- [forId]: {
4324
- ...currentState.comments_by_entity_id[forId],
4325
- comments: newComments,
4326
- },
4327
- },
4328
- };
4329
- });
4330
- },
4331
- 'feeds.feed.created': Feed.noop,
4332
- 'feeds.feed.deleted': Feed.noop,
4333
- 'feeds.feed.updated': (event) => {
4334
- this.state.partialNext({ ...event.feed });
4335
- },
4336
- 'feeds.feed_group.changed': Feed.noop,
4337
- 'feeds.feed_group.deleted': Feed.noop,
4338
- 'feeds.follow.created': (event) => {
4339
- this.handleFollowCreated(event.follow);
4340
- },
4341
- 'feeds.follow.deleted': (event) => {
4342
- this.handleFollowDeleted(event.follow);
4343
- },
4344
- 'feeds.follow.updated': (_event) => {
4345
- handleFollowUpdated(this.currentState);
4346
- },
4347
- 'feeds.comment.reaction.added': this.handleCommentReactionEvent.bind(this),
4348
- 'feeds.comment.reaction.deleted': this.handleCommentReactionEvent.bind(this),
4349
- 'feeds.comment.reaction.updated': Feed.noop,
4350
- 'feeds.feed_member.added': (event) => {
4351
- const { connected_user: connectedUser } = this.client.state.getLatestValue();
4352
- this.state.next((currentState) => {
4353
- let newState;
4354
- if (typeof currentState.members !== 'undefined') {
4355
- newState ?? (newState = {
4356
- ...currentState,
4357
- });
4358
- newState.members = [event.member, ...currentState.members];
4359
- }
4360
- if (connectedUser?.id === event.member.user.id) {
4361
- newState ?? (newState = {
4362
- ...currentState,
4363
- });
4364
- newState.own_membership = event.member;
4365
- }
4366
- return newState ?? currentState;
4367
- });
4368
- },
4369
- 'feeds.feed_member.removed': (event) => {
4370
- const { connected_user: connectedUser } = this.client.state.getLatestValue();
4371
- this.state.next((currentState) => {
4372
- const newState = {
4373
- ...currentState,
4374
- members: currentState.members?.filter((member) => member.user.id !== event.user?.id),
4375
- };
4376
- if (connectedUser?.id === event.member_id) {
4377
- delete newState.own_membership;
4378
- }
4379
- return newState;
4380
- });
4381
- },
4382
- 'feeds.feed_member.updated': (event) => {
4383
- const { connected_user: connectedUser } = this.client.state.getLatestValue();
4384
- this.state.next((currentState) => {
4385
- const memberIndex = currentState.members?.findIndex((member) => member.user.id === event.member.user.id) ?? -1;
4386
- let newState;
4387
- if (memberIndex !== -1) {
4388
- // if there's an index, there's a member to update
4389
- const newMembers = [...currentState.members];
4390
- newMembers[memberIndex] = event.member;
4391
- newState ?? (newState = {
4392
- ...currentState,
4393
- });
4394
- newState.members = newMembers;
4052
+ ownVotesByOptionId = Object.entries(ownVotesByOptionId).reduce((acc, [optionId, vote]) => {
4053
+ if (optionId !== event.poll_vote.option_id &&
4054
+ vote.id === event.poll_vote.id) {
4055
+ return acc;
4056
+ }
4057
+ acc[optionId] = vote;
4058
+ return acc;
4059
+ }, {});
4060
+ // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
4061
+ ownVotesByOptionId[event.poll_vote.option_id] = event.poll_vote;
4395
4062
  }
4396
- if (connectedUser?.id === event.member.user.id) {
4397
- newState ?? (newState = {
4398
- ...currentState,
4399
- });
4400
- newState.own_membership = event.member;
4063
+ if (ownAnswer?.id === event.poll_vote.id) {
4064
+ ownAnswer = undefined;
4401
4065
  }
4402
- return newState ?? currentState;
4403
- });
4404
- },
4405
- 'feeds.notification_feed.updated': (event) => {
4406
- console.info('notification feed updated', event);
4407
- // TODO: handle notification feed updates
4408
- },
4409
- // the poll events should be removed from here
4410
- 'feeds.poll.closed': Feed.noop,
4411
- 'feeds.poll.deleted': Feed.noop,
4412
- 'feeds.poll.updated': Feed.noop,
4413
- 'feeds.poll.vote_casted': Feed.noop,
4414
- 'feeds.poll.vote_changed': Feed.noop,
4415
- 'feeds.poll.vote_removed': Feed.noop,
4416
- 'feeds.activity.pinned': Feed.noop,
4417
- 'feeds.activity.unpinned': Feed.noop,
4418
- 'feeds.activity.marked': Feed.noop,
4419
- 'moderation.custom_action': Feed.noop,
4420
- 'moderation.flagged': Feed.noop,
4421
- 'moderation.mark_reviewed': Feed.noop,
4422
- 'health.check': Feed.noop,
4423
- 'app.updated': Feed.noop,
4424
- 'user.banned': Feed.noop,
4425
- 'user.deactivated': Feed.noop,
4426
- 'user.muted': Feed.noop,
4427
- 'user.reactivated': Feed.noop,
4428
- 'user.updated': Feed.noop,
4429
- };
4430
- this.eventDispatcher = new EventDispatcher();
4431
- this.on = this.eventDispatcher.on;
4432
- this.off = this.eventDispatcher.off;
4433
- this.state = new StateStore({
4434
- fid: `${groupId}:${id}`,
4435
- group_id: groupId,
4436
- id,
4437
- ...(data ?? {}),
4438
- is_loading: false,
4439
- is_loading_activities: false,
4440
- comments_by_entity_id: {},
4441
- watch,
4442
- });
4443
- this.client = client;
4444
- }
4445
- get fid() {
4446
- return `${this.group}:${this.id}`;
4447
- }
4448
- get currentState() {
4449
- return this.state.getLatestValue();
4450
- }
4451
- handleCommentReactionEvent(event) {
4452
- const { comment, reaction } = event;
4453
- const connectedUser = this.client.state.getLatestValue().connected_user;
4454
- this.state.next((currentState) => {
4455
- const forId = comment.parent_id ?? comment.object_id;
4456
- const entityState = currentState.comments_by_entity_id[forId];
4457
- const commentIndex = this.getCommentIndex(comment, currentState);
4458
- if (commentIndex === -1)
4459
- return currentState;
4460
- const newComments = entityState?.comments?.concat([]) ?? [];
4461
- const commentCopy = { ...comment };
4462
- delete commentCopy.own_reactions;
4463
- const newComment = {
4464
- ...newComments[commentIndex],
4465
- ...commentCopy,
4466
- // TODO: FIXME this should be handled by the backend
4467
- latest_reactions: commentCopy.latest_reactions ?? [],
4468
- reaction_groups: commentCopy.reaction_groups ?? {},
4469
- };
4470
- newComments[commentIndex] = newComment;
4471
- if (reaction.user.id === connectedUser?.id) {
4472
- if (event.type === 'feeds.comment.reaction.added') {
4473
- newComment.own_reactions = newComment.own_reactions.concat(reaction) ?? [reaction];
4474
- }
4475
- else if (event.type === 'feeds.comment.reaction.deleted') {
4476
- newComment.own_reactions = newComment.own_reactions.filter((r) => r.type !== reaction.type);
4066
+ maxVotedOptionIds = getMaxVotedOptionIds(event.poll.vote_counts_by_option);
4477
4067
  }
4068
+ // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
4478
4069
  }
4479
- return {
4480
- ...currentState,
4481
- comments_by_entity_id: {
4482
- ...currentState.comments_by_entity_id,
4483
- [forId]: {
4484
- ...entityState,
4485
- comments: newComments,
4486
- },
4487
- },
4488
- };
4489
- });
4490
- }
4491
- async synchronize() {
4492
- const { last_get_or_create_request_config } = this.state.getLatestValue();
4493
- if (last_get_or_create_request_config?.watch) {
4494
- await this.getOrCreate(last_get_or_create_request_config);
4495
- }
4496
- }
4497
- async getOrCreate(request) {
4498
- if (this.currentState.is_loading_activities) {
4499
- throw new Error('Only one getOrCreate call is allowed at a time');
4500
- }
4501
- this.state.partialNext({
4502
- is_loading: !request?.next,
4503
- is_loading_activities: true,
4504
- });
4505
- // TODO: pull comments/comment_pagination from activities and comment_sort from request
4506
- // and pre-populate comments_by_entity_id (once comment_sort and comment_limit are supported)
4507
- try {
4508
- const response = await super.getOrCreate(request);
4509
- if (request?.next) {
4510
- const { activities: currentActivities = [] } = this.currentState;
4511
- const result = addActivitiesToState(response.activities, currentActivities, 'end');
4512
- if (result.changed) {
4513
- this.state.partialNext({
4514
- activities: result.activities,
4515
- next: response.next,
4516
- prev: response.prev,
4517
- });
4070
+ else if (isVoteAnswer(event.poll_vote)) {
4071
+ // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
4072
+ latestAnswers = [event.poll_vote, ...latestAnswers];
4073
+ }
4074
+ else {
4075
+ maxVotedOptionIds = getMaxVotedOptionIds(event.poll.vote_counts_by_option);
4076
+ }
4077
+ const { answers_count, latest_votes_by_option, vote_count, vote_counts_by_option, } = event.poll;
4078
+ this.state.partialNext({
4079
+ answers_count,
4080
+ // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
4081
+ latest_votes_by_option,
4082
+ vote_count,
4083
+ vote_counts_by_option,
4084
+ latest_answers: latestAnswers,
4085
+ last_activity_at: new Date(event.created_at),
4086
+ own_answer: ownAnswer,
4087
+ own_votes_by_option_id: ownVotesByOptionId,
4088
+ max_voted_option_ids: maxVotedOptionIds,
4089
+ });
4090
+ };
4091
+ this.handleVoteRemoved = (event) => {
4092
+ if (event.poll?.id && event.poll.id !== this.id)
4093
+ return;
4094
+ if (!isPollVoteRemovedEvent(event))
4095
+ return;
4096
+ const currentState = this.data;
4097
+ const isOwnVote = event.poll_vote.user_id ===
4098
+ this.client.state.getLatestValue().connected_user?.id;
4099
+ let latestAnswers = [...currentState.latest_answers];
4100
+ let ownAnswer = currentState.own_answer;
4101
+ const ownVotesByOptionId = { ...currentState.own_votes_by_option_id };
4102
+ let maxVotedOptionIds = currentState.max_voted_option_ids;
4103
+ // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
4104
+ if (isVoteAnswer(event.poll_vote)) {
4105
+ latestAnswers = latestAnswers.filter((answer) => answer.id !== event.poll_vote.id);
4106
+ if (isOwnVote) {
4107
+ ownAnswer = undefined;
4518
4108
  }
4519
4109
  }
4520
4110
  else {
4521
- // Empty queue when reinitializing the state
4522
- this.stateUpdateQueue.clear();
4523
- const responseCopy = {
4524
- ...response,
4525
- ...response.feed,
4526
- };
4527
- delete responseCopy.feed;
4528
- delete responseCopy.metadata;
4529
- delete responseCopy.duration;
4530
- this.state.next((currentState) => {
4531
- const nextState = {
4532
- ...currentState,
4533
- ...responseCopy,
4534
- };
4535
- if (!request?.followers_pagination?.limit) {
4536
- delete nextState.followers;
4537
- }
4538
- if (!request?.following_pagination?.limit) {
4539
- delete nextState.following;
4540
- }
4541
- if (response.members.length === 0 && response.feed.member_count > 0) {
4542
- delete nextState.members;
4543
- }
4544
- nextState.last_get_or_create_request_config = request;
4545
- nextState.watch = request?.watch ? request.watch : currentState.watch;
4546
- return nextState;
4547
- });
4111
+ maxVotedOptionIds = getMaxVotedOptionIds(event.poll.vote_counts_by_option);
4112
+ if (isOwnVote && event.poll_vote.option_id) {
4113
+ delete ownVotesByOptionId[event.poll_vote.option_id];
4114
+ }
4548
4115
  }
4549
- this.client.hydratePollCache(response.activities);
4550
- return response;
4551
- }
4552
- finally {
4116
+ const { answers_count, latest_votes_by_option, vote_count, vote_counts_by_option, } = event.poll;
4553
4117
  this.state.partialNext({
4554
- is_loading: false,
4555
- is_loading_activities: false,
4118
+ answers_count,
4119
+ // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
4120
+ latest_votes_by_option,
4121
+ vote_count,
4122
+ vote_counts_by_option,
4123
+ latest_answers: latestAnswers,
4124
+ last_activity_at: new Date(event.created_at),
4125
+ own_answer: ownAnswer,
4126
+ own_votes_by_option_id: ownVotesByOptionId,
4127
+ max_voted_option_ids: maxVotedOptionIds,
4556
4128
  });
4557
- }
4129
+ };
4130
+ this.client = client;
4131
+ this.id = poll.id;
4132
+ this.state = new StateStore(this.getInitialStateFromPollResponse(poll));
4558
4133
  }
4559
- /**
4560
- * @internal
4561
- */
4562
- handleFollowCreated(follow) {
4563
- if (!shouldUpdateState({
4564
- stateUpdateId: getStateUpdateQueueIdForFollow(follow),
4565
- stateUpdateQueue: this.stateUpdateQueue,
4566
- watch: this.currentState.watch,
4567
- })) {
4568
- return;
4569
- }
4570
- const connectedUser = this.client.state.getLatestValue().connected_user;
4571
- const result = handleFollowCreated(follow, this.currentState, this.fid, connectedUser?.id);
4572
- if (result.changed) {
4573
- this.state.next(result.data);
4574
- }
4134
+ get data() {
4135
+ return this.state.getLatestValue();
4575
4136
  }
4576
- /**
4577
- * @internal
4578
- */
4579
- handleFollowDeleted(follow) {
4580
- if (!shouldUpdateState({
4581
- stateUpdateId: getStateUpdateQueueIdForUnfollow(follow),
4582
- stateUpdateQueue: this.stateUpdateQueue,
4583
- watch: this.currentState.watch,
4584
- })) {
4585
- return;
4137
+ }
4138
+ function getMaxVotedOptionIds(voteCountsByOption) {
4139
+ let maxVotes = 0;
4140
+ let winningOptions = [];
4141
+ for (const [id, count] of Object.entries(voteCountsByOption ?? {})) {
4142
+ if (count > maxVotes) {
4143
+ winningOptions = [id];
4144
+ maxVotes = count;
4586
4145
  }
4587
- const connectedUser = this.client.state.getLatestValue().connected_user;
4588
- const result = handleFollowDeleted(follow, this.currentState, this.fid, connectedUser?.id);
4589
- {
4590
- this.state.next(result.data);
4146
+ else if (count === maxVotes) {
4147
+ winningOptions.push(id);
4591
4148
  }
4592
4149
  }
4593
- /**
4594
- * @internal
4595
- */
4596
- handleWatchStopped() {
4597
- this.state.partialNext({
4598
- watch: false,
4150
+ return winningOptions;
4151
+ }
4152
+ function getOwnVotesByOptionId(ownVotes) {
4153
+ return !ownVotes
4154
+ ? {}
4155
+ : ownVotes.reduce((acc, vote) => {
4156
+ if (isVoteAnswer(vote) || !vote.option_id)
4157
+ return acc;
4158
+ acc[vote.option_id] = vote;
4159
+ return acc;
4160
+ }, {});
4161
+ }
4162
+
4163
+ class FeedApi {
4164
+ constructor(feedsApi, group, id) {
4165
+ this.feedsApi = feedsApi;
4166
+ this.group = group;
4167
+ this.id = id;
4168
+ }
4169
+ delete(request) {
4170
+ return this.feedsApi.deleteFeed({
4171
+ feed_id: this.id,
4172
+ feed_group_id: this.group,
4173
+ ...request,
4599
4174
  });
4600
4175
  }
4601
- /**
4602
- * @internal
4603
- */
4604
- handleWatchStarted() {
4605
- this.state.partialNext({
4606
- watch: true,
4176
+ getOrCreate(request) {
4177
+ return this.feedsApi.getOrCreateFeed({
4178
+ feed_id: this.id,
4179
+ feed_group_id: this.group,
4180
+ ...request,
4607
4181
  });
4608
4182
  }
4609
- handleBookmarkAdded(event) {
4610
- const currentActivities = this.currentState.activities;
4611
- const { connected_user: connectedUser } = this.client.state.getLatestValue();
4612
- const isCurrentUser = event.bookmark.user.id === connectedUser?.id;
4613
- const result = addBookmarkToActivities(event, currentActivities, isCurrentUser);
4614
- if (result.changed) {
4615
- this.state.partialNext({ activities: result.activities });
4616
- }
4183
+ update(request) {
4184
+ return this.feedsApi.updateFeed({
4185
+ feed_id: this.id,
4186
+ feed_group_id: this.group,
4187
+ ...request,
4188
+ });
4617
4189
  }
4618
- handleBookmarkDeleted(event) {
4619
- const currentActivities = this.currentState.activities;
4620
- const { connected_user: connectedUser } = this.client.state.getLatestValue();
4621
- const isCurrentUser = event.bookmark.user.id === connectedUser?.id;
4622
- const result = removeBookmarkFromActivities(event, currentActivities, isCurrentUser);
4623
- if (result.changed) {
4624
- this.state.partialNext({ activities: result.activities });
4625
- }
4190
+ markActivity(request) {
4191
+ return this.feedsApi.markActivity({
4192
+ feed_id: this.id,
4193
+ feed_group_id: this.group,
4194
+ ...request,
4195
+ });
4626
4196
  }
4627
- handleBookmarkUpdated(event) {
4628
- const currentActivities = this.currentState.activities;
4629
- const { connected_user: connectedUser } = this.client.state.getLatestValue();
4630
- const isCurrentUser = event.bookmark.user.id === connectedUser?.id;
4631
- const result = updateBookmarkInActivities(event, currentActivities, isCurrentUser);
4632
- if (result.changed) {
4633
- this.state.partialNext({ activities: result.activities });
4634
- }
4197
+ unpinActivity(request) {
4198
+ return this.feedsApi.unpinActivity({
4199
+ feed_id: this.id,
4200
+ feed_group_id: this.group,
4201
+ ...request,
4202
+ });
4635
4203
  }
4636
- /**
4637
- * Returns index of the provided comment object.
4638
- */
4639
- getCommentIndex(comment, state) {
4640
- const { comments_by_entity_id = {} } = state ?? this.currentState;
4641
- const currentComments = comments_by_entity_id[comment.parent_id ?? comment.object_id]?.comments;
4642
- if (!currentComments?.length) {
4643
- return -1;
4644
- }
4645
- // @ts-expect-error this will just fail if the comment is not object from state
4646
- let commentIndex = currentComments.indexOf(comment);
4647
- // fast lookup failed, try slower approach
4648
- if (commentIndex === -1) {
4649
- commentIndex = currentComments.findIndex((comment_) => comment_.id === comment.id);
4650
- }
4651
- return commentIndex;
4204
+ pinActivity(request) {
4205
+ return this.feedsApi.pinActivity({
4206
+ feed_id: this.id,
4207
+ feed_group_id: this.group,
4208
+ ...request,
4209
+ });
4652
4210
  }
4653
- /**
4654
- * Load child comments of entity (activity or comment) into the state, if the target entity is comment,
4655
- * `entityParentId` should be provided (`CommentResponse.parent_id ?? CommentResponse.object_id`).
4656
- */
4657
- loadCommentsIntoState(data) {
4658
- // add initial (top level) object for processing
4659
- const traverseArray = [
4660
- {
4661
- entityId: data.entityId,
4662
- entityParentId: data.entityParentId,
4663
- comments: data.comments,
4664
- next: data.next,
4665
- },
4666
- ];
4667
- this.state.next((currentState) => {
4668
- const newCommentsByEntityId = {
4669
- ...currentState.comments_by_entity_id,
4670
- };
4671
- while (traverseArray.length) {
4672
- const item = traverseArray.pop();
4673
- const entityId = item.entityId;
4674
- // go over entity comments and generate new objects
4675
- // for further processing if there are any replies
4676
- item.comments.forEach((comment) => {
4677
- if (!comment.replies?.length)
4678
- return;
4679
- traverseArray.push({
4680
- entityId: comment.id,
4681
- entityParentId: entityId,
4682
- comments: comment.replies,
4683
- next: comment.meta?.next_cursor,
4684
- });
4685
- });
4686
- // omit replies & meta from the comments (transform ThreadedCommentResponse to CommentResponse)
4687
- // this is somehow faster than copying the whole
4688
- // object and deleting the desired properties
4689
- const newComments = item.comments.map(({ replies: _r, meta: _m, ...restOfTheCommentResponse }) => restOfTheCommentResponse);
4690
- newCommentsByEntityId[entityId] = {
4691
- ...newCommentsByEntityId[entityId],
4692
- entity_parent_id: item.entityParentId,
4693
- pagination: {
4694
- ...newCommentsByEntityId[entityId]?.pagination,
4695
- next: item.next,
4696
- sort: data.sort,
4697
- },
4698
- comments: newCommentsByEntityId[entityId]?.comments
4699
- ? newCommentsByEntityId[entityId].comments?.concat(newComments)
4700
- : newComments,
4701
- };
4702
- }
4703
- return {
4704
- ...currentState,
4705
- comments_by_entity_id: newCommentsByEntityId,
4706
- };
4211
+ updateFeedMembers(request) {
4212
+ return this.feedsApi.updateFeedMembers({
4213
+ feed_id: this.id,
4214
+ feed_group_id: this.group,
4215
+ ...request,
4707
4216
  });
4708
4217
  }
4709
- async loadNextPageComments({ entityId, base, sort, entityParentId, }) {
4710
- let error;
4711
- try {
4712
- this.state.next((currentState) => ({
4713
- ...currentState,
4714
- comments_by_entity_id: {
4715
- ...currentState.comments_by_entity_id,
4716
- [entityId]: {
4717
- ...currentState.comments_by_entity_id[entityId],
4718
- pagination: {
4719
- ...currentState.comments_by_entity_id[entityId]?.pagination,
4720
- loading_next_page: true,
4721
- },
4722
- },
4723
- },
4724
- }));
4725
- const { next, comments } = await base();
4726
- this.loadCommentsIntoState({
4727
- entityId,
4728
- comments,
4729
- entityParentId,
4730
- next,
4731
- sort,
4732
- });
4733
- }
4734
- catch (e) {
4735
- error = e;
4736
- }
4737
- finally {
4738
- this.state.next((currentState) => ({
4739
- ...currentState,
4740
- comments_by_entity_id: {
4741
- ...currentState.comments_by_entity_id,
4742
- [entityId]: {
4743
- ...currentState.comments_by_entity_id[entityId],
4744
- pagination: {
4745
- ...currentState.comments_by_entity_id[entityId]?.pagination,
4746
- loading_next_page: false,
4747
- },
4748
- },
4749
- },
4750
- }));
4751
- }
4752
- if (error) {
4753
- throw error;
4754
- }
4218
+ acceptFeedMemberInvite(request) {
4219
+ return this.feedsApi.acceptFeedMemberInvite({
4220
+ feed_id: this.id,
4221
+ feed_group_id: this.group,
4222
+ ...request,
4223
+ });
4755
4224
  }
4756
- async loadNextPageActivityComments(activity, request) {
4757
- const currentEntityState = this.currentState.comments_by_entity_id[activity.id];
4758
- const currentPagination = currentEntityState?.pagination;
4759
- const currentNextCursor = currentPagination?.next;
4760
- const currentSort = currentPagination?.sort;
4761
- const isLoading = currentPagination?.loading_next_page;
4762
- const sort = currentSort ?? request?.sort ?? Constants.DEFAULT_COMMENT_PAGINATION;
4763
- if (isLoading ||
4764
- !checkHasAnotherPage(currentEntityState?.comments, currentNextCursor)) {
4765
- return;
4766
- }
4767
- await this.loadNextPageComments({
4768
- entityId: activity.id,
4769
- base: () => this.client.getComments({
4770
- ...request,
4771
- sort,
4772
- object_id: activity.id,
4773
- object_type: 'activity',
4774
- next: currentNextCursor,
4775
- }),
4776
- sort,
4225
+ queryFeedMembers(request) {
4226
+ return this.feedsApi.queryFeedMembers({
4227
+ feed_id: this.id,
4228
+ feed_group_id: this.group,
4229
+ ...request,
4777
4230
  });
4778
4231
  }
4779
- async loadNextPageCommentReplies(comment, request) {
4780
- const currentEntityState = this.currentState.comments_by_entity_id[comment.id];
4781
- const currentPagination = currentEntityState?.pagination;
4782
- const currentNextCursor = currentPagination?.next;
4783
- const currentSort = currentPagination?.sort;
4784
- const isLoading = currentPagination?.loading_next_page;
4785
- const sort = currentSort ?? request?.sort ?? Constants.DEFAULT_COMMENT_PAGINATION;
4786
- if (isLoading ||
4787
- !checkHasAnotherPage(currentEntityState?.comments, currentNextCursor)) {
4788
- return;
4789
- }
4790
- await this.loadNextPageComments({
4791
- entityId: comment.id,
4792
- base: () => this.client.getCommentReplies({
4793
- ...request,
4794
- comment_id: comment.id,
4795
- // use known sort first (prevents broken pagination)
4796
- sort: currentSort ??
4797
- request?.sort ??
4798
- Constants.DEFAULT_COMMENT_PAGINATION,
4799
- next: currentNextCursor,
4800
- }),
4801
- entityParentId: comment.parent_id ?? comment.object_id,
4802
- sort,
4232
+ rejectFeedMemberInvite(request) {
4233
+ return this.feedsApi.rejectFeedMemberInvite({
4234
+ feed_id: this.id,
4235
+ feed_group_id: this.group,
4236
+ ...request,
4803
4237
  });
4804
4238
  }
4805
- async loadNextPageFollows(type, request) {
4806
- const paginationKey = `${type}_pagination`;
4807
- const method = `query${capitalize(type)}`;
4808
- const currentFollows = this.currentState[type];
4809
- const currentNextCursor = this.currentState[paginationKey]?.next;
4810
- const isLoading = this.currentState[paginationKey]?.loading_next_page;
4811
- const sort = this.currentState[paginationKey]?.sort ?? request.sort;
4812
- let error;
4813
- if (isLoading || !checkHasAnotherPage(currentFollows, currentNextCursor)) {
4814
- return;
4815
- }
4816
- try {
4817
- this.state.next((currentState) => {
4818
- return {
4819
- ...currentState,
4820
- [paginationKey]: {
4821
- ...currentState[paginationKey],
4822
- loading_next_page: true,
4823
- },
4824
- };
4825
- });
4826
- const { next: newNextCursor, follows } = await this[method]({
4827
- ...request,
4828
- next: currentNextCursor,
4829
- sort,
4830
- });
4831
- this.state.next((currentState) => {
4832
- return {
4833
- ...currentState,
4834
- [type]: currentState[type] === undefined
4835
- ? follows
4836
- : uniqueArrayMerge(currentState[type], follows, (follow) => `${follow.source_feed.fid}-${follow.target_feed.fid}`),
4837
- [paginationKey]: {
4838
- ...currentState[paginationKey],
4839
- next: newNextCursor,
4840
- sort,
4841
- },
4842
- };
4843
- });
4239
+ stopWatching(request) {
4240
+ return this.feedsApi.stopWatchingFeed({
4241
+ feed_id: this.id,
4242
+ feed_group_id: this.group,
4243
+ ...request,
4244
+ });
4245
+ }
4246
+ }
4247
+
4248
+ const checkHasAnotherPage = (v, cursor) => (typeof v === 'undefined' && typeof cursor === 'undefined') ||
4249
+ typeof cursor === 'string';
4250
+
4251
+ const uniqueArrayMerge = (existingArray, arrayToMerge, getKey) => {
4252
+ const existing = new Set();
4253
+ existingArray.forEach((value) => {
4254
+ const key = getKey(value);
4255
+ existing.add(key);
4256
+ });
4257
+ const filteredArrayToMerge = arrayToMerge.filter((value) => {
4258
+ const key = getKey(value);
4259
+ return !existing.has(key);
4260
+ });
4261
+ return existingArray.concat(filteredArrayToMerge);
4262
+ };
4263
+
4264
+ const Constants = {
4265
+ DEFAULT_COMMENT_PAGINATION: 'first',
4266
+ };
4267
+
4268
+ const isFollowResponse = (data) => {
4269
+ return 'source_feed' in data && 'target_feed' in data;
4270
+ };
4271
+ const isCommentResponse = (entity) => {
4272
+ return typeof entity?.object_id === 'string';
4273
+ };
4274
+ const isImageFile = (file) => {
4275
+ // photoshop files begin with 'image/'
4276
+ return file.type.startsWith('image/') && !file.type.endsWith('.photoshop');
4277
+ };
4278
+ const isVideoFile = (file) => {
4279
+ return file.type.startsWith('video/');
4280
+ };
4281
+
4282
+ const shouldUpdateState = ({ stateUpdateQueueId, stateUpdateQueue, watch, }) => {
4283
+ if (!watch) {
4284
+ return true;
4285
+ }
4286
+ if (watch && stateUpdateQueue.has(stateUpdateQueueId)) {
4287
+ stateUpdateQueue.delete(stateUpdateQueueId);
4288
+ return false;
4289
+ }
4290
+ stateUpdateQueue.add(stateUpdateQueueId);
4291
+ return true;
4292
+ };
4293
+ function getStateUpdateQueueId(data, prefix) {
4294
+ if (isFollowResponse(data)) {
4295
+ const toJoin = [data.source_feed.feed, data.target_feed.feed];
4296
+ if (prefix) {
4297
+ toJoin.unshift(prefix);
4844
4298
  }
4845
- catch (e) {
4846
- error = e;
4299
+ return toJoin.join('-');
4300
+ }
4301
+ // else if (isMemberResponse(data)) {
4302
+ // }
4303
+ throw new Error(`Cannot create state update queueId for data: ${JSON.stringify(data)}`);
4304
+ }
4305
+
4306
+ const updateStateFollowCreated = (follow, currentState, currentFeedId, connectedUserId) => {
4307
+ // filter non-accepted follows (the way getOrCreate does by default)
4308
+ if (follow.status !== 'accepted') {
4309
+ return { changed: false, data: currentState };
4310
+ }
4311
+ let newState = { ...currentState };
4312
+ // this feed followed someone
4313
+ if (follow.source_feed.feed === currentFeedId) {
4314
+ newState = {
4315
+ ...newState,
4316
+ // Update FeedResponse fields, that has the new follower/following count
4317
+ ...follow.source_feed,
4318
+ };
4319
+ // Only update if following array already exists
4320
+ if (currentState.following !== undefined) {
4321
+ newState.following = [follow, ...currentState.following];
4847
4322
  }
4848
- finally {
4849
- this.state.next((currentState) => {
4850
- return {
4851
- ...currentState,
4852
- [paginationKey]: {
4853
- ...currentState[paginationKey],
4854
- loading_next_page: false,
4855
- },
4856
- };
4857
- });
4323
+ }
4324
+ else if (
4325
+ // someone followed this feed
4326
+ follow.target_feed.feed === currentFeedId) {
4327
+ const source = follow.source_feed;
4328
+ newState = {
4329
+ ...newState,
4330
+ // Update FeedResponse fields, that has the new follower/following count
4331
+ ...follow.target_feed,
4332
+ };
4333
+ if (source.created_by.id === connectedUserId) {
4334
+ newState.own_follows = currentState.own_follows
4335
+ ? currentState.own_follows.concat(follow)
4336
+ : [follow];
4858
4337
  }
4859
- if (error) {
4860
- throw error;
4338
+ // Only update if followers array already exists
4339
+ if (currentState.followers !== undefined) {
4340
+ newState.followers = [follow, ...currentState.followers];
4861
4341
  }
4862
4342
  }
4863
- async loadNextPageFollowers(request) {
4864
- await this.loadNextPageFollows('followers', request);
4343
+ return { changed: true, data: newState };
4344
+ };
4345
+ function handleFollowCreated(eventOrResponse) {
4346
+ const follow = eventOrResponse.follow;
4347
+ if (!shouldUpdateState({
4348
+ stateUpdateQueueId: getStateUpdateQueueId(follow, 'created'),
4349
+ stateUpdateQueue: this.stateUpdateQueue,
4350
+ watch: this.currentState.watch,
4351
+ })) {
4352
+ return;
4865
4353
  }
4866
- async loadNextPageFollowing(request) {
4867
- await this.loadNextPageFollows('following', request);
4354
+ const connectedUser = this.client.state.getLatestValue().connected_user;
4355
+ const result = updateStateFollowCreated(follow, this.currentState, this.feed, connectedUser?.id);
4356
+ if (result.changed) {
4357
+ this.state.next(result.data);
4868
4358
  }
4869
- async loadNextPageMembers(request) {
4870
- const currentMembers = this.currentState.members;
4871
- const currentNextCursor = this.currentState.member_pagination?.next;
4872
- const isLoading = this.currentState.member_pagination?.loading_next_page;
4873
- const sort = this.currentState.member_pagination?.sort ?? request.sort;
4874
- let error;
4875
- if (isLoading || !checkHasAnotherPage(currentMembers, currentNextCursor)) {
4876
- return;
4359
+ }
4360
+
4361
+ const updateStateFollowDeleted = (follow, currentState, currentFeedId, connectedUserId) => {
4362
+ let newState = { ...currentState };
4363
+ // this feed unfollowed someone
4364
+ if (follow.source_feed.feed === currentFeedId) {
4365
+ newState = {
4366
+ ...newState,
4367
+ // Update FeedResponse fields, that has the new follower/following count
4368
+ ...follow.source_feed,
4369
+ };
4370
+ // Only update if following array already exists
4371
+ if (currentState.following !== undefined) {
4372
+ newState.following = currentState.following.filter((followItem) => followItem.target_feed.feed !== follow.target_feed.feed);
4877
4373
  }
4878
- try {
4879
- this.state.next((currentState) => ({
4374
+ }
4375
+ else if (
4376
+ // someone unfollowed this feed
4377
+ follow.target_feed.feed === currentFeedId) {
4378
+ const source = follow.source_feed;
4379
+ newState = {
4380
+ ...newState,
4381
+ // Update FeedResponse fields, that has the new follower/following count
4382
+ ...follow.target_feed,
4383
+ };
4384
+ if (source.created_by.id === connectedUserId &&
4385
+ currentState.own_follows !== undefined) {
4386
+ newState.own_follows = currentState.own_follows.filter((followItem) => followItem.source_feed.feed !== follow.source_feed.feed);
4387
+ }
4388
+ // Only update if followers array already exists
4389
+ if (currentState.followers !== undefined) {
4390
+ newState.followers = currentState.followers.filter((followItem) => followItem.source_feed.feed !== follow.source_feed.feed);
4391
+ }
4392
+ }
4393
+ return { changed: true, data: newState };
4394
+ };
4395
+ function handleFollowDeleted(eventOrResponse) {
4396
+ const follow = eventOrResponse.follow;
4397
+ if (!shouldUpdateState({
4398
+ stateUpdateQueueId: getStateUpdateQueueId(follow, 'deleted'),
4399
+ stateUpdateQueue: this.stateUpdateQueue,
4400
+ watch: this.currentState.watch,
4401
+ })) {
4402
+ return;
4403
+ }
4404
+ const connectedUser = this.client.state.getLatestValue().connected_user;
4405
+ const result = updateStateFollowDeleted(follow, this.currentState, this.feed, connectedUser?.id);
4406
+ {
4407
+ this.state.next(result.data);
4408
+ }
4409
+ }
4410
+
4411
+ function handleFollowUpdated(eventOrResponse) {
4412
+ const follow = eventOrResponse.follow;
4413
+ const connectedUserId = this.client.state.getLatestValue().connected_user?.id;
4414
+ const currentFeedId = this.feed;
4415
+ if (!shouldUpdateState({
4416
+ stateUpdateQueueId: getStateUpdateQueueId(follow, 'updated'),
4417
+ stateUpdateQueue: this.stateUpdateQueue,
4418
+ watch: this.currentState.watch,
4419
+ })) {
4420
+ return;
4421
+ }
4422
+ this.state.next((currentState) => {
4423
+ let newState;
4424
+ // this feed followed someone
4425
+ if (follow.source_feed.feed === currentFeedId) {
4426
+ newState ?? (newState = {
4880
4427
  ...currentState,
4881
- member_pagination: {
4882
- ...currentState.member_pagination,
4883
- loading_next_page: true,
4884
- },
4885
- }));
4886
- const { next: newNextCursor, members } = await this.client.queryFeedMembers({
4887
- ...request,
4888
- sort,
4889
- feed_id: this.id,
4890
- feed_group_id: this.group,
4891
- next: currentNextCursor,
4428
+ // Update FeedResponse fields, that has the new follower/following count
4429
+ ...follow.source_feed,
4892
4430
  });
4893
- this.state.next((currentState) => ({
4431
+ const index = currentState.following?.findIndex((f) => f.target_feed.feed === follow.target_feed.feed) ?? -1;
4432
+ if (index >= 0) {
4433
+ newState.following = [...newState.following];
4434
+ newState.following[index] = follow;
4435
+ }
4436
+ }
4437
+ else if (
4438
+ // someone followed this feed
4439
+ follow.target_feed.feed === currentFeedId) {
4440
+ const source = follow.source_feed;
4441
+ newState ?? (newState = {
4894
4442
  ...currentState,
4895
- members: currentState.members
4896
- ? uniqueArrayMerge(currentState.members, members, ({ user }) => user.id)
4897
- : members,
4898
- member_pagination: {
4899
- ...currentState.member_pagination,
4900
- next: newNextCursor,
4901
- // set sort if not defined yet
4902
- sort: currentState.member_pagination?.sort ?? request.sort,
4903
- },
4904
- }));
4443
+ // Update FeedResponse fields, that has the new follower/following count
4444
+ ...follow.target_feed,
4445
+ });
4446
+ if (source.created_by.id === connectedUserId &&
4447
+ currentState.own_follows) {
4448
+ const index = currentState.own_follows.findIndex((f) => f.source_feed.feed === follow.source_feed.feed);
4449
+ if (index >= 0) {
4450
+ newState.own_follows = [...currentState.own_follows];
4451
+ newState.own_follows[index] = follow;
4452
+ }
4453
+ }
4454
+ const index = currentState.followers?.findIndex((f) => f.source_feed.feed === follow.source_feed.feed) ?? -1;
4455
+ if (index >= 0) {
4456
+ newState.followers = [...newState.followers];
4457
+ newState.followers[index] = follow;
4458
+ }
4905
4459
  }
4906
- catch (e) {
4907
- error = e;
4460
+ return newState ?? currentState;
4461
+ });
4462
+ }
4463
+
4464
+ function handleCommentAdded(event) {
4465
+ const { comment } = event;
4466
+ const entityId = comment.parent_id ?? comment.object_id;
4467
+ this.state.next((currentState) => {
4468
+ const entityState = currentState.comments_by_entity_id[entityId];
4469
+ if (typeof entityState?.comments === 'undefined') {
4470
+ return currentState;
4908
4471
  }
4909
- finally {
4910
- this.state.next((currentState) => ({
4911
- ...currentState,
4912
- member_pagination: {
4913
- ...currentState.member_pagination,
4914
- loading_next_page: false,
4915
- },
4916
- }));
4472
+ const newComments = entityState?.comments ? [...entityState.comments] : [];
4473
+ if (entityState.pagination?.sort === 'last') {
4474
+ newComments.unshift(comment);
4917
4475
  }
4918
- if (error) {
4919
- throw error;
4476
+ else {
4477
+ // 'first' and other sort options
4478
+ newComments.push(comment);
4920
4479
  }
4921
- }
4922
- /**
4923
- * Method which queries followers of this feed (feeds which target this feed).
4924
- *
4925
- * _Note: Useful only for feeds with `groupId` of `user` value._
4926
- */
4927
- async queryFollowers(request) {
4928
- const filter = {
4929
- target_feed: this.fid,
4480
+ return {
4481
+ ...currentState,
4482
+ comments_by_entity_id: {
4483
+ ...currentState.comments_by_entity_id,
4484
+ [entityId]: {
4485
+ ...currentState.comments_by_entity_id[entityId],
4486
+ comments: newComments,
4487
+ },
4488
+ },
4930
4489
  };
4931
- const response = await this.client.queryFollows({
4932
- filter,
4933
- ...request,
4934
- });
4935
- return response;
4936
- }
4937
- /**
4938
- * Method which queries following of this feed (target feeds of this feed).
4939
- *
4940
- * _Note: Useful only for feeds with `groupId` of `timeline` value._
4941
- */
4942
- async queryFollowing(request) {
4943
- const filter = {
4944
- source_feed: this.fid,
4490
+ });
4491
+ }
4492
+
4493
+ function handleCommentDeleted({ comment }) {
4494
+ const entityId = comment.parent_id ?? comment.object_id;
4495
+ this.state.next((currentState) => {
4496
+ const newCommentsByEntityId = {
4497
+ ...currentState.comments_by_entity_id,
4498
+ [entityId]: {
4499
+ ...currentState.comments_by_entity_id[entityId],
4500
+ },
4945
4501
  };
4946
- const response = await this.client.queryFollows({
4947
- filter,
4948
- ...request,
4949
- });
4950
- return response;
4951
- }
4952
- async follow(feedOrFid, options) {
4953
- const fid = typeof feedOrFid === 'string' ? feedOrFid : feedOrFid.fid;
4954
- const response = await this.client.follow({
4955
- ...options,
4956
- source: this.fid,
4957
- target: fid,
4958
- });
4959
- return response;
4960
- }
4961
- async unfollow(feedOrFid) {
4962
- const fid = typeof feedOrFid === 'string' ? feedOrFid : feedOrFid.fid;
4963
- const response = await this.client.unfollow({
4964
- source: this.fid,
4965
- target: fid,
4966
- });
4967
- return response;
4968
- }
4969
- async getNextPage() {
4970
- const currentState = this.currentState;
4971
- return await this.getOrCreate({
4972
- member_pagination: {
4973
- limit: 0,
4974
- },
4975
- followers_pagination: {
4976
- limit: 0,
4502
+ const index = this.getCommentIndex(comment, currentState);
4503
+ if (newCommentsByEntityId?.[entityId]?.comments?.length && index !== -1) {
4504
+ newCommentsByEntityId[entityId].comments = [
4505
+ ...newCommentsByEntityId[entityId].comments,
4506
+ ];
4507
+ newCommentsByEntityId[entityId]?.comments?.splice(index, 1);
4508
+ }
4509
+ delete newCommentsByEntityId[comment.id];
4510
+ return {
4511
+ ...currentState,
4512
+ comments_by_entity_id: newCommentsByEntityId,
4513
+ };
4514
+ });
4515
+ }
4516
+
4517
+ function handleCommentUpdated(event) {
4518
+ const { comment } = event;
4519
+ const entityId = comment.parent_id ?? comment.object_id;
4520
+ this.state.next((currentState) => {
4521
+ const entityState = currentState.comments_by_entity_id[entityId];
4522
+ if (!entityState?.comments?.length)
4523
+ return currentState;
4524
+ const index = this.getCommentIndex(comment, currentState);
4525
+ if (index === -1)
4526
+ return currentState;
4527
+ const newComments = [...entityState.comments];
4528
+ newComments[index] = comment;
4529
+ return {
4530
+ ...currentState,
4531
+ comments_by_entity_id: {
4532
+ ...currentState.comments_by_entity_id,
4533
+ [entityId]: {
4534
+ ...currentState.comments_by_entity_id[entityId],
4535
+ comments: newComments,
4536
+ },
4977
4537
  },
4978
- following_pagination: {
4979
- limit: 0,
4538
+ };
4539
+ });
4540
+ }
4541
+
4542
+ function handleCommentReaction(event) {
4543
+ const { comment, reaction } = event;
4544
+ const connectedUser = this.client.state.getLatestValue().connected_user;
4545
+ this.state.next((currentState) => {
4546
+ const forId = comment.parent_id ?? comment.object_id;
4547
+ const entityState = currentState.comments_by_entity_id[forId];
4548
+ const commentIndex = this.getCommentIndex(comment, currentState);
4549
+ if (commentIndex === -1)
4550
+ return currentState;
4551
+ const newComments = entityState?.comments?.concat([]) ?? [];
4552
+ const commentCopy = { ...comment };
4553
+ delete commentCopy.own_reactions;
4554
+ const newComment = {
4555
+ ...newComments[commentIndex],
4556
+ ...commentCopy,
4557
+ // TODO: FIXME this should be handled by the backend
4558
+ latest_reactions: commentCopy.latest_reactions ?? [],
4559
+ reaction_groups: commentCopy.reaction_groups ?? {},
4560
+ };
4561
+ newComments[commentIndex] = newComment;
4562
+ if (reaction.user.id === connectedUser?.id) {
4563
+ if (event.type === 'feeds.comment.reaction.added') {
4564
+ newComment.own_reactions = newComment.own_reactions.concat(reaction) ?? [reaction];
4565
+ }
4566
+ else if (event.type === 'feeds.comment.reaction.deleted') {
4567
+ newComment.own_reactions = newComment.own_reactions.filter((r) => r.type !== reaction.type);
4568
+ }
4569
+ }
4570
+ return {
4571
+ ...currentState,
4572
+ comments_by_entity_id: {
4573
+ ...currentState.comments_by_entity_id,
4574
+ [forId]: {
4575
+ ...entityState,
4576
+ comments: newComments,
4577
+ },
4980
4578
  },
4981
- next: currentState.next,
4982
- limit: currentState.last_get_or_create_request_config?.limit ?? 20,
4983
- });
4984
- }
4985
- addActivity(request) {
4986
- return this.feedsApi.addActivity({
4987
- ...request,
4988
- fids: [this.fid],
4989
- });
4990
- }
4991
- handleWSEvent(event) {
4992
- const eventHandler = this.eventHandlers[event.type];
4993
- // no need to run noop function
4994
- if (eventHandler !== Feed.noop) {
4995
- // @ts-expect-error intersection of handler arguments results to never
4996
- eventHandler?.(event);
4579
+ };
4580
+ });
4581
+ }
4582
+
4583
+ function handleFeedMemberAdded(event) {
4584
+ const { connected_user: connectedUser } = this.client.state.getLatestValue();
4585
+ this.state.next((currentState) => {
4586
+ let newState;
4587
+ if (typeof currentState.members !== 'undefined') {
4588
+ newState ?? (newState = {
4589
+ ...currentState,
4590
+ });
4591
+ newState.members = [event.member, ...currentState.members];
4997
4592
  }
4998
- if (typeof eventHandler === 'undefined') {
4999
- console.warn(`Received unknown event type: ${event.type}`, event);
4593
+ if (connectedUser?.id === event.member.user.id) {
4594
+ newState ?? (newState = {
4595
+ ...currentState,
4596
+ });
4597
+ newState.own_membership = event.member;
5000
4598
  }
5001
- this.eventDispatcher.dispatch(event);
5002
- }
4599
+ return newState ?? currentState;
4600
+ });
5003
4601
  }
5004
- Feed.noop = () => { };
5005
4602
 
5006
- class ModerationApi {
5007
- constructor(apiClient) {
5008
- this.apiClient = apiClient;
5009
- }
5010
- async ban(request) {
5011
- const body = {
5012
- target_user_id: request?.target_user_id,
5013
- banned_by_id: request?.banned_by_id,
5014
- channel_cid: request?.channel_cid,
5015
- delete_messages: request?.delete_messages,
5016
- ip_ban: request?.ip_ban,
5017
- reason: request?.reason,
5018
- shadow: request?.shadow,
5019
- timeout: request?.timeout,
5020
- banned_by: request?.banned_by,
5021
- };
5022
- const response = await this.apiClient.sendRequest('POST', '/api/v2/moderation/ban', undefined, undefined, body, 'application/json');
5023
- decoders.BanResponse?.(response.body);
5024
- return { ...response.body, metadata: response.metadata };
5025
- }
5026
- async upsertConfig(request) {
5027
- const body = {
5028
- key: request?.key,
5029
- async: request?.async,
5030
- team: request?.team,
5031
- ai_image_config: request?.ai_image_config,
5032
- ai_text_config: request?.ai_text_config,
5033
- ai_video_config: request?.ai_video_config,
5034
- automod_platform_circumvention_config: request?.automod_platform_circumvention_config,
5035
- automod_semantic_filters_config: request?.automod_semantic_filters_config,
5036
- automod_toxicity_config: request?.automod_toxicity_config,
5037
- aws_rekognition_config: request?.aws_rekognition_config,
5038
- block_list_config: request?.block_list_config,
5039
- bodyguard_config: request?.bodyguard_config,
5040
- google_vision_config: request?.google_vision_config,
5041
- rule_builder_config: request?.rule_builder_config,
5042
- velocity_filter_config: request?.velocity_filter_config,
5043
- video_call_rule_config: request?.video_call_rule_config,
5044
- };
5045
- const response = await this.apiClient.sendRequest('POST', '/api/v2/moderation/config', undefined, undefined, body, 'application/json');
5046
- decoders.UpsertConfigResponse?.(response.body);
5047
- return { ...response.body, metadata: response.metadata };
5048
- }
5049
- async deleteConfig(request) {
5050
- const queryParams = {
5051
- team: request?.team,
5052
- };
5053
- const pathParams = {
5054
- key: request?.key,
4603
+ function handleFeedMemberUpdated(event) {
4604
+ const { connected_user: connectedUser } = this.client.state.getLatestValue();
4605
+ this.state.next((currentState) => {
4606
+ const memberIndex = currentState.members?.findIndex((member) => member.user.id === event.member.user.id) ?? -1;
4607
+ let newState;
4608
+ if (memberIndex !== -1) {
4609
+ // if there's an index, there's a member to update
4610
+ const newMembers = [...currentState.members];
4611
+ newMembers[memberIndex] = event.member;
4612
+ newState ?? (newState = {
4613
+ ...currentState,
4614
+ });
4615
+ newState.members = newMembers;
4616
+ }
4617
+ if (connectedUser?.id === event.member.user.id) {
4618
+ newState ?? (newState = {
4619
+ ...currentState,
4620
+ });
4621
+ newState.own_membership = event.member;
4622
+ }
4623
+ return newState ?? currentState;
4624
+ });
4625
+ }
4626
+
4627
+ function handleFeedMemberRemoved(event) {
4628
+ const { connected_user: connectedUser } = this.client.state.getLatestValue();
4629
+ this.state.next((currentState) => {
4630
+ const newState = {
4631
+ ...currentState,
4632
+ members: currentState.members?.filter((member) => member.user.id !== event.user?.id),
4633
+ };
4634
+ if (connectedUser?.id === event.member_id) {
4635
+ delete newState.own_membership;
4636
+ }
4637
+ return newState;
4638
+ });
4639
+ }
4640
+
4641
+ const addActivitiesToState = (newActivities, activities, position) => {
4642
+ let result;
4643
+ if (activities === undefined) {
4644
+ activities = [];
4645
+ result = {
4646
+ changed: true,
4647
+ activities,
5055
4648
  };
5056
- const response = await this.apiClient.sendRequest('DELETE', '/api/v2/moderation/config/{key}', pathParams, queryParams);
5057
- decoders.DeleteModerationConfigResponse?.(response.body);
5058
- return { ...response.body, metadata: response.metadata };
5059
4649
  }
5060
- async getConfig(request) {
5061
- const queryParams = {
5062
- team: request?.team,
5063
- };
5064
- const pathParams = {
5065
- key: request?.key,
4650
+ else {
4651
+ result = {
4652
+ changed: false,
4653
+ activities,
5066
4654
  };
5067
- const response = await this.apiClient.sendRequest('GET', '/api/v2/moderation/config/{key}', pathParams, queryParams);
5068
- decoders.GetConfigResponse?.(response.body);
5069
- return { ...response.body, metadata: response.metadata };
5070
4655
  }
5071
- async queryModerationConfigs(request) {
5072
- const body = {
5073
- limit: request?.limit,
5074
- next: request?.next,
5075
- prev: request?.prev,
5076
- sort: request?.sort,
5077
- filter: request?.filter,
5078
- };
5079
- const response = await this.apiClient.sendRequest('POST', '/api/v2/moderation/configs', undefined, undefined, body, 'application/json');
5080
- decoders.QueryModerationConfigsResponse?.(response.body);
5081
- return { ...response.body, metadata: response.metadata };
4656
+ const newActivitiesDeduplicated = [];
4657
+ newActivities.forEach((newActivityResponse) => {
4658
+ const index = activities.findIndex((a) => a.id === newActivityResponse.id);
4659
+ if (index === -1) {
4660
+ newActivitiesDeduplicated.push(newActivityResponse);
4661
+ }
4662
+ });
4663
+ if (newActivitiesDeduplicated.length > 0) {
4664
+ // TODO: since feed activities are not necessarily ordered by created_at (personalization) we don't order by created_at
4665
+ // Maybe we can add a flag to the JS client to support order by created_at
4666
+ const updatedActivities = [
4667
+ ...(position === 'start' ? newActivitiesDeduplicated : []),
4668
+ ...activities,
4669
+ ...(position === 'end' ? newActivitiesDeduplicated : []),
4670
+ ];
4671
+ result = { changed: true, activities: updatedActivities };
5082
4672
  }
5083
- async flag(request) {
5084
- const body = {
5085
- entity_id: request?.entity_id,
5086
- entity_type: request?.entity_type,
5087
- entity_creator_id: request?.entity_creator_id,
5088
- reason: request?.reason,
5089
- custom: request?.custom,
5090
- moderation_payload: request?.moderation_payload,
5091
- };
5092
- const response = await this.apiClient.sendRequest('POST', '/api/v2/moderation/flag', undefined, undefined, body, 'application/json');
5093
- decoders.FlagResponse?.(response.body);
5094
- return { ...response.body, metadata: response.metadata };
4673
+ return result;
4674
+ };
4675
+ function handleActivityAdded(event) {
4676
+ const currentActivities = this.currentState.activities;
4677
+ const result = addActivitiesToState([event.activity], currentActivities, 'start');
4678
+ if (result.changed) {
4679
+ this.client.hydratePollCache([event.activity]);
4680
+ this.state.partialNext({ activities: result.activities });
5095
4681
  }
5096
- async mute(request) {
5097
- const body = {
5098
- target_ids: request?.target_ids,
5099
- timeout: request?.timeout,
5100
- };
5101
- const response = await this.apiClient.sendRequest('POST', '/api/v2/moderation/mute', undefined, undefined, body, 'application/json');
5102
- decoders.MuteResponse?.(response.body);
5103
- return { ...response.body, metadata: response.metadata };
4682
+ }
4683
+
4684
+ const removeActivityFromState = (activityResponse, activities) => {
4685
+ const index = activities.findIndex((a) => a.id === activityResponse.id);
4686
+ if (index !== -1) {
4687
+ const newActivities = [...activities];
4688
+ newActivities.splice(index, 1);
4689
+ return { changed: true, activities: newActivities };
5104
4690
  }
5105
- async queryReviewQueue(request) {
5106
- const body = {
5107
- limit: request?.limit,
5108
- lock_count: request?.lock_count,
5109
- lock_duration: request?.lock_duration,
5110
- lock_items: request?.lock_items,
5111
- next: request?.next,
5112
- prev: request?.prev,
5113
- stats_only: request?.stats_only,
5114
- sort: request?.sort,
5115
- filter: request?.filter,
5116
- };
5117
- const response = await this.apiClient.sendRequest('POST', '/api/v2/moderation/review_queue', undefined, undefined, body, 'application/json');
5118
- decoders.QueryReviewQueueResponse?.(response.body);
5119
- return { ...response.body, metadata: response.metadata };
4691
+ else {
4692
+ return { changed: false, activities };
5120
4693
  }
5121
- async submitAction(request) {
5122
- const body = {
5123
- action_type: request?.action_type,
5124
- item_id: request?.item_id,
5125
- ban: request?.ban,
5126
- custom: request?.custom,
5127
- delete_activity: request?.delete_activity,
5128
- delete_message: request?.delete_message,
5129
- delete_reaction: request?.delete_reaction,
5130
- delete_user: request?.delete_user,
5131
- mark_reviewed: request?.mark_reviewed,
5132
- unban: request?.unban,
5133
- };
5134
- const response = await this.apiClient.sendRequest('POST', '/api/v2/moderation/submit_action', undefined, undefined, body, 'application/json');
5135
- decoders.SubmitActionResponse?.(response.body);
5136
- return { ...response.body, metadata: response.metadata };
4694
+ };
4695
+ function handleActivityDeleted(event) {
4696
+ const currentActivities = this.currentState.activities;
4697
+ if (currentActivities) {
4698
+ const result = removeActivityFromState(event.activity, currentActivities);
4699
+ if (result.changed) {
4700
+ this.state.partialNext({ activities: result.activities });
4701
+ }
5137
4702
  }
5138
4703
  }
5139
4704
 
5140
- class ModerationClient extends ModerationApi {
4705
+ function handleActivityRemovedFromFeed(event) {
4706
+ const currentActivities = this.currentState.activities;
4707
+ if (currentActivities) {
4708
+ const result = removeActivityFromState(event.activity, currentActivities);
4709
+ if (result.changed) {
4710
+ this.state.partialNext({ activities: result.activities });
4711
+ }
4712
+ }
5141
4713
  }
5142
4714
 
5143
- const isPollUpdatedEvent = (e) => e.type === 'feeds.poll.updated';
5144
- const isPollClosedEventEvent = (e) => e.type === 'feeds.poll.closed';
5145
- const isPollVoteCastedEvent = (e) => e.type === 'feeds.poll.vote_casted';
5146
- const isPollVoteChangedEvent = (e) => e.type === 'feeds.poll.vote_changed';
5147
- const isPollVoteRemovedEvent = (e) => e.type === 'feeds.poll.vote_removed';
5148
- const isVoteAnswer = (vote) => !!vote?.answer_text;
5149
- class StreamPoll {
5150
- constructor({ client, poll }) {
5151
- this.getInitialStateFromPollResponse = (poll) => {
5152
- const { own_votes, id, ...pollResponseForState } = poll;
5153
- const { ownAnswer, ownVotes } = own_votes?.reduce((acc, voteOrAnswer) => {
5154
- if (isVoteAnswer(voteOrAnswer)) {
5155
- acc.ownAnswer = voteOrAnswer;
5156
- }
5157
- else {
5158
- acc.ownVotes.push(voteOrAnswer);
5159
- }
5160
- return acc;
5161
- }, { ownVotes: [] }) ?? { ownVotes: [] };
5162
- return {
5163
- ...pollResponseForState,
5164
- last_activity_at: new Date(),
5165
- max_voted_option_ids: getMaxVotedOptionIds(pollResponseForState.vote_counts_by_option),
5166
- own_answer: ownAnswer,
5167
- own_votes_by_option_id: getOwnVotesByOptionId(ownVotes),
4715
+ const updateActivityInState = (updatedActivityResponse, activities, replaceCompletely = false) => {
4716
+ const index = activities.findIndex((a) => a.id === updatedActivityResponse.id);
4717
+ if (index !== -1) {
4718
+ const newActivities = [...activities];
4719
+ const activity = activities[index];
4720
+ if (replaceCompletely) {
4721
+ newActivities[index] = updatedActivityResponse;
4722
+ }
4723
+ else {
4724
+ newActivities[index] = {
4725
+ ...updatedActivityResponse,
4726
+ own_reactions: activity.own_reactions,
4727
+ own_bookmarks: activity.own_bookmarks,
4728
+ latest_reactions: activity.latest_reactions,
4729
+ reaction_groups: activity.reaction_groups,
5168
4730
  };
4731
+ }
4732
+ return { changed: true, activities: newActivities };
4733
+ }
4734
+ else {
4735
+ return { changed: false, activities };
4736
+ }
4737
+ };
4738
+ function handleActivityUpdated(event) {
4739
+ const currentActivities = this.currentState.activities;
4740
+ if (currentActivities) {
4741
+ const result = updateActivityInState(event.activity, currentActivities);
4742
+ if (result.changed) {
4743
+ this.client.hydratePollCache([event.activity]);
4744
+ this.state.partialNext({ activities: result.activities });
4745
+ }
4746
+ }
4747
+ }
4748
+
4749
+ const addReactionToActivity = (event, activity, isCurrentUser) => {
4750
+ // Update own_reactions if the reaction is from the current user
4751
+ const ownReactions = [...(activity.own_reactions || [])];
4752
+ if (isCurrentUser) {
4753
+ ownReactions.push(event.reaction);
4754
+ }
4755
+ return {
4756
+ ...activity,
4757
+ own_reactions: ownReactions,
4758
+ latest_reactions: event.activity.latest_reactions,
4759
+ reaction_groups: event.activity.reaction_groups,
4760
+ changed: true,
4761
+ };
4762
+ };
4763
+ const addReactionToActivities = (event, activities, isCurrentUser) => {
4764
+ if (!activities) {
4765
+ return { changed: false, activities: [] };
4766
+ }
4767
+ const activityIndex = activities.findIndex((a) => a.id === event.activity.id);
4768
+ if (activityIndex === -1) {
4769
+ return { changed: false, activities };
4770
+ }
4771
+ const activity = activities[activityIndex];
4772
+ const updatedActivity = addReactionToActivity(event, activity, isCurrentUser);
4773
+ return updateActivityInState(updatedActivity, activities, true);
4774
+ };
4775
+ function handleActivityReactionAdded(event) {
4776
+ const currentActivities = this.currentState.activities;
4777
+ const connectedUser = this.client.state.getLatestValue().connected_user;
4778
+ const isCurrentUser = Boolean(connectedUser && event.reaction.user.id === connectedUser.id);
4779
+ const result = addReactionToActivities(event, currentActivities, isCurrentUser);
4780
+ if (result.changed) {
4781
+ this.state.partialNext({ activities: result.activities });
4782
+ }
4783
+ }
4784
+
4785
+ const removeReactionFromActivity = (event, activity, isCurrentUser) => {
4786
+ // Update own_reactions if the reaction is from the current user
4787
+ const ownReactions = isCurrentUser
4788
+ ? (activity.own_reactions || []).filter((r) => !(r.type === event.reaction.type &&
4789
+ r.user.id === event.reaction.user.id))
4790
+ : activity.own_reactions;
4791
+ return {
4792
+ ...activity,
4793
+ own_reactions: ownReactions,
4794
+ latest_reactions: event.activity.latest_reactions,
4795
+ reaction_groups: event.activity.reaction_groups,
4796
+ changed: true,
4797
+ };
4798
+ };
4799
+ const removeReactionFromActivities = (event, activities, isCurrentUser) => {
4800
+ if (!activities) {
4801
+ return { changed: false, activities: [] };
4802
+ }
4803
+ const activityIndex = activities.findIndex((a) => a.id === event.activity.id);
4804
+ if (activityIndex === -1) {
4805
+ return { changed: false, activities };
4806
+ }
4807
+ const activity = activities[activityIndex];
4808
+ const updatedActivity = removeReactionFromActivity(event, activity, isCurrentUser);
4809
+ return updateActivityInState(updatedActivity, activities, true);
4810
+ };
4811
+ function handleActivityReactionDeleted(event) {
4812
+ const currentActivities = this.currentState.activities;
4813
+ const connectedUser = this.client.state.getLatestValue().connected_user;
4814
+ const isCurrentUser = Boolean(connectedUser && event.reaction.user.id === connectedUser.id);
4815
+ const result = removeReactionFromActivities(event, currentActivities, isCurrentUser);
4816
+ if (result.changed) {
4817
+ this.state.partialNext({ activities: result.activities });
4818
+ }
4819
+ }
4820
+
4821
+ const addBookmarkToActivity = (event, activity, isCurrentUser) => {
4822
+ // Update own_bookmarks if the bookmark is from the current user
4823
+ const ownBookmarks = [...(activity.own_bookmarks || [])];
4824
+ if (isCurrentUser) {
4825
+ ownBookmarks.push(event.bookmark);
4826
+ }
4827
+ return {
4828
+ ...activity,
4829
+ own_bookmarks: ownBookmarks,
4830
+ changed: true,
4831
+ };
4832
+ };
4833
+ const addBookmarkToActivities = (event, activities, isCurrentUser) => {
4834
+ if (!activities) {
4835
+ return { changed: false, activities: [] };
4836
+ }
4837
+ const activityIndex = activities.findIndex((a) => a.id === event.bookmark.activity.id);
4838
+ if (activityIndex === -1) {
4839
+ return { changed: false, activities };
4840
+ }
4841
+ const activity = activities[activityIndex];
4842
+ const updatedActivity = addBookmarkToActivity(event, activity, isCurrentUser);
4843
+ return updateActivityInState(updatedActivity, activities, true);
4844
+ };
4845
+ function handleBookmarkAdded(event) {
4846
+ const currentActivities = this.currentState.activities;
4847
+ const { connected_user: connectedUser } = this.client.state.getLatestValue();
4848
+ const isCurrentUser = event.bookmark.user.id === connectedUser?.id;
4849
+ const result = addBookmarkToActivities(event, currentActivities, isCurrentUser);
4850
+ if (result.changed) {
4851
+ this.state.partialNext({ activities: result.activities });
4852
+ }
4853
+ }
4854
+
4855
+ // Helper function to check if two bookmarks are the same
4856
+ // A bookmark is identified by activity_id + folder_id + user_id
4857
+ const isSameBookmark = (bookmark1, bookmark2) => {
4858
+ return (bookmark1.user.id === bookmark2.user.id &&
4859
+ bookmark1.activity.id === bookmark2.activity.id &&
4860
+ bookmark1.folder?.id === bookmark2.folder?.id);
4861
+ };
4862
+ const removeBookmarkFromActivities = (event, activities, isCurrentUser) => {
4863
+ if (!activities) {
4864
+ return { changed: false, activities: [] };
4865
+ }
4866
+ const activityIndex = activities.findIndex((a) => a.id === event.bookmark.activity.id);
4867
+ if (activityIndex === -1) {
4868
+ return { changed: false, activities };
4869
+ }
4870
+ const activity = activities[activityIndex];
4871
+ const updatedActivity = removeBookmarkFromActivity(event, activity, isCurrentUser);
4872
+ return updateActivityInState(updatedActivity, activities, true);
4873
+ };
4874
+ const removeBookmarkFromActivity = (event, activity, isCurrentUser) => {
4875
+ // Update own_bookmarks if the bookmark is from the current user
4876
+ const ownBookmarks = isCurrentUser
4877
+ ? (activity.own_bookmarks || []).filter((bookmark) => !isSameBookmark(bookmark, event.bookmark))
4878
+ : activity.own_bookmarks;
4879
+ return {
4880
+ ...activity,
4881
+ own_bookmarks: ownBookmarks,
4882
+ changed: true,
4883
+ };
4884
+ };
4885
+ function handleBookmarkDeleted(event) {
4886
+ const currentActivities = this.currentState.activities;
4887
+ const { connected_user: connectedUser } = this.client.state.getLatestValue();
4888
+ const isCurrentUser = event.bookmark.user.id === connectedUser?.id;
4889
+ const result = removeBookmarkFromActivities(event, currentActivities, isCurrentUser);
4890
+ if (result.changed) {
4891
+ this.state.partialNext({ activities: result.activities });
4892
+ }
4893
+ }
4894
+
4895
+ const updateBookmarkInActivity = (event, activity, isCurrentUser) => {
4896
+ // Update own_bookmarks if the bookmark is from the current user
4897
+ let ownBookmarks = activity.own_bookmarks || [];
4898
+ if (isCurrentUser) {
4899
+ const bookmarkIndex = ownBookmarks.findIndex((bookmark) => isSameBookmark(bookmark, event.bookmark));
4900
+ if (bookmarkIndex !== -1) {
4901
+ ownBookmarks = [...ownBookmarks];
4902
+ ownBookmarks[bookmarkIndex] = event.bookmark;
4903
+ }
4904
+ }
4905
+ return {
4906
+ ...activity,
4907
+ own_bookmarks: ownBookmarks,
4908
+ changed: true,
4909
+ };
4910
+ };
4911
+ const updateBookmarkInActivities = (event, activities, isCurrentUser) => {
4912
+ if (!activities) {
4913
+ return { changed: false, activities: [] };
4914
+ }
4915
+ const activityIndex = activities.findIndex((a) => a.id === event.bookmark.activity.id);
4916
+ if (activityIndex === -1) {
4917
+ return { changed: false, activities };
4918
+ }
4919
+ const activity = activities[activityIndex];
4920
+ const updatedActivity = updateBookmarkInActivity(event, activity, isCurrentUser);
4921
+ return updateActivityInState(updatedActivity, activities, true);
4922
+ };
4923
+ function handleBookmarkUpdated(event) {
4924
+ const currentActivities = this.currentState.activities;
4925
+ const { connected_user: connectedUser } = this.client.state.getLatestValue();
4926
+ const isCurrentUser = event.bookmark.user.id === connectedUser?.id;
4927
+ const result = updateBookmarkInActivities(event, currentActivities, isCurrentUser);
4928
+ if (result.changed) {
4929
+ this.state.partialNext({ activities: result.activities });
4930
+ }
4931
+ }
4932
+
4933
+ function handleFeedUpdated(event) {
4934
+ this.state.partialNext({ ...event.feed });
4935
+ }
4936
+
4937
+ function handleNotificationFeedUpdated(event) {
4938
+ console.info('notification feed updated', event);
4939
+ // TODO: handle notification feed updates
4940
+ }
4941
+
4942
+ function handleWatchStarted() {
4943
+ this.state.partialNext({ watch: true });
4944
+ }
4945
+
4946
+ function handleWatchStopped() {
4947
+ this.state.partialNext({ watch: false });
4948
+ }
4949
+
4950
+ class Feed extends FeedApi {
4951
+ constructor(client, groupId, id, data, watch = false) {
4952
+ super(client, groupId, id);
4953
+ this.stateUpdateQueue = new Set();
4954
+ this.eventHandlers = {
4955
+ 'feeds.activity.added': handleActivityAdded.bind(this),
4956
+ 'feeds.activity.deleted': handleActivityDeleted.bind(this),
4957
+ 'feeds.activity.reaction.added': handleActivityReactionAdded.bind(this),
4958
+ 'feeds.activity.reaction.deleted': handleActivityReactionDeleted.bind(this),
4959
+ 'feeds.activity.reaction.updated': Feed.noop,
4960
+ 'feeds.activity.removed_from_feed': handleActivityRemovedFromFeed.bind(this),
4961
+ 'feeds.activity.updated': handleActivityUpdated.bind(this),
4962
+ 'feeds.bookmark.added': handleBookmarkAdded.bind(this),
4963
+ 'feeds.bookmark.deleted': handleBookmarkDeleted.bind(this),
4964
+ 'feeds.bookmark.updated': handleBookmarkUpdated.bind(this),
4965
+ 'feeds.bookmark_folder.deleted': Feed.noop,
4966
+ 'feeds.bookmark_folder.updated': Feed.noop,
4967
+ 'feeds.comment.added': handleCommentAdded.bind(this),
4968
+ 'feeds.comment.deleted': handleCommentDeleted.bind(this),
4969
+ 'feeds.comment.updated': handleCommentUpdated.bind(this),
4970
+ 'feeds.feed.created': Feed.noop,
4971
+ 'feeds.feed.deleted': Feed.noop,
4972
+ 'feeds.feed.updated': handleFeedUpdated.bind(this),
4973
+ 'feeds.feed_group.changed': Feed.noop,
4974
+ 'feeds.feed_group.deleted': Feed.noop,
4975
+ 'feeds.follow.created': handleFollowCreated.bind(this),
4976
+ 'feeds.follow.deleted': handleFollowDeleted.bind(this),
4977
+ 'feeds.follow.updated': handleFollowUpdated.bind(this),
4978
+ 'feeds.comment.reaction.added': handleCommentReaction.bind(this),
4979
+ 'feeds.comment.reaction.deleted': handleCommentReaction.bind(this),
4980
+ 'feeds.comment.reaction.updated': Feed.noop,
4981
+ 'feeds.feed_member.added': handleFeedMemberAdded.bind(this),
4982
+ 'feeds.feed_member.removed': handleFeedMemberRemoved.bind(this),
4983
+ 'feeds.feed_member.updated': handleFeedMemberUpdated.bind(this),
4984
+ 'feeds.notification_feed.updated': handleNotificationFeedUpdated.bind(this),
4985
+ // the poll events should be removed from here
4986
+ 'feeds.poll.closed': Feed.noop,
4987
+ 'feeds.poll.deleted': Feed.noop,
4988
+ 'feeds.poll.updated': Feed.noop,
4989
+ 'feeds.poll.vote_casted': Feed.noop,
4990
+ 'feeds.poll.vote_changed': Feed.noop,
4991
+ 'feeds.poll.vote_removed': Feed.noop,
4992
+ 'feeds.activity.pinned': Feed.noop,
4993
+ 'feeds.activity.unpinned': Feed.noop,
4994
+ 'feeds.activity.marked': Feed.noop,
4995
+ 'moderation.custom_action': Feed.noop,
4996
+ 'moderation.flagged': Feed.noop,
4997
+ 'moderation.mark_reviewed': Feed.noop,
4998
+ 'health.check': Feed.noop,
4999
+ 'app.updated': Feed.noop,
5000
+ 'user.banned': Feed.noop,
5001
+ 'user.deactivated': Feed.noop,
5002
+ 'user.muted': Feed.noop,
5003
+ 'user.reactivated': Feed.noop,
5004
+ 'user.updated': Feed.noop,
5169
5005
  };
5170
- this.reinitializeState = (poll) => {
5171
- this.state.partialNext(this.getInitialStateFromPollResponse(poll));
5172
- };
5173
- this.handlePollUpdated = (event) => {
5174
- if (event.poll?.id && event.poll.id !== this.id)
5175
- return;
5176
- if (!isPollUpdatedEvent(event))
5177
- return;
5178
- const { id, ...pollData } = event.poll;
5179
- // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
5180
- this.state.partialNext({
5181
- ...pollData,
5182
- last_activity_at: new Date(event.created_at),
5183
- });
5184
- };
5185
- this.handlePollClosed = (event) => {
5186
- if (event.poll?.id && event.poll.id !== this.id)
5187
- return;
5188
- if (!isPollClosedEventEvent(event))
5189
- return;
5190
- this.state.partialNext({
5191
- is_closed: true,
5192
- last_activity_at: new Date(event.created_at),
5193
- });
5194
- };
5195
- this.handleVoteCasted = (event) => {
5196
- if (event.poll?.id && event.poll.id !== this.id)
5197
- return;
5198
- if (!isPollVoteCastedEvent(event))
5199
- return;
5200
- const currentState = this.data;
5201
- const isOwnVote = event.poll_vote.user_id ===
5202
- this.client.state.getLatestValue().connected_user?.id;
5203
- let latestAnswers = [...currentState.latest_answers];
5204
- let ownAnswer = currentState.own_answer;
5205
- const ownVotesByOptionId = currentState.own_votes_by_option_id;
5206
- let maxVotedOptionIds = currentState.max_voted_option_ids;
5207
- if (isOwnVote) {
5208
- // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
5209
- if (isVoteAnswer(event.poll_vote)) {
5210
- // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
5211
- ownAnswer = event.poll_vote;
5212
- }
5213
- else if (event.poll_vote.option_id) {
5214
- // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
5215
- ownVotesByOptionId[event.poll_vote.option_id] = event.poll_vote;
5216
- }
5217
- }
5218
- // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
5219
- if (isVoteAnswer(event.poll_vote)) {
5220
- // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
5221
- latestAnswers = [event.poll_vote, ...latestAnswers];
5222
- }
5223
- else {
5224
- maxVotedOptionIds = getMaxVotedOptionIds(event.poll.vote_counts_by_option);
5225
- }
5226
- const { answers_count, latest_votes_by_option, vote_count, vote_counts_by_option, } = event.poll;
5227
- this.state.partialNext({
5228
- answers_count,
5229
- // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
5230
- latest_votes_by_option,
5231
- vote_count,
5232
- vote_counts_by_option,
5233
- latest_answers: latestAnswers,
5234
- last_activity_at: new Date(event.created_at),
5235
- own_answer: ownAnswer,
5236
- own_votes_by_option_id: ownVotesByOptionId,
5237
- max_voted_option_ids: maxVotedOptionIds,
5238
- });
5239
- };
5240
- this.handleVoteChanged = (event) => {
5241
- // this event is triggered only when event.poll.enforce_unique_vote === true
5242
- if (event.poll?.id && event.poll.id !== this.id)
5243
- return;
5244
- if (!isPollVoteChangedEvent(event))
5245
- return;
5246
- const currentState = this.data;
5247
- const isOwnVote = event.poll_vote.user_id ===
5248
- this.client.state.getLatestValue().connected_user?.id;
5249
- let latestAnswers = [...currentState.latest_answers];
5250
- let ownAnswer = currentState.own_answer;
5251
- let ownVotesByOptionId = currentState.own_votes_by_option_id;
5252
- let maxVotedOptionIds = currentState.max_voted_option_ids;
5253
- if (isOwnVote) {
5254
- // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
5255
- if (isVoteAnswer(event.poll_vote)) {
5256
- latestAnswers = [
5257
- // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
5258
- event.poll_vote,
5259
- ...latestAnswers.filter((answer) => answer.id !== event.poll_vote.id),
5260
- ];
5261
- // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
5262
- ownAnswer = event.poll_vote;
5006
+ this.eventDispatcher = new EventDispatcher();
5007
+ this.on = this.eventDispatcher.on;
5008
+ this.off = this.eventDispatcher.off;
5009
+ this.state = new StateStore({
5010
+ feed: `${groupId}:${id}`,
5011
+ group_id: groupId,
5012
+ id,
5013
+ ...(data ?? {}),
5014
+ is_loading: false,
5015
+ is_loading_activities: false,
5016
+ comments_by_entity_id: {},
5017
+ watch,
5018
+ });
5019
+ this.client = client;
5020
+ }
5021
+ get feed() {
5022
+ return `${this.group}:${this.id}`;
5023
+ }
5024
+ get currentState() {
5025
+ return this.state.getLatestValue();
5026
+ }
5027
+ async synchronize() {
5028
+ const { last_get_or_create_request_config } = this.state.getLatestValue();
5029
+ if (last_get_or_create_request_config?.watch) {
5030
+ await this.getOrCreate(last_get_or_create_request_config);
5031
+ }
5032
+ }
5033
+ async getOrCreate(request) {
5034
+ if (this.currentState.is_loading_activities) {
5035
+ throw new Error('Only one getOrCreate call is allowed at a time');
5036
+ }
5037
+ this.state.partialNext({
5038
+ is_loading: !request?.next,
5039
+ is_loading_activities: true,
5040
+ });
5041
+ // TODO: pull comments/comment_pagination from activities and comment_sort from request
5042
+ // and pre-populate comments_by_entity_id (once comment_sort and comment_limit are supported)
5043
+ try {
5044
+ const response = await super.getOrCreate(request);
5045
+ if (request?.next) {
5046
+ const { activities: currentActivities = [] } = this.currentState;
5047
+ const result = addActivitiesToState(response.activities, currentActivities, 'end');
5048
+ if (result.changed) {
5049
+ this.state.partialNext({
5050
+ activities: result.activities,
5051
+ next: response.next,
5052
+ prev: response.prev,
5053
+ });
5263
5054
  }
5264
- else if (event.poll_vote.option_id) {
5265
- if (event.poll.enforce_unique_vote) {
5266
- // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
5267
- ownVotesByOptionId = { [event.poll_vote.option_id]: event.poll_vote };
5055
+ }
5056
+ else {
5057
+ // Empty queue when reinitializing the state
5058
+ this.stateUpdateQueue.clear();
5059
+ const responseCopy = {
5060
+ ...response,
5061
+ ...response.feed,
5062
+ };
5063
+ delete responseCopy.feed;
5064
+ delete responseCopy.metadata;
5065
+ delete responseCopy.duration;
5066
+ // TODO: lazy-load comments from activities when comment_sort and comment_pagination are supported
5067
+ this.state.next((currentState) => {
5068
+ const nextState = {
5069
+ ...currentState,
5070
+ ...responseCopy,
5071
+ };
5072
+ if (!request?.followers_pagination?.limit) {
5073
+ delete nextState.followers;
5268
5074
  }
5269
- else {
5270
- ownVotesByOptionId = Object.entries(ownVotesByOptionId).reduce((acc, [optionId, vote]) => {
5271
- if (optionId !== event.poll_vote.option_id &&
5272
- vote.id === event.poll_vote.id) {
5273
- return acc;
5274
- }
5275
- acc[optionId] = vote;
5276
- return acc;
5277
- }, {});
5278
- // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
5279
- ownVotesByOptionId[event.poll_vote.option_id] = event.poll_vote;
5075
+ if (!request?.following_pagination?.limit) {
5076
+ delete nextState.following;
5280
5077
  }
5281
- if (ownAnswer?.id === event.poll_vote.id) {
5282
- ownAnswer = undefined;
5078
+ if (response.members.length === 0 && response.feed.member_count > 0) {
5079
+ delete nextState.members;
5283
5080
  }
5284
- maxVotedOptionIds = getMaxVotedOptionIds(event.poll.vote_counts_by_option);
5285
- }
5286
- // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
5287
- }
5288
- else if (isVoteAnswer(event.poll_vote)) {
5289
- // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
5290
- latestAnswers = [event.poll_vote, ...latestAnswers];
5291
- }
5292
- else {
5293
- maxVotedOptionIds = getMaxVotedOptionIds(event.poll.vote_counts_by_option);
5081
+ nextState.last_get_or_create_request_config = request;
5082
+ nextState.watch = request?.watch ? request.watch : currentState.watch;
5083
+ return nextState;
5084
+ });
5294
5085
  }
5295
- const { answers_count, latest_votes_by_option, vote_count, vote_counts_by_option, } = event.poll;
5086
+ this.client.hydratePollCache(response.activities);
5087
+ return response;
5088
+ }
5089
+ finally {
5296
5090
  this.state.partialNext({
5297
- answers_count,
5298
- // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
5299
- latest_votes_by_option,
5300
- vote_count,
5301
- vote_counts_by_option,
5302
- latest_answers: latestAnswers,
5303
- last_activity_at: new Date(event.created_at),
5304
- own_answer: ownAnswer,
5305
- own_votes_by_option_id: ownVotesByOptionId,
5306
- max_voted_option_ids: maxVotedOptionIds,
5091
+ is_loading: false,
5092
+ is_loading_activities: false,
5093
+ });
5094
+ }
5095
+ }
5096
+ /**
5097
+ * Returns index of the provided comment object.
5098
+ */
5099
+ getCommentIndex(comment, state) {
5100
+ const { comments_by_entity_id = {} } = state ?? this.currentState;
5101
+ const currentComments = comments_by_entity_id[comment.parent_id ?? comment.object_id]?.comments;
5102
+ if (!currentComments?.length) {
5103
+ return -1;
5104
+ }
5105
+ // @ts-expect-error this will just fail if the comment is not object from state
5106
+ let commentIndex = currentComments.indexOf(comment);
5107
+ // fast lookup failed, try slower approach
5108
+ if (commentIndex === -1) {
5109
+ commentIndex = currentComments.findIndex((comment_) => comment_.id === comment.id);
5110
+ }
5111
+ return commentIndex;
5112
+ }
5113
+ /**
5114
+ * Load child comments of entity (activity or comment) into the state, if the target entity is comment,
5115
+ * `entityParentId` should be provided (`CommentResponse.parent_id ?? CommentResponse.object_id`).
5116
+ */
5117
+ loadCommentsIntoState(data) {
5118
+ // add initial (top level) object for processing
5119
+ const traverseArray = [
5120
+ {
5121
+ entityId: data.entityId,
5122
+ entityParentId: data.entityParentId,
5123
+ comments: data.comments,
5124
+ next: data.next,
5125
+ },
5126
+ ];
5127
+ this.state.next((currentState) => {
5128
+ const newCommentsByEntityId = {
5129
+ ...currentState.comments_by_entity_id,
5130
+ };
5131
+ while (traverseArray.length) {
5132
+ const item = traverseArray.pop();
5133
+ const entityId = item.entityId;
5134
+ // go over entity comments and generate new objects
5135
+ // for further processing if there are any replies
5136
+ item.comments.forEach((comment) => {
5137
+ if (!comment.replies?.length)
5138
+ return;
5139
+ traverseArray.push({
5140
+ entityId: comment.id,
5141
+ entityParentId: entityId,
5142
+ comments: comment.replies,
5143
+ next: comment.meta?.next_cursor,
5144
+ });
5145
+ });
5146
+ // omit replies & meta from the comments (transform ThreadedCommentResponse to CommentResponse)
5147
+ // this is somehow faster than copying the whole
5148
+ // object and deleting the desired properties
5149
+ const newComments = item.comments.map(({ replies: _r, meta: _m, ...restOfTheCommentResponse }) => restOfTheCommentResponse);
5150
+ const existingComments = newCommentsByEntityId[entityId]?.comments;
5151
+ newCommentsByEntityId[entityId] = {
5152
+ ...newCommentsByEntityId[entityId],
5153
+ entity_parent_id: item.entityParentId,
5154
+ pagination: {
5155
+ ...newCommentsByEntityId[entityId]?.pagination,
5156
+ next: item.next,
5157
+ sort: data.sort,
5158
+ },
5159
+ comments: existingComments
5160
+ ? uniqueArrayMerge(existingComments, newComments, (comment) => comment.id)
5161
+ : newComments,
5162
+ };
5163
+ }
5164
+ return {
5165
+ ...currentState,
5166
+ comments_by_entity_id: newCommentsByEntityId,
5167
+ };
5168
+ });
5169
+ }
5170
+ async loadNextPageComments({ entityId, base, sort, entityParentId, }) {
5171
+ let error;
5172
+ try {
5173
+ this.state.next((currentState) => ({
5174
+ ...currentState,
5175
+ comments_by_entity_id: {
5176
+ ...currentState.comments_by_entity_id,
5177
+ [entityId]: {
5178
+ ...currentState.comments_by_entity_id[entityId],
5179
+ pagination: {
5180
+ ...currentState.comments_by_entity_id[entityId]?.pagination,
5181
+ loading_next_page: true,
5182
+ },
5183
+ },
5184
+ },
5185
+ }));
5186
+ const { next, comments } = await base();
5187
+ this.loadCommentsIntoState({
5188
+ entityId,
5189
+ comments,
5190
+ entityParentId,
5191
+ next,
5192
+ sort,
5193
+ });
5194
+ }
5195
+ catch (e) {
5196
+ error = e;
5197
+ }
5198
+ finally {
5199
+ this.state.next((currentState) => ({
5200
+ ...currentState,
5201
+ comments_by_entity_id: {
5202
+ ...currentState.comments_by_entity_id,
5203
+ [entityId]: {
5204
+ ...currentState.comments_by_entity_id[entityId],
5205
+ pagination: {
5206
+ ...currentState.comments_by_entity_id[entityId]?.pagination,
5207
+ loading_next_page: false,
5208
+ },
5209
+ },
5210
+ },
5211
+ }));
5212
+ }
5213
+ if (error) {
5214
+ throw error;
5215
+ }
5216
+ }
5217
+ async loadNextPageActivityComments(activity, request) {
5218
+ const currentEntityState = this.currentState.comments_by_entity_id[activity.id];
5219
+ const currentPagination = currentEntityState?.pagination;
5220
+ const currentNextCursor = currentPagination?.next;
5221
+ const currentSort = currentPagination?.sort;
5222
+ const isLoading = currentPagination?.loading_next_page;
5223
+ const sort = currentSort ?? request?.sort ?? Constants.DEFAULT_COMMENT_PAGINATION;
5224
+ if (isLoading ||
5225
+ !checkHasAnotherPage(currentEntityState?.comments, currentNextCursor)) {
5226
+ return;
5227
+ }
5228
+ await this.loadNextPageComments({
5229
+ entityId: activity.id,
5230
+ base: () => this.client.getComments({
5231
+ ...request,
5232
+ sort,
5233
+ object_id: activity.id,
5234
+ object_type: 'activity',
5235
+ next: currentNextCursor,
5236
+ }),
5237
+ sort,
5238
+ });
5239
+ }
5240
+ async loadNextPageCommentReplies(comment, request) {
5241
+ const currentEntityState = this.currentState.comments_by_entity_id[comment.id];
5242
+ const currentPagination = currentEntityState?.pagination;
5243
+ const currentNextCursor = currentPagination?.next;
5244
+ const currentSort = currentPagination?.sort;
5245
+ const isLoading = currentPagination?.loading_next_page;
5246
+ const sort = currentSort ?? request?.sort ?? Constants.DEFAULT_COMMENT_PAGINATION;
5247
+ if (isLoading ||
5248
+ !checkHasAnotherPage(currentEntityState?.comments, currentNextCursor)) {
5249
+ return;
5250
+ }
5251
+ await this.loadNextPageComments({
5252
+ entityId: comment.id,
5253
+ base: () => this.client.getCommentReplies({
5254
+ ...request,
5255
+ id: comment.id,
5256
+ // use known sort first (prevents broken pagination)
5257
+ sort,
5258
+ next: currentNextCursor,
5259
+ }),
5260
+ entityParentId: comment.parent_id ?? comment.object_id,
5261
+ sort,
5262
+ });
5263
+ }
5264
+ async loadNextPageFollows(type, request) {
5265
+ const paginationKey = `${type}_pagination`;
5266
+ const method = `query${capitalize(type)}`;
5267
+ const currentFollows = this.currentState[type];
5268
+ const currentNextCursor = this.currentState[paginationKey]?.next;
5269
+ const isLoading = this.currentState[paginationKey]?.loading_next_page;
5270
+ const sort = this.currentState[paginationKey]?.sort ?? request.sort;
5271
+ let error;
5272
+ if (isLoading || !checkHasAnotherPage(currentFollows, currentNextCursor)) {
5273
+ return;
5274
+ }
5275
+ try {
5276
+ this.state.next((currentState) => {
5277
+ return {
5278
+ ...currentState,
5279
+ [paginationKey]: {
5280
+ ...currentState[paginationKey],
5281
+ loading_next_page: true,
5282
+ },
5283
+ };
5307
5284
  });
5308
- };
5309
- this.handleVoteRemoved = (event) => {
5310
- if (event.poll?.id && event.poll.id !== this.id)
5311
- return;
5312
- if (!isPollVoteRemovedEvent(event))
5313
- return;
5314
- const currentState = this.data;
5315
- const isOwnVote = event.poll_vote.user_id ===
5316
- this.client.state.getLatestValue().connected_user?.id;
5317
- let latestAnswers = [...currentState.latest_answers];
5318
- let ownAnswer = currentState.own_answer;
5319
- const ownVotesByOptionId = { ...currentState.own_votes_by_option_id };
5320
- let maxVotedOptionIds = currentState.max_voted_option_ids;
5321
- // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
5322
- if (isVoteAnswer(event.poll_vote)) {
5323
- latestAnswers = latestAnswers.filter((answer) => answer.id !== event.poll_vote.id);
5324
- if (isOwnVote) {
5325
- ownAnswer = undefined;
5326
- }
5327
- }
5328
- else {
5329
- maxVotedOptionIds = getMaxVotedOptionIds(event.poll.vote_counts_by_option);
5330
- if (isOwnVote && event.poll_vote.option_id) {
5331
- delete ownVotesByOptionId[event.poll_vote.option_id];
5332
- }
5333
- }
5334
- const { answers_count, latest_votes_by_option, vote_count, vote_counts_by_option, } = event.poll;
5335
- this.state.partialNext({
5336
- answers_count,
5337
- // @ts-expect-error Incompatibility between PollResponseData and Poll due to teams_role, remove when OpenAPI spec is fixed
5338
- latest_votes_by_option,
5339
- vote_count,
5340
- vote_counts_by_option,
5341
- latest_answers: latestAnswers,
5342
- last_activity_at: new Date(event.created_at),
5343
- own_answer: ownAnswer,
5344
- own_votes_by_option_id: ownVotesByOptionId,
5345
- max_voted_option_ids: maxVotedOptionIds,
5285
+ const { next: newNextCursor, follows } = await this[method]({
5286
+ ...request,
5287
+ next: currentNextCursor,
5288
+ sort,
5289
+ });
5290
+ this.state.next((currentState) => {
5291
+ return {
5292
+ ...currentState,
5293
+ [type]: currentState[type] === undefined
5294
+ ? follows
5295
+ : uniqueArrayMerge(currentState[type], follows, (follow) => `${follow.source_feed.feed}-${follow.target_feed.feed}`),
5296
+ [paginationKey]: {
5297
+ ...currentState[paginationKey],
5298
+ next: newNextCursor,
5299
+ sort,
5300
+ },
5301
+ };
5302
+ });
5303
+ }
5304
+ catch (e) {
5305
+ error = e;
5306
+ }
5307
+ finally {
5308
+ this.state.next((currentState) => {
5309
+ return {
5310
+ ...currentState,
5311
+ [paginationKey]: {
5312
+ ...currentState[paginationKey],
5313
+ loading_next_page: false,
5314
+ },
5315
+ };
5316
+ });
5317
+ }
5318
+ if (error) {
5319
+ throw error;
5320
+ }
5321
+ }
5322
+ async loadNextPageFollowers(request) {
5323
+ await this.loadNextPageFollows('followers', request);
5324
+ }
5325
+ async loadNextPageFollowing(request) {
5326
+ await this.loadNextPageFollows('following', request);
5327
+ }
5328
+ async loadNextPageMembers(request) {
5329
+ const currentMembers = this.currentState.members;
5330
+ const currentNextCursor = this.currentState.member_pagination?.next;
5331
+ const isLoading = this.currentState.member_pagination?.loading_next_page;
5332
+ const sort = this.currentState.member_pagination?.sort ?? request.sort;
5333
+ let error;
5334
+ if (isLoading || !checkHasAnotherPage(currentMembers, currentNextCursor)) {
5335
+ return;
5336
+ }
5337
+ try {
5338
+ this.state.next((currentState) => ({
5339
+ ...currentState,
5340
+ member_pagination: {
5341
+ ...currentState.member_pagination,
5342
+ loading_next_page: true,
5343
+ },
5344
+ }));
5345
+ const { next: newNextCursor, members } = await this.client.queryFeedMembers({
5346
+ ...request,
5347
+ sort,
5348
+ feed_id: this.id,
5349
+ feed_group_id: this.group,
5350
+ next: currentNextCursor,
5346
5351
  });
5352
+ this.state.next((currentState) => ({
5353
+ ...currentState,
5354
+ members: currentState.members
5355
+ ? uniqueArrayMerge(currentState.members, members, ({ user }) => user.id)
5356
+ : members,
5357
+ member_pagination: {
5358
+ ...currentState.member_pagination,
5359
+ next: newNextCursor,
5360
+ // set sort if not defined yet
5361
+ sort: currentState.member_pagination?.sort ?? request.sort,
5362
+ },
5363
+ }));
5364
+ }
5365
+ catch (e) {
5366
+ error = e;
5367
+ }
5368
+ finally {
5369
+ this.state.next((currentState) => ({
5370
+ ...currentState,
5371
+ member_pagination: {
5372
+ ...currentState.member_pagination,
5373
+ loading_next_page: false,
5374
+ },
5375
+ }));
5376
+ }
5377
+ if (error) {
5378
+ throw error;
5379
+ }
5380
+ }
5381
+ /**
5382
+ * Method which queries followers of this feed (feeds which target this feed).
5383
+ *
5384
+ * _Note: Useful only for feeds with `groupId` of `user` value._
5385
+ */
5386
+ async queryFollowers(request) {
5387
+ const filter = {
5388
+ target_feed: this.feed,
5347
5389
  };
5348
- this.client = client;
5349
- this.id = poll.id;
5350
- this.state = new StateStore(this.getInitialStateFromPollResponse(poll));
5390
+ const response = await this.client.queryFollows({
5391
+ filter,
5392
+ ...request,
5393
+ });
5394
+ return response;
5351
5395
  }
5352
- get data() {
5353
- return this.state.getLatestValue();
5396
+ /**
5397
+ * Method which queries following of this feed (target feeds of this feed).
5398
+ *
5399
+ * _Note: Useful only for feeds with `groupId` of `timeline` value._
5400
+ */
5401
+ async queryFollowing(request) {
5402
+ const filter = {
5403
+ source_feed: this.feed,
5404
+ };
5405
+ const response = await this.client.queryFollows({
5406
+ filter,
5407
+ ...request,
5408
+ });
5409
+ return response;
5354
5410
  }
5355
- }
5356
- function getMaxVotedOptionIds(voteCountsByOption) {
5357
- let maxVotes = 0;
5358
- let winningOptions = [];
5359
- for (const [id, count] of Object.entries(voteCountsByOption ?? {})) {
5360
- if (count > maxVotes) {
5361
- winningOptions = [id];
5362
- maxVotes = count;
5411
+ async follow(feedOrFid, options) {
5412
+ const fid = typeof feedOrFid === 'string' ? feedOrFid : feedOrFid.feed;
5413
+ const response = await this.client.follow({
5414
+ ...options,
5415
+ source: this.feed,
5416
+ target: fid,
5417
+ });
5418
+ return response;
5419
+ }
5420
+ async unfollow(feedOrFid) {
5421
+ const fid = typeof feedOrFid === 'string' ? feedOrFid : feedOrFid.feed;
5422
+ const response = await this.client.unfollow({
5423
+ source: this.feed,
5424
+ target: fid,
5425
+ });
5426
+ return response;
5427
+ }
5428
+ async getNextPage() {
5429
+ const currentState = this.currentState;
5430
+ return await this.getOrCreate({
5431
+ member_pagination: {
5432
+ limit: 0,
5433
+ },
5434
+ followers_pagination: {
5435
+ limit: 0,
5436
+ },
5437
+ following_pagination: {
5438
+ limit: 0,
5439
+ },
5440
+ next: currentState.next,
5441
+ limit: currentState.last_get_or_create_request_config?.limit ?? 20,
5442
+ });
5443
+ }
5444
+ addActivity(request) {
5445
+ return this.feedsApi.addActivity({
5446
+ ...request,
5447
+ feeds: [this.feed],
5448
+ });
5449
+ }
5450
+ handleWSEvent(event) {
5451
+ const eventHandler = this.eventHandlers[event.type];
5452
+ // no need to run noop function
5453
+ if (eventHandler !== Feed.noop) {
5454
+ // @ts-expect-error intersection of handler arguments results to never
5455
+ eventHandler?.(event);
5363
5456
  }
5364
- else if (count === maxVotes) {
5365
- winningOptions.push(id);
5457
+ if (typeof eventHandler === 'undefined') {
5458
+ console.warn(`Received unknown event type: ${event.type}`, event);
5366
5459
  }
5460
+ this.eventDispatcher.dispatch(event);
5367
5461
  }
5368
- return winningOptions;
5369
5462
  }
5370
- function getOwnVotesByOptionId(ownVotes) {
5371
- return !ownVotes
5372
- ? {}
5373
- : ownVotes.reduce((acc, vote) => {
5374
- if (isVoteAnswer(vote) || !vote.option_id)
5375
- return acc;
5376
- acc[vote.option_id] = vote;
5377
- return acc;
5378
- }, {});
5463
+ Feed.noop = () => { };
5464
+
5465
+ function handleUserUpdated(event) {
5466
+ this.state.next((currentState) => {
5467
+ let newState;
5468
+ const { connected_user } = currentState;
5469
+ if (connected_user && connected_user.id === event.user.id) {
5470
+ newState ?? (newState = {
5471
+ ...currentState,
5472
+ });
5473
+ newState.connected_user = {
5474
+ ...connected_user,
5475
+ ...event.user,
5476
+ };
5477
+ }
5478
+ // TODO: update other users in user map (if/once applicable)
5479
+ return newState ?? currentState;
5480
+ });
5379
5481
  }
5380
5482
 
5381
5483
  class FeedsClient extends FeedsApi {
@@ -5485,7 +5587,7 @@ class FeedsClient extends FeedsApi {
5485
5587
  if (this.activeFeeds[fid]) {
5486
5588
  const feed = this.activeFeeds[fid];
5487
5589
  if (watch && !feed.currentState.watch) {
5488
- feed.handleWatchStarted();
5590
+ handleWatchStarted.bind(feed)();
5489
5591
  }
5490
5592
  return feed;
5491
5593
  }
@@ -5521,7 +5623,7 @@ class FeedsClient extends FeedsApi {
5521
5623
  }
5522
5624
  else {
5523
5625
  for (const activeFeed of Object.values(this.activeFeeds)) {
5524
- activeFeed.handleWatchStopped();
5626
+ handleWatchStopped.bind(activeFeed)();
5525
5627
  }
5526
5628
  }
5527
5629
  break;
@@ -5601,6 +5703,10 @@ class FeedsClient extends FeedsApi {
5601
5703
  feeds.forEach((f) => f.handleWSEvent(event));
5602
5704
  break;
5603
5705
  }
5706
+ case 'user.updated': {
5707
+ handleUserUpdated.call(this, event);
5708
+ break;
5709
+ }
5604
5710
  default: {
5605
5711
  feed?.handleWSEvent(event);
5606
5712
  }
@@ -5626,7 +5732,7 @@ class FeedsClient extends FeedsApi {
5626
5732
  }
5627
5733
  }
5628
5734
  async queryFeeds(request) {
5629
- const response = await this.feedsQueryFeeds(request);
5735
+ const response = await this._queryFeeds(request);
5630
5736
  const feeds = response.feeds.map((f) => this.getOrCreateActiveFeed(f.group_id, f.id, f, request?.watch));
5631
5737
  return {
5632
5738
  feeds,
@@ -5636,13 +5742,29 @@ class FeedsClient extends FeedsApi {
5636
5742
  duration: response.duration,
5637
5743
  };
5638
5744
  }
5745
+ async updateFollow(request) {
5746
+ const response = await super.updateFollow(request);
5747
+ [
5748
+ response.follow.source_feed.feed,
5749
+ response.follow.target_feed.feed,
5750
+ ].forEach((fid) => {
5751
+ const feed = this.activeFeeds[fid];
5752
+ if (feed) {
5753
+ handleFollowUpdated.bind(feed)(response);
5754
+ }
5755
+ });
5756
+ return response;
5757
+ }
5639
5758
  // For follow API endpoints we update the state after HTTP response to allow queryFeeds with watch: false
5640
5759
  async follow(request) {
5641
5760
  const response = await super.follow(request);
5642
- [response.follow.source_feed.fid, response.follow.target_feed.fid].forEach((fid) => {
5761
+ [
5762
+ response.follow.source_feed.feed,
5763
+ response.follow.target_feed.feed,
5764
+ ].forEach((fid) => {
5643
5765
  const feed = this.activeFeeds[fid];
5644
5766
  if (feed) {
5645
- feed.handleFollowCreated(response.follow);
5767
+ handleFollowCreated.bind(feed)(response);
5646
5768
  }
5647
5769
  });
5648
5770
  return response;
@@ -5650,9 +5772,9 @@ class FeedsClient extends FeedsApi {
5650
5772
  async followBatch(request) {
5651
5773
  const response = await super.followBatch(request);
5652
5774
  response.follows.forEach((follow) => {
5653
- const feed = this.activeFeeds[follow.source_feed.fid];
5775
+ const feed = this.activeFeeds[follow.source_feed.feed];
5654
5776
  if (feed) {
5655
- feed.handleFollowCreated(follow);
5777
+ handleFollowCreated.bind(feed)({ follow });
5656
5778
  }
5657
5779
  });
5658
5780
  return response;
@@ -5662,10 +5784,7 @@ class FeedsClient extends FeedsApi {
5662
5784
  [request.source, request.target].forEach((fid) => {
5663
5785
  const feed = this.activeFeeds[fid];
5664
5786
  if (feed) {
5665
- feed.handleFollowDeleted({
5666
- source_feed: { fid: request.source },
5667
- target_feed: { fid: request.target },
5668
- });
5787
+ handleFollowDeleted.bind(feed)(response);
5669
5788
  }
5670
5789
  });
5671
5790
  return response;
@@ -5678,7 +5797,7 @@ class FeedsClient extends FeedsApi {
5678
5797
  });
5679
5798
  const feed = this.activeFeeds[`${request.feed_group_id}:${request.feed_id}`];
5680
5799
  if (feed) {
5681
- feed.handleWatchStopped();
5800
+ handleWatchStopped.bind(feed)();
5682
5801
  }
5683
5802
  return response;
5684
5803
  }
@@ -6110,10 +6229,13 @@ exports.StreamApiError = StreamApiError;
6110
6229
  exports.StreamPoll = StreamPoll;
6111
6230
  exports.UserSearchSource = UserSearchSource;
6112
6231
  exports.checkHasAnotherPage = checkHasAnotherPage;
6232
+ exports.getStateUpdateQueueId = getStateUpdateQueueId;
6113
6233
  exports.isCommentResponse = isCommentResponse;
6234
+ exports.isFollowResponse = isFollowResponse;
6114
6235
  exports.isImageFile = isImageFile;
6115
6236
  exports.isPatch = isPatch;
6116
6237
  exports.isVideoFile = isVideoFile;
6117
6238
  exports.isVoteAnswer = isVoteAnswer;
6239
+ exports.shouldUpdateState = shouldUpdateState;
6118
6240
  exports.uniqueArrayMerge = uniqueArrayMerge;
6119
6241
  //# sourceMappingURL=index.node.cjs.map