@stream-io/feeds-client 0.1.0 → 0.1.2

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 (43) hide show
  1. package/@react-bindings/index.ts +13 -0
  2. package/CHANGELOG.md +31 -0
  3. package/dist/@react-bindings/contexts/StreamFeedsContext.d.ts +15 -0
  4. package/dist/@react-bindings/hooks/clientStateHooks.d.ts +10 -0
  5. package/dist/@react-bindings/hooks/useCreateFeedsClient.d.ts +13 -0
  6. package/dist/@react-bindings/hooks/useOwnCapabilities.d.ts +33 -0
  7. package/dist/@react-bindings/hooks/useStateStore.d.ts +1 -1
  8. package/dist/@react-bindings/index.d.ts +5 -0
  9. package/dist/@react-bindings/wrappers/StreamFeeds.d.ts +6 -0
  10. package/dist/index-react-bindings.browser.cjs +5462 -12
  11. package/dist/index-react-bindings.browser.cjs.map +1 -1
  12. package/dist/index-react-bindings.browser.js +5457 -14
  13. package/dist/index-react-bindings.browser.js.map +1 -1
  14. package/dist/index-react-bindings.node.cjs +5462 -12
  15. package/dist/index-react-bindings.node.cjs.map +1 -1
  16. package/dist/index-react-bindings.node.js +5457 -14
  17. package/dist/index-react-bindings.node.js.map +1 -1
  18. package/dist/index.browser.cjs +100 -31
  19. package/dist/index.browser.cjs.map +1 -1
  20. package/dist/index.browser.js +100 -31
  21. package/dist/index.browser.js.map +1 -1
  22. package/dist/index.node.cjs +100 -31
  23. package/dist/index.node.cjs.map +1 -1
  24. package/dist/index.node.js +100 -31
  25. package/dist/index.node.js.map +1 -1
  26. package/dist/src/Feed.d.ts +2 -2
  27. package/dist/src/FeedsClient.d.ts +8 -5
  28. package/dist/src/common/StateStore.d.ts +1 -0
  29. package/dist/src/common/real-time/StableWSConnection.d.ts +3 -3
  30. package/dist/src/gen/feeds/FeedsApi.d.ts +7 -1
  31. package/dist/src/gen/models/index.d.ts +41 -0
  32. package/dist/src/types.d.ts +2 -0
  33. package/dist/tsconfig.tsbuildinfo +1 -1
  34. package/package.json +2 -6
  35. package/src/Feed.ts +22 -12
  36. package/src/FeedsClient.ts +24 -17
  37. package/src/common/StateStore.ts +30 -12
  38. package/src/common/utils.ts +1 -1
  39. package/src/gen/feeds/FeedsApi.ts +51 -0
  40. package/src/gen/model-decoders/decoders.ts +29 -0
  41. package/src/gen/model-decoders/event-decoder-mapping.ts +6 -0
  42. package/src/gen/models/index.ts +60 -0
  43. package/src/types.ts +4 -0
@@ -259,6 +259,14 @@ decoders.BookmarkDeletedEvent = (input) => {
259
259
  };
260
260
  return decode(typeMappings, input);
261
261
  };
