@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
@@ -874,6 +874,18 @@ decoders.MuteResponse = (input) => {
874
874
  };
875
875
  return decode(typeMappings, input);
876
876
  };
877
+ decoders.NotificationFeedUpdatedEvent = (input) => {
878
+ const typeMappings = {
879
+ created_at: { type: 'DatetimeType', isSingle: true },
880
+ received_at: { type: 'DatetimeType', isSingle: true },
881
+ aggregated_activities: {
882
+ type: 'AggregatedActivityResponse',
883
+ isSingle: false,
884
+ },
885
+ notification_status: { type: 'NotificationStatusResponse', isSingle: true },
886
+ };
887
+ return decode(typeMappings, input);
888
+ };
877
889
  decoders.NotificationStatusResponse = (input) => {
878
890
  const typeMappings = {
879
891
  last_seen_at: { type: 'DatetimeType', isSingle: true },
@@ -2349,7 +2361,6 @@ class FeedsApi {
2349
2361
  }
2350
2362
  async updateLiveLocation(request) {
2351
2363
  const body = {
2352
- created_by_device_id: request?.created_by_device_id,
2353
2364
  message_id: request?.message_id,
2354
2365
  end_at: request?.end_at,
2355
2366
  latitude: request?.latitude,
@@ -3714,6 +3725,7 @@ const eventDecoderMapping = {
3714
3725
  'feeds.follow.created': (data) => decoders.FollowCreatedEvent(data),
3715
3726
  'feeds.follow.deleted': (data) => decoders.FollowDeletedEvent(data),
3716
3727
  'feeds.follow.updated': (data) => decoders.FollowUpdatedEvent(data),
3728
+ 'feeds.notification_feed.updated': (data) => decoders.NotificationFeedUpdatedEvent(data),
3717
3729
  'feeds.poll.closed': (data) => decoders.PollClosedFeedEvent(data),
3718
3730
  'feeds.poll.deleted': (data) => decoders.PollDeletedFeedEvent(data),
3719
3731
  'feeds.poll.updated': (data) => decoders.PollUpdatedFeedEvent(data),
@@ -4046,6 +4058,89 @@ const updateBookmarkInActivities = (event, activities, isCurrentUser) => {
4046
4058
  return updateActivityInActivities(updatedActivity, activities);
4047
4059
  };
4048
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
+
4049
4144
  const isImageFile = (file) => {
4050
4145
  // photoshop files begin with 'image/'
4051
4146
  return file.type.startsWith('image/') && !file.type.endsWith('.photoshop');
@@ -4061,11 +4156,41 @@ const isCommentResponse = (entity) => {
4061
4156
  const Constants = {
4062
4157
  DEFAULT_COMMENT_PAGINATION: 'first',
4063
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
+ };
4064
4189
 
4065
4190
  class Feed extends FeedApi {
4066
- constructor(client, groupId, id, data) {
4067
- // Need this ugly cast because fileUpload endpoints :(
4191
+ constructor(client, groupId, id, data, watch = false) {
4068
4192
  super(client, groupId, id);
4193
+ this.stateUpdateQueue = new Set();
4069
4194
  this.eventHandlers = {
4070
4195
  'feeds.activity.added': (event) => {
4071
4196
  const currentActivities = this.currentState.activities;
@@ -4211,74 +4336,14 @@ class Feed extends FeedApi {
4211
4336
  'feeds.feed_group.changed': Feed.noop,
4212
4337
  'feeds.feed_group.deleted': Feed.noop,
4213
4338
  'feeds.follow.created': (event) => {
4214
- // filter non-accepted follows (the way getOrCreate does by default)
4215
- if (event.follow.status !== 'accepted')
4216
- return;
4217
- // this feed followed someone
4218
- if (event.follow.source_feed.fid === this.fid) {
4219
- this.state.next((currentState) => {
4220
- const newState = {
4221
- ...currentState,
4222
- ...event.follow.source_feed,
4223
- };
4224
- if (!checkHasAnotherPage(currentState.following, currentState.following_pagination?.next)) {
4225
- // TODO: respect sort
4226
- newState.following = currentState.following
4227
- ? currentState.following.concat(event.follow)
4228
- : [event.follow];
4229
- }
4230
- return newState;
4231
- });
4232
- }
4233
- else if (
4234
- // someone followed this feed
4235
- event.follow.target_feed.fid === this.fid) {
4236
- const source = event.follow.source_feed;
4237
- const connectedUser = this.client.state.getLatestValue().connected_user;
4238
- this.state.next((currentState) => {
4239
- const newState = { ...currentState, ...event.follow.target_feed };
4240
- if (source.created_by.id === connectedUser?.id) {
4241
- newState.own_follows = currentState.own_follows
4242
- ? currentState.own_follows.concat(event.follow)
4243
- : [event.follow];
4244
- }
4245
- if (!checkHasAnotherPage(currentState.followers, currentState.followers_pagination?.next)) {
4246
- // TODO: respect sort
4247
- newState.followers = currentState.followers
4248
- ? currentState.followers.concat(event.follow)
4249
- : [event.follow];
4250
- }
4251
- return newState;
4252
- });
4253
- }
4339
+ this.handleFollowCreated(event.follow);
4254
4340
  },
4255
4341
  'feeds.follow.deleted': (event) => {
4256
- // this feed unfollowed someone
4257
- if (event.follow.source_feed.fid === this.fid) {
4258
- this.state.next((currentState) => {
4259
- return {
4260
- ...currentState,
4261
- ...event.follow.source_feed,
4262
- following: currentState.following?.filter((follow) => follow.target_feed.fid !== event.follow.target_feed.fid),
4263
- };
4264
- });
4265
- }
4266
- else if (
4267
- // someone unfollowed this feed
4268
- event.follow.target_feed.fid === this.fid) {
4269
- const source = event.follow.source_feed;
4270
- const connectedUser = this.client.state.getLatestValue().connected_user;
4271
- this.state.next((currentState) => {
4272
- const newState = { ...currentState, ...event.follow.target_feed };
4273
- if (source.created_by.id === connectedUser?.id) {
4274
- newState.own_follows = currentState.own_follows?.filter((follow) => follow.source_feed.fid !== event.follow.source_feed.fid);
4275
- }
4276
- newState.followers = currentState.followers?.filter((follow) => follow.source_feed.fid !== event.follow.source_feed.fid);
4277
- return newState;
4278
- });
4279
- }
4342
+ this.handleFollowDeleted(event.follow);
4343
+ },
4344
+ 'feeds.follow.updated': (_event) => {
4345
+ handleFollowUpdated(this.currentState);
4280
4346
  },
4281
- 'feeds.follow.updated': Feed.noop,
4282
4347
  'feeds.comment.reaction.added': this.handleCommentReactionEvent.bind(this),
4283
4348
  'feeds.comment.reaction.deleted': this.handleCommentReactionEvent.bind(this),
4284
4349
  'feeds.comment.reaction.updated': Feed.noop,
@@ -4286,13 +4351,11 @@ class Feed extends FeedApi {
4286
4351
  const { connected_user: connectedUser } = this.client.state.getLatestValue();
4287
4352
  this.state.next((currentState) => {
4288
4353
  let newState;
4289
- if (!checkHasAnotherPage(currentState.members, currentState.member_pagination?.next)) {
4354
+ if (typeof currentState.members !== 'undefined') {
4290
4355
  newState ?? (newState = {
4291
4356
  ...currentState,
4292
4357
  });
4293
- newState.members = newState.members?.concat(event.member) ?? [
4294
- event.member,
4295
- ];
4358
+ newState.members = [event.member, ...currentState.members];
4296
4359
  }
4297
4360
  if (connectedUser?.id === event.member.user.id) {
4298
4361
  newState ?? (newState = {
@@ -4339,6 +4402,10 @@ class Feed extends FeedApi {
4339
4402
  return newState ?? currentState;
4340
4403
  });
4341
4404
  },
4405
+ 'feeds.notification_feed.updated': (event) => {
4406
+ console.info('notification feed updated', event);
4407
+ // TODO: handle notification feed updates
4408
+ },
4342
4409
  // the poll events should be removed from here
4343
4410
  'feeds.poll.closed': Feed.noop,
4344
4411
  'feeds.poll.deleted': Feed.noop,
@@ -4371,6 +4438,7 @@ class Feed extends FeedApi {
4371
4438
  is_loading: false,
4372
4439
  is_loading_activities: false,
4373
4440
  comments_by_entity_id: {},
4441
+ watch,
4374
4442
  });
4375
4443
  this.client = client;
4376
4444
  }
@@ -4450,6 +4518,8 @@ class Feed extends FeedApi {
4450
4518
  }
4451
4519
  }
4452
4520
  else {
4521
+ // Empty queue when reinitializing the state
4522
+ this.stateUpdateQueue.clear();
4453
4523
  const responseCopy = {
4454
4524
  ...response,
4455
4525
  ...response.feed,
@@ -4468,7 +4538,11 @@ class Feed extends FeedApi {
4468
4538
  if (!request?.following_pagination?.limit) {
4469
4539
  delete nextState.following;
4470
4540
  }
4541
+ if (response.members.length === 0 && response.feed.member_count > 0) {
4542
+ delete nextState.members;
4543
+ }
4471
4544
  nextState.last_get_or_create_request_config = request;
4545
+ nextState.watch = request?.watch ? request.watch : currentState.watch;
4472
4546
  return nextState;
4473
4547
  });
4474
4548
  }
@@ -4482,6 +4556,56 @@ class Feed extends FeedApi {
4482
4556
  });
4483
4557
  }
4484
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
+ }
4485
4609
  handleBookmarkAdded(event) {
4486
4610
  const currentActivities = this.currentState.activities;
4487
4611
  const { connected_user: connectedUser } = this.client.state.getLatestValue();
@@ -4526,70 +4650,85 @@ class Feed extends FeedApi {
4526
4650
  }
4527
4651
  return commentIndex;
4528
4652
  }
4529
- getActivityIndex(activity, state) {
4530
- const { activities } = state ?? this.currentState;
4531
- if (!activities) {
4532
- return -1;
4533
- }
4534
- let activityIndex = activities.indexOf(activity);
4535
- // fast lookup failed, try slower approach
4536
- if (activityIndex === -1) {
4537
- activityIndex = activities.findIndex((activity_) => activity_.id === activity.id);
4538
- }
4539
- return activityIndex;
4540
- }
4541
- 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
+ ];
4542
4667
  this.state.next((currentState) => {
4543
- const activityIndex = this.getActivityIndex(activity, currentState);
4544
- if (activityIndex === -1)
4545
- return currentState;
4546
- const nextActivities = [...currentState.activities];
4547
- 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
+ }
4548
4703
  return {
4549
4704
  ...currentState,
4550
- activities: nextActivities,
4705
+ comments_by_entity_id: newCommentsByEntityId,
4551
4706
  };
4552
4707
  });
4553
4708
  }
4554
- async loadNextPageComments({ forId, base, sort, parentId, }) {
4709
+ async loadNextPageComments({ entityId, base, sort, entityParentId, }) {
4555
4710
  let error;
4556
4711
  try {
4557
4712
  this.state.next((currentState) => ({
4558
4713
  ...currentState,
4559
4714
  comments_by_entity_id: {
4560
4715
  ...currentState.comments_by_entity_id,
4561
- [forId]: {
4562
- ...currentState.comments_by_entity_id[forId],
4716
+ [entityId]: {
4717
+ ...currentState.comments_by_entity_id[entityId],
4563
4718
  pagination: {
4564
- ...currentState.comments_by_entity_id[forId]?.pagination,
4719
+ ...currentState.comments_by_entity_id[entityId]?.pagination,
4565
4720
  loading_next_page: true,
4566
4721
  },
4567
4722
  },
4568
4723
  },
4569
4724
  }));
4570
- const { next: newNextCursor, comments } = await base();
4571
- this.state.next((currentState) => {
4572
- const newPagination = {
4573
- ...currentState.comments_by_entity_id[forId]?.pagination,
4574
- next: newNextCursor,
4575
- };
4576
- if (typeof newPagination.sort === 'undefined') {
4577
- newPagination.sort = sort;
4578
- }
4579
- return {
4580
- ...currentState,
4581
- comments_by_entity_id: {
4582
- ...currentState.comments_by_entity_id,
4583
- [forId]: {
4584
- ...currentState.comments_by_entity_id[forId],
4585
- parent_id: parentId,
4586
- pagination: newPagination,
4587
- comments: currentState.comments_by_entity_id[forId]?.comments
4588
- ? currentState.comments_by_entity_id[forId].comments?.concat(comments)
4589
- : comments,
4590
- },
4591
- },
4592
- };
4725
+ const { next, comments } = await base();
4726
+ this.loadCommentsIntoState({
4727
+ entityId,
4728
+ comments,
4729
+ entityParentId,
4730
+ next,
4731
+ sort,
4593
4732
  });
4594
4733
  }
4595
4734
  catch (e) {
@@ -4600,10 +4739,10 @@ class Feed extends FeedApi {
4600
4739
  ...currentState,
4601
4740
  comments_by_entity_id: {
4602
4741
  ...currentState.comments_by_entity_id,
4603
- [forId]: {
4604
- ...currentState.comments_by_entity_id[forId],
4742
+ [entityId]: {
4743
+ ...currentState.comments_by_entity_id[entityId],
4605
4744
  pagination: {
4606
- ...currentState.comments_by_entity_id[forId]?.pagination,
4745
+ ...currentState.comments_by_entity_id[entityId]?.pagination,
4607
4746
  loading_next_page: false,
4608
4747
  },
4609
4748
  },
@@ -4626,7 +4765,7 @@ class Feed extends FeedApi {
4626
4765
  return;
4627
4766
  }
4628
4767
  await this.loadNextPageComments({
4629
- forId: activity.id,
4768
+ entityId: activity.id,
4630
4769
  base: () => this.client.getComments({
4631
4770
  ...request,
4632
4771
  sort,
@@ -4649,7 +4788,7 @@ class Feed extends FeedApi {
4649
4788
  return;
4650
4789
  }
4651
4790
  await this.loadNextPageComments({
4652
- forId: comment.id,
4791
+ entityId: comment.id,
4653
4792
  base: () => this.client.getCommentReplies({
4654
4793
  ...request,
4655
4794
  comment_id: comment.id,
@@ -4659,7 +4798,7 @@ class Feed extends FeedApi {
4659
4798
  Constants.DEFAULT_COMMENT_PAGINATION,
4660
4799
  next: currentNextCursor,
4661
4800
  }),
4662
- parentId: comment.parent_id ?? comment.object_id,
4801
+ entityParentId: comment.parent_id ?? comment.object_id,
4663
4802
  sort,
4664
4803
  });
4665
4804
  }
@@ -4689,17 +4828,19 @@ class Feed extends FeedApi {
4689
4828
  next: currentNextCursor,
4690
4829
  sort,
4691
4830
  });
4692
- this.state.next((currentState) => ({
4693
- ...currentState,
4694
- [type]: currentState[type]
4695
- ? currentState[type].concat(follows)
4696
- : follows,
4697
- [paginationKey]: {
4698
- ...currentState[paginationKey],
4699
- next: newNextCursor,
4700
- sort,
4701
- },
4702
- }));
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
+ });
4703
4844
  }
4704
4845
  catch (e) {
4705
4846
  error = e;
@@ -4752,7 +4893,7 @@ class Feed extends FeedApi {
4752
4893
  this.state.next((currentState) => ({
4753
4894
  ...currentState,
4754
4895
  members: currentState.members
4755
- ? currentState.members.concat(members)
4896
+ ? uniqueArrayMerge(currentState.members, members, ({ user }) => user.id)
4756
4897
  : members,
4757
4898
  member_pagination: {
4758
4899
  ...currentState.member_pagination,
@@ -5339,13 +5480,17 @@ class FeedsClient extends FeedsApi {
5339
5480
  };
5340
5481
  this.eventDispatcher.dispatch(networkEvent);
5341
5482
  };
5342
- this.getOrCreateActiveFeed = (group, id, data) => {
5483
+ this.getOrCreateActiveFeed = (group, id, data, watch) => {
5343
5484
  const fid = `${group}:${id}`;
5344
5485
  if (this.activeFeeds[fid]) {
5345
- return this.activeFeeds[fid];
5486
+ const feed = this.activeFeeds[fid];
5487
+ if (watch && !feed.currentState.watch) {
5488
+ feed.handleWatchStarted();
5489
+ }
5490
+ return feed;
5346
5491
  }
5347
5492
  else {
5348
- const feed = new Feed(this, group, id, data);
5493
+ const feed = new Feed(this, group, id, data, watch);
5349
5494
  this.activeFeeds[fid] = feed;
5350
5495
  return feed;
5351
5496
  }
@@ -5374,6 +5519,11 @@ class FeedsClient extends FeedsApi {
5374
5519
  }
5375
5520
  }
5376
5521
  }
5522
+ else {
5523
+ for (const activeFeed of Object.values(this.activeFeeds)) {
5524
+ activeFeed.handleWatchStopped();
5525
+ }
5526
+ }
5377
5527
  break;
5378
5528
  }
5379
5529
  case 'feeds.feed.created': {
@@ -5477,7 +5627,7 @@ class FeedsClient extends FeedsApi {
5477
5627
  }
5478
5628
  async queryFeeds(request) {
5479
5629
  const response = await this.feedsQueryFeeds(request);
5480
- 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));
5481
5631
  return {
5482
5632
  feeds,
5483
5633
  next: response.next,
@@ -5486,6 +5636,52 @@ class FeedsClient extends FeedsApi {
5486
5636
  duration: response.duration,
5487
5637
  };
5488
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
+ }
5489
5685
  findActiveFeedByActivityId(activityId) {
5490
5686
  return Object.values(this.activeFeeds).filter((feed) => feed.currentState.activities?.some((activity) => activity.id === activityId));
5491
5687
  }
@@ -6018,4 +6214,5 @@ exports.isImageFile = isImageFile;
6018
6214
  exports.isPatch = isPatch;
6019
6215
  exports.isVideoFile = isVideoFile;
6020
6216
  exports.isVoteAnswer = isVoteAnswer;
6217
+ exports.uniqueArrayMerge = uniqueArrayMerge;
6021
6218
  //# sourceMappingURL=index.node.cjs.map