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