@livedigital/client 3.23.0 → 3.24.0

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 (34) hide show
  1. package/dist/engine/ChannelStateSynchronizer.d.ts +4 -1
  2. package/dist/engine/EventsQueue.d.ts +26 -0
  3. package/dist/engine/Peer.d.ts +5 -0
  4. package/dist/engine/handlers/ChannelStateSyncEventHandler/ChannelStateConsistencyCheckResult.d.ts +4 -0
  5. package/dist/engine/handlers/ChannelStateSyncEventHandler/ChannelStateConsistencyChecker.d.ts +8 -0
  6. package/dist/engine/handlers/ChannelStateSyncEventHandler/index.d.ts +2 -0
  7. package/dist/engine/handlers/ChannelStateSyncEventHandler/types.d.ts +4 -1
  8. package/dist/engine/index.d.ts +2 -0
  9. package/dist/engine/media/tracks/DefaultBaseTrack.d.ts +8 -0
  10. package/dist/engine/media/tracks/DefaultVideoTrack.d.ts +10 -1
  11. package/dist/engine/media/tracks/PeerTrack.d.ts +1 -0
  12. package/dist/index.es.js +3 -3
  13. package/dist/index.js +4 -4
  14. package/dist/types/channelStateSyncronizer.d.ts +6 -0
  15. package/package.json +1 -1
  16. package/src/engine/ChannelStateSynchronizer.ts +42 -1
  17. package/src/engine/EventsQueue.ts +114 -0
  18. package/src/engine/Peer.ts +11 -3
  19. package/src/engine/handlers/ChannelEventHandler.ts +47 -41
  20. package/src/engine/handlers/ChannelStateSyncEventHandler/ChannelStateConsistencyCheckResult.ts +10 -0
  21. package/src/engine/handlers/ChannelStateSyncEventHandler/ChannelStateConsistencyChecker.ts +35 -0
  22. package/src/engine/handlers/ChannelStateSyncEventHandler/index.ts +46 -5
  23. package/src/engine/handlers/ChannelStateSyncEventHandler/types.ts +9 -0
  24. package/src/engine/handlers/MediaSoupEventHandler.ts +48 -44
  25. package/src/engine/index.ts +13 -6
  26. package/src/engine/media/index.ts +1 -1
  27. package/src/engine/media/streamEffects/audio/noiseSuppression/TrackProcessor.ts +1 -1
  28. package/src/engine/media/tracks/DefaultAudioTrack.ts +2 -2
  29. package/src/engine/media/tracks/DefaultBaseTrack.ts +21 -10
  30. package/src/engine/media/tracks/DefaultVideoTrack.ts +16 -4
  31. package/src/engine/media/tracks/PeerTrack.ts +29 -17
  32. package/src/engine/network/Socket.ts +0 -4
  33. package/src/engine/network/index.ts +6 -6
  34. package/src/types/channelStateSyncronizer.ts +7 -0
@@ -2,10 +2,16 @@ export interface ChannelStateProducer {
2
2
  id: string;
3
3
  paused: boolean;
4
4
  }
5
+ export interface ChannelStateConsumer {
6
+ id: string;
7
+ paused: boolean;
8
+ producerId: string;
9
+ }
5
10
  export interface ChannelStatePeer {
6
11
  id: string;
7
12
  appDataHash: string;
8
13
  producers: ChannelStateProducer[];
14
+ consumers?: ChannelStateConsumer[];
9
15
  }