262
+ decoders.BookmarkFolderDeletedEvent = (input) => {
263
+ const typeMappings = {
264
+ created_at: { type: 'DatetimeType', isSingle: true },
265
+ bookmark_folder: { type: 'BookmarkFolderResponse', isSingle: true },
266
+ received_at: { type: 'DatetimeType', isSingle: true },
267
+ };
268
+ return decode(typeMappings, input);
269
+ };
262
270
  decoders.BookmarkFolderResponse = (input) => {
263
271
  const typeMappings = {
264
272
  created_at: { type: 'DatetimeType', isSingle: true },
@@ -266,6 +274,14 @@ decoders.BookmarkFolderResponse = (input) => {
266
274
  };
267
275
  return decode(typeMappings, input);
268
276
  };
277
+ decoders.BookmarkFolderUpdatedEvent = (input) => {
278
+ const typeMappings = {
279
+ created_at: { type: 'DatetimeType', isSingle: true },
280
+ bookmark_folder: { type: 'BookmarkFolderResponse', isSingle: true },
281
+ received_at: { type: 'DatetimeType', isSingle: true },
282
+ };
283
+ return decode(typeMappings, input);
284
+ };
269
285
  decoders.BookmarkResponse = (input) => {
270
286
  const typeMappings = {
271
287
  created_at: { type: 'DatetimeType', isSingle: true },
@@ -1225,6 +1241,12 @@ decoders.UpdateBlockListResponse = (input) => {
1225
1241
  };
1226
1242
  return decode(typeMappings, input);
1227
1243
  };
1244
+ decoders.UpdateBookmarkFolderResponse = (input) => {
1245
+ const typeMappings = {
1246
+ bookmark_folder: { type: 'BookmarkFolderResponse', isSingle: true },
1247
+ };
1248
+ return decode(typeMappings, input);
1249
+ };
1228
1250
  decoders.UpdateBookmarkResponse = (input) => {
1229
1251
  const typeMappings = {
1230
1252
  bookmark: { type: 'BookmarkResponse', isSingle: true },
@@ -1645,6 +1667,26 @@ class FeedsApi {
1645
1667
  decoders.QueryBookmarkFoldersResponse?.(response.body);
1646
1668
  return { ...response.body, metadata: response.metadata };
1647
1669
  }
1670
+ async deleteBookmarkFolder(request) {
1671
+ const pathParams = {
1672
+ folder_id: request?.folder_id,
1673
+ };
1674
+ const response = await this.apiClient.sendRequest('DELETE', '/api/v2/feeds/bookmark_folders/{folder_id}', pathParams, undefined);
1675
+ decoders.DeleteBookmarkFolderResponse?.(response.body);
1676
+ return { ...response.body, metadata: response.metadata };
1677
+ }
1678
+ async updateBookmarkFolder(request) {
1679
+ const pathParams = {
1680
+ folder_id: request?.folder_id,
1681
+ };
1682
+ const body = {
1683
+ name: request?.name,
1684
+ custom: request?.custom,
1685
+ };
1686
+ const response = await this.apiClient.sendRequest('PATCH', '/api/v2/feeds/bookmark_folders/{folder_id}', pathParams, undefined, body, 'application/json');
1687
+ decoders.UpdateBookmarkFolderResponse?.(response.body);
1688
+ return { ...response.body, metadata: response.metadata };
1689
+ }
1648
1690
  async queryBookmarks(request) {
1649
1691
  const body = {
1650
1692
  limit: request?.limit,
@@ -2321,14 +2363,9 @@ class StateStore {
2321
2363
  let previouslySelectedValues;
2322
2364
  const wrappedHandler = (nextValue) => {
2323
2365
  const newlySelectedValues = selector(nextValue);
2324
- let hasUpdatedValues = typeof previouslySelectedValues === 'undefined';
2325
- for (const key in previouslySelectedValues) {
2326
- if (previouslySelectedValues[key] === newlySelectedValues[key])
2327
- continue;
2328
- hasUpdatedValues = true;
2329
- break;
2330
- }
2331
- if (!hasUpdatedValues)
2366
+ // shallow comparison of previouslySelectedValues and newlySelectedValues
2367
+ const selectionsAreEqual = StateStore.doSelectionsEqual(previouslySelectedValues, newlySelectedValues);
2368
+ if (selectionsAreEqual)
2332
2369
  return;
2333
2370
  // save a copy of previouslySelectedValues before running
2334
2371
  // handler - if previouslySelectedValues are set to
@@ -2359,7 +2396,7 @@ class StateStore {
2359
2396
  ? newValueOrPatch(this.value)
2360
2397
  : newValueOrPatch;
2361
2398
  // do not notify subscribers if the value hasn't changed
2362
- if (newValue === this.value)
2399
+ if (Object.is(newValue, this.value))
2363
2400
  return;
2364
2401
  this.preprocessors.forEach((preprocessor) => preprocessor(newValue, this.value));
2365
2402
  const oldValue = this.value;
@@ -2376,6 +2413,19 @@ class StateStore {
2376
2413
  this.handlers.delete(handler);
2377
2414
  };
2378
2415
  }
2416
+ static doSelectionsEqual(previouslySelectedValues, newlySelectedValues) {
2417
+ let selectionsAreEqual;
2418
+ if ((selectionsAreEqual = typeof previouslySelectedValues !== 'undefined')) {
2419
+ for (const key in newlySelectedValues) {
2420
+ if (Object.is(previouslySelectedValues[key], newlySelectedValues[key])) {
2421
+ continue;
2422
+ }
2423
+ selectionsAreEqual = false;
2424
+ break;
2425
+ }
2426
+ }
2427
+ return selectionsAreEqual;
2428
+ }
2379
2429
  /**
2380
2430
  * Registers a preprocessor function that will be called before the state is updated.
2381
2431
  *
@@ -2601,7 +2651,7 @@ function getRandomValuesWithMathRandom(bytes) {
2601
2651
  }
2602
2652
  }
2603
2653
  const getRandomValues = (() => {
2604
- if (typeof crypto?.getRandomValues !== 'undefined') {
2654
+ if (typeof crypto !== 'undefined' && typeof crypto?.getRandomValues !== 'undefined') {
2605
2655
  return crypto.getRandomValues.bind(crypto);
2606
2656
  }
2607
2657
  else if (typeof msCrypto !== 'undefined') {
@@ -3610,6 +3660,8 @@ const eventDecoderMapping = {
3610
3660
  'feeds.bookmark.added': (data) => decoders.BookmarkAddedEvent(data),
3611
3661
  'feeds.bookmark.deleted': (data) => decoders.BookmarkDeletedEvent(data),
3612
3662
  'feeds.bookmark.updated': (data) => decoders.BookmarkUpdatedEvent(data),
3663
+ 'feeds.bookmark_folder.deleted': (data) => decoders.BookmarkFolderDeletedEvent(data),
3664
+ 'feeds.bookmark_folder.updated': (data) => decoders.BookmarkFolderUpdatedEvent(data),
3613
3665
  'feeds.comment.added': (data) => decoders.CommentAddedEvent(data),
3614
3666
  'feeds.comment.deleted': (data) => decoders.CommentDeletedEvent(data),
3615
3667
  'feeds.comment.reaction.added': (data) => decoders.CommentReactionAddedEvent(data),
@@ -4012,6 +4064,8 @@ class Feed extends FeedApi {
4012
4064
  'feeds.bookmark.added': this.handleBookmarkAdded.bind(this),
4013
4065
  'feeds.bookmark.deleted': this.handleBookmarkDeleted.bind(this),
4014
4066
  'feeds.bookmark.updated': this.handleBookmarkUpdated.bind(this),
4067
+ 'feeds.bookmark_folder.deleted': Feed.noop,
4068
+ 'feeds.bookmark_folder.updated': Feed.noop,
4015
4069
  'feeds.comment.added': (event) => {
4016
4070
  const { comment } = event;
4017
4071
  const forId = comment.parent_id ?? comment.object_id;
@@ -4101,16 +4155,19 @@ class Feed extends FeedApi {
4101
4155
  return;
4102
4156
  // this feed followed someone
4103
4157
  if (event.follow.source_feed.fid === this.fid) {
4104
- if (this.currentState.following_pagination?.next === END_OF_LIST) {
4105
- this.state.next((currentState) => ({
4158
+ this.state.next((currentState) => {
4159
+ const newState = {
4106
4160
  ...currentState,
4107
4161
  ...event.follow.source_feed,
4162
+ };
4163
+ if (currentState.following_pagination?.next === END_OF_LIST) {
4108
4164
  // TODO: respect sort
4109
- following: currentState.following
4165
+ newState.following = currentState.following
4110
4166
  ? currentState.following.concat(event.follow)
4111
- : [event.follow],
4112
- }));
4113
- }
4167
+ : [event.follow];
4168
+ }
4169
+ return newState;
4170
+ });
4114
4171
  }
4115
4172
  else if (
4116
4173
  // someone followed this feed
@@ -4120,14 +4177,14 @@ class Feed extends FeedApi {
4120
4177
  this.state.next((currentState) => {
4121
4178
  const newState = { ...currentState, ...event.follow.target_feed };
4122
4179
  if (source.created_by.id === connectedUser?.id) {
4123
- newState.own_follows = newState.own_follows
4124
- ? newState.own_follows.concat(event.follow)
4180
+ newState.own_follows = currentState.own_follows
4181
+ ? currentState.own_follows.concat(event.follow)
4125
4182
  : [event.follow];
4126
4183
  }
4127
4184
  if (currentState.followers_pagination?.next === END_OF_LIST) {
4128
4185
  // TODO: respect sort
4129
- newState.followers = newState.followers
4130
- ? newState.followers.concat(event.follow)
4186
+ newState.followers = currentState.followers
4187
+ ? currentState.followers.concat(event.follow)
4131
4188
  : [event.follow];
4132
4189
  }
4133
4190
  return newState;
@@ -4153,9 +4210,9 @@ class Feed extends FeedApi {
4153
4210
  this.state.next((currentState) => {
4154
4211
  const newState = { ...currentState, ...event.follow.target_feed };
4155
4212
  if (source.created_by.id === connectedUser?.id) {
4156
- newState.own_follows = newState.own_follows?.filter((follow) => follow.source_feed.fid !== event.follow.source_feed.fid);
4213
+ newState.own_follows = currentState.own_follows?.filter((follow) => follow.source_feed.fid !== event.follow.source_feed.fid);
4157
4214
  }
4158
- newState.followers = newState.followers?.filter((follow) => follow.source_feed.fid !== event.follow.source_feed.fid);
4215
+ newState.followers = currentState.followers?.filter((follow) => follow.source_feed.fid !== event.follow.source_feed.fid);
4159
4216
  return newState;
4160
4217
  });
4161
4218
  }
@@ -4257,6 +4314,9 @@ class Feed extends FeedApi {
4257
4314
  const newComment = {
4258
4315
  ...newComments[commentIndex],
4259
4316
  ...commentCopy,
4317
+ // TODO: FIXME this should be handled by the backend
4318
+ latest_reactions: commentCopy.latest_reactions ?? [],
4319
+ reaction_groups: commentCopy.reaction_groups ?? {},
4260
4320
  };
4261
4321
  newComments[commentIndex] = newComment;
4262
4322
  if (reaction.user.id === connectedUser?.id) {
@@ -5060,7 +5120,10 @@ class FeedsClient extends FeedsApi {
5060
5120
  }, this.tokenManager, this.connectionIdManager, [decodeWSEvent]);
5061
5121
  this.wsConnection.on('all', (event) => this.eventDispatcher.dispatch(event));
5062
5122
  const connectedEvent = await this.wsConnection.connect();
5063
- this.state.partialNext({ connectedUser: connectedEvent?.me });
5123
+ this.state.partialNext({
5124
+ connectedUser: connectedEvent?.me,
5125
+ isWsConnectionHealthy: this.wsConnection.isHealthy,
5126
+ });
5064
5127
  }
5065
5128
  catch (err) {
5066
5129
  await this.disconnectUser();
@@ -5115,13 +5178,20 @@ class FeedsClient extends FeedsApi {
5115
5178
  removeConnectionEventListeners(this.updateNetworkConnectionStatus);
5116
5179
  this.connectionIdManager.reset();
5117
5180
  this.tokenManager.reset();
5118
- this.state.partialNext({ connectedUser: undefined });
5181
+ this.state.partialNext({ connectedUser: undefined, isWsConnectionHealthy: false });
5119
5182
  };
5120
5183
  this.on = this.eventDispatcher.on;
5121
5184
  this.off = this.eventDispatcher.off;
5122
5185
  this.feed = (groupId, id) => {
5123
5186
  return this.getOrCreateActiveFeed(groupId, id);
5124
5187
  };
5188
+ this.updateNetworkConnectionStatus = (event) => {
5189
+ const networkEvent = {
5190
+ type: 'network.changed',
5191
+ online: event.type === 'online',
5192
+ };
5193
+ this.eventDispatcher.dispatch(networkEvent);
5194
+ };
5125
5195
  this.getOrCreateActiveFeed = (group, id, data) => {
5126
5196
  const fid = `${group}:${id}`;
5127
5197
  if (this.activeFeeds[fid]) {
@@ -5133,15 +5203,9 @@ class FeedsClient extends FeedsApi {
5133
5203
  return feed;
5134
5204
  }
5135
5205
  };
5136
- this.updateNetworkConnectionStatus = (event) => {
5137
- const networkEvent = {
5138
- type: 'network.changed',
5139
- online: event.type === 'online',
5140
- };
5141
- this.eventDispatcher.dispatch(networkEvent);
5142
- };
5143
5206
  this.state = new StateStore({
5144
5207
  connectedUser: undefined,
5208
+ isWsConnectionHealthy: false,
5145
5209
  });
5146
5210
  this.moderation = new ModerationClient(apiClient);
5147
5211
  this.tokenManager = tokenManager;
@@ -5151,6 +5215,11 @@ class FeedsClient extends FeedsApi {
5151
5215
  const fid = event.fid;
5152
5216
  const feed = typeof fid === 'string' ? this.activeFeeds[fid] : undefined;
5153
5217
  switch (event.type) {
5218
+ case 'connection.changed': {
5219
+ const { online } = event;
5220
+ this.state.partialNext({ isWsConnectionHealthy: online });
5221
+ break;
5222
+ }
5154
5223
  case 'feeds.feed.created': {
5155
5224
  if (feed)
5156
5225
  break;