@stream-io/feeds-client 0.1.3 → 0.1.5

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 (54) hide show
  1. package/@react-bindings/hooks/client-state-hooks/index.ts +2 -0
  2. package/@react-bindings/hooks/feed-state-hooks/index.ts +5 -0
  3. package/@react-bindings/hooks/internal/index.ts +1 -0
  4. package/@react-bindings/hooks/util/index.ts +1 -0
  5. package/@react-bindings/index.ts +6 -3
  6. package/CHANGELOG.md +21 -0
  7. package/dist/@react-bindings/contexts/StreamFeedContext.d.ts +12 -0
  8. package/dist/@react-bindings/hooks/client-state-hooks/index.d.ts +2 -0
  9. package/dist/@react-bindings/hooks/client-state-hooks/useClientConnectedUser.d.ts +4 -0
  10. package/dist/@react-bindings/hooks/client-state-hooks/useWsConnectionState.d.ts +6 -0
  11. package/dist/@react-bindings/hooks/feed-state-hooks/index.d.ts +5 -0
  12. package/dist/@react-bindings/hooks/feed-state-hooks/useComments.d.ts +19 -0
  13. package/dist/@react-bindings/hooks/feed-state-hooks/useFeedActivities.d.ts +11 -0
  14. package/dist/@react-bindings/hooks/feed-state-hooks/useFollowers.d.ts +16 -0
  15. package/dist/@react-bindings/hooks/feed-state-hooks/useFollowing.d.ts +16 -0
  16. package/dist/@react-bindings/hooks/{useOwnCapabilities.d.ts → feed-state-hooks/useOwnCapabilities.d.ts} +2 -2
  17. package/dist/@react-bindings/hooks/internal/index.d.ts +1 -0
  18. package/dist/@react-bindings/hooks/internal/useStableCallback.d.ts +25 -0
  19. package/dist/@react-bindings/hooks/util/index.d.ts +1 -0
  20. package/dist/@react-bindings/hooks/util/useReactionActions.d.ts +17 -0
  21. package/dist/@react-bindings/index.d.ts +5 -3
  22. package/dist/@react-bindings/wrappers/StreamFeed.d.ts +12 -0
  23. package/dist/index-react-bindings.browser.cjs +456 -183
  24. package/dist/index-react-bindings.browser.cjs.map +1 -1
  25. package/dist/index-react-bindings.browser.js +451 -185
  26. package/dist/index-react-bindings.browser.js.map +1 -1
  27. package/dist/index-react-bindings.node.cjs +456 -183
  28. package/dist/index-react-bindings.node.cjs.map +1 -1
  29. package/dist/index-react-bindings.node.js +451 -185
  30. package/dist/index-react-bindings.node.js.map +1 -1
  31. package/dist/index.browser.cjs +172 -72
  32. package/dist/index.browser.cjs.map +1 -1
  33. package/dist/index.browser.js +169 -73
  34. package/dist/index.browser.js.map +1 -1
  35. package/dist/index.node.cjs +172 -72
  36. package/dist/index.node.cjs.map +1 -1
  37. package/dist/index.node.js +169 -73
  38. package/dist/index.node.js.map +1 -1
  39. package/dist/src/Feed.d.ts +7 -3
  40. package/dist/src/FeedsClient.d.ts +4 -3
  41. package/dist/src/gen/models/index.d.ts +92 -15
  42. package/dist/src/types.d.ts +7 -0
  43. package/dist/src/utils.d.ts +9 -1
  44. package/dist/tsconfig.tsbuildinfo +1 -1
  45. package/package.json +2 -1
  46. package/src/Feed.ts +200 -89
  47. package/src/FeedsClient.ts +8 -3
  48. package/src/common/real-time/StableWSConnection.ts +4 -1
  49. package/src/gen/feeds/FeedsApi.ts +5 -0
  50. package/src/gen/models/index.ts +143 -17
  51. package/src/types.ts +12 -1
  52. package/src/utils.ts +25 -1
  53. package/dist/@react-bindings/hooks/clientStateHooks.d.ts +0 -10
  54. package/dist/@react-bindings/hooks/useComments.d.ts +0 -12
@@ -1,4 +1,4 @@
1
- import { useCallback, useMemo, useSyncExternalStore, createContext, useContext, useState, useEffect } from 'react';
1
+ import { useCallback, useMemo, useSyncExternalStore, useState, useEffect, createContext, useContext, useRef } from 'react';
2
2
  import axios from 'axios';
3
3
  import { jsx } from 'react/jsx-runtime';
4
4
 
@@ -273,122 +273,6 @@ function useStateStore(store, selector) {
273
273
  return state;
274
274
  }
275
275
 