10
16
  export interface ChannelState {
11
17
  peers: ChannelStatePeer[];
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@livedigital/client",
3
3
  "author": "vlprojects",
4
4
  "license": "MIT",
5
- "version": "3.23.0",
5
+ "version": "3.24.0",
6
6
  "private": false,
7
7
  "bugs": {
8
8
  "url": "https://github.com/vlprojects/livedigital-sdk/issues"
@@ -9,8 +9,10 @@ import {
9
9
  import Peer from './Peer';
10
10
  import { debounce, deepEqualObject } from '../helpers/common';
11
11
  import Engine from './index';
12
+ import { InconsistenceType } from './handlers/ChannelStateSyncEventHandler/types';
12
13
 
13
14
  const RESTORE_STATE_DEBOUNCE_TIME_MS = 3000;
15
+ const SYNC_CONSUMERS_DEBOUNCE_TIME_MS = 1000;
14
16
 
15
17
  export interface ChannelStateSynchronizerParams {
16
18
  engine: Engine;
@@ -24,7 +26,9 @@ class ChannelStateSynchronizer {
24
26
 
25
27
  readonly #logger: Logger;
26
28
 
27
- readonly debouncedRestoreState: <U>(this: U, ...args: Parameters<() => Promise<void>>) => void;
29
+ readonly debouncedRestoreState: <U>(this: U, ...args: Parameters<() => Promise<void>>) => void | Promise<void>;
30
+
31
+ readonly debouncedSyncPeerState: <U>(this: U, type: InconsistenceType) => void | Promise<void>;
28
32
 
29
33
  constructor({
30
34
  engine, onLogMessage, logLevel, sendAnalytics,
@@ -38,12 +42,49 @@ class ChannelStateSynchronizer {
38
42
  });
39
43
  this.watchNetworkState();
40
44
  this.debouncedRestoreState = debounce(this.restoreState.bind(this), RESTORE_STATE_DEBOUNCE_TIME_MS);
45
+ this.debouncedSyncPeerState = debounce(this.syncPeerState.bind(this), SYNC_CONSUMERS_DEBOUNCE_TIME_MS);
41
46
  }
42
47
 
43
48
  private watchNetworkState(): void {
44
49
  this.#engine.clientEventEmitter.on(CLIENT_EVENTS.connectionRestored, async () => {
45
50
  await this.debouncedRestoreState();
46
51
  });
52
+
53
+ this.#engine.clientEventEmitter.on(CLIENT_EVENTS.channelStateInconsistent, async ({ type }) => {
54
+ if ([InconsistenceType.IncorrectConsumerState, InconsistenceType.MissingConsumers].includes(type)) {
55
+ await this.debouncedSyncPeerState(type);
56
+ }
57
+ });
58
+ }
59
+
60
+ private async syncPeerState(type: InconsistenceType): Promise<void> {
61
+ try {
62
+ if (!this.#engine.isSocketConnectionActive) {
63
+ this.#logger.info('Connection inactive, skipping re-syncing local peers state', {
64
+ case: 'restoreLocalState',
65
+ type,
66
+ });
67
+ return;
68
+ }
69
+
70
+ this.#engine.eventsQueue.pause();
71
+
72
+ this.#logger.info('Re-syncing local peers state', {
73
+ case: 'restoreLocalState',
74
+ type,
75
+ });
76
+
77
+ const { peers: clusterPeers } = await this.#engine.network.getChannelPeers('host');
78
+ this.restoreLocalPeersState(clusterPeers);
79
+ } catch (error) {
80
+ this.#logger.info('Failed to re-sync local peers state', {
81
+ case: 'restoreLocalState',
82
+ type,
83
+ error: serializeError(error),
84
+ });
85
+ } finally {
86
+ this.#engine.eventsQueue.resume();
87
+ }
47
88
  }
48
89
 
