@stream-io/feeds-client 0.1.7 → 0.1.9

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 (46) hide show
  1. package/@react-bindings/hooks/util/index.ts +1 -0
  2. package/CHANGELOG.md +20 -0
  3. package/dist/@react-bindings/hooks/util/index.d.ts +1 -0
  4. package/dist/@react-bindings/hooks/util/useBookmarkActions.d.ts +13 -0
  5. package/dist/@react-bindings/hooks/util/useReactionActions.d.ts +1 -1
  6. package/dist/index-react-bindings.browser.cjs +363 -141
  7. package/dist/index-react-bindings.browser.cjs.map +1 -1
  8. package/dist/index-react-bindings.browser.js +363 -142
  9. package/dist/index-react-bindings.browser.js.map +1 -1
  10. package/dist/index-react-bindings.node.cjs +363 -141
  11. package/dist/index-react-bindings.node.cjs.map +1 -1
  12. package/dist/index-react-bindings.node.js +363 -142
  13. package/dist/index-react-bindings.node.js.map +1 -1
  14. package/dist/index.browser.cjs +337 -140
  15. package/dist/index.browser.cjs.map +1 -1
  16. package/dist/index.browser.js +337 -141
  17. package/dist/index.browser.js.map +1 -1
  18. package/dist/index.node.cjs +337 -140
  19. package/dist/index.node.cjs.map +1 -1
  20. package/dist/index.node.js +337 -141
  21. package/dist/index.node.js.map +1 -1
  22. package/dist/src/Feed.d.ts +42 -11
  23. package/dist/src/FeedsClient.d.ts +10 -3
  24. package/dist/src/common/real-time/StableWSConnection.d.ts +3 -3
  25. package/dist/src/gen/models/index.d.ts +25 -2
  26. package/dist/src/gen-imports.d.ts +1 -1
  27. package/dist/src/state-updates/follow-utils.d.ts +19 -0
  28. package/dist/src/state-updates/state-update-queue.d.ts +15 -0
  29. package/dist/src/utils.d.ts +1 -0
  30. package/dist/tsconfig.tsbuildinfo +1 -1
  31. package/package.json +1 -1
  32. package/src/Feed.ts +230 -192
  33. package/src/FeedsClient.ts +75 -3
  34. package/src/gen/feeds/FeedsApi.ts +0 -1
  35. package/src/gen/model-decoders/decoders.ts +16 -0
  36. package/src/gen/model-decoders/event-decoder-mapping.ts +3 -0
  37. package/src/gen/models/index.ts +42 -4
  38. package/src/gen-imports.ts +1 -1
  39. package/src/state-updates/activity-reaction-utils.test.ts +1 -0
  40. package/src/state-updates/activity-utils.test.ts +1 -0
  41. package/src/state-updates/follow-utils.test.ts +552 -0
  42. package/src/state-updates/follow-utils.ts +126 -0
  43. package/src/state-updates/state-update-queue.test.ts +53 -0
  44. package/src/state-updates/state-update-queue.ts +35 -0
  45. package/src/utils.test.ts +175 -0
  46. package/src/utils.ts +20 -0
@@ -872,6 +872,18 @@ decoders.MuteResponse = (input) => {
872
872
  };
873
873
  return decode(typeMappings, input);
874
874
  };
