@stream-io/feeds-client 0.1.8 → 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 (40) hide show
  1. package/@react-bindings/hooks/util/index.ts +1 -0
  2. package/CHANGELOG.md +15 -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 +346 -140
  7. package/dist/index-react-bindings.browser.cjs.map +1 -1
  8. package/dist/index-react-bindings.browser.js +346 -141
  9. package/dist/index-react-bindings.browser.js.map +1 -1
  10. package/dist/index-react-bindings.node.cjs +346 -140
  11. package/dist/index-react-bindings.node.cjs.map +1 -1
  12. package/dist/index-react-bindings.node.js +346 -141
  13. package/dist/index-react-bindings.node.js.map +1 -1
  14. package/dist/index.browser.cjs +320 -139
  15. package/dist/index.browser.cjs.map +1 -1
  16. package/dist/index.browser.js +320 -140
  17. package/dist/index.browser.js.map +1 -1
  18. package/dist/index.node.cjs +320 -139
  19. package/dist/index.node.cjs.map +1 -1
  20. package/dist/index.node.js +320 -140
  21. package/dist/index.node.js.map +1 -1
  22. package/dist/src/Feed.d.ts +40 -9
  23. package/dist/src/FeedsClient.d.ts +8 -1
  24. package/dist/src/gen-imports.d.ts +1 -1
  25. package/dist/src/state-updates/follow-utils.d.ts +19 -0
  26. package/dist/src/state-updates/state-update-queue.d.ts +15 -0
  27. package/dist/src/utils.d.ts +1 -0
  28. package/dist/tsconfig.tsbuildinfo +1 -1
  29. package/package.json +1 -1
  30. package/src/Feed.ts +226 -192
  31. package/src/FeedsClient.ts +75 -3
  32. package/src/gen-imports.ts +1 -1
  33. package/src/state-updates/activity-reaction-utils.test.ts +1 -0
  34. package/src/state-updates/activity-utils.test.ts +1 -0
  35. package/src/state-updates/follow-utils.test.ts +552 -0
  36. package/src/state-updates/follow-utils.ts +126 -0
  37. package/src/state-updates/state-update-queue.test.ts +53 -0
  38. package/src/state-updates/state-update-queue.ts +35 -0
  39. package/src/utils.test.ts +175 -0
  40. package/src/utils.ts +20 -0
@@ -4058,6 +4058,89 @@ const updateBookmarkInActivities = (event, activities, isCurrentUser) => {
4058
4058
  return updateActivityInActivities(updatedActivity, activities);
4059
4059
  };
4060
4060
 
