@livedigital/client 3.14.0 → 3.14.1

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.
@@ -1,270 +0,0 @@
1
- import { decompressSync, strFromU8 } from 'fflate';
2
- import { serializeError } from 'serialize-error';
3
- import { LogMessageHandler } from '../../types/common';
4
- import Engine from '../index';
5
- import Logger from '../Logger';
6
- import {
7
- ChannelState,
8
- ChannelStateSyncEventPayload,
9
- LastProbablyInconsistencyCheckResults,
10
- ProbablyInconsistencyCheckResult,
11
- } from '../../types/channelStateSyncronizer';
12
-
13
- const MAX_CHECK_RETRIES_BEFORE_CONFIRM = 2;
14
-
15
- class ChannelStateSyncEventHandler {
16
- private readonly engine: Engine;
17
-
18
- private readonly logger: Logger;
19
-
20
- #confirmedMissingPeers: string[] = [];
21
-
22
- #confirmedMissingProducers: string[] = [];
23
-
24
- #confirmedProducersWithInconsistentState: string[] = [];
25
-
26
- #lastProbablyInconsistencyCheckResults: LastProbablyInconsistencyCheckResults = [];
27
-
28
- constructor(params: { engine: Engine, onLogMessage?: LogMessageHandler }) {
29
- const { engine, onLogMessage } = params;
30
- this.engine = engine;
31
- this.logger = new Logger({
32
- logLevel: engine.logLevel,
33
- namespace: 'ChannelStateSyncEventHandler',
34
- onLogMessage,
35
- });
36
- }
37
-
38
- async handle(payload: Buffer): Promise<void> {
39
- try {
40
- const { event, data } = await this.parseChannelStateSyncEventPayload(payload);
41
- if (event === 'channel-state') {
42
- this.handleChannelStateEvent(data);
43
- }
44
- } catch (error: unknown) {
45
- this.logger.error('handleChannelStateSyncEvent()', {
46
- error: serializeError(error),
47
- });
48
- }
49
- }
50
-
51
- private handleChannelStateEvent(remoteState: ChannelState) {
52
- const localState = this.getLocalState();
53
- this.processStateInconsistency(localState, remoteState);
54
- }
55
-
56
- private processStateInconsistency(localState: ChannelState, remoteState: ChannelState) {
57
- this.notifyIfHasConfirmedInconsistency(localState, remoteState);
58
- this.prepareProbableInconsistencyStats(localState, remoteState);
59
- }
60
-
61
- private notifyIfHasConfirmedInconsistency(localState: ChannelState, remoteState: ChannelState) {
62
- remoteState.peers.forEach((remotePeer) => {
63
- const peer = localState.peers.find((p) => p.id === remotePeer.id);
64
- if (!peer) {
65
- this.notifyIfConfirmedPeerInconsistency(remotePeer.id);
66
- return;
67
- }
68
-
69
- this.resetPeerInconsistency(remotePeer.id);
70
-
71
- remotePeer.producers.forEach((remoteProducer) => {
72
- const producer = peer.producers.find((p) => p.id === remoteProducer.id);
73
- if (!producer) {
74
- this.notifyIfConfirmedProducerInconsistency(remoteProducer.id);
75
- return;
76
- }
77
-
78
- this.resetProducerInconsistency(producer.id);
79
-
80
- const isInconsistentState = producer.paused !== remoteProducer.paused;
81
- if (isInconsistentState) {
82
- this.notifyIfConfirmedProducerStateInconsistency(producer.id, {
83
- localPaused: producer.paused,
84
- remotePaused: remoteProducer.paused,
85
- });
86
-
87
- return;
88
- }
89
-
90
- this.resetProducerStateInconsistency(producer.id);
91
- });
92
- });
93
- }
94
-
95
- private prepareProbableInconsistencyStats(localState: ChannelState, remoteState: ChannelState) {
96
- const probablyMissingPeers: string[] = [];
97
- const probablyMissingProducers: string[] = [];
98
- const probablyProducersWithInconsistentState: string[] = [];
99
-
100
- if (!this.getIsConnectionActive()) {
101
- this.#lastProbablyInconsistencyCheckResults = [];
102
- return;
103
- }
104
-
105
- // fill probably missings for the second check
106
- remoteState.peers.forEach((remotePeer) => {
107
- if (this.#confirmedMissingPeers.includes(remotePeer.id)) {
108
- // already alerted about
109
- return;
110
- }
111
-
112
- const localPeer = localState.peers.find((peer) => peer.id === remotePeer.id);
113
- if (!localPeer) {
114
- probablyMissingPeers.push(remotePeer.id);
115
- }
116
-
117
- if (!localPeer) {
118
- return;
119
- }
120
-
121
- remotePeer.producers.forEach((remoteProducer) => {
122
- if (this.#confirmedMissingProducers.includes(remoteProducer.id)) {
123
- // already alerted about
124
- return;
125
- }
126
-
127
- const localProducer = localPeer.producers.find((producer) => producer.id === remoteProducer.id);
128
- if (!localProducer) {
129
- probablyMissingProducers.push(remoteProducer.id);
130
- }
131
-
132
- if (!localProducer) {
133
- return;
134
- }
135
-
136
- if (this.#confirmedProducersWithInconsistentState.includes(remoteProducer.id)) {
137
- // already alerted about
138
- return;
139
- }
140
-
141
- const isInconsistentState = localProducer.paused !== remoteProducer.paused;
142
- if (isInconsistentState) {
143
- probablyProducersWithInconsistentState.push(remoteProducer.id);
144
- }
145
- });
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
- }
237
- }
238
-
239
- private async parseChannelStateSyncEventPayload(data: Blob | Buffer): Promise<ChannelStateSyncEventPayload> {
240
- const arrayData = 'arrayBuffer' in data
241
- ? new Uint8Array(await data.arrayBuffer())
242
- : new Uint8Array(data);
243
- const decompressed = decompressSync(arrayData);
244
- return JSON.parse(strFromU8(decompressed)) as ChannelStateSyncEventPayload;
245
- }
246
-
247
- private getLocalState(): ChannelState {
248
- const { hostPeers } = this.engine;
249
- return {
250
- peers: hostPeers.map((peer) => ({
251
- id: peer.id,
252
- producers: peer.getProducers().map((producer) => ({
253
- id: producer.id,
254
- paused: producer.paused,
255
- })),
256
- })),
257
- };
258
- }
259
-
260
- private getIsConnectionActive() {
261
- try {
262
- this.engine.checkConnectionActivity();
263
- return true;
264
- } catch (error: unknown) {
265
- return false;
266
- }
267
- }
268
- }
269
-
270
- export default ChannelStateSyncEventHandler;