276
- const useComments = (feed,
277
- /**
278
- * The parent (activity or comment) for which to fetch comments.
279
- */
280
- parent) => {
281
- const selector = useCallback((state) => ({
282
- comments: state.comments_by_entity_id?.[parent.id]?.comments ?? [],
283
- comment_pagination: state.comments_by_entity_id?.[parent.id]?.pagination,
284
- }), [parent.id]);
285
- return useStateStore(feed.state, selector);
286
- };
287
-
288
- const FeedOwnCapability = {
289
- ADD_ACTIVITY: 'add-activity',
290
- ADD_ACTIVITY_REACTION: 'add-activity-reaction',
291
- ADD_COMMENT: 'add-comment',
292
- ADD_COMMENT_REACTION: 'add-comment-reaction',
293
- BOOKMARK_ACTIVITY: 'bookmark-activity',
294
- CREATE_FEED: 'create-feed',
295
- DELETE_BOOKMARK: 'delete-bookmark',
296
- DELETE_COMMENT: 'delete-comment',
297
- DELETE_FEED: 'delete-feed',
298
- EDIT_BOOKMARK: 'edit-bookmark',
299
- FOLLOW: 'follow',
300
- INVITE_FEED: 'invite-feed',
301
- JOIN_FEED: 'join-feed',
302
- LEAVE_FEED: 'leave-feed',
303
- MANAGE_FEED_GROUP: 'manage-feed-group',
304
- MARK_ACTIVITY: 'mark-activity',
305
- PIN_ACTIVITY: 'pin-activity',
306
- QUERY_FEED_MEMBERS: 'query-feed-members',
307
- QUERY_FOLLOWS: 'query-follows',
308
- READ_ACTIVITIES: 'read-activities',
309
- READ_FEED: 'read-feed',
310
- REMOVE_ACTIVITY: 'remove-activity',
311
- REMOVE_ACTIVITY_REACTION: 'remove-activity-reaction',
312
- REMOVE_COMMENT_REACTION: 'remove-comment-reaction',
313
- UNFOLLOW: 'unfollow',
314
- UPDATE_ACTIVITY: 'update-activity',
315
- UPDATE_COMMENT: 'update-comment',
316
- UPDATE_FEED: 'update-feed',
317
- UPDATE_FEED_FOLLOWERS: 'update-feed-followers',
318
- UPDATE_FEED_MEMBERS: 'update-feed-members',
319
- };
320
-
321
- const stableEmptyArray = [];
322
- const selector = (currentState) => ({
323
- oc: currentState.own_capabilities ?? stableEmptyArray,
324
- });
325
- const useOwnCapabilities = (feed) => {
326
- const { oc = stableEmptyArray } = useStateStore(feed?.state, selector) ?? {};
327
- return useMemo(() => ({
328
- canAddActivity: oc.indexOf(FeedOwnCapability.ADD_ACTIVITY) > -1,
329
- canAddActivityReaction: oc.indexOf(FeedOwnCapability.ADD_ACTIVITY_REACTION) > -1,
330
- canAddComment: oc.indexOf(FeedOwnCapability.ADD_COMMENT) > -1,
331
- canAddCommentReaction: oc.indexOf(FeedOwnCapability.ADD_COMMENT_REACTION) > -1,
332
- canBookmarkActivity: oc.indexOf(FeedOwnCapability.BOOKMARK_ACTIVITY) > -1,
333
- canCreateFeed: oc.indexOf(FeedOwnCapability.CREATE_FEED) > -1,
334
- canDeleteBookmark: oc.indexOf(FeedOwnCapability.DELETE_BOOKMARK) > -1,
335
- canDeleteComment: oc.indexOf(FeedOwnCapability.DELETE_COMMENT) > -1,
336
- canDeleteFeed: oc.indexOf(FeedOwnCapability.DELETE_FEED) > -1,
337
- canEditBookmark: oc.indexOf(FeedOwnCapability.EDIT_BOOKMARK) > -1,
338
- canFollow: oc.indexOf(FeedOwnCapability.FOLLOW) > -1,
339
- canRemoveActivity: oc.indexOf(FeedOwnCapability.REMOVE_ACTIVITY) > -1,
340
- canRemoveActivityReaction: oc.indexOf(FeedOwnCapability.REMOVE_ACTIVITY_REACTION) > -1,
341
- canRemoveCommentReaction: oc.indexOf(FeedOwnCapability.REMOVE_COMMENT_REACTION) > -1,
342
- canUnfollow: oc.indexOf(FeedOwnCapability.UNFOLLOW) > -1,
343
- canUpdateFeed: oc.indexOf(FeedOwnCapability.UPDATE_FEED) > -1,
344
- canInviteFeed: oc.indexOf(FeedOwnCapability.INVITE_FEED) > -1,
345
- canJoinFeed: oc.indexOf(FeedOwnCapability.JOIN_FEED) > -1,
346
- canLeaveFeed: oc.indexOf(FeedOwnCapability.LEAVE_FEED) > -1,
347
- canManageFeedGroup: oc.indexOf(FeedOwnCapability.MANAGE_FEED_GROUP) > -1,
348
- canMarkActivity: oc.indexOf(FeedOwnCapability.MARK_ACTIVITY) > -1,
349
- canPinActivity: oc.indexOf(FeedOwnCapability.PIN_ACTIVITY) > -1,
350
- canQueryFeedMembers: oc.indexOf(FeedOwnCapability.QUERY_FEED_MEMBERS) > -1,
351
- canQueryFollows: oc.indexOf(FeedOwnCapability.QUERY_FOLLOWS) > -1,
352
- canReadActivities: oc.indexOf(FeedOwnCapability.READ_ACTIVITIES) > -1,
353
- canReadFeed: oc.indexOf(FeedOwnCapability.READ_FEED) > -1,
354
- canUpdateActivity: oc.indexOf(FeedOwnCapability.UPDATE_ACTIVITY) > -1,
355
- canUpdateComment: oc.indexOf(FeedOwnCapability.UPDATE_COMMENT) > -1,
356
- canUpdateFeedFollowers: oc.indexOf(FeedOwnCapability.UPDATE_FEED_FOLLOWERS) > -1,
357
- canUpdateFeedMembers: oc.indexOf(FeedOwnCapability.UPDATE_FEED_MEMBERS) > -1,
358
- }), [oc]);
359
- };
360
-
361
- const StreamFeedsContext = createContext(undefined);
362
- /**
363
- * Hook to access the nearest FeedsClient instance.
364
- */
365
- const useFeedsClient = () => {
366
- return useContext(StreamFeedsContext);
367
- };
368
-
369
- /**
370
- * A React hook that returns the currently connected user on a `FeedsClient` instance and null otherwise.
371
- */
372
- const useClientConnectedUser = () => {
373
- const client = useFeedsClient();
374
- const { user } = useStateStore(client?.state, clientConnectedUserSelector) ?? {};
375
- return user;
376
- };
377
- /**
378
- * A React hook that returns the websocket connection state of `FeedsClient`.
379
- */
380
- const useWsConnectionState = () => {
381
- const client = useFeedsClient();
382
- const { isHealthy } = useStateStore(client?.state, wsConnectionStateSelector) ?? {};
383
- return { isHealthy };
384
- };
385
- const clientConnectedUserSelector = (nextState) => ({
386
- user: nextState.connectedUser,
387
- });
388
- const wsConnectionStateSelector = (nextState) => ({
389
- isHealthy: nextState.isWsConnectionHealthy,
390
- });
391
-
392
276
  const decoders = {};
393
277
  const decodeDatetimeType = (input) => typeof input === 'number'
394
278
  ? new Date(Math.floor(input / 1000000))
@@ -2014,6 +1898,7 @@ class FeedsApi {
2014
1898
  };
