@livedigital/client 3.17.0 → 3.18.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.
@@ -49,6 +49,8 @@ export declare type Role = 'audience' | 'host';
49
49
  export declare type PeerResponse = {
50
50
  id: string;
51
51
  channelIds: string[];
52
+ groups: PeerGroup[];
53
+ producePermissions: TrackLabel[];
52
54
  appId: string;
53
55
  producers: ProducerData[];
54
56
  uid?: string;
@@ -335,3 +337,13 @@ export declare type PeerSyncData = Omit<PeerShortData, 'appData'> & {
335
337
  appData?: string;
336
338
  };
337
339
  export declare type TransportAppData = AppData;
340
+ export declare type PeerGroup = 'moderator' | 'user';
341
+ export declare type ChangePeerProducePermissionPayload = {
342
+ peerId: string;
343
+ producePermissions: TrackLabel[];
344
+ };
345
+ export interface ChannelChangeProducePermissionsPayload {
346
+ groups: PeerGroup[];
347
+ producePermissions: TrackLabel[];
348
+ }
349
+ export declare type TrackLabelString = 'camera' | 'microphone' | 'screen-video' | 'screen-audio';
@@ -78,6 +78,9 @@ export interface ChannelStateInconsistentPayload {
78
78
  peerId?: string;
79
79
  producerId?: string;
80
80
  }
81
+ export interface ProducePermissionsChangedPayload {
82
+ labels: TrackLabel[];
83
+ }
81
84
  export interface EngineDependenciesFactory {
82
85
  createSystem: (params: CreateSystemParams) => System;
83
86
  createMedia: (params: CreateMediaParams) => Media;
@@ -116,6 +119,7 @@ export declare type ClientObserverEvents = {
116
119
  [CLIENT_EVENTS.forcedDisconnect]: [];
117
120
  [CLIENT_EVENTS.rejectUnauthorized]: [];
118
121
  [CLIENT_EVENTS.channelStateInconsistent]: [ChannelStateInconsistentPayload];
122
+ [CLIENT_EVENTS.producePermissionsChanged]: [ProducePermissionsChangedPayload];
119
123
  };
120
124
  export declare type InternalObserverEvents = {
121
125
  [INTERNAL_CLIENT_EVENTS.trackProduced]: [Track | BaseTrack];
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@livedigital/client",
3
3
  "author": "vlprojects",
4
4
  "license": "MIT",
5
- "version": "3.17.0",
5
+ "version": "3.18.0",
6
6
  "private": false,
7
7
  "bugs": {
8
8
  "url": "https://github.com/vlprojects/livedigital-sdk/issues"
@@ -11,6 +11,8 @@ export const CHANNEL_EVENTS = {
11
11
  getAudioObserverProducer: 'channel.getAudioObserverProducer',
12
12
  getGeneralDataProducer: 'channel.getGeneralDataProducer',
13
13
  getChannelStateSyncDataProducer: 'channel.getChannelStateSyncDataProducer',
14
+ changeProducePermissions: 'channel.changeProducePermissions',
15
+ changePeerProducePermissions: 'peer.changeProducePermissions',
14
16
  ping: 'peer.ping',
15
17
  } as const;
16
18
 
@@ -32,6 +34,7 @@ export const CLIENT_EVENTS = {
32
34
  connectionRestored: 'connection-restored',
33
35
  forcedDisconnect: 'forced-disconnect',
34
36
  rejectUnauthorized: 'reject-unauthorized',
37
+ producePermissionsChanged: 'produce-permissions-changed',
35
38
  } as const;
36
39
 
37
40
  export const TRACK_EVENTS = {
@@ -11,19 +11,27 @@ import {
11
11
  PeerInfo,
12
12
  SubscribeOptions,
13
13
  PeerShortData,
14
+ PeerGroup,
15
+ TrackLabel, TrackLabelString,
14
16
  } from '../types/common';
15
17
  import EnhancedEventEmitter from '../EnhancedEventEmitter';
16
18
  import Engine from './index';
17
- import { CHANNEL_EVENTS, MEDIASOUP_EVENTS, PEER_EVENTS } from '../constants/events';
19
+ import {
20
+ CHANNEL_EVENTS, CLIENT_EVENTS, MEDIASOUP_EVENTS, PEER_EVENTS,
21
+ } from '../constants/events';
18
22
  import Logger from './Logger';
19
23
  import PeerProducer from './PeerProducer';
20
24
  import PeerConsumer from './PeerConsumer';
21
25
  import PeerTrack from './media/tracks/PeerTrack';
22
26
  import validateAppData from '../helpers/appDataValidator';
27
+ import { ProducePermissionsChangedPayload } from '../types/engine';
28
+ import { logResponse } from '../helpers/peer';
23
29
 
24
30
  interface PeerConstructor {
25
31
  id: string;
26
32
  channelIds: string[];
33
+ groups: PeerGroup[];
34
+ producePermissions: TrackLabel[];
27
35
  appId: string;
28
36
  producers: ProducerData[],
29
37
  videoConsumer?: PeerConsumer;
@@ -54,6 +62,7 @@ export type PeerObserverEvents = {
54
62
  [PEER_EVENTS.trackResumed]: [PeerTrack];
55
63
  [PEER_EVENTS.trackPaused]: [PeerTrack];
56
64
  [PEER_EVENTS.trackFailed]: [PeerTrack];
65
+ [CLIENT_EVENTS.producePermissionsChanged]: [ProducePermissionsChangedPayload];
57
66
  [MEDIASOUP_EVENTS.producerClose]: [ProducerData];
58
67
  [MEDIASOUP_EVENTS.newProducer]: [ProducerData];
59
68
  [MEDIASOUP_EVENTS.closeConsumer]: [consumerId: string];
@@ -71,6 +80,10 @@ class Peer {
71
80
 
72
81
  public channelIds: string[];
73
82
 
83
+ public groups: PeerGroup[];
84
+
85
+ public producePermissions: TrackLabel[];
86
+
74
87
  public appId: string;
75
88
 
76
89
  public loginDate: Date;
@@ -100,6 +113,8 @@ class Peer {
100
113
  constructor({
101
114
  id,
102
115
  channelIds,
116
+ groups,
117
+ producePermissions,
103
118
  appId,
104
119
  loginDate,
105
120
  producers = [],
@@ -110,6 +125,8 @@ class Peer {
110
125
  }: PeerConstructor) {
111
126
  this.id = id;
112
127
  this.channelIds = channelIds;
128
+ this.groups = groups;
129
+ this.producePermissions = producePermissions;
113
130
  this.appId = appId;
114
131
  this.loginDate = loginDate;
115
132
  this.applicationData = appData || {};
@@ -136,6 +153,14 @@ class Peer {
136
153
  return this.id === this.engine.myPeerId;
137
154
  }
138
155
 
156
+ public get isModerator(): boolean {
157
+ return this.groups.includes('moderator');
158
+ }
159
+
160
+ public get isUser(): boolean {
161
+ return this.groups.includes('user');
162
+ }
163
+
139
164
  public get publishedMedia(): PayloadOfPublishedMedia[] {
140
165
  return Array.from(this.producers.values()).map((producer) => ({
141
166
  producerId: producer.id,
@@ -156,17 +181,27 @@ class Peer {
156
181
  };
157
182
  }
158
183
 
184
+ public hasPermission(producePermission: TrackLabel): boolean {
185
+ return this.producePermissions.includes(producePermission);
186
+ }
187
+
188
+ public hasGroup(group: PeerGroup): boolean {
189
+ return this.groups.includes(group);
190
+ }
191
+
159
192
  public async subscribe({ producerId, muted = false }: SubscribeOptions): Promise<void> {
160
193
  try {
161
194
  const producer = this.producers.get(producerId);
162
195
  if (!producer) {
163
- this.logger.warn('subscribe()', { message: 'Peer does not have a producer', peer: this, producerId });
196
+ this.logger.warn('subscribe()',
197
+ { message: 'Peer does not have a producer', peer: logResponse(this), producerId });
164
198
  return;
165
199
  }
166
200
 
167
201
  const peerConsumer = this.getAllConsumers().find((item) => item.producerId === producerId);
168
202
  if (peerConsumer) {
169
- this.logger.debug('subscribe()', { message: 'Already subscribed to producer', peer: this, producerId });
203
+ this.logger.debug('subscribe()',
204
+ { message: 'Already subscribed to producer', peer: logResponse(this), producerId });
170
205
  return;
171
206
  }
172
207
 
@@ -193,7 +228,7 @@ class Peer {
193
228
  }
194
229
 
195
230
  this.tracks.set(track.label, track);
196
- this.logger.debug(`Subscribed for ${producer.kind}`, { peer: this });
231
+ this.logger.debug(`Subscribed for ${producer.kind}`, { peer: logResponse(this) });
197
232
  } catch (error) {
198
233
  this.logger.error('subscribe()', { producerId, error });
199
234
  throw new Error('Error subscribe media');
@@ -214,13 +249,27 @@ class Peer {
214
249
 
215
250
  await track.close();
216
251
  this.tracks.delete(track.label);
217
- this.logger.debug(`Unsubscribed consumer ${consumer.kind}`, { peer: this });
252
+ this.logger.debug(`Unsubscribed consumer ${consumer.kind}`, { peer: logResponse(this) });
218
253
  } catch (error) {
219
254
  this.logger.error('unsubscribe()', { producerId, error });
220
255
  throw new Error('Error unsubscribe media');
221
256
  }
222
257
  }
223
258
 
259
+ public async changeProducePermissions(producePermissions: TrackLabelString[]): Promise<void> {
260
+ const isIamModerator = this.engine.peersRepository.get(this.engine.myPeerId ?? '')?.isModerator;
261
+ if (!isIamModerator) {
262
+ throw new Error('Not enough access');
263
+ }
264
+
265
+ await this.engine.network.socket.request(CHANNEL_EVENTS.changePeerProducePermissions, {
266
+ peerId: this.id,
267
+ producePermissions,
268
+ });
269
+
270
+ this.logger.debug('Produce permissions changed', { peerId: this.id, producePermissions });
271
+ }
272
+
224
273
  async getInfo(): Promise<PeerInfo> {
225
274
  try {
226
275
  return {
@@ -235,7 +284,7 @@ class Peer {
235
284
  tracks: await Promise.all(Array.from(this.tracks.values()).map((track) => track.getInfo())),
236
285
  };
237
286
  } catch (error) {
238
- this.logger.error('getInfo()', { peer: this, error });
287
+ this.logger.error('getInfo()', { peer: logResponse(this), error });
239
288
  throw new Error('Error get info');
240
289
  }
241
290
  }
@@ -251,7 +300,7 @@ class Peer {
251
300
 
252
301
  const producer = new PeerProducer(producerData);
253
302
  this.producers.set(producer.id, producer);
254
- this.logger.debug('Peer published media:', { peer: this, producer });
303
+ this.logger.debug('Peer published media:', { peer: logResponse(this), producer });
255
304
  this.observer.safeEmit(PEER_EVENTS.mediaPublished, {
256
305
  producerId: producer.id,
257
306
  kind: producer.kind,
@@ -265,7 +314,7 @@ class Peer {
265
314
 
266
315
  this.observer.on(MEDIASOUP_EVENTS.producerClose, async (producer: ProducerData) => {
267
316
  this.producers.delete(producer.id);
268
- this.logger.debug('Peer unpublished media:', { peer: this, producer });
317
+ this.logger.debug('Peer unpublished media:', { peer: logResponse(this), producer });
269
318
  this.observer.safeEmit(PEER_EVENTS.mediaUnPublished, {
270
319
  producerId: producer.id,
271
320
  kind: producer.kind,
@@ -296,7 +345,7 @@ class Peer {
296
345
 
297
346
  await track.close();
298
347
  this.tracks.delete(track.label);
299
- this.logger.debug('Peer video track was ended:', { peer: this, track });
348
+ this.logger.debug('Peer video track was ended:', { peer: logResponse(this), track });
300
349
  });
301
350
 
302
351
  this.observer.on(MEDIASOUP_EVENTS.pauseConsumer, async (consumerId) => {
@@ -464,9 +513,9 @@ class Peer {
464
513
  validateAppData(appData);
465
514
  this.setAppData(appData);
466
515
  this.observer.emit(PEER_EVENTS.appDataUpdated, appData);
467
- this.logger.debug('updatePeerAppData', { peer: this, appData });
516
+ this.logger.debug('updatePeerAppData', { peer: logResponse(this), appData });
468
517
  } catch (error) {
469
- this.logger.error('updatePeerAppData', { peer: this, appData, error });
518
+ this.logger.error('updatePeerAppData', { peer: logResponse(this), appData, error });
470
519
  }
471
520
  });
472
521
  }
@@ -521,7 +570,7 @@ class Peer {
521
570
  });
522
571
 
523
572
  this.overallConnectionQuality = connectionQuality;
524
- this.logger.debug('emitConnectionQuality()', { peer: this, connectionQuality });
573
+ this.logger.debug('emitConnectionQuality()', { peer: logResponse(this), connectionQuality });
525
574
  }, 1000);
526
575
  }
527
576
 
@@ -1,5 +1,5 @@
1
1
  import {
2
- ActivityConfirmationRequiredPayload,
2
+ ActivityConfirmationRequiredPayload, ChangePeerProducePermissionPayload, ChannelChangeProducePermissionsPayload,
3
3
  ChannelEvent,
4
4
  LogMessageHandler,
5
5
  PeerResponse,
@@ -78,6 +78,44 @@ class ChannelEventHandler {
78
78
  this.engine.clientEventEmitter.safeEmit(CLIENT_EVENTS.activityConfirmationAcquired, event);
79
79
  this.logger.info('Notify the channel that the activity has been acquired.', event);
80
80
  });
81
+
82
+ connection.on(CHANNEL_EVENTS.changeProducePermissions, (payload: ChannelChangeProducePermissionsPayload) => {
83
+ const { producePermissions, groups } = payload;
84
+ const peers = this.engine.peers.filter((peer) => peer.groups.some((group) => groups.includes(group)));
85
+ peers.forEach((peer) => {
86
+ // eslint-disable-next-line no-param-reassign
87
+ peer.producePermissions = producePermissions;
88
+
89
+ if (!peer.isMe) {
90
+ return;
91
+ }
92
+
93
+ this.engine.clientEventEmitter.safeEmit(CLIENT_EVENTS.producePermissionsChanged, {
94
+ labels: producePermissions,
95
+ });
96
+ });
97
+
98
+ this.logger.info('Peers changed produce permissions', { groups, producePermissions });
99
+ });
100
+
101
+ connection.on(CHANNEL_EVENTS.changePeerProducePermissions, (payload: ChangePeerProducePermissionPayload) => {
102
+ const { peerId, producePermissions } = payload;
103
+ const peer = this.engine.peersRepository.get(peerId);
104
+
105
+ if (!peer) {
106
+ return;
107
+ }
108
+
109
+ peer.producePermissions = producePermissions;
110
+
111
+ if (!peer.isMe) {
112
+ return;
113
+ }
114
+
115
+ this.engine.clientEventEmitter.safeEmit(CLIENT_EVENTS.producePermissionsChanged, {
116
+ labels: producePermissions,
117
+ });
118
+ });
81
119
  }
82
120
 
83
121
  private removeEventListeners() {
@@ -95,6 +133,7 @@ class ChannelEventHandler {
95
133
  CHANNEL_EVENTS.activityConfirmationExpired,
96
134
  CHANNEL_EVENTS.activityConfirmationAcquired,
97
135
  CHANNEL_EVENTS.updatePeerAppData,
136
+ CHANNEL_EVENTS.changeProducePermissions,
98
137
  ];
99
138
 
100
139
  eventNames.forEach((x) => connection.removeAllListeners(x));
@@ -0,0 +1,20 @@
1
+ import Peer from '../engine/Peer';
2
+
3
+ type PeerLogResponse = {
4
+ id: Peer['id'];
5
+ appId: Peer['appId'];
6
+ role: Peer['role'];
7
+ groups: Peer['groups'];
8
+ producePermissions: Peer['producePermissions'];
9
+ };
10
+
11
+ // eslint-disable-next-line import/prefer-default-export
12
+ export function logResponse(peer: Peer): PeerLogResponse {
13
+ return {
14
+ id: peer.id,
15
+ appId: peer.appId,
16
+ role: peer.role,
17
+ groups: peer.groups,
18
+ producePermissions: peer.producePermissions,
19
+ };
20
+ }
@@ -67,6 +67,8 @@ export type Role = 'audience' | 'host';
67
67
  export type PeerResponse = {
68
68
  id: string,
69
69
  channelIds: string[],
70
+ groups: PeerGroup[],
71
+ producePermissions: TrackLabel[],
70
72
  appId: string,
71
73
  producers: ProducerData[],
72
74
  uid?: string,
@@ -411,3 +413,17 @@ export type PeerSyncData = Omit<PeerShortData, 'appData'> & {
411
413
  };
412
414
 
413
415
  export type TransportAppData = AppData;
416
+
417
+ export type PeerGroup = 'moderator' | 'user';
418
+
419
+ export type ChangePeerProducePermissionPayload = {
420
+ peerId: string;
421
+ producePermissions: TrackLabel[];
422
+ };
423
+
424
+ export interface ChannelChangeProducePermissionsPayload {
425
+ groups: PeerGroup[];
426
+ producePermissions: TrackLabel[];
427
+ }
428
+
429
+ export type TrackLabelString = 'camera' | 'microphone' | 'screen-video' | 'screen-audio';
@@ -107,6 +107,10 @@ export interface ChannelStateInconsistentPayload {
107
107
  producerId?: string;
108
108
  }
109
109
 
110
+ export interface ProducePermissionsChangedPayload {
111
+ labels: TrackLabel[];
112
+ }
113
+
110
114
  export interface EngineDependenciesFactory {
111
115
  createSystem: (params: CreateSystemParams) => System;
112
116
  createMedia: (params: CreateMediaParams) => Media;
@@ -137,6 +141,7 @@ export type ClientObserverEvents = {
137
141
  [CLIENT_EVENTS.forcedDisconnect]:[];
138
142
  [CLIENT_EVENTS.rejectUnauthorized]:[];
139
143
  [CLIENT_EVENTS.channelStateInconsistent]:[ChannelStateInconsistentPayload];
144
+ [CLIENT_EVENTS.producePermissionsChanged]:[ProducePermissionsChangedPayload];
140
145
  };
141
146
 
142
147
  export type InternalObserverEvents = {