@stream-io/feeds-client 0.2.4 → 0.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/feeds-client",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "packageManager": "yarn@3.2.4",
5
5
  "main": "./dist/index.node.js",
6
6
  "exports": {
@@ -36,3 +36,19 @@ export interface ConnectedEvent {
36
36
  */
37
37
  type: string;
38
38
  }
39
+
40
+ export enum UnhandledErrorType {
41
+ ReconnectionReconciliation = 'reconnection-reconciliation',
42
+ }
43
+
44
+ export type SyncFailure = { feed: string, reason: unknown };
45
+
46
+ export type UnhandledErrorEvent = {
47
+ type: 'errors.unhandled';
48
+ error_type: UnhandledErrorType;
49
+ } & (
50
+ | {
51
+ error_type: UnhandledErrorType.ReconnectionReconciliation;
52
+ failures: SyncFailure[];
53
+ }
54
+ );
@@ -1,9 +1,9 @@
1
1
  import { Feed } from '../../../feed';
2
- import { EventPayload } from '../../../types-internal';
2
+ import { EventPayload, PartializeAllBut } from '../../../types-internal';
3
3
 
4
4
  export function handleFeedUpdated(
5
5
  this: Feed,
6
- event: EventPayload<'feeds.feed.updated'>,
6
+ event: PartializeAllBut<EventPayload<'feeds.feed.updated'>, 'feed'>,
7
7
  ) {
8
8
  this.state.partialNext({ ...event.feed });
9
9
  }
@@ -37,6 +37,7 @@ import { ModerationClient } from '../moderation-client';
37
37
  import { StreamPoll } from '../common/Poll';
38
38
  import {
39
39
  Feed,
40
+ handleFeedUpdated,
40
41
  handleFollowCreated,
41
42
  handleFollowDeleted,
42
43
  handleFollowUpdated,
@@ -44,6 +45,10 @@ import {
44
45
  handleWatchStopped,
45
46
  } from '../feed';
46
47
  import { handleUserUpdated } from './event-handlers';
48
+ import {
49
+ SyncFailure,
50
+ UnhandledErrorType,
51
+ } from '../common/real-time/event-models';
47
52
 
48
53
  export type FeedsClientState = {
49
54
  connected_user: OwnUser | undefined;
@@ -100,14 +105,7 @@ export class FeedsClient extends FeedsApi {
100
105
  this.state.partialNext({ is_ws_connection_healthy: online });
101
106
 
102
107
  if (online) {
103
- this.healthyConnectionChangedEventCount++;
104
-
105
- // we skip the first event as we could potentially be querying twice
106
- if (this.healthyConnectionChangedEventCount > 1) {
107
- for (const activeFeed of Object.values(this.activeFeeds)) {
108
- activeFeed.synchronize();
109
- }
110
- }
108
+ this.recoverOnReconnect();
111
109
  } else {
112
110
  for (const activeFeed of Object.values(this.activeFeeds)) {
113
111
  handleWatchStopped.bind(activeFeed)();
@@ -208,6 +206,31 @@ export class FeedsClient extends FeedsApi {
208
206
  });
209
207
  }
210
208
 
209
+ private recoverOnReconnect = async () => {
210
+ this.healthyConnectionChangedEventCount++;
211
+
212
+ // we skip the first event as we could potentially be querying twice
213
+ if (this.healthyConnectionChangedEventCount > 1) {
214
+ const entries = Object.entries(this.activeFeeds);
215
+
216
+ const results = await Promise.allSettled(
217
+ entries.map(([, feed]) => feed.synchronize()),
218
+ );
219
+
220
+ const failures: SyncFailure[] = results.flatMap((result, index) =>
221
+ result.status === 'rejected'
222
+ ? [{ feed: entries[index][0], reason: result.reason }]
223
+ : [],
224
+ );
225
+
226
+ this.eventDispatcher.dispatch({
227
+ type: 'errors.unhandled',
228
+ error_type: UnhandledErrorType.ReconnectionReconciliation,
229
+ failures,
230
+ });
231
+ }
232
+ };
233
+
211
234
  public pollFromState = (id: string) => this.polls_by_id.get(id);
212
235
 
213
236
  public hydratePollCache(activities: ActivityResponse[]) {
@@ -353,8 +376,13 @@ export class FeedsClient extends FeedsApi {
353
376
  async queryFeeds(request?: QueryFeedsRequest) {
354
377
  const response = await this._queryFeeds(request);
355
378
 
356
- const feeds = response.feeds.map((f) =>
357
- this.getOrCreateActiveFeed(f.group_id, f.id, f, request?.watch),
379
+ const feeds = response.feeds.map((feedResponse) =>
380
+ this.getOrCreateActiveFeed(
381
+ feedResponse.group_id,
382
+ feedResponse.id,
383
+ feedResponse,
384
+ request?.watch,
385
+ ),
358
386
  );
359
387
 
360
388
  return {
@@ -458,17 +486,20 @@ export class FeedsClient extends FeedsApi {
458
486
  watch?: boolean,
459
487
  ) => {
460
488
  const fid = `${group}:${id}`;
461
- if (this.activeFeeds[fid]) {
462
- const feed = this.activeFeeds[fid];
463
- if (watch && !feed.currentState.watch) {
464
- handleWatchStarted.bind(feed)();
465
- }
466
- return feed;
467
- } else {
468
- const feed = new Feed(this, group, id, data, watch);
469
- this.activeFeeds[fid] = feed;
470
- return feed;
489
+
490
+ if (!this.activeFeeds[fid]) {
491
+ this.activeFeeds[fid] = new Feed(this, group, id, data, watch);
471
492
  }
493
+
494
+ const feed = this.activeFeeds[fid];
495
+
496
+ if (!feed.currentState.watch) {
497
+ // feed isn't watched and may be stale, update it
498
+ if (data) handleFeedUpdated.call(feed, { feed: data });
499
+ if (watch) handleWatchStarted.call(feed);
500
+ }
501
+
502
+ return feed;
472
503
  };
473
504
 
474
505
  private findActiveFeedByActivityId(activityId: string) {
package/src/types.ts CHANGED
@@ -1,4 +1,7 @@
1
- import { ConnectionChangedEvent } from './common/real-time/event-models';
1
+ import {
2
+ ConnectionChangedEvent,
3
+ UnhandledErrorEvent,
4
+ } from './common/real-time/event-models';
2
5
  import { NetworkChangedEvent } from './common/types';
3
6
  import {
4
7
  PagerResponse,
@@ -10,7 +13,7 @@ import type {
10
13
  } from './gen/models';
11
14
  import { FeedsClient } from './feeds-client';
12
15
 
13
- export type FeedsEvent = WSEvent | ConnectionChangedEvent | NetworkChangedEvent;
16
+ export type FeedsEvent = WSEvent | ConnectionChangedEvent | NetworkChangedEvent | UnhandledErrorEvent;
14
17
  export type ActivityIdOrCommentId = string;
15
18
 
16
19
  export type GetCommentsRequest = Parameters<FeedsClient['getComments']>[0];