2015
1899
  const body = {
2016
1900
  type: request?.type,
1901
+ create_notification_activity: request?.create_notification_activity,
2017
1902
  custom: request?.custom,
2018
1903
  };
2019
1904
  const response = await this.apiClient.sendRequest('POST', '/api/v2/feeds/activities/{activity_id}/reactions', pathParams, undefined, body, 'application/json');
@@ -2108,6 +1993,7 @@ class FeedsApi {
2108
1993
  comment: request?.comment,
2109
1994
  object_id: request?.object_id,
2110
1995
  object_type: request?.object_type,
1996
+ create_notification_activity: request?.create_notification_activity,
2111
1997
  parent_id: request?.parent_id,
2112
1998
  attachments: request?.attachments,
2113
1999
  mentioned_user_ids: request?.mentioned_user_ids,
@@ -2171,6 +2057,7 @@ class FeedsApi {
2171
2057
  };
2172
2058
  const body = {
2173
2059
  type: request?.type,
2060
+ create_notification_activity: request?.create_notification_activity,
2174
2061
  custom: request?.custom,
2175
2062
  };
2176
2063
  const response = await this.apiClient.sendRequest('POST', '/api/v2/feeds/comments/{comment_id}/reactions', pathParams, undefined, body, 'application/json');
@@ -2396,6 +2283,7 @@ class FeedsApi {
2396
2283
  const body = {
2397
2284
  source: request?.source,
2398
2285
  target: request?.target,
2286
+ create_notification_activity: request?.create_notification_activity,
2399
2287
  follower_role: request?.follower_role,
2400
2288
  push_preference: request?.push_preference,
2401
2289
  custom: request?.custom,
@@ -2408,6 +2296,7 @@ class FeedsApi {
2408
2296
  const body = {
2409
2297
  source: request?.source,
2410
2298
  target: request?.target,
2299
+ create_notification_activity: request?.create_notification_activity,
2411
2300
  push_preference: request?.push_preference,
2412
2301
  custom: request?.custom,
2413
2302
  };
@@ -2828,6 +2717,13 @@ function removeConnectionEventListeners(cb) {
2828
2717
  window.removeEventListener('online', cb);
2829
2718
  }
2830
2719
  }
2720
+ const streamDevToken = (userId) => {
2721
+ return [
2722
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9', // {"alg": "HS256", "typ": "JWT"}
2723
+ window.btoa(JSON.stringify({ user_id: userId })),
2724
+ 'devtoken', // hardcoded signature
2725
+ ].join('.');
2726
+ };
2831
2727
  const capitalize = (s) => {
2832
2728
  return `${s.charAt(0).toLocaleUpperCase()}${s.slice(1)}`;
2833
2729
  };
@@ -3098,7 +2994,10 @@ class StableWSConnection {
3098
2994
  this.onmessage = (wsID, event) => {
3099
2995
  if (this.wsID !== wsID)
3100
2996
  return;
3101
- this._log('onmessage() - onmessage callback', { event, wsID });
2997
+ this._log('onmessage() - onmessage callback', {
2998
+ event: { ...event, data: JSON.parse(event.data) },
2999
+ wsID,
3000
+ });
3102
3001
  let data = typeof event.data === 'string' ? JSON.parse(event.data) : null;
3103
3002
  this.decoders.forEach((decode) => {
3104
3003
  data = decode(data);
@@ -4113,8 +4012,15 @@ const updateBookmarkInActivities = (event, activities, isCurrentUser) => {
4113
4012
  return updateActivityInActivities(updatedActivity, activities);
4114
4013
  };
4115
4014
 
4116
- const END_OF_LIST = 'eol';
4117
- const DEFAULT_COMMENT_PAGINATION = 'first';
4015
+ const checkHasAnotherPage = (v, cursor) => (typeof v === 'undefined' && typeof cursor === 'undefined') ||
4016
+ typeof cursor === 'string';
4017
+ const isCommentResponse = (entity) => {
4018
+ return typeof entity?.object_id === 'string';
4019
+ };
4020
+ const Constants = {
4021
+ DEFAULT_COMMENT_PAGINATION: 'first',
4022
+ };
4023
+
4118
4024
  class Feed extends FeedApi {
4119
4025
  constructor(client, groupId, id, data) {
4120
4026
  // Need this ugly cast because fileUpload endpoints :(
@@ -4187,7 +4093,7 @@ class Feed extends FeedApi {
4187
4093
  const entityState = currentState.comments_by_entity_id[forId];
4188
4094
  const newComments = entityState?.comments?.concat([]) ?? [];
4189
4095
  if (entityState?.pagination?.sort === 'last' &&
4190
- entityState?.pagination.next === END_OF_LIST) {
4096
+ !checkHasAnotherPage(entityState.comments, entityState?.pagination.next)) {
4191
4097
  newComments.unshift(comment);
4192
4098
  }
4193
4099
  else if (entityState?.pagination?.sort === 'first') {
@@ -4274,7 +4180,7 @@ class Feed extends FeedApi {
4274
4180
  ...currentState,
4275
4181
  ...event.follow.source_feed,
4276
4182
  };
4277
- if (currentState.following_pagination?.next === END_OF_LIST) {
4183
+ if (!checkHasAnotherPage(currentState.following, currentState.following_pagination?.next)) {
4278
4184
  // TODO: respect sort
4279
4185
  newState.following = currentState.following
4280
4186
  ? currentState.following.concat(event.follow)
@@ -4295,7 +4201,7 @@ class Feed extends FeedApi {
4295
4201
  ? currentState.own_follows.concat(event.follow)
4296
4202
  : [event.follow];
4297
4203
  }
4298
- if (currentState.followers_pagination?.next === END_OF_LIST) {
4204
+ if (!checkHasAnotherPage(currentState.followers, currentState.followers_pagination?.next)) {
4299
4205
  // TODO: respect sort
4300
4206
  newState.followers = currentState.followers
4301
4207
  ? currentState.followers.concat(event.follow)
@@ -4336,40 +4242,60 @@ class Feed extends FeedApi {
4336
4242
  'feeds.comment.reaction.deleted': this.handleCommentReactionEvent.bind(this),
4337
4243
  'feeds.comment.reaction.updated': Feed.noop,
4338
4244
  'feeds.feed_member.added': (event) => {
4339
- const { member } = event;
4340
- // do not add a member if the pagination has reached the end of the list
4341
- if (this.currentState.member_pagination?.next !== END_OF_LIST)
4342
- return;
4245
+ const { connectedUser } = this.client.state.getLatestValue();
4343
4246
  this.state.next((currentState) => {
4344
- return {
4345
- ...currentState,
4346
- // TODO: respect sort
4347
- members: currentState.members
4348
- ? currentState.members.concat(member)
4349
- : [member],
4350
- };
4247
+ let newState;
4248
+ if (!checkHasAnotherPage(currentState.members, currentState.member_pagination?.next)) {
4249
+ newState ?? (newState = {
4250
+ ...currentState,
4251
+ });
4252
+ newState.members = newState.members?.concat(event.member) ?? [
4253
+ event.member,
4254
+ ];
4255
+ }
4256
+ if (connectedUser?.id === event.member.user.id) {
4257
+ newState ?? (newState = {
4258
+ ...currentState,
4259
+ });
4260
+ newState.own_membership = event.member;
4261
+ }
4262
+ return newState ?? currentState;
4351
4263
  });
4352
4264
  },
4353
4265
  'feeds.feed_member.removed': (event) => {
4266
+ const { connectedUser } = this.client.state.getLatestValue();
4354
4267
  this.state.next((currentState) => {
4355
- return {
4268
+ const newState = {
4356
4269
  ...currentState,
4357
4270
  members: currentState.members?.filter((member) => member.user.id !== event.user?.id),
4358
4271
  };
4272
+ if (connectedUser?.id === event.member_id) {
4273
+ delete newState.own_membership;
4274
+ }
4275
+ return newState;
4359
4276
  });
4360
4277
  },
4361
4278
  'feeds.feed_member.updated': (event) => {
4279
+ const { connectedUser } = this.client.state.getLatestValue();
4362
4280
  this.state.next((currentState) => {
4363
4281
  const memberIndex = currentState.members?.findIndex((member) => member.user.id === event.member.user.id) ?? -1;
4282
+ let newState;
4364
4283
  if (memberIndex !== -1) {
4284
+ // if there's an index, there's a member to update
4365
4285
  const newMembers = [...currentState.members];
4366
4286
  newMembers[memberIndex] = event.member;
4367
- return {
4287
+ newState ?? (newState = {
4368
4288
  ...currentState,
4369
- members: newMembers,
4370
- };
4289
+ });
4290
+ newState.members = newMembers;
4291
+ }
4292
+ if (connectedUser?.id === event.member.user.id) {
4293
+ newState ?? (newState = {
4294
+ ...currentState,
4295
+ });
4296
+ newState.own_membership = event.member;
4371
4297
  }
4372
- return currentState;
4298
+ return newState ?? currentState;
4373
4299
  });
4374
4300
  },
4375
4301
  // the poll events should be removed from here
@@ -4495,29 +4421,6 @@ class Feed extends FeedApi {
4495
4421
  ...currentState,
4496
4422
  ...responseCopy,
4497
4423
  };
4498
- // if there is no next cursor, set it to END_OF_LIST
4499
- // request has to have a limit set for this to work
4500
- if ((request?.followers_pagination?.limit ?? 0) > 0 &&
4501
- typeof nextState.followers_pagination?.next === 'undefined') {
4502
- nextState.followers_pagination = {
4503
- ...nextState.followers_pagination,
4504
- next: END_OF_LIST,
4505
- };
4506
- }
4507
- if ((request?.following_pagination?.limit ?? 0) > 0 &&
4508
- typeof nextState.following_pagination?.next === 'undefined') {
4509
- nextState.following_pagination = {
4510
- ...nextState.following_pagination,
4511
- next: END_OF_LIST,
4512
- };
4513
- }
4514
- if ((request?.member_pagination?.limit ?? 0) > 0 &&
4515
- typeof nextState.member_pagination?.next === 'undefined') {
4516
- nextState.member_pagination = {
4517
- ...nextState.member_pagination,
4518
- next: END_OF_LIST,
4519
- };
4520
- }
4521
4424
  if (!request?.followers_pagination?.limit) {
4522
4425
  delete nextState.followers;
4523
4426
  }
@@ -4608,6 +4511,7 @@ class Feed extends FeedApi {
4608
4511
  });
4609
4512
  }
4610
4513
  async loadNextPageComments({ forId, base, sort, parentId, }) {
4514
+ let error;
4611
4515
  try {
4612
4516
  this.state.next((currentState) => ({
4613
4517
  ...currentState,
@@ -4622,7 +4526,7 @@ class Feed extends FeedApi {
4622
4526
  },
4623
4527
  },
4624
4528
  }));
4625
- const { next: newNextCursor = END_OF_LIST, comments } = await base();
4529
+ const { next: newNextCursor, comments } = await base();
4626
4530
  this.state.next((currentState) => {
4627
4531
  const newPagination = {
4628
4532
  ...currentState.comments_by_entity_id[forId]?.pagination,
@@ -4647,9 +4551,8 @@ class Feed extends FeedApi {
4647
4551
  };
4648
4552
  });
4649
4553
  }
4650
- catch (error) {
4651
- console.error(error);
4652
- // TODO: figure out how to handle errorss
4554
+ catch (e) {
4555
+ error = e;
4653
4556
  }
4654
4557
  finally {
4655
4558
  this.state.next((currentState) => ({
@@ -4666,15 +4569,21 @@ class Feed extends FeedApi {
4666
4569
  },
4667
4570
  }));
4668
4571
  }
4572
+ if (error) {
4573
+ throw error;
4574
+ }
4669
4575
  }
4670
4576
  async loadNextPageActivityComments(activity, request) {
4671
- const pagination = this.currentState.comments_by_entity_id[activity.id]?.pagination;
4672
- const currentNextCursor = pagination?.next;
4673
- const currentSort = pagination?.sort;
4674
- const isLoading = pagination?.loading_next_page;
4675
- const sort = currentSort ?? request?.sort ?? DEFAULT_COMMENT_PAGINATION;
4676
- if (isLoading || currentNextCursor === END_OF_LIST)
4577
+ const currentEntityState = this.currentState.comments_by_entity_id[activity.id];
4578
+ const currentPagination = currentEntityState?.pagination;
4579
+ const currentNextCursor = currentPagination?.next;
4580
+ const currentSort = currentPagination?.sort;
4581
+ const isLoading = currentPagination?.loading_next_page;
4582
+ const sort = currentSort ?? request?.sort ?? Constants.DEFAULT_COMMENT_PAGINATION;
4583
+ if (isLoading ||
4584
+ !checkHasAnotherPage(currentEntityState?.comments, currentNextCursor)) {
4677
4585
  return;
4586
+ }
4678
4587
  await this.loadNextPageComments({
4679
4588
  forId: activity.id,
4680
4589
  base: () => this.client.getComments({
@@ -4688,20 +4597,25 @@ class Feed extends FeedApi {
4688
4597
  });
4689
4598
  }
4690
4599
  async loadNextPageCommentReplies(comment, request) {
4691
- const pagination = this.currentState.comments_by_entity_id[comment.id]?.pagination;
4692
- const currentNextCursor = pagination?.next;
4693
- const currentSort = pagination?.sort;
4694
- const isLoading = pagination?.loading_next_page;
4695
- const sort = currentSort ?? request?.sort ?? DEFAULT_COMMENT_PAGINATION;
4696
- if (isLoading || currentNextCursor === END_OF_LIST)
4600
+ const currentEntityState = this.currentState.comments_by_entity_id[comment.id];
4601
+ const currentPagination = currentEntityState?.pagination;
4602
+ const currentNextCursor = currentPagination?.next;
4603
+ const currentSort = currentPagination?.sort;
4604
+ const isLoading = currentPagination?.loading_next_page;
4605
+ const sort = currentSort ?? request?.sort ?? Constants.DEFAULT_COMMENT_PAGINATION;
4606
+ if (isLoading ||
4607
+ !checkHasAnotherPage(currentEntityState?.comments, currentNextCursor)) {
4697
4608
  return;
4609
+ }
4698
4610
  await this.loadNextPageComments({
4699
4611
  forId: comment.id,
4700
4612
  base: () => this.client.getCommentReplies({
4701
4613
  ...request,
4702
4614
  comment_id: comment.id,
4703
4615
  // use known sort first (prevents broken pagination)
4704
- sort: currentSort ?? request?.sort ?? DEFAULT_COMMENT_PAGINATION,
4616
+ sort: currentSort ??
4617
+ request?.sort ??
4618
+ Constants.DEFAULT_COMMENT_PAGINATION,
4705
4619
  next: currentNextCursor,
4706
4620
  }),
4707
4621
  parentId: comment.parent_id ?? comment.object_id,
@@ -4711,10 +4625,14 @@ class Feed extends FeedApi {
4711
4625
  async loadNextPageFollows(type, request) {
4712
4626
  const paginationKey = `${type}_pagination`;
4713
4627
  const method = `query${capitalize(type)}`;
4628
+ const currentFollows = this.currentState[type];
4714
4629
  const currentNextCursor = this.currentState[paginationKey]?.next;
4715
4630
  const isLoading = this.currentState[paginationKey]?.loading_next_page;
4716
- if (isLoading || currentNextCursor === END_OF_LIST)
4631
+ const sort = this.currentState[paginationKey]?.sort ?? request.sort;
4632
+ let error;
4633
+ if (isLoading || !checkHasAnotherPage(currentFollows, currentNextCursor)) {
4717
4634
  return;
4635
+ }
4718
4636
  try {
4719
4637
  this.state.next((currentState) => {
4720
4638
  return {
@@ -4725,9 +4643,10 @@ class Feed extends FeedApi {
4725
4643
  },
4726
4644
  };
4727
4645
  });
4728
- const { next: newNextCursor = END_OF_LIST, follows } = await this[method]({
4646
+ const { next: newNextCursor, follows } = await this[method]({
4729
4647
  ...request,
4730
4648
  next: currentNextCursor,
4649
+ sort,
4731
4650
  });
4732
4651
  this.state.next((currentState) => ({
4733
4652
  ...currentState,
@@ -4737,12 +4656,12 @@ class Feed extends FeedApi {
4737
4656
  [paginationKey]: {
4738
4657
  ...currentState[paginationKey],
4739
4658
  next: newNextCursor,
4659
+ sort,
4740
4660
  },
4741
4661
  }));
4742
4662
  }
4743
- catch (error) {
4744
- console.error(error);
4745
- // TODO: figure out how to handle errorss
4663
+ catch (e) {
4664
+ error = e;
4746
4665
  }
4747
4666
  finally {
4748
4667
  this.state.next((currentState) => {
@@ -4755,6 +4674,9 @@ class Feed extends FeedApi {
4755
4674
  };
4756
4675
  });
4757
4676
  }
4677
+ if (error) {
4678
+ throw error;
4679
+ }
4758
4680
  }
4759
4681
  async loadNextPageFollowers(request) {
4760
4682
  await this.loadNextPageFollows('followers', request);
@@ -4762,6 +4684,59 @@ class Feed extends FeedApi {
4762
4684
  async loadNextPageFollowing(request) {
4763
4685
  await this.loadNextPageFollows('following', request);
4764
4686
  }
4687
+ async loadNextPageMembers(request) {
4688
+ const currentMembers = this.currentState.members;
4689
+ const currentNextCursor = this.currentState.member_pagination?.next;
4690
+ const isLoading = this.currentState.member_pagination?.loading_next_page;
4691
+ const sort = this.currentState.member_pagination?.sort ?? request.sort;
4692
+ let error;
4693
+ if (isLoading || !checkHasAnotherPage(currentMembers, currentNextCursor)) {
4694
+ return;
4695
+ }
4696
+ try {
4697
+ this.state.next((currentState) => ({
4698
+ ...currentState,
4699
+ member_pagination: {
4700
+ ...currentState.member_pagination,
4701
+ loading_next_page: true,
4702
+ },
4703
+ }));
4704
+ const { next: newNextCursor, members } = await this.client.queryFeedMembers({
4705
+ ...request,
4706
+ sort,
4707
+ feed_id: this.id,
4708
+ feed_group_id: this.group,
4709
+ next: currentNextCursor,
4710
+ });
4711
+ this.state.next((currentState) => ({
4712
+ ...currentState,
4713
+ members: currentState.members
4714
+ ? currentState.members.concat(members)
4715
+ : members,
4716
+ member_pagination: {
4717
+ ...currentState.member_pagination,
4718
+ next: newNextCursor,
4719
+ // set sort if not defined yet
4720
+ sort: currentState.member_pagination?.sort ?? request.sort,
4721
+ },
4722
+ }));
4723
+ }
4724
+ catch (e) {
4725
+ error = e;
4726
+ }
4727
+ finally {
4728
+ this.state.next((currentState) => ({
4729
+ ...currentState,
4730
+ member_pagination: {
4731
+ ...currentState.member_pagination,
4732
+ loading_next_page: false,
4733
+ },
4734
+ }));
4735
+ }
4736
+ if (error) {
4737
+ throw error;
4738
+ }
4739
+ }
4765
4740
  /**
4766
4741
  * Method which queries followers of this feed (feeds which target this feed).
4767
4742
  *
@@ -5252,6 +5227,9 @@ class FeedsClient extends FeedsApi {
5252
5227
  throw err;
5253
5228
  }
5254
5229
  };
5230
+ this.devToken = (userId) => {
5231
+ return streamDevToken(userId);
5232
+ };
5255
5233
  this.closePoll = async (request) => {
5256
5234
  return await this.updatePollPartial({
5257
5235
  poll_id: request.poll_id,
@@ -5510,10 +5488,298 @@ const useCreateFeedsClient = ({ apiKey, tokenOrProvider, userData, options, }) =
5510
5488
  return client;
5511
5489
  };
5512
5490
 
5491
+ const StreamFeedsContext = createContext(undefined);
5492
+ /**
5493
+ * Hook to access the nearest FeedsClient instance.
5494
+ */
5495
+ const useFeedsClient = () => {
5496
+ return useContext(StreamFeedsContext);
5497
+ };
5498
+
5499
+ /**
5500
+ * A React hook that returns the currently connected user on a `FeedsClient` instance and null otherwise.
5501
+ */
5502
+ const useClientConnectedUser = () => {
5503
+ const client = useFeedsClient();
5504
+ const { user } = useStateStore(client?.state, selector$5) ?? {};
5505
+ return user;
5506
+ };
5507
+ const selector$5 = (nextState) => ({
5508
+ user: nextState.connectedUser,
5509
+ });
5510
+
5511
+ /**
5512
+ * A React hook that returns the websocket connection state of `FeedsClient`.
5513
+ */
5514
+ const useWsConnectionState = () => {
5515
+ const client = useFeedsClient();
5516
+ const { isHealthy } = useStateStore(client?.state, selector$4) ?? {};
5517
+ return { isHealthy };
5518
+ };
5519
+ const selector$4 = (nextState) => ({
5520
+ isHealthy: nextState.isWsConnectionHealthy,
5521
+ });
5522
+
5523
+ const StreamFeedContext = createContext(undefined);
5524
+ /**
5525
+ * Hook to access the nearest Feed instance.
5526
+ */
5527
+ const useFeedContext = () => {
5528
+ return useContext(StreamFeedContext);
5529
+ };
5530
+
5531
+ /**
5532
+ * A utility hook implementing a stable callback. It takes in an unstable method that
5533
+ * is supposed to be invoked somewhere deeper in the DOM tree without making it
5534
+ * change its reference every time the parent component rerenders. It will also return
5535
+ * the value of the callback if it does return one.
5536
+ * A common use-case would be having a function whose invocation depends on state
5537
+ * somewhere high up in the DOM tree and wanting to use the same function deeper
5538
+ * down, for example in a leaf node and simply using useCallback results in
5539
+ * cascading dependency hell. If we wrap it in useStableCallback, we would be able
5540
+ * to:
5541
+ * - Use the same function as a dependency of another hook (since it is stable)
5542
+ * - Still invoke it and get the latest state
5543
+ *
5544
+ * **Caveats:**
5545
+ * - Never wrap a function that is supposed to return a React.ReactElement in
5546
+ * useStableCallback, since React will not know that the DOM needs to be updated
5547
+ * whenever the callback value changes (for example, renderItem from FlatList must
5548
+ * never be wrapped in this hook)
5549
+ * - Always prefer using a standard useCallback/stable function wherever possible
5550
+ * (the purpose of useStableCallback is to bridge the gap between top level contexts
5551
+ * and cascading rereders in downstream components - **not** as an escape hatch)
5552
+ * @param callback - the callback we want to stabilize
5553
+ */
5554
+ const useStableCallback = (callback) => {
5555
+ const ref = useRef(callback);
5556
+ ref.current = callback;
5557
+ return useCallback((...args) => {
5558
+ return ref.current(...args);
5559
+ }, []);
5560
+ };
5561
+
5562
+ /**
5563
+ * A React hook that returns a reactive object containing the current activities,
5564
+ * loading state and whether there is a next page to paginate to or not.
5565
+ */
5566
+ const useFeedActivities = (feedFromProps) => {
5567
+ const feedFromContext = useFeedContext();
5568
+ const feed = feedFromProps ?? feedFromContext;
5569
+ const data = useStateStore(feed?.state, selector$3);
5570
+ const loadNextPage = useStableCallback(async () => {
5571
+ if (!feed || !data?.hasNextPage || data?.isLoading) {
5572
+ return;
5573
+ }
5574
+ await feed.getNextPage();
5575
+ });
5576
+ return useMemo(() => ({ ...data, loadNextPage }), [data, loadNextPage]);
5577
+ };
5578
+ const selector$3 = (nextState) => ({
5579
+ isLoading: nextState.is_loading_activities,
5580
+ hasNextPage: typeof nextState.next !== 'undefined',
5581
+ activities: nextState.activities ?? [],
5582
+ });
5583
+
5584
+ function useComments({ feed: feedFromProps, parent, }) {
5585
+ const feedFromContext = useFeedContext();
5586
+ const feed = feedFromProps ?? feedFromContext;
5587
+ const selector = useCallback((state) => ({
5588
+ comments: state.comments_by_entity_id?.[parent.id]?.comments,
5589
+ comments_pagination: state.comments_by_entity_id?.[parent.id]?.pagination,
5590
+ }), [parent.id]);
5591
+ const data = useStateStore(feed?.state, selector);
5592
+ const loadNextPage = useMemo(() => {
5593
+ if (!feed)
5594
+ return undefined;
5595
+ return (request) => {
5596
+ if (isCommentResponse(parent)) {
5597
+ return feed.loadNextPageCommentReplies(parent, request);
5598
+ }
5599
+ else {
5600
+ return feed.loadNextPageActivityComments(parent, request);
5601
+ }
5602
+ };
5603
+ }, [feed, parent]);
5604
+ return useMemo(() => {
5605
+ if (!data) {
5606
+ return undefined;
5607
+ }
5608
+ return {
5609
+ ...data,
5610
+ hasNextPage: checkHasAnotherPage(data.comments, data.comments_pagination?.next),
5611
+ isLoadingNextPage: data?.comments_pagination?.loading_next_page ?? false,
5612
+ loadNextPage,
5613
+ };
5614
+ }, [data, loadNextPage]);
5615
+ }
5616
+
5617
+ const FeedOwnCapability = {
5618
+ ADD_ACTIVITY: 'add-activity',
5619
+ ADD_ACTIVITY_REACTION: 'add-activity-reaction',
5620
+ ADD_COMMENT: 'add-comment',
5621
+ ADD_COMMENT_REACTION: 'add-comment-reaction',
5622
+ BOOKMARK_ACTIVITY: 'bookmark-activity',
5623
+ CREATE_FEED: 'create-feed',
5624
+ DELETE_BOOKMARK: 'delete-bookmark',
5625
+ DELETE_COMMENT: 'delete-comment',
5626
+ DELETE_FEED: 'delete-feed',
5627
+ EDIT_BOOKMARK: 'edit-bookmark',
5628
+ FOLLOW: 'follow',
5629
+ INVITE_FEED: 'invite-feed',
5630
+ JOIN_FEED: 'join-feed',
5631
+ LEAVE_FEED: 'leave-feed',
5632
+ MANAGE_FEED_GROUP: 'manage-feed-group',
5633
+ MARK_ACTIVITY: 'mark-activity',
5634
+ PIN_ACTIVITY: 'pin-activity',
5635
+ QUERY_FEED_MEMBERS: 'query-feed-members',
5636
+ QUERY_FOLLOWS: 'query-follows',
5637
+ READ_ACTIVITIES: 'read-activities',
5638
+ READ_FEED: 'read-feed',
5639
+ REMOVE_ACTIVITY: 'remove-activity',
5640
+ REMOVE_ACTIVITY_REACTION: 'remove-activity-reaction',
5641
+ REMOVE_COMMENT_REACTION: 'remove-comment-reaction',
5642
+ UNFOLLOW: 'unfollow',
5643
+ UPDATE_ACTIVITY: 'update-activity',
5644
+ UPDATE_COMMENT: 'update-comment',
5645
+ UPDATE_FEED: 'update-feed',
5646
+ UPDATE_FEED_FOLLOWERS: 'update-feed-followers',
5647
+ UPDATE_FEED_MEMBERS: 'update-feed-members',
5648
+ };
5649
+
5650
+ const stableEmptyArray = [];
5651
+ const selector$2 = (currentState) => ({
5652
+ oc: currentState.own_capabilities ?? stableEmptyArray,
5653
+ });
5654
+ const useOwnCapabilities = (feedFromProps) => {
5655
+ const feedFromContext = useFeedContext();
5656
+ const feed = feedFromProps ?? feedFromContext;
5657
+ const { oc = stableEmptyArray } = useStateStore(feed?.state, selector$2) ?? {};
5658
+ return useMemo(() => ({
5659
+ canAddActivity: oc.indexOf(FeedOwnCapability.ADD_ACTIVITY) > -1,
5660
+ canAddActivityReaction: oc.indexOf(FeedOwnCapability.ADD_ACTIVITY_REACTION) > -1,
5661
+ canAddComment: oc.indexOf(FeedOwnCapability.ADD_COMMENT) > -1,
5662
+ canAddCommentReaction: oc.indexOf(FeedOwnCapability.ADD_COMMENT_REACTION) > -1,
5663
+ canBookmarkActivity: oc.indexOf(FeedOwnCapability.BOOKMARK_ACTIVITY) > -1,
5664
+ canCreateFeed: oc.indexOf(FeedOwnCapability.CREATE_FEED) > -1,
5665
+ canDeleteBookmark: oc.indexOf(FeedOwnCapability.DELETE_BOOKMARK) > -1,
5666
+ canDeleteComment: oc.indexOf(FeedOwnCapability.DELETE_COMMENT) > -1,
5667
+ canDeleteFeed: oc.indexOf(FeedOwnCapability.DELETE_FEED) > -1,
5668
+ canEditBookmark: oc.indexOf(FeedOwnCapability.EDIT_BOOKMARK) > -1,
5669
+ canFollow: oc.indexOf(FeedOwnCapability.FOLLOW) > -1,
5670
+ canRemoveActivity: oc.indexOf(FeedOwnCapability.REMOVE_ACTIVITY) > -1,
5671
+ canRemoveActivityReaction: oc.indexOf(FeedOwnCapability.REMOVE_ACTIVITY_REACTION) > -1,
5672
+ canRemoveCommentReaction: oc.indexOf(FeedOwnCapability.REMOVE_COMMENT_REACTION) > -1,
5673
+ canUnfollow: oc.indexOf(FeedOwnCapability.UNFOLLOW) > -1,
5674
+ canUpdateFeed: oc.indexOf(FeedOwnCapability.UPDATE_FEED) > -1,
5675
+ canInviteFeed: oc.indexOf(FeedOwnCapability.INVITE_FEED) > -1,
5676
+ canJoinFeed: oc.indexOf(FeedOwnCapability.JOIN_FEED) > -1,
5677
+ canLeaveFeed: oc.indexOf(FeedOwnCapability.LEAVE_FEED) > -1,
5678
+ canManageFeedGroup: oc.indexOf(FeedOwnCapability.MANAGE_FEED_GROUP) > -1,
5679
+ canMarkActivity: oc.indexOf(FeedOwnCapability.MARK_ACTIVITY) > -1,
5680
+ canPinActivity: oc.indexOf(FeedOwnCapability.PIN_ACTIVITY) > -1,
5681
+ canQueryFeedMembers: oc.indexOf(FeedOwnCapability.QUERY_FEED_MEMBERS) > -1,
5682
+ canQueryFollows: oc.indexOf(FeedOwnCapability.QUERY_FOLLOWS) > -1,
5683
+ canReadActivities: oc.indexOf(FeedOwnCapability.READ_ACTIVITIES) > -1,
5684
+ canReadFeed: oc.indexOf(FeedOwnCapability.READ_FEED) > -1,
5685
+ canUpdateActivity: oc.indexOf(FeedOwnCapability.UPDATE_ACTIVITY) > -1,
5686
+ canUpdateComment: oc.indexOf(FeedOwnCapability.UPDATE_COMMENT) > -1,
5687
+ canUpdateFeedFollowers: oc.indexOf(FeedOwnCapability.UPDATE_FEED_FOLLOWERS) > -1,
5688
+ canUpdateFeedMembers: oc.indexOf(FeedOwnCapability.UPDATE_FEED_MEMBERS) > -1,
5689
+ }), [oc]);
5690
+ };
5691
+
5692
+ const selector$1 = ({ follower_count, followers, followers_pagination, }) => ({
5693
+ follower_count,
5694
+ followers,
5695
+ followers_pagination,
5696
+ });
5697
+ function useFollowers(feedFromProps) {
5698
+ const feedFromContext = useFeedContext();
5699
+ const feed = feedFromProps ?? feedFromContext;
5700
+ const data = useStateStore(feed?.state, selector$1);
5701
+ const loadNextPage = useCallback((...options) => feed?.loadNextPageFollowers(...options), [feed]);
5702
+ return useMemo(() => {
5703
+ if (!data) {
5704
+ return undefined;
5705
+ }
5706
+ return {
5707
+ ...data,
5708
+ isLoadingNextPage: data.followers_pagination?.loading_next_page ?? false,
5709
+ hasNextPage: checkHasAnotherPage(data.followers, data.followers_pagination?.next),
5710
+ loadNextPage,
5711
+ };
5712
+ }, [data, loadNextPage]);
5713
+ }
5714
+
5715
+ const selector = ({ following_count, following, following_pagination, }) => ({
5716
+ following_count,
5717
+ following,
5718
+ following_pagination,
5719
+ });
5720
+ function useFollowing(feedFromProps) {
5721
+ const feedFromContext = useFeedContext();
5722
+ const feed = feedFromProps ?? feedFromContext;
5723
+ const data = useStateStore(feed?.state, selector);
5724
+ const loadNextPage = useCallback((...options) => feed?.loadNextPageFollowing(...options), [feed]);
5725
+ return useMemo(() => {
5726
+ if (!data) {
5727
+ return undefined;
5728
+ }
5729
+ return {
5730
+ ...data,
5731
+ isLoadingNextPage: data.following_pagination?.loading_next_page ?? false,
5732
+ hasNextPage: checkHasAnotherPage(data.following, data.following_pagination?.next),
5733
+ loadNextPage,
5734
+ };
5735
+ }, [data, loadNextPage]);
5736
+ }
5737
+
5738
+ /**
5739
+ * A utility hook that takes in an entity and a reaction type, and creates reaction actions
5740
+ * that can then be used on the UI. The entity can be either an ActivityResponse or a CommentResponse
5741
+ * as the hook determines internally which APIs it is supposed to use, while taking the
5742
+ * correct ownCapabilities into account.
5743
+ * @param entity - The entity to which we want to add a reaction, can be either ActivityResponse or CommentResponse.
5744
+ * @param type - The type of reaction we want to add or remove.
5745
+ */
5746
+ const useReactionActions = ({ entity, type, }) => {
5747
+ const client = useFeedsClient();
5748
+ const isComment = isCommentResponse(entity);
5749
+ const hasOwnReaction = useMemo(() => !!entity.own_reactions?.find((r) => r.type === type), [entity.own_reactions, type]);
5750
+ const addReaction = useStableCallback(async () => {
5751
+ await (isComment
5752
+ ? client?.addCommentReaction({ comment_id: entity.id, type })
5753
+ : client?.addReaction({ activity_id: entity.id, type }));
5754
+ });
5755
+ const removeReaction = useStableCallback(async () => {
5756
+ await (isComment
5757
+ ? client?.deleteCommentReaction({ comment_id: entity.id, type })
5758
+ : client?.deleteActivityReaction({
5759
+ activity_id: entity.id,
5760
+ type,
5761
+ }));
5762
+ });
5763
+ const toggleReaction = useStableCallback(async () => {
5764
+ if (hasOwnReaction) {
5765
+ await removeReaction();
5766
+ }
5767
+ else {
5768
+ await addReaction();
5769
+ }
5770
+ });
5771
+ return useMemo(() => ({ addReaction, removeReaction, toggleReaction }), [addReaction, removeReaction, toggleReaction]);
5772
+ };
5773
+
5513
5774
  const StreamFeeds = ({ client, children }) => {
5514
5775
  return (jsx(StreamFeedsContext.Provider, { value: client, children: children }));
5515
5776
  };
5516
5777
  StreamFeeds.displayName = 'StreamFeeds';
5517
5778
 
5518
- export { StreamFeeds, StreamFeedsContext, useClientConnectedUser, useComments, useCreateFeedsClient, useFeedsClient, useOwnCapabilities, useStateStore, useWsConnectionState };
5779
+ const StreamFeed = ({ feed, children }) => {
5780
+ return (jsx(StreamFeedContext.Provider, { value: feed, children: children }));
5781
+ };
5782
+ StreamFeed.displayName = 'StreamFeed';
5783
+
5784
+ export { StreamFeed, StreamFeedContext, StreamFeeds, StreamFeedsContext, useClientConnectedUser, useComments, useCreateFeedsClient, useFeedActivities, useFeedContext, useFeedsClient, useFollowers, useFollowing, useOwnCapabilities, useReactionActions, useStateStore, useWsConnectionState };
5519
5785
  //# sourceMappingURL=index-react-bindings.node.js.map