@livedigital/client 2.22.1 → 2.23.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/Peer.d.ts +2 -1
- package/dist/engine/PeerConsumer.d.ts +2 -1
- package/dist/engine/media/tracks/BaseTrack.d.ts +4 -1
- package/dist/engine/media/tracks/PeerTrack.d.ts +4 -1
- package/dist/engine/wid/detectors/VideoCodecMismatchDetector.d.ts +3 -0
- package/dist/helpers/filterStatsCodecs.d.ts +2 -0
- package/dist/index.es.js +1 -1
- package/dist/index.js +1 -1
- package/dist/types/common.d.ts +53 -0
- package/package.json +1 -1
- package/src/engine/Peer.ts +20 -0
- package/src/engine/PeerConsumer.ts +20 -0
- package/src/engine/media/tracks/BaseTrack.ts +67 -2
- package/src/engine/media/tracks/PeerTrack.ts +39 -1
- package/src/engine/wid/detectors/VideoCodecMismatchDetector.ts +36 -5
- package/src/helpers/filterStatsCodecs.ts +3 -0
- package/src/types/common.ts +58 -0
package/dist/types/common.d.ts
CHANGED
|
@@ -236,6 +236,59 @@ export declare enum DeviceErrors {
|
|
|
236
236
|
DeviceIsBusy = "DeviceIsBusy",
|
|
237
237
|
NotAllowedError = "NotAllowedError"
|
|
238
238
|
}
|
|
239
|
+
export declare type TrackInboundStats = {
|
|
240
|
+
consumerId: string;
|
|
241
|
+
codec?: RTCRtpCodecParameters;
|
|
242
|
+
currentSpatialLayerParams?: SpatialLayerParams;
|
|
243
|
+
availableSpatialLayers?: SpatialLayerParams[];
|
|
244
|
+
requestedSpatialLayer?: number;
|
|
245
|
+
score: number;
|
|
246
|
+
dtlsState?: RTCDtlsTransportState;
|
|
247
|
+
rtcStats?: ExtendedRTCInboundRtpStreamStats;
|
|
248
|
+
};
|
|
249
|
+
export declare type TrackOutboundStats = {
|
|
250
|
+
producerId?: string;
|
|
251
|
+
codec?: RTCRtpCodecParameters;
|
|
252
|
+
dtlsState?: RTCDtlsTransportState;
|
|
253
|
+
rtcStats?: RTCOutboundRtpStreamStats;
|
|
254
|
+
};
|
|
255
|
+
export declare type PeerTrackInfo = {
|
|
256
|
+
trackId: string;
|
|
257
|
+
readyState: MediaStreamTrackState;
|
|
258
|
+
isRemote: boolean;
|
|
259
|
+
kind: MediaKind;
|
|
260
|
+
label: TrackLabel;
|
|
261
|
+
paused: boolean;
|
|
262
|
+
width?: number;
|
|
263
|
+
height?: number;
|
|
264
|
+
frameRate?: number;
|
|
265
|
+
aspectRatio?: number;
|
|
266
|
+
trackInboundStats?: TrackInboundStats;
|
|
267
|
+
trackOutboundStats?: TrackOutboundStats;
|
|
268
|
+
};
|
|
269
|
+
export declare type BaseTrackInfo = {
|
|
270
|
+
trackId: string;
|
|
271
|
+
readyState: MediaStreamTrackState;
|
|
272
|
+
kind: MediaKind;
|
|
273
|
+
label: TrackLabel;
|
|
274
|
+
paused: boolean;
|
|
275
|
+
width?: number;
|
|
276
|
+
height?: number;
|
|
277
|
+
frameRate?: number;
|
|
278
|
+
aspectRatio?: number;
|
|
279
|
+
trackOutboundStats?: TrackOutboundStats;
|
|
280
|
+
};
|
|
281
|
+
export declare type PeerInfo = {
|
|
282
|
+
id: string;
|
|
283
|
+
appData: Record<string, unknown>;
|
|
284
|
+
role: Role;
|
|
285
|
+
channelIds: string[];
|
|
286
|
+
appId: string;
|
|
287
|
+
uid?: string;
|
|
288
|
+
loginDate: Date;
|
|
289
|
+
connectionQuality: number;
|
|
290
|
+
tracks: PeerTrackInfo[];
|
|
291
|
+
};
|
|
239
292
|
export declare type UpdatePeerAppDataPayload = {
|
|
240
293
|
peerId: string;
|
|
241
294
|
appData: Record<string, unknown>;
|
package/package.json
CHANGED
package/src/engine/Peer.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
ChangePreferredLayersPayload,
|
|
9
9
|
Role,
|
|
10
10
|
ProducerSetMaxSpatialLayer,
|
|
11
|
+
PeerInfo,
|
|
11
12
|
} from '../types/common';
|
|
12
13
|
import EnhancedEventEmitter from '../EnhancedEventEmitter';
|
|
13
14
|
import Engine from './index';
|
|
@@ -157,6 +158,25 @@ class Peer {
|
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
160
|
|
|
161
|
+
async getInfo(): Promise<PeerInfo> {
|
|
162
|
+
try {
|
|
163
|
+
return {
|
|
164
|
+
id: this.id,
|
|
165
|
+
appData: this.appData,
|
|
166
|
+
role: this.role,
|
|
167
|
+
channelIds: this.channelIds,
|
|
168
|
+
appId: this.appId,
|
|
169
|
+
uid: this.uid,
|
|
170
|
+
loginDate: this.loginDate,
|
|
171
|
+
connectionQuality: this.overallConnectionQuality,
|
|
172
|
+
tracks: await Promise.all(Array.from(this.tracks.values()).map((track) => track.getInfo())),
|
|
173
|
+
};
|
|
174
|
+
} catch (error) {
|
|
175
|
+
this.logger.error('getInfo()', { peer: this, error });
|
|
176
|
+
throw new Error('Error get info');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
160
180
|
private handleNewProducer(producerData: ProducerData): void {
|
|
161
181
|
if (this.producers.get(producerData.id)) {
|
|
162
182
|
return;
|
|
@@ -6,8 +6,10 @@ import {
|
|
|
6
6
|
ExtendedRTCInboundRtpStreamStats,
|
|
7
7
|
LogLevel,
|
|
8
8
|
SpatialLayerParams,
|
|
9
|
+
TrackInboundStats,
|
|
9
10
|
} from '../types/common';
|
|
10
11
|
import Logger from './Logger';
|
|
12
|
+
import filterStatsCodecs from '../helpers/filterStatsCodecs';
|
|
11
13
|
|
|
12
14
|
export type PeerConsumerConstructorParams = {
|
|
13
15
|
consumer: MediasoupConsumer,
|
|
@@ -167,6 +169,24 @@ class PeerConsumer {
|
|
|
167
169
|
this.currentMaxSpatialLayer = spatialLayer;
|
|
168
170
|
}
|
|
169
171
|
|
|
172
|
+
async getStats(): Promise<TrackInboundStats | undefined> {
|
|
173
|
+
try {
|
|
174
|
+
return {
|
|
175
|
+
consumerId: this.consumer.id,
|
|
176
|
+
codec: this.consumer.rtpParameters.codecs.find((codec) => filterStatsCodecs(codec)),
|
|
177
|
+
score: this.score,
|
|
178
|
+
dtlsState: this.consumer.rtpReceiver?.transport?.state,
|
|
179
|
+
availableSpatialLayers: this.availableSpatialLayers,
|
|
180
|
+
currentSpatialLayerParams: this.currentSpatialLayerParams,
|
|
181
|
+
requestedSpatialLayer: this.requestedSpatialLayer,
|
|
182
|
+
rtcStats: await this.getInboundRTPStreamStats(),
|
|
183
|
+
};
|
|
184
|
+
} catch (error) {
|
|
185
|
+
this.logger.debug('getStats()', { error });
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
170
190
|
async getInboundRTPStreamStats(): Promise<ExtendedRTCInboundRtpStreamStats> {
|
|
171
191
|
return new Promise((resolve, reject) => {
|
|
172
192
|
const getRTCStatsReport = async (attempt = 0) => {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Producer } from 'mediasoup-client/lib/Producer';
|
|
2
2
|
import { MediaKind } from 'mediasoup-client/lib/RtpParameters';
|
|
3
3
|
import {
|
|
4
|
+
BaseTrackInfo,
|
|
4
5
|
EncoderConfig,
|
|
5
6
|
LogLevel,
|
|
6
7
|
SocketResponse,
|
|
7
|
-
TrackLabel,
|
|
8
|
+
TrackLabel, TrackOutboundStats,
|
|
8
9
|
TrackProduceParams,
|
|
9
10
|
} from '../../../types/common';
|
|
10
11
|
import Logger from '../../Logger';
|
|
@@ -14,6 +15,7 @@ import { PRODUCER_CHECK_STATE_TIMEOUT } from '../../../constants/common';
|
|
|
14
15
|
import Timeout = NodeJS.Timeout;
|
|
15
16
|
import { MEDIASOUP_EVENTS, INTERNAL_CLIENT_EVENTS, CLIENT_EVENTS } from '../../../constants/events';
|
|
16
17
|
import EnhancedEventEmitter from '../../../EnhancedEventEmitter';
|
|
18
|
+
import filterStatsCodecs from '../../../helpers/filterStatsCodecs';
|
|
17
19
|
|
|
18
20
|
export type BaseTrackConstructorParams = {
|
|
19
21
|
mediaStreamTrack: MediaStreamTrack,
|
|
@@ -41,7 +43,7 @@ class BaseTrack {
|
|
|
41
43
|
|
|
42
44
|
#producerRestarted = false;
|
|
43
45
|
|
|
44
|
-
#clientEventEmitter: EnhancedEventEmitter;
|
|
46
|
+
readonly #clientEventEmitter: EnhancedEventEmitter;
|
|
45
47
|
|
|
46
48
|
#closed = false;
|
|
47
49
|
|
|
@@ -379,6 +381,69 @@ class BaseTrack {
|
|
|
379
381
|
throw new Error('Can`t resume track');
|
|
380
382
|
}
|
|
381
383
|
}
|
|
384
|
+
|
|
385
|
+
async getInfo(): Promise<BaseTrackInfo> {
|
|
386
|
+
const {
|
|
387
|
+
width, height, frameRate, aspectRatio,
|
|
388
|
+
} = this.#mediaStreamTrack.getSettings();
|
|
389
|
+
return {
|
|
390
|
+
trackId: this.#mediaStreamTrack.id,
|
|
391
|
+
readyState: this.#mediaStreamTrack.readyState,
|
|
392
|
+
kind: this.kind,
|
|
393
|
+
label: this.label,
|
|
394
|
+
width,
|
|
395
|
+
height,
|
|
396
|
+
frameRate,
|
|
397
|
+
aspectRatio,
|
|
398
|
+
paused: this.isPaused,
|
|
399
|
+
trackOutboundStats: await this.getStats(),
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async getStats(): Promise<TrackOutboundStats | undefined> {
|
|
404
|
+
try {
|
|
405
|
+
return {
|
|
406
|
+
producerId: this.producer?.id,
|
|
407
|
+
codec: this.producer?.rtpParameters.codecs.find((codec) => filterStatsCodecs(codec)),
|
|
408
|
+
dtlsState: this.producer?.rtpSender?.transport?.state,
|
|
409
|
+
rtcStats: await this.getOutboundRTPStreamStats(),
|
|
410
|
+
};
|
|
411
|
+
} catch (error) {
|
|
412
|
+
this.logger.debug('getStats()', { error });
|
|
413
|
+
return undefined;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
private getOutboundRTPStreamStats(): Promise<RTCOutboundRtpStreamStats> {
|
|
418
|
+
return new Promise((resolve, reject) => {
|
|
419
|
+
const getRTCStatsReport = async (attempt = 0) => {
|
|
420
|
+
if (!this.producer) {
|
|
421
|
+
throw new Error('Producer is missed');
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
try {
|
|
425
|
+
const rtcStatsReport = await this.producer.getStats();
|
|
426
|
+
const outboundRTPStreamStats: RTCOutboundRtpStreamStats = Array.from(rtcStatsReport.values())
|
|
427
|
+
.find((stat) => stat.type === 'outbound-rtp');
|
|
428
|
+
if (!outboundRTPStreamStats && attempt < 5) {
|
|
429
|
+
setTimeout(() => getRTCStatsReport(attempt + 1), 150);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (attempt >= 5 && !outboundRTPStreamStats) {
|
|
434
|
+
reject(new Error('OutboundRTPStreamStat not exist'));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
resolve(outboundRTPStreamStats);
|
|
438
|
+
} catch (error) {
|
|
439
|
+
reject(new Error('Can not get RTCStatsReport'));
|
|
440
|
+
this.logger.debug('getOutboundRTPStreamStats()', { error });
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
getRTCStatsReport();
|
|
445
|
+
});
|
|
446
|
+
}
|
|
382
447
|
}
|
|
383
448
|
|
|
384
449
|
export default BaseTrack;
|
|
@@ -3,7 +3,8 @@ import {
|
|
|
3
3
|
PreferredLayersParams,
|
|
4
4
|
SetConsumerPriorityParams,
|
|
5
5
|
SpatialLayerParams,
|
|
6
|
-
|
|
6
|
+
PeerTrackInfo,
|
|
7
|
+
TrackLabel, TrackOutboundStats,
|
|
7
8
|
} from '../../../types/common';
|
|
8
9
|
import Logger from '../../Logger';
|
|
9
10
|
import { MEDIASOUP_EVENTS, PEER_EVENTS } from '../../../constants/events';
|
|
@@ -57,6 +58,10 @@ class PeerTrack {
|
|
|
57
58
|
this.#peerEventEmitter.safeEmit(PEER_EVENTS.trackStart, this);
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
get isRemote(): boolean {
|
|
62
|
+
return !!this.consumer;
|
|
63
|
+
}
|
|
64
|
+
|
|
60
65
|
get mediaStreamTrack(): MediaStreamTrack {
|
|
61
66
|
return this.#mediaStreamTrack;
|
|
62
67
|
}
|
|
@@ -238,6 +243,39 @@ class PeerTrack {
|
|
|
238
243
|
return this.consumer.availableSpatialLayers;
|
|
239
244
|
}
|
|
240
245
|
|
|
246
|
+
async getInfo(): Promise<PeerTrackInfo> {
|
|
247
|
+
const {
|
|
248
|
+
width, height, frameRate, aspectRatio,
|
|
249
|
+
} = this.#mediaStreamTrack.getSettings();
|
|
250
|
+
return {
|
|
251
|
+
trackId: this.#mediaStreamTrack.id,
|
|
252
|
+
readyState: this.#mediaStreamTrack.readyState,
|
|
253
|
+
isRemote: this.isRemote,
|
|
254
|
+
kind: this.kind,
|
|
255
|
+
label: this.label,
|
|
256
|
+
width,
|
|
257
|
+
height,
|
|
258
|
+
frameRate,
|
|
259
|
+
aspectRatio,
|
|
260
|
+
paused: this.#paused,
|
|
261
|
+
trackInboundStats: await this.consumer?.getStats(),
|
|
262
|
+
trackOutboundStats: await this.getOutboundStats(),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private async getOutboundStats(): Promise<TrackOutboundStats | undefined> {
|
|
267
|
+
if (this.isRemote) {
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const outboundTrack = this.#engine.media.getAllTracks().find((track) => track.getLabel() === this.label);
|
|
272
|
+
if (!outboundTrack) {
|
|
273
|
+
return undefined;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return outboundTrack.getStats();
|
|
277
|
+
}
|
|
278
|
+
|
|
241
279
|
private async requestMaxSpatialLayer(consumerId: string, spatialLayer: number): Promise<void> {
|
|
242
280
|
try {
|
|
243
281
|
if (!this.consumer) {
|
|
@@ -7,8 +7,14 @@ import {
|
|
|
7
7
|
} from '../types';
|
|
8
8
|
|
|
9
9
|
class VideoCodecMismatchDetector implements IssueDetector {
|
|
10
|
+
readonly UNKNOWN_DECODER = 'unknown';
|
|
11
|
+
|
|
10
12
|
#lastProcessedStats: { [connectionId: string]: WebRTCStatsEventData | undefined } = {};
|
|
11
13
|
|
|
14
|
+
#lastDecoderWithIssue: {
|
|
15
|
+
[connectionId: string]: { [ssrc: string]: string | undefined } | undefined;
|
|
16
|
+
} = {};
|
|
17
|
+
|
|
12
18
|
detect(data: WebRTCStatsEventData): IssueDetectorResult {
|
|
13
19
|
const issues = this.processData(data);
|
|
14
20
|
this.#lastProcessedStats[data.connection.id] = data;
|
|
@@ -17,31 +23,56 @@ class VideoCodecMismatchDetector implements IssueDetector {
|
|
|
17
23
|
|
|
18
24
|
private processData(data: WebRTCStatsEventData): IssueDetectorResult {
|
|
19
25
|
const issues: IssueDetectorResult = [];
|
|
20
|
-
const
|
|
26
|
+
const { id: connectionId } = data.connection;
|
|
27
|
+
const previousInboundRTPVideoStreamsStats = this.#lastProcessedStats[connectionId]?.video.inbound;
|
|
21
28
|
|
|
22
29
|
data.video.inbound.forEach((streamStats) => {
|
|
23
30
|
const { decoderImplementation: currentDecoder, ssrc } = streamStats;
|
|
24
31
|
const prevStats = previousInboundRTPVideoStreamsStats?.find((item) => item.ssrc === ssrc);
|
|
25
32
|
|
|
33
|
+
// skipping the first iteration on purpose
|
|
26
34
|
if (!prevStats) {
|
|
27
35
|
return;
|
|
28
36
|
}
|
|
29
37
|
|
|
30
|
-
|
|
38
|
+
if (currentDecoder !== this.UNKNOWN_DECODER) {
|
|
39
|
+
this.setLastDecoderWithIssue(connectionId, ssrc, undefined);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!this.hadLastDecoderWithIssue(connectionId, ssrc)) {
|
|
44
|
+
this.setLastDecoderWithIssue(connectionId, ssrc, this.UNKNOWN_DECODER);
|
|
31
45
|
|
|
32
|
-
if (currentDecoder === 'unknown' && prevDecoder !== 'unknown') {
|
|
33
46
|
issues.push({
|
|
47
|
+
ssrc,
|
|
34
48
|
type: IssueType.Stream,
|
|
35
49
|
reason: IssueReason.VideoCodecMismatchIssue,
|
|
36
|
-
ssrc: streamStats.ssrc,
|
|
37
50
|
trackIdentifier: streamStats.track.trackIdentifier,
|
|
38
|
-
debug: `mimeType: ${streamStats.mimeType}, decoderImplementation: ${
|
|
51
|
+
debug: `mimeType: ${streamStats.mimeType}, decoderImplementation: ${currentDecoder}`,
|
|
39
52
|
});
|
|
40
53
|
}
|
|
41
54
|
});
|
|
42
55
|
|
|
43
56
|
return issues;
|
|
44
57
|
}
|
|
58
|
+
|
|
59
|
+
private setLastDecoderWithIssue(connectionId: string, ssrc: number, decoder: string | undefined): void {
|
|
60
|
+
const issues = this.#lastDecoderWithIssue[connectionId] ?? {};
|
|
61
|
+
|
|
62
|
+
if (decoder === undefined) {
|
|
63
|
+
delete issues[ssrc];
|
|
64
|
+
} else {
|
|
65
|
+
issues[ssrc] = decoder;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.#lastDecoderWithIssue[connectionId] = issues;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private hadLastDecoderWithIssue(connectionId: string, ssrc: number): boolean {
|
|
72
|
+
const issues = this.#lastDecoderWithIssue[connectionId];
|
|
73
|
+
const decoder = issues && issues[ssrc];
|
|
74
|
+
return decoder === this.UNKNOWN_DECODER;
|
|
75
|
+
}
|
|
45
76
|
}
|
|
46
77
|
|
|
47
78
|
export default VideoCodecMismatchDetector;
|
package/src/types/common.ts
CHANGED
|
@@ -274,6 +274,64 @@ export enum DeviceErrors {
|
|
|
274
274
|
NotAllowedError = 'NotAllowedError',
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
+
export type TrackInboundStats = {
|
|
278
|
+
consumerId: string,
|
|
279
|
+
codec?: RTCRtpCodecParameters,
|
|
280
|
+
currentSpatialLayerParams?: SpatialLayerParams,
|
|
281
|
+
availableSpatialLayers?: SpatialLayerParams[],
|
|
282
|
+
requestedSpatialLayer?: number,
|
|
283
|
+
score: number,
|
|
284
|
+
dtlsState?: RTCDtlsTransportState,
|
|
285
|
+
rtcStats?: ExtendedRTCInboundRtpStreamStats,
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
export type TrackOutboundStats = {
|
|
289
|
+
producerId?: string,
|
|
290
|
+
codec?: RTCRtpCodecParameters,
|
|
291
|
+
dtlsState?: RTCDtlsTransportState,
|
|
292
|
+
rtcStats?: RTCOutboundRtpStreamStats,
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
export type PeerTrackInfo = {
|
|
296
|
+
trackId: string,
|
|
297
|
+
readyState: MediaStreamTrackState,
|
|
298
|
+
isRemote: boolean,
|
|
299
|
+
kind: MediaKind,
|
|
300
|
+
label: TrackLabel,
|
|
301
|
+
paused: boolean,
|
|
302
|
+
width?: number,
|
|
303
|
+
height?: number,
|
|
304
|
+
frameRate?: number,
|
|
305
|
+
aspectRatio?: number,
|
|
306
|
+
trackInboundStats?: TrackInboundStats,
|
|
307
|
+
trackOutboundStats?: TrackOutboundStats,
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
export type BaseTrackInfo = {
|
|
311
|
+
trackId: string,
|
|
312
|
+
readyState: MediaStreamTrackState,
|
|
313
|
+
kind: MediaKind,
|
|
314
|
+
label: TrackLabel,
|
|
315
|
+
paused: boolean,
|
|
316
|
+
width?: number,
|
|
317
|
+
height?: number,
|
|
318
|
+
frameRate?: number,
|
|
319
|
+
aspectRatio?: number,
|
|
320
|
+
trackOutboundStats?: TrackOutboundStats,
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
export type PeerInfo = {
|
|
324
|
+
id: string,
|
|
325
|
+
appData: Record<string, unknown>,
|
|
326
|
+
role: Role,
|
|
327
|
+
channelIds: string[],
|
|
328
|
+
appId: string,
|
|
329
|
+
uid?: string,
|
|
330
|
+
loginDate: Date,
|
|
331
|
+
connectionQuality: number,
|
|
332
|
+
tracks: PeerTrackInfo[],
|
|
333
|
+
};
|
|
334
|
+
|
|
277
335
|
export type UpdatePeerAppDataPayload = {
|
|
278
336
|
peerId: string,
|
|
279
337
|
appData: Record<string, unknown>,
|