49
90
  private async restoreState(): Promise<void> {
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Single point of events handling
3
+ * All events are redirected to that queue
4
+ * And later flushed to subscribed events handlers
5
+ *
6
+ * Has ability to postpone events processing
7
+ *
8
+ * When subscribing to event from all places: sockets, data channels
9
+ * All events should be sent to that queue
10
+ * And handlers should subscribe to events of that queue
11
+ */
12
+ import { DataConsumer } from 'mediasoup-client/lib/DataConsumer';
13
+ import { Socket } from 'socket.io-client';
14
+
15
+ interface EventArgs {
16
+ event: string
17
+ args: any[]
18
+ }
19
+
20
+ export default class EventsQueue {
21
+ readonly #queue: EventArgs[] = [];
22
+
23
+ readonly #listeners: Map<string, ((...args: any[]) => void | Promise<void>)[]> = new Map();
24
+
25
+ readonly #connections: Set<Socket> = new Set();
26
+
27
+ #paused: boolean = false;
28
+
29
+ subscribeToSocket(emmiter: Socket) {
30
+ if (this.#connections.has(emmiter)) {
31
+ return;
32
+ }
33
+
34
+ emmiter.onAny((event: string, ...args: any[]) => {
35
+ this.push(event, args);
36
+ });
37
+
38
+ this.#connections.add(emmiter);
39
+ }
40
+
41
+ subscribeToDataChannel(dataChannel: DataConsumer) {
42
+ dataChannel.on('message', (...args: any[]) => {
43
+ this.push('message', args);
44
+ });
45
+ }
46
+
47
+ on(event: string, handler: (...args: any[]) => void | Promise<void>) {
48
+ if (this.#listeners.has(event)) {
49
+ this.#listeners.get(event)?.push(handler);
50
+ return;
51
+ }
52
+
53
+ this.#listeners.set(event, [handler]);
54
+ }
55
+
56
+ removeListener(event: string) {
57
+ this.#listeners.delete(event);
58
+ }
59
+
60
+ removeAllListeners(events: string[], emitter?: Socket) {
61
+ if (emitter && this.#connections.has(emitter)) {
62
+ emitter.offAny();
63
+ this.#connections.delete(emitter);
64
+ }
65
+
66
+ events.forEach((event) => {
67
+ this.#listeners.delete(event);
68
+ });
69
+ }
70
+
71
+ private push(event: string, args: any[]) {
72
+ this.#queue.push({
73
+ event,
74
+ args,
75
+ });
76
+
77
+ this.process();
78
+ }
79
+
80
+ /*
81
+ * Process all events accumulated in the this.#queue
82
+ * Should happen immediately if queue is not paused
83
+ *
84
+ * Read existing listeners/handlers and emit events to them
85
+ */
86
+ private process() {
87
+ if (this.#paused) {
88
+ return;
89
+ }
90
+
91
+ while (this.#queue.length > 0) {
92
+ const eventArgs = this.#queue.shift()!;
93
+
94
+ this.emit(eventArgs.event, eventArgs.args);
95
+ }
96
+ }
97
+
98
+ private emit(event: string, args: any[]) {
99
+ const handlers = this.#listeners.get(event) || [];
100
+
101
+ handlers.forEach((handler) => {
102
+ handler(...args);
103
+ });
104
+ }
105
+
106
+ pause() {
107
+ this.#paused = true;
108
+ }
109
+
110
+ resume() {
111
+ this.#paused = false;
112
+ this.process();
113
+ }
114
+ }
@@ -242,7 +242,7 @@ class Peer {
242
242
  this.tracks.set(track.label, track);
243
243
  this.logger.debug(`Subscribed for ${producer.kind}`, { peer: logResponse(this) });
244
244
  } catch (error) {
245
- this.logger.error('subscribe()', { producerId, error });
245
+ this.logger.error('subscribe()', { producerId, error, peer: logResponse(this) });
246
246
  throw new Error('Error subscribe media');
247
247
  }
248
248
  }
@@ -324,6 +324,14 @@ class Peer {
324
324
  return Array.from(this.producers.values());
325
325
  }
326
326
 
327
+ getConsumersState(): { id: string, producerId: string, paused: boolean }[] {
328
+ return this.getAllConsumers().map((consumer) => ({
329
+ id: consumer.id,
330
+ paused: consumer.paused,
331
+ producerId: consumer.producerId,
332
+ }));
333
+ }
334
+
327
335
  private handleNewProducer(producerData: ProducerData): void {
328
336
  if (this.producers.get(producerData.id)) {
329
337
  return;
@@ -376,7 +384,7 @@ class Peer {
376
384
 
377
385
  await track.close();
378
386
  this.tracks.delete(track.label);
379
- this.logger.debug('Peer video track was ended:', { peer: logResponse(this), track });
387
+ this.logger.debug('Peer video track was ended:', { peer: logResponse(this), track: track.logCtx });
380
388
  });
381
389
 
382
390
  this.observer.on(MEDIASOUP_EVENTS.pauseConsumer, async (consumerId) => {
@@ -495,7 +503,7 @@ class Peer {
495
503
  consumer.setRequestedSpatialLayer(undefined);
496
504
  }
497
505
 
498
- this.logger.debug('consumerChangePreferredLayers()', { consumer, ...payload });
506
+ this.logger.debug('consumerChangePreferredLayers()', { ...payload });
499
507
  }
500
508
  });