875
+ decoders.NotificationFeedUpdatedEvent = (input) => {
876
+ const typeMappings = {
877
+ created_at: { type: 'DatetimeType', isSingle: true },
878
+ received_at: { type: 'DatetimeType', isSingle: true },
879
+ aggregated_activities: {
880
+ type: 'AggregatedActivityResponse',
881
+ isSingle: false,
882
+ },
883
+ notification_status: { type: 'NotificationStatusResponse', isSingle: true },
884
+ };
885
+ return decode(typeMappings, input);
886
+ };
875
887
  decoders.NotificationStatusResponse = (input) => {
876
888
  const typeMappings = {
877
889
  last_seen_at: { type: 'DatetimeType', isSingle: true },
@@ -2347,7 +2359,6 @@ class FeedsApi {
2347
2359
  }
2348
2360
  async updateLiveLocation(request) {
2349
2361
  const body = {
2350
- created_by_device_id: request?.created_by_device_id,
2351
2362
  message_id: request?.message_id,
2352
2363
  end_at: request?.end_at,
2353
2364
  latitude: request?.latitude,
@@ -3712,6 +3723,7 @@ const eventDecoderMapping = {
3712
3723
  'feeds.follow.created': (data) => decoders.FollowCreatedEvent(data),
3713
3724
  'feeds.follow.deleted': (data) => decoders.FollowDeletedEvent(data),
3714
3725
  'feeds.follow.updated': (data) => decoders.FollowUpdatedEvent(data),
3726
+ 'feeds.notification_feed.updated': (data) => decoders.NotificationFeedUpdatedEvent(data),
3715
3727
  'feeds.poll.closed': (data) => decoders.PollClosedFeedEvent(data),
3716
3728
  'feeds.poll.deleted': (data) => decoders.PollDeletedFeedEvent(data),
3717
3729
  'feeds.poll.updated': (data) => decoders.PollUpdatedFeedEvent(data),
@@ -4044,6 +4056,89 @@ const updateBookmarkInActivities = (event, activities, isCurrentUser) => {
4044
4056
  return updateActivityInActivities(updatedActivity, activities);
4045
4057
  };
4046
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,
4074
+ };
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,
4088
+ };
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,
4109
+ };
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,
4123
+ };
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
+
4047
4142
  const isImageFile = (file) => {
4048
4143
  // photoshop files begin with 'image/'
4049
4144
  return file.type.startsWith('image/') && !file.type.endsWith('.photoshop');
@@ -4059,11 +4154,41 @@ const isCommentResponse = (entity) => {
4059
4154
  const Constants = {
4060
4155
  DEFAULT_COMMENT_PAGINATION: 'first',
4061
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
+ };
4062
4187
 
4063
4188
  class Feed extends FeedApi {
4064
- constructor(client, groupId, id, data) {
4065
- // Need this ugly cast because fileUpload endpoints :(
4189
+ constructor(client, groupId, id, data, watch = false) {
4066
4190
  super(client, groupId, id);
4191
+ this.stateUpdateQueue = new Set();
4067
4192
  this.eventHandlers = {
4068
4193
  'feeds.activity.added': (event) => {
4069
4194
  const currentActivities = this.currentState.activities;
@@ -4209,74 +4334,14 @@ class Feed extends FeedApi {
4209
4334
  'feeds.feed_group.changed': Feed.noop,
4210
4335
  'feeds.feed_group.deleted': Feed.noop,
4211
4336
  'feeds.follow.created': (event) => {
4212
- // filter non-accepted follows (the way getOrCreate does by default)
4213
- if (event.follow.status !== 'accepted')
4214
- return;
4215
- // this feed followed someone
4216
- if (event.follow.source_feed.fid === this.fid) {
4217
- this.state.next((currentState) => {
4218
- const newState = {
4219
- ...currentState,
4220
- ...event.follow.source_feed,
4221
- };
4222
- if (!checkHasAnotherPage(currentState.following, currentState.following_pagination?.next)) {
4223
- // TODO: respect sort
4224
- newState.following = currentState.following
4225
- ? currentState.following.concat(event.follow)
4226
- : [event.follow];
4227
- }
4228
- return newState;
4229
- });
4230
- }
4231
- else if (
4232
- // someone followed this feed
4233
- event.follow.target_feed.fid === this.fid) {
4234
- const source = event.follow.source_feed;
4235
- const connectedUser = this.client.state.getLatestValue().connected_user;
4236
- this.state.next((currentState) => {
4237
- const newState = { ...currentState, ...event.follow.target_feed };
4238
- if (source.created_by.id === connectedUser?.id) {
4239
- newState.own_follows = currentState.own_follows
4240
- ? currentState.own_follows.concat(event.follow)
4241
- : [event.follow];
4242
- }
4243
- if (!checkHasAnotherPage(currentState.followers, currentState.followers_pagination?.next)) {
4244
- // TODO: respect sort
4245
- newState.followers = currentState.followers
4246
- ? currentState.followers.concat(event.follow)
4247
- : [event.follow];
4248
- }
4249
- return newState;
4250
- });
4251
- }
4337
+ this.handleFollowCreated(event.follow);
4252
4338
  },
4253
4339
  'feeds.follow.deleted': (event) => {
4254
- // this feed unfollowed someone
4255
- if (event.follow.source_feed.fid === this.fid) {
4256
- this.state.next((currentState) => {
4257
- return {
4258
- ...currentState,
4259
- ...event.follow.source_feed,
4260
- following: currentState.following?.filter((follow) => follow.target_feed.fid !== event.follow.target_feed.fid),
4261
- };
4262
- });
4263
- }
4264
- else if (
4265
- // someone unfollowed this feed
4266
- event.follow.target_feed.fid === this.fid) {
4267
- const source = event.follow.source_feed;
4268
- const connectedUser = this.client.state.getLatestValue().connected_user;
4269
- this.state.next((currentState) => {
4270
- const newState = { ...currentState, ...event.follow.target_feed };
4271
- if (source.created_by.id === connectedUser?.id) {
4272
- newState.own_follows = currentState.own_follows?.filter((follow) => follow.source_feed.fid !== event.follow.source_feed.fid);
4273
- }
4274
- newState.followers = currentState.followers?.filter((follow) => follow.source_feed.fid !== event.follow.source_feed.fid);
4275
- return newState;
4276
- });
4277
- }
4340
+ this.handleFollowDeleted(event.follow);
4341
+ },
4342
+ 'feeds.follow.updated': (_event) => {
4343
+ handleFollowUpdated(this.currentState);
4278
4344
  },
4279
- 'feeds.follow.updated': Feed.noop,
4280
4345
  'feeds.comment.reaction.added': this.handleCommentReactionEvent.bind(this),
4281
4346
  'feeds.comment.reaction.deleted': this.handleCommentReactionEvent.bind(this),
4282
4347
  'feeds.comment.reaction.updated': Feed.noop,
@@ -4284,13 +4349,11 @@ class Feed extends FeedApi {
4284
4349
  const { connected_user: connectedUser } = this.client.state.getLatestValue();
4285
4350
  this.state.next((currentState) => {
4286
4351
  let newState;
4287
- if (!checkHasAnotherPage(currentState.members, currentState.member_pagination?.next)) {
4352
+ if (typeof currentState.members !== 'undefined') {
4288
4353
  newState ?? (newState = {
4289
4354
  ...currentState,
4290
4355
  });
4291
- newState.members = newState.members?.concat(event.member) ?? [
4292
- event.member,
4293
- ];
4356
+ newState.members = [event.member, ...currentState.members];
4294
4357
  }
4295
4358
  if (connectedUser?.id === event.member.user.id) {
4296
4359
  newState ?? (newState = {
@@ -4337,6 +4400,10 @@ class Feed extends FeedApi {
4337
4400
  return newState ?? currentState;
4338
4401
  });
4339
4402
  },
4403
+ 'feeds.notification_feed.updated': (event) => {
4404
+ console.info('notification feed updated', event);
4405
+ // TODO: handle notification feed updates
4406
+ },
4340
4407
  // the poll events should be removed from here
4341
4408
  'feeds.poll.closed': Feed.noop,
4342
4409
  'feeds.poll.deleted': Feed.noop,
@@ -4369,6 +4436,7 @@ class Feed extends FeedApi {
4369
4436
  is_loading: false,
4370
4437
  is_loading_activities: false,
4371
4438
  comments_by_entity_id: {},
4439
+ watch,
4372
4440
  });
4373
4441
  this.client = client;
4374
4442
  }
@@ -4448,6 +4516,8 @@ class Feed extends FeedApi {
4448
4516
  }
4449
4517
  }
4450
4518
  else {
4519
+ // Empty queue when reinitializing the state
4520
+ this.stateUpdateQueue.clear();
4451
4521
  const responseCopy = {
4452
4522
  ...response,
4453
4523
  ...response.feed,
@@ -4466,7 +4536,11 @@ class Feed extends FeedApi {
4466
4536
  if (!request?.following_pagination?.limit) {
4467
4537
  delete nextState.following;
4468
4538
  }
4539
+ if (response.members.length === 0 && response.feed.member_count > 0) {
4540
+ delete nextState.members;
4541
+ }
4469
4542
  nextState.last_get_or_create_request_config = request;
4543
+ nextState.watch = request?.watch ? request.watch : currentState.watch;
4470
4544
  return nextState;
4471
4545
  });
4472
4546
  }
@@ -4480,6 +4554,56 @@ class Feed extends FeedApi {
4480
4554
  });
4481
4555
  }
4482
4556
  }
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
+ }
4573
+ }
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;
4584
+ }
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);
4589
+ }
4590
+ }
4591
+ /**
4592
+ * @internal
4593
+ */
4594
+ handleWatchStopped() {
4595
+ this.state.partialNext({
4596
+ watch: false,
4597
+ });
4598
+ }
4599
+ /**
4600
+ * @internal
4601
+ */
4602
+ handleWatchStarted() {
4603
+ this.state.partialNext({
4604
+ watch: true,
4605
+ });
4606
+ }
4483
4607
  handleBookmarkAdded(event) {
4484
4608
  const currentActivities = this.currentState.activities;
4485
4609
  const { connected_user: connectedUser } = this.client.state.getLatestValue();
@@ -4524,70 +4648,85 @@ class Feed extends FeedApi {
4524
4648
  }
4525
4649
  return commentIndex;
4526
4650
  }
4527
- getActivityIndex(activity, state) {
4528
- const { activities } = state ?? this.currentState;
4529
- if (!activities) {
4530
- return -1;
4531
- }
4532
- let activityIndex = activities.indexOf(activity);
4533
- // fast lookup failed, try slower approach
4534
- if (activityIndex === -1) {
4535
- activityIndex = activities.findIndex((activity_) => activity_.id === activity.id);
4536
- }
4537
- return activityIndex;
4538
- }
4539
- updateActivityInState(activity, patch) {
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
+ ];
4540
4665
  this.state.next((currentState) => {
4541
- const activityIndex = this.getActivityIndex(activity, currentState);
4542
- if (activityIndex === -1)
4543
- return currentState;
4544
- const nextActivities = [...currentState.activities];
4545
- nextActivities[activityIndex] = patch(currentState.activities[activityIndex]);
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
+ }
4546
4701
  return {
4547
4702
  ...currentState,
4548
- activities: nextActivities,
4703
+ comments_by_entity_id: newCommentsByEntityId,
4549
4704
  };
4550
4705
  });
4551
4706
  }