4061
+ const isFeedResponse = (follow) => {
4062
+ return 'created_by' in follow;
4063
+ };
4064
+ const handleFollowCreated = (follow, currentState, currentFeedId, connectedUserId) => {
4065
+ // filter non-accepted follows (the way getOrCreate does by default)
4066
+ if (follow.status !== 'accepted') {
4067
+ return { changed: false, data: currentState };
4068
+ }
4069
+ let newState = { ...currentState };
4070
+ // this feed followed someone
4071
+ if (follow.source_feed.fid === currentFeedId) {
4072
+ newState = {
4073
+ ...newState,
4074
+ // Update FeedResponse fields, that has the new follower/following count
4075
+ ...follow.source_feed,
4076
+ };
4077
+ // Only update if following array already exists
4078
+ if (currentState.following !== undefined) {
4079
+ newState.following = [follow, ...currentState.following];
4080
+ }
4081
+ }
4082
+ else if (
4083
+ // someone followed this feed
4084
+ follow.target_feed.fid === currentFeedId) {
4085
+ const source = follow.source_feed;
4086
+ newState = {
4087
+ ...newState,
4088
+ // Update FeedResponse fields, that has the new follower/following count
4089
+ ...follow.target_feed,
4090
+ };
4091
+ if (source.created_by.id === connectedUserId) {
4092
+ newState.own_follows = currentState.own_follows
4093
+ ? currentState.own_follows.concat(follow)
4094
+ : [follow];
4095
+ }
4096
+ // Only update if followers array already exists
4097
+ if (currentState.followers !== undefined) {
4098
+ newState.followers = [follow, ...currentState.followers];
4099
+ }
4100
+ }
4101
+ return { changed: true, data: newState };
4102
+ };
4103
+ const handleFollowDeleted = (follow, currentState, currentFeedId, connectedUserId) => {
4104
+ let newState = { ...currentState };
4105
+ // this feed unfollowed someone
4106
+ if (follow.source_feed.fid === currentFeedId) {
4107
+ newState = {
4108
+ ...newState,
4109
+ // Update FeedResponse fields, that has the new follower/following count
4110
+ ...follow.source_feed,
4111
+ };
4112
+ // Only update if following array already exists
4113
+ if (currentState.following !== undefined) {
4114
+ newState.following = currentState.following.filter((followItem) => followItem.target_feed.fid !== follow.target_feed.fid);
4115
+ }
4116
+ }
4117
+ else if (
4118
+ // someone unfollowed this feed
4119
+ follow.target_feed.fid === currentFeedId) {
4120
+ const source = follow.source_feed;
4121
+ newState = {
4122
+ ...newState,
4123
+ // Update FeedResponse fields, that has the new follower/following count
4124
+ ...follow.target_feed,
4125
+ };
4126
+ if (isFeedResponse(source) &&
4127
+ source.created_by.id === connectedUserId &&
4128
+ currentState.own_follows !== undefined) {
4129
+ newState.own_follows = currentState.own_follows.filter((followItem) => followItem.source_feed.fid !== follow.source_feed.fid);
4130
+ }
4131
+ // Only update if followers array already exists
4132
+ if (currentState.followers !== undefined) {
4133
+ newState.followers = currentState.followers.filter((followItem) => followItem.source_feed.fid !== follow.source_feed.fid);
4134
+ }
4135
+ }
4136
+ return { changed: true, data: newState };
4137
+ };
4138
+ const handleFollowUpdated = (currentState) => {
4139
+ // For now, we'll treat follow updates as no-ops since the current implementation does
4140
+ // This can be enhanced later if needed
4141
+ return { changed: false, data: currentState };
4142
+ };
4143
+
4061
4144
  const isImageFile = (file) => {
4062
4145
  // photoshop files begin with 'image/'
4063
4146
  return file.type.startsWith('image/') && !file.type.endsWith('.photoshop');
@@ -4073,11 +4156,41 @@ const isCommentResponse = (entity) => {
4073
4156
  const Constants = {
4074
4157
  DEFAULT_COMMENT_PAGINATION: 'first',
4075
4158
  };
4159
+ const uniqueArrayMerge = (existingArray, arrayToMerge, getKey) => {
4160
+ const existing = new Set();
4161
+ existingArray.forEach((value) => {
4162
+ const key = getKey(value);
4163
+ existing.add(key);
4164
+ });
4165
+ const filteredArrayToMerge = arrayToMerge.filter((value) => {
4166
+ const key = getKey(value);
4167
+ return !existing.has(key);
4168
+ });
4169
+ return existingArray.concat(filteredArrayToMerge);
4170
+ };
4171
+
4172
+ const shouldUpdateState = ({ stateUpdateId, stateUpdateQueue, watch, }) => {
4173
+ if (!watch) {
4174
+ return true;
4175
+ }
4176
+ if (watch && stateUpdateQueue.has(stateUpdateId)) {
4177
+ stateUpdateQueue.delete(stateUpdateId);
4178
+ return false;
4179
+ }
4180
+ stateUpdateQueue.add(stateUpdateId);
4181
+ return true;
4182
+ };
4183
+ const getStateUpdateQueueIdForFollow = (follow) => {
4184
+ return `follow${follow.source_feed.fid}-${follow.target_feed.fid}`;
4185
+ };
4186
+ const getStateUpdateQueueIdForUnfollow = (follow) => {
4187
+ return `unfollow${follow.source_feed.fid}-${follow.target_feed.fid}`;
4188
+ };
4076
4189
 
4077
4190
  class Feed extends FeedApi {
4078
- constructor(client, groupId, id, data) {
4079
- // Need this ugly cast because fileUpload endpoints :(
4191
+ constructor(client, groupId, id, data, watch = false) {
4080
4192
  super(client, groupId, id);
4193
+ this.stateUpdateQueue = new Set();
4081
4194
  this.eventHandlers = {
4082
4195
  'feeds.activity.added': (event) => {
4083
4196
  const currentActivities = this.currentState.activities;
@@ -4223,74 +4336,14 @@ class Feed extends FeedApi {
4223
4336
  'feeds.feed_group.changed': Feed.noop,
4224
4337
  'feeds.feed_group.deleted': Feed.noop,
4225
4338
  'feeds.follow.created': (event) => {
4226
- // filter non-accepted follows (the way getOrCreate does by default)
4227
- if (event.follow.status !== 'accepted')
4228
- return;
4229
- // this feed followed someone
4230
- if (event.follow.source_feed.fid === this.fid) {
4231
- this.state.next((currentState) => {
4232
- const newState = {
4233
- ...currentState,
4234
- ...event.follow.source_feed,
4235
- };
4236
- if (!checkHasAnotherPage(currentState.following, currentState.following_pagination?.next)) {
4237
- // TODO: respect sort
4238
- newState.following = currentState.following
4239
- ? currentState.following.concat(event.follow)
4240
- : [event.follow];
4241
- }
4242
- return newState;
4243
- });
4244
- }
4245
- else if (
4246
- // someone followed this feed
4247
- event.follow.target_feed.fid === this.fid) {
4248
- const source = event.follow.source_feed;
4249
- const connectedUser = this.client.state.getLatestValue().connected_user;
4250
- this.state.next((currentState) => {
4251
- const newState = { ...currentState, ...event.follow.target_feed };
4252
- if (source.created_by.id === connectedUser?.id) {
4253
- newState.own_follows = currentState.own_follows
4254
- ? currentState.own_follows.concat(event.follow)
4255
- : [event.follow];
4256
- }
4257
- if (!checkHasAnotherPage(currentState.followers, currentState.followers_pagination?.next)) {
4258
- // TODO: respect sort
4259
- newState.followers = currentState.followers
4260
- ? currentState.followers.concat(event.follow)
4261
- : [event.follow];
4262
- }
4263
- return newState;
4264
- });
4265
- }
4339
+ this.handleFollowCreated(event.follow);
4266
4340
  },
4267
4341
  'feeds.follow.deleted': (event) => {
4268
- // this feed unfollowed someone
4269
- if (event.follow.source_feed.fid === this.fid) {
4270
- this.state.next((currentState) => {
4271
- return {
4272
- ...currentState,
4273
- ...event.follow.source_feed,
4274
- following: currentState.following?.filter((follow) => follow.target_feed.fid !== event.follow.target_feed.fid),
4275
- };
4276
- });
4277
- }
4278
- else if (
4279
- // someone unfollowed this feed
4280
- event.follow.target_feed.fid === this.fid) {
4281
- const source = event.follow.source_feed;
4282
- const connectedUser = this.client.state.getLatestValue().connected_user;
4283
- this.state.next((currentState) => {
4284
- const newState = { ...currentState, ...event.follow.target_feed };
4285
- if (source.created_by.id === connectedUser?.id) {
4286
- newState.own_follows = currentState.own_follows?.filter((follow) => follow.source_feed.fid !== event.follow.source_feed.fid);
4287
- }
4288
- newState.followers = currentState.followers?.filter((follow) => follow.source_feed.fid !== event.follow.source_feed.fid);
4289
- return newState;
4290
- });
4291
- }
4342
+ this.handleFollowDeleted(event.follow);
4343
+ },
4344
+ 'feeds.follow.updated': (_event) => {
4345
+ handleFollowUpdated(this.currentState);
4292
4346
  },
4293
- 'feeds.follow.updated': Feed.noop,
4294
4347
  'feeds.comment.reaction.added': this.handleCommentReactionEvent.bind(this),
4295
4348
  'feeds.comment.reaction.deleted': this.handleCommentReactionEvent.bind(this),
4296
4349
  'feeds.comment.reaction.updated': Feed.noop,
@@ -4298,13 +4351,11 @@ class Feed extends FeedApi {
4298
4351
  const { connected_user: connectedUser } = this.client.state.getLatestValue();
4299
4352
  this.state.next((currentState) => {
4300
4353
  let newState;
4301
- if (!checkHasAnotherPage(currentState.members, currentState.member_pagination?.next)) {
4354
+ if (typeof currentState.members !== 'undefined') {
4302
4355
  newState ?? (newState = {
4303
4356
  ...currentState,
4304
4357
  });
4305
- newState.members = newState.members?.concat(event.member) ?? [
4306
- event.member,
4307
- ];
4358
+ newState.members = [event.member, ...currentState.members];
4308
4359
  }
4309
4360
  if (connectedUser?.id === event.member.user.id) {
4310
4361
  newState ?? (newState = {
@@ -4387,6 +4438,7 @@ class Feed extends FeedApi {
4387
4438
  is_loading: false,
4388
4439
  is_loading_activities: false,
4389
4440
  comments_by_entity_id: {},
4441
+ watch,
4390
4442
  });
4391
4443
  this.client = client;
4392
4444
  }
@@ -4466,6 +4518,8 @@ class Feed extends FeedApi {
4466
4518
  }
4467
4519
  }
4468
4520
  else {
4521
+ // Empty queue when reinitializing the state
4522
+ this.stateUpdateQueue.clear();
4469
4523
  const responseCopy = {
4470
4524
  ...response,
4471
4525
  ...response.feed,
@@ -4484,7 +4538,11 @@ class Feed extends FeedApi {
4484
4538
  if (!request?.following_pagination?.limit) {
4485
4539
  delete nextState.following;
4486
4540
  }
4541
+ if (response.members.length === 0 && response.feed.member_count > 0) {
4542
+ delete nextState.members;
4543
+ }
4487
4544
  nextState.last_get_or_create_request_config = request;
4545
+ nextState.watch = request?.watch ? request.watch : currentState.watch;
4488
4546
  return nextState;
4489
4547
  });
4490
4548
  }
@@ -4498,6 +4556,56 @@ class Feed extends FeedApi {
4498
4556
  });
4499
4557
  }
4500
4558
  }
4559
+ /**
4560
+ * @internal
4561
+ */
4562
+ handleFollowCreated(follow) {
4563
+ if (!shouldUpdateState({
4564
+ stateUpdateId: getStateUpdateQueueIdForFollow(follow),
4565
+ stateUpdateQueue: this.stateUpdateQueue,
4566
+ watch: this.currentState.watch,
4567
+ })) {
4568
+ return;
4569
+ }
4570
+ const connectedUser = this.client.state.getLatestValue().connected_user;
4571
+ const result = handleFollowCreated(follow, this.currentState, this.fid, connectedUser?.id);
4572
+ if (result.changed) {
4573
+ this.state.next(result.data);
4574
+ }
4575
+ }
4576
+ /**
4577
+ * @internal
4578
+ */
4579
+ handleFollowDeleted(follow) {
4580
+ if (!shouldUpdateState({
4581
+ stateUpdateId: getStateUpdateQueueIdForUnfollow(follow),
4582
+ stateUpdateQueue: this.stateUpdateQueue,
4583
+ watch: this.currentState.watch,
4584
+ })) {
4585
+ return;
4586
+ }
4587
+ const connectedUser = this.client.state.getLatestValue().connected_user;
4588
+ const result = handleFollowDeleted(follow, this.currentState, this.fid, connectedUser?.id);
4589
+ {
4590
+ this.state.next(result.data);
4591
+ }
4592
+ }
4593
+ /**
4594
+ * @internal
4595
+ */
4596
+ handleWatchStopped() {
4597
+ this.state.partialNext({
4598
+ watch: false,
4599
+ });
4600
+ }
4601
+ /**
4602
+ * @internal
4603
+ */
4604
+ handleWatchStarted() {
4605
+ this.state.partialNext({
4606
+ watch: true,
4607
+ });
4608
+ }
4501
4609
  handleBookmarkAdded(event) {
4502
4610
  const currentActivities = this.currentState.activities;
4503
4611
  const { connected_user: connectedUser } = this.client.state.getLatestValue();
@@ -4542,70 +4650,85 @@ class Feed extends FeedApi {
4542
4650
  }
4543
4651
  return commentIndex;
4544
4652
  }
4545
- getActivityIndex(activity, state) {
4546
- const { activities } = state ?? this.currentState;
4547
- if (!activities) {
4548
- return -1;
4549
- }
4550
- let activityIndex = activities.indexOf(activity);
4551
- // fast lookup failed, try slower approach
4552
- if (activityIndex === -1) {
4553
- activityIndex = activities.findIndex((activity_) => activity_.id === activity.id);
4554
- }
4555
- return activityIndex;
4556
- }
4557
- updateActivityInState(activity, patch) {
4653
+ /**
4654
+ * Load child comments of entity (activity or comment) into the state, if the target entity is comment,
4655
+ * `entityParentId` should be provided (`CommentResponse.parent_id ?? CommentResponse.object_id`).
4656
+ */
4657
+ loadCommentsIntoState(data) {
4658
+ // add initial (top level) object for processing
4659
+ const traverseArray = [
4660
+ {
4661
+ entityId: data.entityId,
4662
+ entityParentId: data.entityParentId,
4663
+ comments: data.comments,
4664
+ next: data.next,
4665
+ },
4666
+ ];
4558
4667
  this.state.next((currentState) => {
4559
- const activityIndex = this.getActivityIndex(activity, currentState);
4560
- if (activityIndex === -1)
4561
- return currentState;
4562
- const nextActivities = [...currentState.activities];
4563
- nextActivities[activityIndex] = patch(currentState.activities[activityIndex]);
4668
+ const newCommentsByEntityId = {
4669
+ ...currentState.comments_by_entity_id,
4670
+ };
4671
+ while (traverseArray.length) {
4672
+ const item = traverseArray.pop();
4673
+ const entityId = item.entityId;
4674
+ // go over entity comments and generate new objects
4675
+ // for further processing if there are any replies
4676
+ item.comments.forEach((comment) => {
4677
+ if (!comment.replies?.length)
4678
+ return;
4679
+ traverseArray.push({
4680
+ entityId: comment.id,
4681
+ entityParentId: entityId,
4682
+ comments: comment.replies,
4683
+ next: comment.meta?.next_cursor,
4684
+ });
4685
+ });
4686
+ // omit replies & meta from the comments (transform ThreadedCommentResponse to CommentResponse)
4687
+ // this is somehow faster than copying the whole
4688
+ // object and deleting the desired properties
4689
+ const newComments = item.comments.map(({ replies: _r, meta: _m, ...restOfTheCommentResponse }) => restOfTheCommentResponse);
4690
+ newCommentsByEntityId[entityId] = {
4691
+ ...newCommentsByEntityId[entityId],
4692
+ entity_parent_id: item.entityParentId,
4693
+ pagination: {
4694
+ ...newCommentsByEntityId[entityId]?.pagination,
4695
+ next: item.next,
4696
+ sort: data.sort,
4697
+ },
4698
+ comments: newCommentsByEntityId[entityId]?.comments
4699
+ ? newCommentsByEntityId[entityId].comments?.concat(newComments)
4700
+ : newComments,
4701
+ };
4702
+ }
4564
4703
  return {
4565
4704
  ...currentState,
4566
- activities: nextActivities,
4705
+ comments_by_entity_id: newCommentsByEntityId,
4567
4706
  };
4568
4707
  });
4569
4708
  }
4570
- async loadNextPageComments({ forId, base, sort, parentId, }) {
4709
+ async loadNextPageComments({ entityId, base, sort, entityParentId, }) {
4571
4710
  let error;
4572
4711
  try {
4573
4712
  this.state.next((currentState) => ({
4574
4713
  ...currentState,
4575
4714
  comments_by_entity_id: {
4576
4715
  ...currentState.comments_by_entity_id,
4577
- [forId]: {
4578
- ...currentState.comments_by_entity_id[forId],
4716
+ [entityId]: {
4717
+ ...currentState.comments_by_entity_id[entityId],
4579
4718
  pagination: {
4580
- ...currentState.comments_by_entity_id[forId]?.pagination,
4719
+ ...currentState.comments_by_entity_id[entityId]?.pagination,
4581
4720
  loading_next_page: true,
4582
4721
  },
4583
4722
  },
4584
4723
  },
4585
4724
  }));
4586
- const { next: newNextCursor, comments } = await base();
4587
- this.state.next((currentState) => {
4588
- const newPagination = {
4589
- ...currentState.comments_by_entity_id[forId]?.pagination,
4590
- next: newNextCursor,
4591
- };
4592
- if (typeof newPagination.sort === 'undefined') {
4593
- newPagination.sort = sort;
4594
- }
4595
- return {
4596
- ...currentState,
4597
- comments_by_entity_id: {
4598
- ...currentState.comments_by_entity_id,
4599
- [forId]: {
4600
- ...currentState.comments_by_entity_id[forId],
4601
- parent_id: parentId,
4602
- pagination: newPagination,
4603
- comments: currentState.comments_by_entity_id[forId]?.comments
4604
- ? currentState.comments_by_entity_id[forId].comments?.concat(comments)
4605
- : comments,
4606
- },
4607
- },
4608
- };
4725
+ const { next, comments } = await base();
4726
+ this.loadCommentsIntoState({
4727
+ entityId,
4728
+ comments,
4729
+ entityParentId,
4730
+ next,
4731
+ sort,
4609
4732
  });
4610
4733
  }
4611
4734
  catch (e) {
@@ -4616,10 +4739,10 @@ class Feed extends FeedApi {
4616
4739
  ...currentState,
4617
4740
  comments_by_entity_id: {
4618
4741
  ...currentState.comments_by_entity_id,
4619
- [forId]: {
4620
- ...currentState.comments_by_entity_id[forId],
4742
+ [entityId]: {
4743
+ ...currentState.comments_by_entity_id[entityId],
4621
4744
  pagination: {
4622
- ...currentState.comments_by_entity_id[forId]?.pagination,
4745
+ ...currentState.comments_by_entity_id[entityId]?.pagination,
4623
4746
  loading_next_page: false,
4624
4747
  },
4625
4748
  },
@@ -4642,7 +4765,7 @@ class Feed extends FeedApi {
4642
4765
  return;
4643
4766
  }
4644
4767
  await this.loadNextPageComments({
4645
- forId: activity.id,
4768
+ entityId: activity.id,
4646
4769
  base: () => this.client.getComments({
4647
4770
  ...request,
4648
4771
  sort,
@@ -4665,7 +4788,7 @@ class Feed extends FeedApi {
4665
4788
  return;
4666
4789
  }
4667
4790
  await this.loadNextPageComments({
4668
- forId: comment.id,
4791
+ entityId: comment.id,
4669
4792
  base: () => this.client.getCommentReplies({
4670
4793
  ...request,
4671
4794
  comment_id: comment.id,
@@ -4675,7 +4798,7 @@ class Feed extends FeedApi {
4675
4798
  Constants.DEFAULT_COMMENT_PAGINATION,
4676
4799
  next: currentNextCursor,
4677
4800
  }),
4678
- parentId: comment.parent_id ?? comment.object_id,
4801
+ entityParentId: comment.parent_id ?? comment.object_id,
4679
4802
  sort,
4680
4803
  });
4681
4804
  }
@@ -4705,17 +4828,19 @@ class Feed extends FeedApi {
4705
4828
  next: currentNextCursor,
4706
4829
  sort,
4707
4830
  });
4708
- this.state.next((currentState) => ({
4709
- ...currentState,
4710
- [type]: currentState[type]
4711
- ? currentState[type].concat(follows)
4712
- : follows,
4713
- [paginationKey]: {
4714
- ...currentState[paginationKey],
4715
- next: newNextCursor,
4716
- sort,
4717
- },
4718
- }));
4831
+ this.state.next((currentState) => {
4832
+ return {
4833
+ ...currentState,
4834
+ [type]: currentState[type] === undefined
4835
+ ? follows
4836
+ : uniqueArrayMerge(currentState[type], follows, (follow) => `${follow.source_feed.fid}-${follow.target_feed.fid}`),
4837
+ [paginationKey]: {
4838
+ ...currentState[paginationKey],
4839
+ next: newNextCursor,
4840
+ sort,
4841
+ },
4842
+ };
4843
+ });
4719
4844
  }
4720
4845
  catch (e) {
4721
4846
  error = e;
@@ -4768,7 +4893,7 @@ class Feed extends FeedApi {
4768
4893
  this.state.next((currentState) => ({
4769
4894
  ...currentState,
4770
4895
  members: currentState.members
4771
- ? currentState.members.concat(members)
4896
+ ? uniqueArrayMerge(currentState.members, members, ({ user }) => user.id)
4772
4897
  : members,
4773
4898
  member_pagination: {
4774
4899
  ...currentState.member_pagination,
@@ -5355,13 +5480,17 @@ class FeedsClient extends FeedsApi {
5355
5480
  };
5356
5481
  this.eventDispatcher.dispatch(networkEvent);
5357
5482
  };
5358
- this.getOrCreateActiveFeed = (group, id, data) => {
5483
+ this.getOrCreateActiveFeed = (group, id, data, watch) => {
5359
5484
  const fid = `${group}:${id}`;
5360
5485
  if (this.activeFeeds[fid]) {
5361
- return this.activeFeeds[fid];
5486
+ const feed = this.activeFeeds[fid];
5487
+ if (watch && !feed.currentState.watch) {
5488
+ feed.handleWatchStarted();
5489
+ }
5490
+ return feed;
5362
5491
  }
5363
5492
  else {
5364
- const feed = new Feed(this, group, id, data);
5493
+ const feed = new Feed(this, group, id, data, watch);
5365
5494
  this.activeFeeds[fid] = feed;
5366
5495
  return feed;
5367
5496
  }
@@ -5390,6 +5519,11 @@ class FeedsClient extends FeedsApi {
5390
5519
  }
5391
5520
  }
5392
5521
  }
5522
+ else {
5523
+ for (const activeFeed of Object.values(this.activeFeeds)) {
5524
+ activeFeed.handleWatchStopped();
5525
+ }
5526
+ }
5393
5527
  break;
5394
5528
  }
5395
5529
  case 'feeds.feed.created': {
@@ -5493,7 +5627,7 @@ class FeedsClient extends FeedsApi {
5493
5627
  }
5494
5628
  async queryFeeds(request) {
5495
5629
  const response = await this.feedsQueryFeeds(request);
5496
- const feeds = response.feeds.map((f) => this.getOrCreateActiveFeed(f.group_id, f.id, f));
5630
+ const feeds = response.feeds.map((f) => this.getOrCreateActiveFeed(f.group_id, f.id, f, request?.watch));
5497
5631
  return {
5498
5632
  feeds,
5499
5633
  next: response.next,
@@ -5502,6 +5636,52 @@ class FeedsClient extends FeedsApi {
5502
5636
  duration: response.duration,
5503
5637
  };
5504
5638
  }
5639
+ // For follow API endpoints we update the state after HTTP response to allow queryFeeds with watch: false
5640
+ async follow(request) {
5641
+ const response = await super.follow(request);
5642
+ [response.follow.source_feed.fid, response.follow.target_feed.fid].forEach((fid) => {
5643
+ const feed = this.activeFeeds[fid];
5644
+ if (feed) {
5645
+ feed.handleFollowCreated(response.follow);
5646
+ }
5647
+ });
5648
+ return response;
5649
+ }
5650
+ async followBatch(request) {
5651
+ const response = await super.followBatch(request);
5652
+ response.follows.forEach((follow) => {
5653
+ const feed = this.activeFeeds[follow.source_feed.fid];
5654
+ if (feed) {
5655
+ feed.handleFollowCreated(follow);
5656
+ }
5657
+ });
5658
+ return response;
5659
+ }
5660
+ async unfollow(request) {
5661
+ const response = await super.unfollow(request);
5662
+ [request.source, request.target].forEach((fid) => {
5663
+ const feed = this.activeFeeds[fid];
5664
+ if (feed) {
5665
+ feed.handleFollowDeleted({
5666
+ source_feed: { fid: request.source },
5667
+ target_feed: { fid: request.target },
5668
+ });
5669
+ }
5670
+ });
5671
+ return response;
5672
+ }
5673
+ async stopWatchingFeed(request) {
5674
+ const connectionId = await this.connectionIdManager.getConnectionId();
5675
+ const response = await super.stopWatchingFeed({
5676
+ ...request,
5677
+ connection_id: connectionId,
5678
+ });
5679
+ const feed = this.activeFeeds[`${request.feed_group_id}:${request.feed_id}`];
5680
+ if (feed) {
5681
+ feed.handleWatchStopped();
5682
+ }
5683
+ return response;
5684
+ }
5505
5685
  findActiveFeedByActivityId(activityId) {
5506
5686
  return Object.values(this.activeFeeds).filter((feed) => feed.currentState.activities?.some((activity) => activity.id === activityId));
5507
5687
  }
@@ -6034,4 +6214,5 @@ exports.isImageFile = isImageFile;
6034
6214
  exports.isPatch = isPatch;
6035
6215
  exports.isVideoFile = isVideoFile;
6036
6216
  exports.isVoteAnswer = isVoteAnswer;
6217
+ exports.uniqueArrayMerge = uniqueArrayMerge;
6037
6218
  //# sourceMappingURL=index.node.cjs.map