@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.
- package/dist/engine/handlers/ChannelStateSyncEventHandler.d.ts +9 -0
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/network/index.d.ts +1 -1
- package/dist/index.es.js +2 -2
- package/dist/index.js +2 -2
- package/dist/types/channelStateSyncronizer.d.ts +6 -0
- package/package.json +1 -1
- package/src/engine/handlers/ChannelStateSyncEventHandler.ts +135 -56
- package/src/engine/index.ts +5 -2
- package/src/engine/network/index.ts +5 -1
- package/src/types/channelStateSyncronizer.ts +8 -0
|
@@ -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
|
@@ -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 {
|
|
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
|
|
61
|
-
if (!
|
|
62
|
-
this
|
|
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
|
-
|
|
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
|
|
80
|
-
if (!
|
|
81
|
-
this
|
|
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
|
-
|
|
93
|
-
if (confirmedMissingProducerIdx > -1) {
|
|
94
|
-
delete this.#confirmedMissingProducers[confirmedMissingProducerIdx];
|
|
95
|
-
}
|
|
78
|
+
this.resetProducerInconsistency(producer.id);
|
|
96
79
|
|
|
97
|
-
const isInconsistentState =
|
|
98
|
-
if (isInconsistentState
|
|
99
|
-
this
|
|
100
|
-
|
|
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
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/src/engine/index.ts
CHANGED
|
@@ -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
|
|
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[];
|