@livedigital/client 3.10.0 → 3.11.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.
@@ -11,3 +11,9 @@ export interface ChannelStateSyncEventPayload {
11
11
  event: 'channel-state';
12
12
  data: ChannelState;
13
13
  }
14
+ export interface ProbablyInconsistencyCheckResult {
15
+ probablyMissingPeers: string[];
16
+ probablyMissingProducers: string[];
17
+ probablyProducersWithInconsistentState: string[];
18
+ }
19
+ export declare type LastProbablyInconsistencyCheckResults = ProbablyInconsistencyCheckResult[];
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@livedigital/client",
3
3
  "author": "vlprojects",
4
4
  "license": "MIT",
5
- "version": "3.10.0",
5
+ "version": "3.11.0",
6
6
  "private": false,
7
7
  "bugs": {
8
8
  "url": "https://github.com/vlprojects/livedigital-sdk/issues"
@@ -3,25 +3,28 @@ import { serializeError } from 'serialize-error';
3
3
  import { LogMessageHandler } from '../../types/common';
4
4
  import Engine from '../index';
5
5
  import Logger from '../Logger';
6
- import { ChannelState, ChannelStateSyncEventPayload } from '../../types/channelStateSyncronizer';
6
+ import {
7
+ ChannelState,
8
+ ChannelStateSyncEventPayload,
9
+ LastProbablyInconsistencyCheckResults,
10
+ ProbablyInconsistencyCheckResult,
11
+ } from '../../types/channelStateSyncronizer';
12
+
13
+ const MAX_CHECK_RETRIES_BEFORE_CONFIRM = 2;
7
14
 
8
15
  class ChannelStateSyncEventHandler {
9
16
  private readonly engine: Engine;
10
17
 
11
18
  private readonly logger: Logger;
12
19
 
13
- #probablyMissingPeers: string[] = [];
14
-
15
20
  #confirmedMissingPeers: string[] = [];
16
21
 
17
- #probablyMissingProducers: string[] = [];
18
-
19
22
  #confirmedMissingProducers: string[] = [];
20
23
 
21
- #probablyProducersWithInconsistentState: string[] = [];
22
-
23
24
  #confirmedProducersWithInconsistentState: string[] = [];
24
25
 
26
+ #lastProbablyInconsistencyCheckResults: LastProbablyInconsistencyCheckResults = [];
27
+
25
28
  constructor(params: { engine: Engine, onLogMessage?: LogMessageHandler }) {
26
29
  const { engine, onLogMessage } = params;
27
30
  this.engine = engine;
@@ -57,70 +60,47 @@ class ChannelStateSyncEventHandler {
57
60
 
58
61
  private notifyIfHasConfirmedInconsistency(localState: ChannelState, remoteState: ChannelState) {
59
62
  remoteState.peers.forEach((remotePeer) => {
60
- const localPeer = localState.peers.find((peer) => peer.id === remotePeer.id);
61
- if (!localPeer && this.#probablyMissingPeers.includes(remotePeer.id)) {
62
- this.#confirmedMissingPeers.push(remotePeer.id);
63
- this.logger.error('Peer is missing', {
64
- case: 'channel-state-sync',
65
- peerId: remotePeer.id,
66
- });
67
- }
68
-
69
- if (!localPeer) {
63
+ const peer = localState.peers.find((p) => p.id === remotePeer.id);
64
+ if (!peer) {
65
+ this.notifyIfConfirmedPeerInconsistency(remotePeer.id);
70
66
  return;
71
67
  }
72
68
 
73
- const confirmedMissingPeerIdx = this.#confirmedMissingPeers.indexOf(remotePeer.id);
74
- if (confirmedMissingPeerIdx > -1) {
75
- delete this.#confirmedMissingPeers[confirmedMissingPeerIdx];
76
- }
69
+ this.resetPeerInconsistency(remotePeer.id);
77
70
 
78
71
  remotePeer.producers.forEach((remoteProducer) => {
79
- const localProducer = localPeer.producers.find((producer) => producer.id === remoteProducer.id);
80
- if (!localProducer && this.#probablyMissingProducers.includes(remoteProducer.id)) {
81
- this.#confirmedMissingProducers.push(remoteProducer.id);
82
- this.logger.error('Producer is missing', {
83
- case: 'channel-state-sync',
84
- producerId: remoteProducer.id,
85
- });
86
- }
87
-
88
- if (!localProducer) {
72
+ const producer = peer.producers.find((p) => p.id === remoteProducer.id);
73
+ if (!producer) {
74
+ this.notifyIfConfirmedProducerInconsistency(remoteProducer.id);
89
75
  return;
90
76
  }
91
77
 
92
- const confirmedMissingProducerIdx = this.#confirmedMissingProducers.indexOf(remoteProducer.id);
93
- if (confirmedMissingProducerIdx > -1) {
94
- delete this.#confirmedMissingProducers[confirmedMissingProducerIdx];
95
- }
78
+ this.resetProducerInconsistency(producer.id);
96
79
 
97
- const isInconsistentState = localProducer.paused !== remoteProducer.paused;
98
- if (isInconsistentState && this.#probablyProducersWithInconsistentState.includes(remoteProducer.id)) {
99
- this.#confirmedProducersWithInconsistentState.push(remoteProducer.id);
100
- this.logger.error('Producer is in inconsistent state', {
101
- case: 'channel-state-sync',
102
- producerId: remoteProducer.id,
103
- localPaused: localProducer.paused,
80
+ const isInconsistentState = producer.paused !== remoteProducer.paused;
81
+ if (isInconsistentState) {
82
+ this.notifyIfConfirmedProducerStateInconsistency(producer.id, {
83
+ localPaused: producer.paused,
104
84
  remotePaused: remoteProducer.paused,
105
85
  });
106
- }
107
-
108
- if (!isInconsistentState) {
109
- const confirmedProducerWithInconsistentStateIdx = this
110
- .#confirmedProducersWithInconsistentState.indexOf(remoteProducer.id);
111
86
 
112
- if (confirmedProducerWithInconsistentStateIdx > -1) {
113
- delete this.#confirmedProducersWithInconsistentState[confirmedProducerWithInconsistentStateIdx];
114
- }
87
+ return;
115
88
  }
89
+
90
+ this.resetProducerStateInconsistency(producer.id);
116
91
  });
117
92
  });
118
93
  }
119
94
 
120
95
  private prepareProbableInconsistencyStats(localState: ChannelState, remoteState: ChannelState) {
121
- this.#probablyMissingPeers = [];
122
- this.#probablyMissingProducers = [];
123
- this.#probablyProducersWithInconsistentState = [];
96
+ const probablyMissingPeers: string[] = [];
97
+ const probablyMissingProducers: string[] = [];
98
+ const probablyProducersWithInconsistentState: string[] = [];
99
+
100
+ if (!this.getIsConnectionActive()) {
101
+ this.#lastProbablyInconsistencyCheckResults = [];
102
+ return;
103
+ }
124
104
 
125
105
  // fill probably missings for the second check
126
106
  remoteState.peers.forEach((remotePeer) => {
@@ -131,7 +111,7 @@ class ChannelStateSyncEventHandler {
131
111
 
132
112
  const localPeer = localState.peers.find((peer) => peer.id === remotePeer.id);
133
113
  if (!localPeer) {
134
- this.#probablyMissingPeers.push(remotePeer.id);
114
+ probablyMissingPeers.push(remotePeer.id);
135
115
  }
136
116
 
137
117
  if (!localPeer) {
@@ -146,7 +126,7 @@ class ChannelStateSyncEventHandler {
146
126
 
147
127
  const localProducer = localPeer.producers.find((producer) => producer.id === remoteProducer.id);
148
128
  if (!localProducer) {
149
- this.#probablyMissingProducers.push(remoteProducer.id);
129
+ probablyMissingProducers.push(remoteProducer.id);
150
130
  }
151
131
 
152
132
  if (!localProducer) {
@@ -160,10 +140,100 @@ class ChannelStateSyncEventHandler {
160
140
 
161
141
  const isInconsistentState = localProducer.paused !== remoteProducer.paused;
162
142
  if (isInconsistentState) {
163
- this.#probablyProducersWithInconsistentState.push(remoteProducer.id);
143
+ probablyProducersWithInconsistentState.push(remoteProducer.id);
164
144
  }
165
145
  });
166
146
  });
147
+
148
+ if (this.#lastProbablyInconsistencyCheckResults.length === MAX_CHECK_RETRIES_BEFORE_CONFIRM) {
149
+ this.#lastProbablyInconsistencyCheckResults.shift();
150
+ }
151
+
152
+ this.#lastProbablyInconsistencyCheckResults.push({
153
+ probablyMissingPeers,
154
+ probablyMissingProducers,
155
+ probablyProducersWithInconsistentState,
156
+ });
157
+ }
158
+
159
+ private isReachedMaxCheckRetries(storageKey: keyof ProbablyInconsistencyCheckResult, id: string) {
160
+ let retries = 0;
161
+ this.#lastProbablyInconsistencyCheckResults.forEach((checkResults) => {
162
+ if (checkResults[storageKey].includes(id)) {
163
+ retries += 1;
164
+ }
165
+ });
166
+
167
+ return retries >= MAX_CHECK_RETRIES_BEFORE_CONFIRM;
168
+ }
169
+
170
+ private isAlreadyAlertedAbout(storage: string[], id: string) {
171
+ return storage.includes(id);
172
+ }
173
+
174
+ notifyIfConfirmedPeerInconsistency(peerId: string): void {
175
+ const isReachedMaxCheckRetries = this.isReachedMaxCheckRetries('probablyMissingPeers', peerId);
176
+ const isAlreadyAlertedAbout = this.isAlreadyAlertedAbout(this.#confirmedMissingPeers, peerId);
177
+ if (isReachedMaxCheckRetries && !isAlreadyAlertedAbout) {
178
+ this.#confirmedMissingPeers.push(peerId);
179
+ this.logger.warn('Peer is missing', {
180
+ case: 'channel-state-sync',
181
+ missingPeerId: peerId,
182
+ });
183
+ }
184
+ }
185
+
186
+ private resetPeerInconsistency(peerId: string) {
187
+ const confirmedMissingPeerIdx = this.#confirmedMissingPeers.indexOf(peerId);
188
+ if (confirmedMissingPeerIdx > -1) {
189
+ // issue resolved
190
+ delete this.#confirmedMissingPeers[confirmedMissingPeerIdx];
191
+ }
192
+ }
193
+
194
+ private notifyIfConfirmedProducerInconsistency(producerId: string) {
195
+ const isReachedMaxCheckRetries = this.isReachedMaxCheckRetries('probablyMissingProducers', producerId);
196
+ const isAlreadyAlertedAbout = this.isAlreadyAlertedAbout(this.#confirmedMissingProducers, producerId);
197
+ if (isReachedMaxCheckRetries && !isAlreadyAlertedAbout) {
198
+ this.#confirmedMissingProducers.push(producerId);
199
+ this.logger.warn('Producer is missing', {
200
+ case: 'channel-state-sync',
201
+ missingProducerId: producerId,
202
+ });
203
+ }
204
+ }
205
+
206
+ private resetProducerInconsistency(producerId: string) {
207
+ const confirmedMissingProducerIdx = this.#confirmedMissingProducers.indexOf(producerId);
208
+ if (confirmedMissingProducerIdx > -1) {
209
+ // issue resolved
210
+ delete this.#confirmedMissingProducers[confirmedMissingProducerIdx];
211
+ }
212
+ }
213
+
214
+ private notifyIfConfirmedProducerStateInconsistency(producerId: string, meta: Record<string, unknown>) {
215
+ const isReachedMaxCheckRetries = this.isReachedMaxCheckRetries(
216
+ 'probablyProducersWithInconsistentState', producerId,
217
+ );
218
+ const isAlreadyAlertedAbout = this.isAlreadyAlertedAbout(this.#confirmedProducersWithInconsistentState, producerId);
219
+ if (isReachedMaxCheckRetries && !isAlreadyAlertedAbout) {
220
+ this.#confirmedProducersWithInconsistentState.push(producerId);
221
+ this.logger.warn('Producer is in inconsistent state', {
222
+ case: 'channel-state-sync',
223
+ producerId,
224
+ ...meta,
225
+ });
226
+ }
227
+ }
228
+
229
+ private resetProducerStateInconsistency(producerId: string) {
230
+ const confirmedProducersWithInconsistentStateIdx = this
231
+ .#confirmedProducersWithInconsistentState.indexOf(producerId);
232
+
233
+ if (confirmedProducersWithInconsistentStateIdx > -1) {
234
+ // issue resolved
235
+ delete this.#confirmedProducersWithInconsistentState[confirmedProducersWithInconsistentStateIdx];
236
+ }
167
237
  }
168
238
 
169
239
  private parseChannelStateSyncEventPayload(data: Buffer): ChannelStateSyncEventPayload {
@@ -183,6 +253,15 @@ class ChannelStateSyncEventHandler {
183
253
  })),
184
254
  };
185
255
  }
256
+
257
+ private getIsConnectionActive() {
258
+ try {
259
+ this.engine.checkConnectionActivity();
260
+ return true;
261
+ } catch (error: unknown) {
262
+ return false;
263
+ }
264
+ }
186
265
  }
187
266
 
188
267
  export default ChannelStateSyncEventHandler;
@@ -272,7 +272,7 @@ class Engine {
272
272
 
273
273
  this.removeClientEventEmitterListeners();
274
274
 
275
- this.network.reset();
275
+ await this.network.reset();
276
276
  this.network.socket.disconnect();
277
277
 
278
278
  this.logger.debug('release()', {
@@ -345,7 +345,7 @@ class Engine {
345
345
  return this.peersRepository.get(id);
346
346
  }
347
347
 
348
- public async confirmActivity(): Promise<void> {
348
+ public async checkConnectionActivity() {
349
349
  if (!this.channel || this.isChannelJoining) {
350
350
  throw new Error('Connect to the channel first');
351
351
  }
@@ -353,7 +353,10 @@ class Engine {
353
353
  if (!this.network.socket.connection?.active) {
354
354
  throw new Error('Socket is inactive');
355
355
  }
356
+ }
356
357
 
358
+ public async confirmActivity(): Promise<void> {
359
+ this.checkConnectionActivity();
357
360
  await this.network.socket.request(CHANNEL_EVENTS.activityConfirm);
358
361
  }
359
362
 
@@ -98,7 +98,11 @@ class Network {
98
98
  }
99
99
  }
100
100
 
101
- public reset() {
101
+ public async reset() {
102
+ await Promise.all([
103
+ this.closeReceiveTransport(),
104
+ this.closeSendTransport(),
105
+ ]);
102
106
  this.stopRestartIceAtempts();
103
107
  this.removeEventListeners();
104
108
  }
@@ -12,3 +12,11 @@ export interface ChannelStateSyncEventPayload {
12
12
  event: 'channel-state';
13
13
  data: ChannelState;
14
14
  }
15
+
16
+ export interface ProbablyInconsistencyCheckResult {
17
+ probablyMissingPeers: string[],
18
+ probablyMissingProducers: string[],
19
+ probablyProducersWithInconsistentState: string[],
20
+ }
21
+
22
+ export type LastProbablyInconsistencyCheckResults = ProbablyInconsistencyCheckResult[];