501
509
 
@@ -53,79 +53,83 @@ class ChannelEventHandler {
53
53
  throw new Error('Socket connection not initialized');
54
54
  }
55
55
 
56
- connection.on(CHANNEL_EVENTS.channelEvent, (event: ChannelEvent) => {
56
+ this.engine.eventsQueue.subscribeToSocket(connection);
57
+
58
+ this.engine.eventsQueue.on(CHANNEL_EVENTS.channelEvent, (event: ChannelEvent) => {
57
59
  this.engine.clientEventEmitter.safeEmit(CLIENT_EVENTS.channelEvent, event);
58
60
  this.logger.debug('New channel event', { event });
59
61
  });
60
62
 
61
- connection.on(CHANNEL_EVENTS.channelJoin, (peerData: PeerResponse) => {
63
+ this.engine.eventsQueue.on(CHANNEL_EVENTS.channelJoin, (peerData: PeerResponse) => {
62
64
  const peer = this.engine.setPeer(peerData);
63
65
  this.logger.debug('Peer joined to the channel', { peer: logResponse(peer) });
64
66
  });
65
67
 
66
- connection.on(CHANNEL_EVENTS.channelLeave, (peerId: string) => {
68
+ this.engine.eventsQueue.on(CHANNEL_EVENTS.channelLeave, (peerId: string) => {
67
69
  this.engine.removePeer(peerId);
68
70
  this.logger.debug('Peer left the channel', { peerId });
69
71
  });
70
72
 
71
- connection.on(CHANNEL_EVENTS.activityConfirmationRequired, (event: ActivityConfirmationRequiredPayload) => {
72
- this.engine.clientEventEmitter.safeEmit(CLIENT_EVENTS.activityConfirmationRequired, event);
73
- this.logger.info('Notify the channel that activity confirmation is required', event);
74
- });
73
+ this.engine.eventsQueue
74
+ .on(CHANNEL_EVENTS.activityConfirmationRequired, (event: ActivityConfirmationRequiredPayload) => {
75
+ this.engine.clientEventEmitter.safeEmit(CLIENT_EVENTS.activityConfirmationRequired, event);
76
+ this.logger.info('Notify the channel that activity confirmation is required', event);
77
+ });
75
78
 
76
- connection.on(CHANNEL_EVENTS.activityConfirmationExpired, (event: ChannelEvent) => {
79
+ this.engine.eventsQueue.on(CHANNEL_EVENTS.activityConfirmationExpired, (event: ChannelEvent) => {
77
80
  this.engine.clientEventEmitter.safeEmit(CLIENT_EVENTS.activityConfirmationExpired, event);
78
81
  this.logger.info('Notify the channel that it has disconnected due to the activity confirmation expiring', event);
79
82
  });
80
83
 
81
- connection.on(CHANNEL_EVENTS.activityConfirmationAcquired, (event: ChannelEvent) => {
84
+ this.engine.eventsQueue.on(CHANNEL_EVENTS.activityConfirmationAcquired, (event: ChannelEvent) => {
82
85
  this.engine.clientEventEmitter.safeEmit(CLIENT_EVENTS.activityConfirmationAcquired, event);
83
86
  this.logger.info('Notify the channel that the activity has been acquired.', event);
84
87
  });
85
88
 
86
- connection.on(CHANNEL_EVENTS.changeProducePermissions, (payload: ChannelChangeProducePermissionsPayload) => {
87
- const { producePermissions, groups, changedProducePermissions } = payload;
88
- const peers = this.engine.peers.filter((peer) => peer.groups.some((group) => groups.includes(group)));
89
- peers.forEach((peer) => {
90
- const newProducePermissions = Array.from(new Set([...peer.producePermissions, ...changedProducePermissions]))
91
- .filter((label) => (changedProducePermissions.includes(label) ? producePermissions.includes(label) : true));
89
+ this.engine.eventsQueue
90
+ .on(CHANNEL_EVENTS.changeProducePermissions, (payload: ChannelChangeProducePermissionsPayload) => {
91
+ const { producePermissions, groups, changedProducePermissions } = payload;
92
+ const peers = this.engine.peers.filter((peer) => peer.groups.some((group) => groups.includes(group)));
93
+ peers.forEach((peer) => {
94
+ const newProducePermissions = Array.from(new Set([...peer.producePermissions, ...changedProducePermissions]))
95
+ .filter((label) => (changedProducePermissions.includes(label) ? producePermissions.includes(label) : true));
96
+
97
+ peer.handleProducePermissionsChange(newProducePermissions);
98
+ });
92
99
 
93
- peer.handleProducePermissionsChange(newProducePermissions);
100
+ this.logger.info('Peers changed produce permissions', { groups, producePermissions });
94
101
  });
95
102
 
96
- this.logger.info('Peers changed produce permissions', { groups, producePermissions });
97
- });
103
+ this.engine.eventsQueue
104
+ .on(CHANNEL_EVENTS.changePeerProducePermissions, (payload: ChangePeerProducePermissionPayload) => {
105
+ const { peerId, producePermissions } = payload;
106
+ const peer = this.engine.peersRepository.get(peerId);
98
107
 
99
- connection.on(CHANNEL_EVENTS.changePeerProducePermissions, (payload: ChangePeerProducePermissionPayload) => {
100
- const { peerId, producePermissions } = payload;
101
- const peer = this.engine.peersRepository.get(peerId);
108
+ if (!peer) {
109
+ return;
110
+ }
102
111
 
103
- if (!peer) {
104
- return;
105
- }
112
+ peer.handleProducePermissionsChange(producePermissions);
113
+ });
106
114
 
107
- peer.handleProducePermissionsChange(producePermissions);
108
- });
115
+ this.engine.eventsQueue
116
+ .on(CHANNEL_EVENTS.deleteProducePermissions, (payload: ChannelChangeProducePermissionsPayload) => {
117
+ const { producePermissions, groups } = payload;
118
+ const peers = this.engine.peers.filter((peer) => peer.groups.some((group) => groups.includes(group)));
119
+ peers.forEach((peer) => {
120
+ const newProducePermissions = peer
121
+ .producePermissions
122
+ .filter((permission) => !producePermissions.includes(permission));
109
123
 
110
- connection.on(CHANNEL_EVENTS.deleteProducePermissions, (payload: ChannelChangeProducePermissionsPayload) => {
111
- const { producePermissions, groups } = payload;
112
- const peers = this.engine.peers.filter((peer) => peer.groups.some((group) => groups.includes(group)));
113
- peers.forEach((peer) => {
114
- const newProducePermissions = peer
115
- .producePermissions
116
- .filter((permission) => !producePermissions.includes(permission));
124
+ peer.handleProducePermissionsChange(newProducePermissions);
125
+ });
117
126
 
118
- peer.handleProducePermissionsChange(newProducePermissions);
127
+ this.logger.info('Peers deleted produce permissions', { groups, producePermissions });
119
128
  });
120
-
121
- this.logger.info('Peers deleted produce permissions', { groups, producePermissions });
122
- });
123
129
  }
124
130
 
125
131
  private removeEventListeners() {
126
- const { connection } = this.engine.network.socket;
127
-
128
- if (!connection) {
132
+ if (!this.engine.eventsQueue) {
129
133
  return;
130
134
  }
131
135
 
@@ -140,7 +144,9 @@ class ChannelEventHandler {
140
144
  CHANNEL_EVENTS.changeProducePermissions,
141
145
  ];
142
146
 
143
- eventNames.forEach((x) => connection.removeAllListeners(x));
147
+ const { connection } = this.engine.network.socket;
148
+
149
+ this.engine.eventsQueue.removeAllListeners(eventNames, connection);
144
150
  }
145
151
 
146
152
  private handlePeerAppDataUpdate({ peerId, appData }: UpdatePeerAppDataPayload) {
@@ -1,6 +1,8 @@
1
1
  interface ChannelStateConsistencyCheckResultsParams {
2
2
  missingPeers: string[];
3
+ missingConsumers: string[];
3
4
  missingProducers: string[];
5
+ consumersWithInconsistentState: string[];
4
6
  producersWithInconsistentState: string[];
5
7
  peersWithInconsistentAppData: string[];
6
8
  }
@@ -8,22 +10,30 @@ interface ChannelStateConsistencyCheckResultsParams {
8
10
  class ChannelStateConsistencyCheckResult {
9
11
  readonly missingPeers: string[];
10
12
 
13
+ readonly missingConsumers: string[];
14
+
11
15
  readonly missingProducers: string[];
12
16
 
17
+ readonly consumersWithInconsistentState: string[];
18
+
13
19
  readonly producersWithInconsistentState: string[];
14
20
 
15
21
  readonly peersWithInconsistentAppData: string[];
16
22
 
17
23
  constructor(params: ChannelStateConsistencyCheckResultsParams) {
18
24
  this.missingPeers = params.missingPeers;
25
+ this.missingConsumers = params.missingConsumers;
19
26
  this.missingProducers = params.missingProducers;
27
+ this.consumersWithInconsistentState = params.consumersWithInconsistentState;
20
28
  this.producersWithInconsistentState = params.producersWithInconsistentState;
21
29
  this.peersWithInconsistentAppData = params.peersWithInconsistentAppData;
22
30
  }
23
31
 
24
32
  hasInconsistencyCases() {
25
33
  return this.missingPeers.length > 0
34
+ || this.missingConsumers.length > 0
26
35
  || this.missingProducers.length > 0
36
+ || this.consumersWithInconsistentState.length > 0
27
37
  || this.producersWithInconsistentState.length > 0
28
38
  || this.peersWithInconsistentAppData.length > 0;
29
39
  }
@@ -20,8 +20,12 @@ class ChannelStateConsistencyChecker {
20
20
 
21
21
  private missingPeers: string[] = [];
22
22
 
23
+ private missingConsumers: string[] = [];
24
+
23
25
  private missingProducers: string[] = [];
24
26
 
27
+ private consumersWithInconsistentState: string[] = [];
28
+
25
29
  private producersWithInconsistentState: string[] = [];
26
30
 
27
31
  private peersWithInconsistentAppData: string[] = [];
@@ -55,9 +59,21 @@ class ChannelStateConsistencyChecker {
55
59
  return;
56
60
  }
57
61
 
62
+ const localConsumer = this.findLocalConsumerByProducerId(localPeer, remoteProducer.id);
63
+ if (localConsumer && localConsumer.paused !== remoteProducer.paused) {
64
+ this.consumersWithInconsistentState.push(localConsumer.id);
65
+ }
66
+
58
67
  const localProducer = this.findLocalProducerById(localPeer, remoteProducer.id);
59
68
  if (!localProducer) {
60
69
  this.missingProducers.push(remoteProducer.id);
70
+ }
71
+
72
+ if (!localConsumer && !localProducer) {
73
+ this.missingConsumers.push(remoteProducer.id);
74
+ }
75
+
76
+ if (!localProducer) {
61
77
  return;
62
78
  }
63
79
 
@@ -70,7 +86,9 @@ class ChannelStateConsistencyChecker {
70
86
 
71
87
  const checkResults = new ChannelStateConsistencyCheckResult({
72
88
  missingPeers: this.missingPeers,
89
+ missingConsumers: this.missingConsumers,
73
90
  missingProducers: this.missingProducers,
91
+ consumersWithInconsistentState: this.consumersWithInconsistentState,
74
92
  producersWithInconsistentState: this.producersWithInconsistentState,
75
93
  peersWithInconsistentAppData: this.peersWithInconsistentAppData,
76
94
  });
@@ -82,10 +100,21 @@ class ChannelStateConsistencyChecker {
82
100
  return this.missingPeers;
83
101
  }
84
102
 
103
+ /**
104
+ * Contains list of remote producer ids for which local consumer is missing
105
+ */
106
+ getMissingConsumers(): string[] {
107
+ return this.missingConsumers;
108
+ }
109
+
85
110
  getMissingProducers(): string[] {
86
111
  return this.missingProducers;
87
112
  }
88
113
 
114
+ getConsumersWithInconsistentState(): string[] {
115
+ return this.consumersWithInconsistentState;
116
+ }
117
+
89
118
  getProducersWithInconsistentState(): string[] {
90
119
  return this.producersWithInconsistentState;
91
120
  }
@@ -98,13 +127,19 @@ class ChannelStateConsistencyChecker {
98
127
  return this.localState.peers.find((item) => item.id === id);
99
128
  }
100
129
 
130
+ private findLocalConsumerByProducerId(peer: ChannelStatePeer, producerId: string) {
131
+ return peer.consumers?.find((item) => item.producerId === producerId);
132
+ }
133
+
101
134
  private findLocalProducerById(peer: ChannelStatePeer, id: string): ChannelStateProducer | undefined {
102
135
  return peer.producers.find((item) => item.id === id);
103
136
  }
104
137
 
105
138
  private resetPreviousCheckResults(): void {
106
139
  this.missingPeers = [];
140
+ this.missingConsumers = [];
107
141
  this.missingProducers = [];
142
+ this.consumersWithInconsistentState = [];
108
143
  this.producersWithInconsistentState = [];
109
144
  this.peersWithInconsistentAppData = [];
110
145
  }
@@ -13,7 +13,7 @@ import ChannelStateConsistencyCheckResult from './ChannelStateConsistencyCheckRe
13
13
  import EnhancedEventEmitter from '../../../EnhancedEventEmitter';
14
14
  import { ChannelStateSyncEventHandlerParams, ClientObserverEvents } from '../../../types/engine';
15
15
  import { CLIENT_EVENTS } from '../../../constants/events';
16
- import { InconsistenceType } from './types';
16
+ import { InconsistenceType, StorageKey } from './types';
17
17
  import {
18
18
  BAD_NETWORK_SCORE,
19
19
  EXCELLENT_NETWORK_SCORE,
@@ -28,11 +28,13 @@ class ChannelStateSyncEventHandler {
28
28
 
29
29
  #confirmedMissingPeers: string[] = [];
30
30
 
31
+ #confirmedMissingConsumers: string[] = [];
32
+
31
33
  #confirmedMissingProducers: string[] = [];
32
34
 
33
- #confirmedProducersWithInconsistentState: string[] = [];
35
+ #confirmedConsumersWithInconsistentState: string[] = [];
34
36
 
35
- #confirmedPeersWithInconsistentAppData: string[] = [];
37
+ #confirmedProducersWithInconsistentState: string[] = [];
36
38
 
37
39
  #checkResults: ChannelStateConsistencyCheckResult[] = [];
38
40
 
@@ -118,6 +120,21 @@ class ChannelStateSyncEventHandler {
118
120
  type: InconsistenceType.IncorrectProducerState,
119
121
  });
120
122
  });
123
+
124
+ if (confirmedInconsistencyResult.missingConsumers.length > 0) {
125
+ this.#confirmedMissingConsumers.push(...confirmedInconsistencyResult.missingConsumers);
126
+ this.clientEventEmitter.safeEmit(CLIENT_EVENTS.channelStateInconsistent, {
127
+ type: InconsistenceType.MissingConsumers,
128
+ });
129
+ }
130
+
131
+ if (confirmedInconsistencyResult.consumersWithInconsistentState.length > 0) {
132
+ this.#confirmedConsumersWithInconsistentState
133
+ .push(...confirmedInconsistencyResult.consumersWithInconsistentState);
134
+ this.clientEventEmitter.safeEmit(CLIENT_EVENTS.channelStateInconsistent, {
135
+ type: InconsistenceType.IncorrectConsumerState,
136
+ });
137
+ }
121
138
  }
122
139
 
123
140
  private getConfirmedInconsistencyResult(): ChannelStateConsistencyCheckResult | undefined {
@@ -128,7 +145,11 @@ class ChannelStateSyncEventHandler {
128
145
 
129
146
  const confirmedCheckResult = new ChannelStateConsistencyCheckResult({
130
147
  missingPeers: this.getConfirmedMissingPeers(lastCheckResult.missingPeers),
148
+ missingConsumers: this.getConfirmedMissingConsumers(lastCheckResult.missingConsumers),
131
149
  missingProducers: this.getConfirmedMissingProducers(lastCheckResult.missingProducers),
150
+ consumersWithInconsistentState: this.getConfirmedConsumersWithInconsistencState(
151
+ lastCheckResult.consumersWithInconsistentState,
152
+ ),
132
153
  producersWithInconsistentState: this.getConfirmedProducersWithInconsistencState(
133
154
  lastCheckResult.producersWithInconsistentState,
134
155
  ),
@@ -176,8 +197,9 @@ class ChannelStateSyncEventHandler {
176
197
  }
177
198
 
178
199
  private isReachedMaxCheckRetries(
179
- storageKey: 'missingPeers' | 'missingProducers' | 'producersWithInconsistentState' | 'peersWithInconsistentAppData',
200
+ storageKey: StorageKey,
180
201
  id: string,
202
+ maxRetries = MAX_CHECK_RETRIES_BEFORE_CONFIRM,
181
203
  ) {
182
204
  let retries = 0;
183
205
  this.#checkResults.forEach((checkResults) => {
@@ -186,17 +208,33 @@ class ChannelStateSyncEventHandler {
186
208
  }
187
209
  });
188
210
 
189
- return retries >= MAX_CHECK_RETRIES_BEFORE_CONFIRM;
211
+ return retries >= maxRetries;
190
212
  }
191
213
 
192
214
  private getConfirmedMissingPeers(peerIds: string[]): string[] {
193
215
  return peerIds.filter((peerId) => this.isReachedMaxCheckRetries('missingPeers', peerId));
194
216
  }
195
217
 
218
+ private getConfirmedMissingConsumers(consumersIds: string[]): string[] {
219
+ return consumersIds.filter((consumerId) => this.isReachedMaxCheckRetries(
220
+ 'missingConsumers',
221
+ consumerId,
222
+ 1,
223
+ ));
224
+ }
225
+
196
226
  private getConfirmedMissingProducers(producerIds: string[]): string[] {
197
227
  return producerIds.filter((producerId) => this.isReachedMaxCheckRetries('missingProducers', producerId));
198
228
  }
199
229
 
230
+ private getConfirmedConsumersWithInconsistencState(consumersIds: string[]): string[] {
231
+ return consumersIds.filter((consumerId) => this.isReachedMaxCheckRetries(
232
+ 'consumersWithInconsistentState',
233
+ consumerId,
234
+ 1,
235
+ ));
236
+ }
237
+
200
238
  private getConfirmedProducersWithInconsistencState(producerIds: string[]): string[] {
201
239
  return producerIds.filter((producerId) => this.isReachedMaxCheckRetries(
202
240
  'producersWithInconsistentState',
@@ -219,7 +257,9 @@ class ChannelStateSyncEventHandler {
219
257
  private getAllConfirmedInconsistentEntityIds(): string[] {
220
258
  return [
221
259
  ...this.#confirmedMissingPeers,
260
+ ...this.#confirmedMissingConsumers,
222
261
  ...this.#confirmedMissingProducers,
262
+ ...this.#confirmedConsumersWithInconsistentState,
223
263
  ...this.#confirmedProducersWithInconsistentState,
224
264
  ];
225
265
  }
@@ -234,6 +274,7 @@ class ChannelStateSyncEventHandler {
234
274
  id: producer.id,
235
275
  paused: producer.paused,
236
276
  })),
277
+ consumers: peer.getConsumersState(),
237
278
  })),
238
279
  };
239
280
  }
@@ -2,6 +2,15 @@
2
2
 
3
3
  export enum InconsistenceType {
4
4
  MissingPeer = 'missing-peer',
5
+ MissingConsumers = 'missing-consumers',
5
6
  MissingProducer = 'missing-producer',
6
7
  IncorrectProducerState = 'incorrect-producer-state',
8
+ IncorrectConsumerState = 'incorrect-consumer-state',
7
9
  }
10
+
11
+ export type StorageKey = 'missingPeers'
12
+ | 'missingConsumers'
13
+ | 'missingProducers'
14
+ | 'consumersWithInconsistentState'
15
+ | 'producersWithInconsistentState'
16
+ | 'peersWithInconsistentAppData';