4552
- async loadNextPageComments({ forId, base, sort, parentId, }) {
4707
+ async loadNextPageComments({ entityId, base, sort, entityParentId, }) {
4553
4708
  let error;
4554
4709
  try {
4555
4710
  this.state.next((currentState) => ({
4556
4711
  ...currentState,
4557
4712
  comments_by_entity_id: {
4558
4713
  ...currentState.comments_by_entity_id,
4559
- [forId]: {
4560
- ...currentState.comments_by_entity_id[forId],
4714
+ [entityId]: {
4715
+ ...currentState.comments_by_entity_id[entityId],
4561
4716
  pagination: {
4562
- ...currentState.comments_by_entity_id[forId]?.pagination,
4717
+ ...currentState.comments_by_entity_id[entityId]?.pagination,
4563
4718
  loading_next_page: true,
4564
4719
  },
4565
4720
  },
4566
4721
  },
4567
4722
  }));
4568
- const { next: newNextCursor, comments } = await base();
4569
- this.state.next((currentState) => {
4570
- const newPagination = {
4571
- ...currentState.comments_by_entity_id[forId]?.pagination,
4572
- next: newNextCursor,
4573
- };
4574
- if (typeof newPagination.sort === 'undefined') {
4575
- newPagination.sort = sort;
4576
- }
4577
- return {
4578
- ...currentState,
4579
- comments_by_entity_id: {
4580
- ...currentState.comments_by_entity_id,
4581
- [forId]: {
4582
- ...currentState.comments_by_entity_id[forId],
4583
- parent_id: parentId,
4584
- pagination: newPagination,
4585
- comments: currentState.comments_by_entity_id[forId]?.comments
4586
- ? currentState.comments_by_entity_id[forId].comments?.concat(comments)
4587
- : comments,
4588
- },
4589
- },
4590
- };
4723
+ const { next, comments } = await base();
4724
+ this.loadCommentsIntoState({
4725
+ entityId,
4726
+ comments,
4727
+ entityParentId,
4728
+ next,
4729
+ sort,
4591
4730
  });
4592
4731
  }
4593
4732
  catch (e) {
@@ -4598,10 +4737,10 @@ class Feed extends FeedApi {
4598
4737
  ...currentState,
4599
4738
  comments_by_entity_id: {
4600
4739
  ...currentState.comments_by_entity_id,
4601
- [forId]: {
4602
- ...currentState.comments_by_entity_id[forId],
4740
+ [entityId]: {
4741
+ ...currentState.comments_by_entity_id[entityId],
4603
4742
  pagination: {
4604
- ...currentState.comments_by_entity_id[forId]?.pagination,
4743
+ ...currentState.comments_by_entity_id[entityId]?.pagination,
4605
4744
  loading_next_page: false,
4606
4745
  },
4607
4746
  },
@@ -4624,7 +4763,7 @@ class Feed extends FeedApi {
4624
4763
  return;
4625
4764
  }
4626
4765
  await this.loadNextPageComments({
4627
- forId: activity.id,
4766
+ entityId: activity.id,
4628
4767
  base: () => this.client.getComments({
4629
4768
  ...request,
4630
4769
  sort,
@@ -4647,7 +4786,7 @@ class Feed extends FeedApi {
4647
4786
  return;
4648
4787
  }
4649
4788
  await this.loadNextPageComments({
4650
- forId: comment.id,
4789
+ entityId: comment.id,
4651
4790
  base: () => this.client.getCommentReplies({
4652
4791
  ...request,
4653
4792
  comment_id: comment.id,
@@ -4657,7 +4796,7 @@ class Feed extends FeedApi {
4657
4796
  Constants.DEFAULT_COMMENT_PAGINATION,
4658
4797
  next: currentNextCursor,
4659
4798
  }),
4660
- parentId: comment.parent_id ?? comment.object_id,
4799
+ entityParentId: comment.parent_id ?? comment.object_id,
4661
4800
  sort,
4662
4801
  });
4663
4802
  }
@@ -4687,17 +4826,19 @@ class Feed extends FeedApi {
4687
4826
  next: currentNextCursor,
4688
4827
  sort,
4689
4828
  });
4690
- this.state.next((currentState) => ({
4691
- ...currentState,
4692
- [type]: currentState[type]
4693
- ? currentState[type].concat(follows)
4694
- : follows,
4695
- [paginationKey]: {
4696
- ...currentState[paginationKey],
4697
- next: newNextCursor,
4698
- sort,
4699
- },
4700
- }));
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
+ });
4701
4842
  }
4702
4843
  catch (e) {
4703
4844
  error = e;
@@ -4750,7 +4891,7 @@ class Feed extends FeedApi {
4750
4891
  this.state.next((currentState) => ({
4751
4892
  ...currentState,
4752
4893
  members: currentState.members
4753
- ? currentState.members.concat(members)
4894
+ ? uniqueArrayMerge(currentState.members, members, ({ user }) => user.id)
4754
4895
  : members,
4755
4896
  member_pagination: {
4756
4897
  ...currentState.member_pagination,
@@ -5337,13 +5478,17 @@ class FeedsClient extends FeedsApi {
5337
5478
  };
5338
5479
  this.eventDispatcher.dispatch(networkEvent);
5339
5480
  };
5340
- this.getOrCreateActiveFeed = (group, id, data) => {
5481
+ this.getOrCreateActiveFeed = (group, id, data, watch) => {
5341
5482
  const fid = `${group}:${id}`;
5342
5483
  if (this.activeFeeds[fid]) {
5343
- return this.activeFeeds[fid];
5484
+ const feed = this.activeFeeds[fid];
5485
+ if (watch && !feed.currentState.watch) {
5486
+ feed.handleWatchStarted();
5487
+ }
5488
+ return feed;
5344
5489
  }
5345
5490
  else {
5346
- const feed = new Feed(this, group, id, data);
5491
+ const feed = new Feed(this, group, id, data, watch);
5347
5492
  this.activeFeeds[fid] = feed;
5348
5493
  return feed;
5349
5494
  }
@@ -5372,6 +5517,11 @@ class FeedsClient extends FeedsApi {
5372
5517
  }
5373
5518
  }
5374
5519
  }
5520
+ else {
5521
+ for (const activeFeed of Object.values(this.activeFeeds)) {
5522
+ activeFeed.handleWatchStopped();
5523
+ }
5524
+ }
5375
5525
  break;
5376
5526
  }
5377
5527
  case 'feeds.feed.created': {
@@ -5475,7 +5625,7 @@ class FeedsClient extends FeedsApi {
5475
5625
  }
5476
5626
  async queryFeeds(request) {
5477
5627
  const response = await this.feedsQueryFeeds(request);
5478
- const feeds = response.feeds.map((f) => this.getOrCreateActiveFeed(f.group_id, f.id, f));
5628
+ const feeds = response.feeds.map((f) => this.getOrCreateActiveFeed(f.group_id, f.id, f, request?.watch));
5479
5629
  return {
5480
5630
  feeds,
5481
5631
  next: response.next,
@@ -5484,6 +5634,52 @@ class FeedsClient extends FeedsApi {
5484
5634
  duration: response.duration,
5485
5635
  };
5486
5636
  }
5637
+ // For follow API endpoints we update the state after HTTP response to allow queryFeeds with watch: false
5638
+ async follow(request) {
5639
+ const response = await super.follow(request);
5640
+ [response.follow.source_feed.fid, response.follow.target_feed.fid].forEach((fid) => {
5641
+ const feed = this.activeFeeds[fid];
5642
+ if (feed) {
5643
+ feed.handleFollowCreated(response.follow);
5644
+ }
5645
+ });
5646
+ return response;
5647
+ }
5648
+ async followBatch(request) {
5649
+ const response = await super.followBatch(request);
5650
+ response.follows.forEach((follow) => {
5651
+ const feed = this.activeFeeds[follow.source_feed.fid];
5652
+ if (feed) {
5653
+ feed.handleFollowCreated(follow);
5654
+ }
5655
+ });
5656
+ return response;
5657
+ }
5658
+ async unfollow(request) {
5659
+ const response = await super.unfollow(request);
5660
+ [request.source, request.target].forEach((fid) => {
5661
+ const feed = this.activeFeeds[fid];
5662
+ if (feed) {
5663
+ feed.handleFollowDeleted({
5664
+ source_feed: { fid: request.source },
5665
+ target_feed: { fid: request.target },
5666
+ });
5667
+ }
5668
+ });
5669
+ return response;
5670
+ }
5671
+ async stopWatchingFeed(request) {
5672
+ const connectionId = await this.connectionIdManager.getConnectionId();
5673
+ const response = await super.stopWatchingFeed({
5674
+ ...request,
5675
+ connection_id: connectionId,
5676
+ });
5677
+ const feed = this.activeFeeds[`${request.feed_group_id}:${request.feed_id}`];
5678
+ if (feed) {
5679
+ feed.handleWatchStopped();
5680
+ }
5681
+ return response;
5682
+ }
5487
5683
  findActiveFeedByActivityId(activityId) {
5488
5684
  return Object.values(this.activeFeeds).filter((feed) => feed.currentState.activities?.some((activity) => activity.id === activityId));
5489
5685
  }
@@ -5996,5 +6192,5 @@ class FeedSearchSource extends BaseSearchSource {
5996
6192
  }
5997
6193
  }
5998
6194
 
5999
- export { ActivitySearchSource, BaseSearchSource, ChannelOwnCapability, Constants, Feed, FeedOwnCapability, FeedSearchSource, FeedsClient, MergedStateStore, SearchController, StateStore, StreamApiError, StreamPoll, UserSearchSource, checkHasAnotherPage, isCommentResponse, isImageFile, isPatch, isVideoFile, isVoteAnswer };
6195
+ export { ActivitySearchSource, BaseSearchSource, ChannelOwnCapability, Constants, Feed, FeedOwnCapability, FeedSearchSource, FeedsClient, MergedStateStore, SearchController, StateStore, StreamApiError, StreamPoll, UserSearchSource, checkHasAnotherPage, isCommentResponse, isImageFile, isPatch, isVideoFile, isVoteAnswer, uniqueArrayMerge };
6000
6196
  //# sourceMappingURL=index.node.js.map