@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
@@ -4056,6 +4056,89 @@ const updateBookmarkInActivities = (event, activities, isCurrentUser) => {
4056
4056
  return updateActivityInActivities(updatedActivity, activities);
4057
4057
  };
4058
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
+
4059
4142
  const isImageFile = (file) => {
4060
4143
  // photoshop files begin with 'image/'
4061
4144
  return file.type.startsWith('image/') && !file.type.endsWith('.photoshop');
@@ -4071,11 +4154,41 @@ const isCommentResponse = (entity) => {
4071
4154
  const Constants = {
4072
4155
  DEFAULT_COMMENT_PAGINATION: 'first',
4073
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
+ };
4074
4187
 
4075
4188
  class Feed extends FeedApi {
4076
- constructor(client, groupId, id, data) {
4077
- // Need this ugly cast because fileUpload endpoints :(
4189
+ constructor(client, groupId, id, data, watch = false) {
4078
4190
  super(client, groupId, id);
4191
+ this.stateUpdateQueue = new Set();
4079
4192
  this.eventHandlers = {
4080
4193
  'feeds.activity.added': (event) => {
4081
4194
  const currentActivities = this.currentState.activities;
@@ -4221,74 +4334,14 @@ class Feed extends FeedApi {
4221
4334
  'feeds.feed_group.changed': Feed.noop,
4222
4335
  'feeds.feed_group.deleted': Feed.noop,
4223
4336
  'feeds.follow.created': (event) => {
4224
- // filter non-accepted follows (the way getOrCreate does by default)
4225
- if (event.follow.status !== 'accepted')
4226
- return;
4227
- // this feed followed someone
4228
- if (event.follow.source_feed.fid === this.fid) {
4229
- this.state.next((currentState) => {
4230
- const newState = {
4231
- ...currentState,
4232
- ...event.follow.source_feed,
4233
- };
4234
- if (!checkHasAnotherPage(currentState.following, currentState.following_pagination?.next)) {
4235
- // TODO: respect sort
4236
- newState.following = currentState.following
4237
- ? currentState.following.concat(event.follow)
4238
- : [event.follow];
4239
- }
4240
- return newState;
4241
- });
4242
- }
4243
- else if (
4244
- // someone followed this feed
4245
- event.follow.target_feed.fid === this.fid) {
4246
- const source = event.follow.source_feed;
4247
- const connectedUser = this.client.state.getLatestValue().connected_user;
4248
- this.state.next((currentState) => {
4249
- const newState = { ...currentState, ...event.follow.target_feed };
4250
- if (source.created_by.id === connectedUser?.id) {
4251
- newState.own_follows = currentState.own_follows
4252
- ? currentState.own_follows.concat(event.follow)
4253
- : [event.follow];
4254
- }
4255
- if (!checkHasAnotherPage(currentState.followers, currentState.followers_pagination?.next)) {
4256
- // TODO: respect sort
4257
- newState.followers = currentState.followers
4258
- ? currentState.followers.concat(event.follow)
4259
- : [event.follow];
4260
- }
4261
- return newState;
4262
- });
4263
- }
4337
+ this.handleFollowCreated(event.follow);
4264
4338
  },
4265
4339
  'feeds.follow.deleted': (event) => {
4266
- // this feed unfollowed someone
4267
- if (event.follow.source_feed.fid === this.fid) {
4268
- this.state.next((currentState) => {
4269
- return {
4270
- ...currentState,
4271
- ...event.follow.source_feed,
4272
- following: currentState.following?.filter((follow) => follow.target_feed.fid !== event.follow.target_feed.fid),
4273
- };
4274
- });
4275
- }
4276
- else if (
4277
- // someone unfollowed this feed
4278
- event.follow.target_feed.fid === this.fid) {
4279
- const source = event.follow.source_feed;
4280
- const connectedUser = this.client.state.getLatestValue().connected_user;
4281
- this.state.next((currentState) => {
4282
- const newState = { ...currentState, ...event.follow.target_feed };
4283
- if (source.created_by.id === connectedUser?.id) {
4284
- newState.own_follows = currentState.own_follows?.filter((follow) => follow.source_feed.fid !== event.follow.source_feed.fid);
4285
- }
4286
- newState.followers = currentState.followers?.filter((follow) => follow.source_feed.fid !== event.follow.source_feed.fid);
4287
- return newState;
4288
- });
4289
- }
4340
+ this.handleFollowDeleted(event.follow);
4341
+ },
4342
+ 'feeds.follow.updated': (_event) => {
4343
+ handleFollowUpdated(this.currentState);
4290
4344
  },
4291
- 'feeds.follow.updated': Feed.noop,
4292
4345
  'feeds.comment.reaction.added': this.handleCommentReactionEvent.bind(this),
4293
4346
  'feeds.comment.reaction.deleted': this.handleCommentReactionEvent.bind(this),
4294
4347
  'feeds.comment.reaction.updated': Feed.noop,
@@ -4296,13 +4349,11 @@ class Feed extends FeedApi {
4296
4349
  const { connected_user: connectedUser } = this.client.state.getLatestValue();
4297
4350
  this.state.next((currentState) => {
4298
4351
  let newState;
4299
- if (!checkHasAnotherPage(currentState.members, currentState.member_pagination?.next)) {
4352
+ if (typeof currentState.members !== 'undefined') {
4300
4353
  newState ?? (newState = {
4301
4354
  ...currentState,
4302
4355
  });
4303
- newState.members = newState.members?.concat(event.member) ?? [
4304
- event.member,
4305
- ];
4356
+ newState.members = [event.member, ...currentState.members];
4306
4357
  }
4307
4358
  if (connectedUser?.id === event.member.user.id) {
4308
4359
  newState ?? (newState = {
@@ -4385,6 +4436,7 @@ class Feed extends FeedApi {
4385
4436
  is_loading: false,
4386
4437
  is_loading_activities: false,
4387
4438
  comments_by_entity_id: {},
4439
+ watch,
4388
4440
  });
4389
4441
  this.client = client;
4390
4442
  }
@@ -4464,6 +4516,8 @@ class Feed extends FeedApi {
4464
4516
  }
4465
4517
  }
4466
4518
  else {
4519
+ // Empty queue when reinitializing the state
4520
+ this.stateUpdateQueue.clear();
4467
4521
  const responseCopy = {
4468
4522
  ...response,
4469
4523
  ...response.feed,
@@ -4482,7 +4536,11 @@ class Feed extends FeedApi {
4482
4536
  if (!request?.following_pagination?.limit) {
4483
4537
  delete nextState.following;
4484
4538
  }
4539
+ if (response.members.length === 0 && response.feed.member_count > 0) {
4540
+ delete nextState.members;
4541
+ }
4485
4542
  nextState.last_get_or_create_request_config = request;
4543
+ nextState.watch = request?.watch ? request.watch : currentState.watch;
4486
4544
  return nextState;
4487
4545
  });
4488
4546
  }
@@ -4496,6 +4554,56 @@ class Feed extends FeedApi {
4496
4554
  });
4497
4555
  }
4498
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
+ }
4499
4607
  handleBookmarkAdded(event) {
4500
4608
  const currentActivities = this.currentState.activities;
4501
4609
  const { connected_user: connectedUser } = this.client.state.getLatestValue();
@@ -4540,70 +4648,85 @@ class Feed extends FeedApi {
4540
4648
  }
4541
4649
  return commentIndex;
4542
4650
  }
4543
- getActivityIndex(activity, state) {
4544
- const { activities } = state ?? this.currentState;
4545
- if (!activities) {
4546
- return -1;
4547
- }
4548
- let activityIndex = activities.indexOf(activity);
4549
- // fast lookup failed, try slower approach
4550
- if (activityIndex === -1) {
4551
- activityIndex = activities.findIndex((activity_) => activity_.id === activity.id);
4552
- }
4553
- return activityIndex;
4554
- }
4555
- 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
+ ];
4556
4665
  this.state.next((currentState) => {
4557
- const activityIndex = this.getActivityIndex(activity, currentState);
4558
- if (activityIndex === -1)
4559
- return currentState;
4560
- const nextActivities = [...currentState.activities];
4561
- 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
+ }
4562
4701
  return {
4563
4702
  ...currentState,
4564
- activities: nextActivities,
4703
+ comments_by_entity_id: newCommentsByEntityId,
4565
4704
  };
4566
4705
  });
4567
4706
  }
4568
- async loadNextPageComments({ forId, base, sort, parentId, }) {
4707
+ async loadNextPageComments({ entityId, base, sort, entityParentId, }) {
4569
4708
  let error;
4570
4709
  try {
4571
4710
  this.state.next((currentState) => ({
4572
4711
  ...currentState,
4573
4712
  comments_by_entity_id: {
4574
4713
  ...currentState.comments_by_entity_id,
4575
- [forId]: {
4576
- ...currentState.comments_by_entity_id[forId],
4714
+ [entityId]: {
4715
+ ...currentState.comments_by_entity_id[entityId],
4577
4716
  pagination: {
4578
- ...currentState.comments_by_entity_id[forId]?.pagination,
4717
+ ...currentState.comments_by_entity_id[entityId]?.pagination,
4579
4718
  loading_next_page: true,
4580
4719
  },
4581
4720
  },
4582
4721
  },
4583
4722
  }));
4584
- const { next: newNextCursor, comments } = await base();
4585
- this.state.next((currentState) => {
4586
- const newPagination = {
4587
- ...currentState.comments_by_entity_id[forId]?.pagination,
4588
- next: newNextCursor,
4589
- };
4590
- if (typeof newPagination.sort === 'undefined') {
4591
- newPagination.sort = sort;
4592
- }
4593
- return {
4594
- ...currentState,
4595
- comments_by_entity_id: {
4596
- ...currentState.comments_by_entity_id,
4597
- [forId]: {
4598
- ...currentState.comments_by_entity_id[forId],
4599
- parent_id: parentId,
4600
- pagination: newPagination,
4601
- comments: currentState.comments_by_entity_id[forId]?.comments
4602
- ? currentState.comments_by_entity_id[forId].comments?.concat(comments)
4603
- : comments,
4604
- },
4605
- },
4606
- };
4723
+ const { next, comments } = await base();
4724
+ this.loadCommentsIntoState({
4725
+ entityId,
4726
+ comments,
4727
+ entityParentId,
4728
+ next,
4729
+ sort,
4607
4730
  });
4608
4731
  }
4609
4732
  catch (e) {
@@ -4614,10 +4737,10 @@ class Feed extends FeedApi {
4614
4737
  ...currentState,
4615
4738
  comments_by_entity_id: {
4616
4739
  ...currentState.comments_by_entity_id,
4617
- [forId]: {
4618
- ...currentState.comments_by_entity_id[forId],
4740
+ [entityId]: {
4741
+ ...currentState.comments_by_entity_id[entityId],
4619
4742
  pagination: {
4620
- ...currentState.comments_by_entity_id[forId]?.pagination,
4743
+ ...currentState.comments_by_entity_id[entityId]?.pagination,
4621
4744
  loading_next_page: false,
4622
4745
  },
4623
4746
  },
@@ -4640,7 +4763,7 @@ class Feed extends FeedApi {
4640
4763
  return;
4641
4764
  }
4642
4765
  await this.loadNextPageComments({
4643
- forId: activity.id,
4766
+ entityId: activity.id,
4644
4767
  base: () => this.client.getComments({
4645
4768
  ...request,
4646
4769
  sort,
@@ -4663,7 +4786,7 @@ class Feed extends FeedApi {
4663
4786
  return;
4664
4787
  }
4665
4788
  await this.loadNextPageComments({
4666
- forId: comment.id,
4789
+ entityId: comment.id,
4667
4790
  base: () => this.client.getCommentReplies({
4668
4791
  ...request,
4669
4792
  comment_id: comment.id,
@@ -4673,7 +4796,7 @@ class Feed extends FeedApi {
4673
4796
  Constants.DEFAULT_COMMENT_PAGINATION,
4674
4797
  next: currentNextCursor,
4675
4798
  }),
4676
- parentId: comment.parent_id ?? comment.object_id,
4799
+ entityParentId: comment.parent_id ?? comment.object_id,
4677
4800
  sort,
4678
4801
  });
4679
4802
  }
@@ -4703,17 +4826,19 @@ class Feed extends FeedApi {
4703
4826
  next: currentNextCursor,
4704
4827
  sort,
4705
4828
  });
4706
- this.state.next((currentState) => ({
4707
- ...currentState,
4708
- [type]: currentState[type]
4709
- ? currentState[type].concat(follows)
4710
- : follows,
4711
- [paginationKey]: {
4712
- ...currentState[paginationKey],
4713
- next: newNextCursor,
4714
- sort,
4715
- },
4716
- }));
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
+ });
4717
4842
  }
4718
4843
  catch (e) {
4719
4844
  error = e;
@@ -4766,7 +4891,7 @@ class Feed extends FeedApi {
4766
4891
  this.state.next((currentState) => ({
4767
4892
  ...currentState,
4768
4893
  members: currentState.members
4769
- ? currentState.members.concat(members)
4894
+ ? uniqueArrayMerge(currentState.members, members, ({ user }) => user.id)
4770
4895
  : members,
4771
4896
  member_pagination: {
4772
4897
  ...currentState.member_pagination,
@@ -5353,13 +5478,17 @@ class FeedsClient extends FeedsApi {
5353
5478
  };
5354
5479
  this.eventDispatcher.dispatch(networkEvent);
5355
5480
  };
5356
- this.getOrCreateActiveFeed = (group, id, data) => {
5481
+ this.getOrCreateActiveFeed = (group, id, data, watch) => {
5357
5482
  const fid = `${group}:${id}`;
5358
5483
  if (this.activeFeeds[fid]) {
5359
- return this.activeFeeds[fid];
5484
+ const feed = this.activeFeeds[fid];
5485
+ if (watch && !feed.currentState.watch) {
5486
+ feed.handleWatchStarted();
5487
+ }
5488
+ return feed;
5360
5489
  }
5361
5490
  else {
5362
- const feed = new Feed(this, group, id, data);
5491
+ const feed = new Feed(this, group, id, data, watch);
5363
5492
  this.activeFeeds[fid] = feed;
5364
5493
  return feed;
5365
5494
  }
@@ -5388,6 +5517,11 @@ class FeedsClient extends FeedsApi {
5388
5517
  }
5389
5518
  }
5390
5519
  }
5520
+ else {
5521
+ for (const activeFeed of Object.values(this.activeFeeds)) {
5522
+ activeFeed.handleWatchStopped();
5523
+ }
5524
+ }
5391
5525
  break;
5392
5526
  }
5393
5527
  case 'feeds.feed.created': {
@@ -5491,7 +5625,7 @@ class FeedsClient extends FeedsApi {
5491
5625
  }
5492
5626
  async queryFeeds(request) {
5493
5627
  const response = await this.feedsQueryFeeds(request);
5494
- 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));
5495
5629
  return {
5496
5630
  feeds,
5497
5631
  next: response.next,
@@ -5500,6 +5634,52 @@ class FeedsClient extends FeedsApi {
5500
5634
  duration: response.duration,
5501
5635
  };
5502
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
+ }
5503
5683
  findActiveFeedByActivityId(activityId) {
5504
5684
  return Object.values(this.activeFeeds).filter((feed) => feed.currentState.activities?.some((activity) => activity.id === activityId));
5505
5685
  }
@@ -6012,5 +6192,5 @@ class FeedSearchSource extends BaseSearchSource {
6012
6192
  }
6013
6193
  }
6014
6194
 
6015
- 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 };
6016
6196
  //# sourceMappingURL=index.browser.js.map