@wazo/sdk 0.38.7 → 0.38.8

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.
Files changed (106) hide show
  1. package/dist/wazo-sdk.js +2 -2
  2. package/esm/__tests__/api-client.test.js +142 -2
  3. package/esm/__tests__/web-rtc-client.test.js +227 -27
  4. package/esm/api/calld.js +8 -0
  5. package/esm/api/confd.js +134 -2
  6. package/esm/api-client.js +142 -2
  7. package/esm/checker/Checker.js +183 -24
  8. package/esm/checker/checks/aor.js +142 -2
  9. package/esm/checker/checks/api.js +142 -2
  10. package/esm/checker/checks/index.js +183 -24
  11. package/esm/checker/checks/wazo-websocket.js +2 -0
  12. package/esm/checker/checks/webrtc-transport.js +181 -24
  13. package/esm/checker/checks/webrtc.js +181 -24
  14. package/esm/domain/AdHocAPIConference.js +142 -2
  15. package/esm/domain/Features.js +142 -2
  16. package/esm/domain/Meeting.js +18 -0
  17. package/esm/domain/MeetingAuthorization.js +70 -0
  18. package/esm/domain/Phone/CTIPhone.js +142 -2
  19. package/esm/domain/__tests__/Meeting.test.js +87 -1
  20. package/esm/domain/__tests__/MeetingAuthorization.test.js +109 -0
  21. package/esm/index.js +226 -27
  22. package/esm/service/CallApi.js +142 -2
  23. package/esm/service/getApiClient.js +142 -2
  24. package/esm/simple/Auth.js +226 -27
  25. package/esm/simple/Configuration.js +226 -27
  26. package/esm/simple/Directory.js +142 -2
  27. package/esm/simple/Phone.js +226 -27
  28. package/esm/simple/Websocket.js +3 -0
  29. package/esm/simple/index.js +226 -27
  30. package/esm/simple/room/LocalParticipant.js +226 -27
  31. package/esm/simple/room/Participant.js +226 -27
  32. package/esm/simple/room/RemoteParticipant.js +226 -27
  33. package/esm/simple/room/Room.js +226 -27
  34. package/esm/simple/room/SipRoom.js +226 -27
  35. package/esm/simple/room/Stream.js +226 -27
  36. package/esm/simple/room/__tests__/Participant.test.js +14747 -0
  37. package/esm/simple/room/__tests__/RemoteParticipant.test.js +226 -27
  38. package/esm/simple/utils.js +226 -27
  39. package/esm/utils/__tests__/object.test.js +57 -0
  40. package/esm/utils/__tests__/string.test.js +30 -0
  41. package/esm/utils/object.js +24 -0
  42. package/esm/utils/string.js +10 -0
  43. package/esm/web-rtc-client.js +181 -24
  44. package/esm/websocket-client.js +3 -1
  45. package/lib/__tests__/api-client.test.js +142 -2
  46. package/lib/__tests__/web-rtc-client.test.js +227 -27
  47. package/lib/api/calld.js +8 -0
  48. package/lib/api/confd.js +134 -2
  49. package/lib/api-client.js +142 -2
  50. package/lib/checker/Checker.js +183 -24
  51. package/lib/checker/checks/aor.js +142 -2
  52. package/lib/checker/checks/api.js +142 -2
  53. package/lib/checker/checks/index.js +183 -24
  54. package/lib/checker/checks/wazo-websocket.js +2 -0
  55. package/lib/checker/checks/webrtc-transport.js +181 -24
  56. package/lib/checker/checks/webrtc.js +181 -24
  57. package/lib/domain/AdHocAPIConference.js +142 -2
  58. package/lib/domain/Features.js +142 -2
  59. package/lib/domain/Meeting.js +18 -0
  60. package/lib/domain/MeetingAuthorization.js +79 -0
  61. package/lib/domain/Phone/CTIPhone.js +142 -2
  62. package/lib/domain/__tests__/Meeting.test.js +87 -1
  63. package/lib/domain/__tests__/MeetingAuthorization.test.js +111 -0
  64. package/lib/index.js +226 -27
  65. package/lib/service/CallApi.js +142 -2
  66. package/lib/service/getApiClient.js +142 -2
  67. package/lib/simple/Auth.js +226 -27
  68. package/lib/simple/Configuration.js +226 -27
  69. package/lib/simple/Directory.js +142 -2
  70. package/lib/simple/Phone.js +226 -27
  71. package/lib/simple/Websocket.js +3 -0
  72. package/lib/simple/index.js +226 -27
  73. package/lib/simple/room/LocalParticipant.js +226 -27
  74. package/lib/simple/room/Participant.js +226 -27
  75. package/lib/simple/room/RemoteParticipant.js +226 -27
  76. package/lib/simple/room/Room.js +226 -27
  77. package/lib/simple/room/SipRoom.js +226 -27
  78. package/lib/simple/room/Stream.js +226 -27
  79. package/lib/simple/room/__tests__/Participant.test.js +14757 -0
  80. package/lib/simple/room/__tests__/RemoteParticipant.test.js +226 -27
  81. package/lib/simple/utils.js +226 -27
  82. package/lib/utils/__tests__/object.test.js +59 -0
  83. package/lib/utils/__tests__/string.test.js +32 -0
  84. package/lib/utils/object.js +28 -0
  85. package/lib/utils/string.js +14 -0
  86. package/lib/web-rtc-client.js +181 -24
  87. package/lib/websocket-client.js +3 -0
  88. package/package.json +2 -2
  89. package/src/__tests__/web-rtc-client.test.js +46 -3
  90. package/src/api/calld.js +8 -0
  91. package/src/api/confd.js +43 -2
  92. package/src/domain/Meeting.js +20 -0
  93. package/src/domain/MeetingAuthorization.js +67 -0
  94. package/src/domain/__tests__/Meeting.test.js +70 -1
  95. package/src/domain/__tests__/MeetingAuthorization.test.js +58 -0
  96. package/src/simple/room/Participant.js +40 -0
  97. package/src/simple/room/Room.js +2 -2
  98. package/src/simple/room/SipRoom.js +1 -1
  99. package/src/simple/room/__tests__/Participant.test.js +81 -0
  100. package/src/types/WebSocketMessage.js +7 -1
  101. package/src/utils/__tests__/object.test.js +35 -0
  102. package/src/utils/__tests__/string.test.js +23 -0
  103. package/src/utils/object.js +15 -0
  104. package/src/utils/string.js +8 -0
  105. package/src/web-rtc-client.js +39 -22
  106. package/src/websocket-client.js +2 -0
@@ -1,9 +1,76 @@
1
1
  // @flow
2
2
  /* global btoa */
3
3
 
4
+ import moment from 'moment';
4
5
  import Meeting from '../Meeting';
5
6
 
7
+ const rawCreationTime = '2021-12-21T15:56:42.880Z';
8
+
6
9
  describe('Meeting domain', () => {
10
+ it('should retain its values when parsed', () => {
11
+ const args = {
12
+ guest_sip_authorization: 'some-guest-sip-authorization',
13
+ ingress_http_uri: 'some-uri',
14
+ name: 'some-name',
15
+ owner_uuids: ['some-owner-uuid'],
16
+ port: 'some-port',
17
+ uuid: 'some-uuid',
18
+ exten: 'some-exten',
19
+ persistent: true,
20
+ creation_time: rawCreationTime,
21
+ require_authorization: true,
22
+ };
23
+
24
+ const meeting = Meeting.parse(args);
25
+ const {
26
+ uri,
27
+ uuid,
28
+ name,
29
+ port,
30
+ extension,
31
+ persistent,
32
+ ownerUuids,
33
+ creationTime,
34
+ requireAuthorization,
35
+ } = meeting;
36
+
37
+ expect(uri).toEqual(args.ingress_http_uri);
38
+ expect(uuid).toEqual(args.uuid);
39
+ expect(name).toEqual(args.name);
40
+ expect(port).toEqual(args.port);
41
+ expect(extension).toEqual(args.exten);
42
+ expect(persistent).toEqual(args.persistent);
43
+ expect(ownerUuids).toEqual(args.owner_uuids);
44
+ expect(creationTime).toEqual(moment(rawCreationTime).toDate());
45
+ expect(requireAuthorization).toEqual(args.require_authorization);
46
+ });
47
+
48
+ it('should retain its values when instantiated', () => {
49
+ const keys = [
50
+ 'uuid',
51
+ 'name',
52
+ 'guestSipAuthorization',
53
+ 'ownerUuids',
54
+ 'port',
55
+ 'uri',
56
+ 'extension',
57
+ 'persistent',
58
+ 'creationTime',
59
+ 'requireAuthorization',
60
+ ];
61
+
62
+ const args = keys.reduce((acc, key) => {
63
+ acc[key] = `some-${key}`;
64
+ return acc;
65
+ }, {});
66
+
67
+ const meeting = new Meeting(args);
68
+
69
+ keys.forEach(key =>
70
+ // $FlowFixMe
71
+ expect(meeting[key]).toEqual(`some-${key}`));
72
+ });
73
+
7
74
  it('can return username and secret from an base64 encrypted string', () => {
8
75
  const USERNAME = 'username';
9
76
  const SECRET = 'secret';
@@ -18,6 +85,7 @@ describe('Meeting domain', () => {
18
85
  uuid: '',
19
86
  exten: '',
20
87
  persistent: false,
88
+ require_authorization: false,
21
89
  };
22
90
 
23
91
  const meeting = Meeting.parse(args);
@@ -30,7 +98,7 @@ describe('Meeting domain', () => {
30
98
 
31
99
  it('should return a date from a string', () => {
32
100
  const args = {
33
- creation_time: '2021-12-21T15:56:42.880Z',
101
+ creation_time: rawCreationTime,
34
102
  guest_sip_authorization: '',
35
103
  ingress_http_uri: '',
36
104
  name: '',
@@ -39,6 +107,7 @@ describe('Meeting domain', () => {
39
107
  uuid: '',
40
108
  exten: '',
41
109
  persistent: false,
110
+ require_authorization: false,
42
111
  };
43
112
 
44
113
  const { creationTime } = Meeting.parse(args);
@@ -0,0 +1,58 @@
1
+ import MeetingAuthorization from '../MeetingAuthorization';
2
+
3
+ describe('MeetingAuthorization', () => {
4
+ describe('parse', () => {
5
+ it('should parse a raw input', () => {
6
+ const rawAuthorization = {
7
+ created_at: 'today',
8
+ meeting_uuid: 'meeting_uuid',
9
+ uuid: 'uuid',
10
+ guest_uuid: 'guest_uuid',
11
+ guest_name: 'guest_name',
12
+ };
13
+
14
+ const authorization = {
15
+ meetingUuid: rawAuthorization.meeting_uuid,
16
+ uuid: rawAuthorization.uuid,
17
+ userUuid: rawAuthorization.guest_uuid,
18
+ userName: rawAuthorization.guest_name,
19
+ status: undefined,
20
+ };
21
+
22
+ const response = MeetingAuthorization.parse(rawAuthorization);
23
+ expect(response).toEqual(authorization);
24
+ });
25
+ });
26
+
27
+ describe('parseMany', () => {
28
+ it('should parse multiple raw inputs', () => {
29
+ const rawAuthorizations = [
30
+ {
31
+ created_at: 'today1',
32
+ meeting_uuid: 'meeting_uuid1',
33
+ uuid: 'uuid1',
34
+ guest_uuid: 'guest_uuid1',
35
+ guest_name: 'guest_name1',
36
+ },
37
+ {
38
+ created_at: 'today2',
39
+ meeting_uuid: 'meeting_uuid2',
40
+ uuid: 'uuid2',
41
+ guest_uuid: 'guest_uuid2',
42
+ guest_name: 'guest_name2',
43
+ },
44
+ ];
45
+
46
+ const authorizations = rawAuthorizations.map(rawAuthorization => ({
47
+ meetingUuid: rawAuthorization.meeting_uuid,
48
+ uuid: rawAuthorization.uuid,
49
+ userUuid: rawAuthorization.guest_uuid,
50
+ userName: rawAuthorization.guest_name,
51
+ status: undefined,
52
+ }));
53
+
54
+ const response = MeetingAuthorization.parseMany(rawAuthorizations);
55
+ expect(response).toEqual(authorizations);
56
+ });
57
+ });
58
+ });
@@ -4,6 +4,7 @@ import IssueReporter from '../../service/IssueReporter';
4
4
 
5
5
  import Room, { SIGNAL_TYPE_PARTICIPANT_UPDATE } from './Room';
6
6
  import Contact from '../../domain/Contact';
7
+ import getApiClient from '../../service/getApiClient';
7
8
 
8
9
  const logger = IssueReporter.loggerFor('room');
9
10
 
@@ -20,6 +21,7 @@ class Participant extends Emitter {
20
21
  videoMuted: boolean;
21
22
  screensharing: boolean;
22
23
  isOnHold: boolean;
24
+ banned: boolean;
23
25
  extra: Object;
24
26
 
25
27
  ON_UPDATED: string;
@@ -37,6 +39,7 @@ class Participant extends Emitter {
37
39
  ON_EXTRA_CHANGE: string;
38
40
  ON_HOLD: string;
39
41
  ON_RESUME: string;
42
+ ON_BAN: string;
40
43
 
41
44
  /**
42
45
  * @param room Room The room where the participant is
@@ -60,6 +63,7 @@ class Participant extends Emitter {
60
63
  this.screensharing = false;
61
64
  this.isOnHold = false;
62
65
  this.extra = extra;
66
+ this.banned = false;
63
67
 
64
68
  this.ON_UPDATED = 'participant/ON_UPDATED';
65
69
  this.ON_START_TALKING = 'participant/ON_START_TALKING';
@@ -76,6 +80,7 @@ class Participant extends Emitter {
76
80
  this.ON_EXTRA_CHANGE = 'participant/ON_EXTRA_CHANGE';
77
81
  this.ON_HOLD = 'participant/ON_HOLD';
78
82
  this.ON_RESUME = 'participant/ON_RESUME';
83
+ this.ON_BAN = 'participant/ON_BAN';
79
84
  }
80
85
 
81
86
  triggerEvent(name: string, ...args: any[]) {
@@ -116,6 +121,10 @@ class Participant extends Emitter {
116
121
  status.extra = this.extra;
117
122
  break;
118
123
  }
124
+ case this.ON_BAN: {
125
+ status.banned = true;
126
+ break;
127
+ }
119
128
  default:
120
129
  break;
121
130
  }
@@ -220,6 +229,11 @@ class Participant extends Emitter {
220
229
  this.triggerUpdate(this.ON_RESUME, broadcast);
221
230
  }
222
231
 
232
+ onBan(broadcast: boolean = true) {
233
+ this.banned = true;
234
+ this.triggerUpdate(this.ON_BAN, broadcast);
235
+ }
236
+
223
237
  getStatus() {
224
238
  return {
225
239
  callId: this.callId,
@@ -266,6 +280,10 @@ class Participant extends Emitter {
266
280
  }
267
281
  }
268
282
 
283
+ if (status.banned) {
284
+ this.onBan(false);
285
+ }
286
+
269
287
  // Poor man's object comparison
270
288
  if (typeof status.extra !== 'undefined' && JSON.stringify(this.extra) !== JSON.stringify(status.extra)) {
271
289
  this.extra = { ...this.extra, ...status.extra };
@@ -293,6 +311,28 @@ class Participant extends Emitter {
293
311
  this.streams = streams;
294
312
  this.videoStreams = streams;
295
313
  }
314
+
315
+ async ban(apiRequestDelay: ?number = null) {
316
+ const { meetingUuid } = this.room;
317
+ if (!meetingUuid) {
318
+ throw new Error('Attempting to ban a participant without a `meetingUuid`');
319
+ }
320
+
321
+ // this notifies all that someone is being banned
322
+ this.onBan(true);
323
+
324
+ // this allows to delay the actual ban, in order for the banned participant as well as others to react to the situation
325
+ if (apiRequestDelay) {
326
+ await this.delay(apiRequestDelay);
327
+ }
328
+
329
+ // proceed with the actual kick
330
+ return getApiClient().calld.banMeetingParticipant(meetingUuid, this.callId);
331
+ }
332
+
333
+ async delay(ms: number) {
334
+ return new Promise(resolve => setTimeout(resolve, ms));
335
+ }
296
336
  }
297
337
 
298
338
  export default Participant;
@@ -143,7 +143,7 @@ class Room extends Emitter {
143
143
  logger.info('connecting to room', { extension, audioOnly, room: !!room });
144
144
 
145
145
  if (!room) {
146
- await Wazo.Phone.connect();
146
+ await Wazo.Phone.connect({ media: constraints });
147
147
 
148
148
  const withCamera = constraints && !!constraints.video;
149
149
 
@@ -590,7 +590,7 @@ class Room extends Emitter {
590
590
  const participant: ?Participant = this._getParticipantFromCallId(status.callId);
591
591
 
592
592
  if (participant) {
593
- // we're received, so no need to broadcast, hence the input false arg
593
+ // we're receiving, so no need to broadcast
594
594
  participant.updateStatus(status, false);
595
595
  }
596
596
  break;
@@ -13,7 +13,7 @@ class SipRoom extends Room {
13
13
  logger.info('connecting to sip room', { extension, audioOnly, room: !!room });
14
14
 
15
15
  if (!room) {
16
- await Wazo.Phone.connect();
16
+ await Wazo.Phone.connect({ media: constraints });
17
17
 
18
18
  const withCamera = constraints && !!constraints.video;
19
19
 
@@ -0,0 +1,81 @@
1
+ // Using directly Wazo to avoid issues with require cycle
2
+ import getApiClient from '../../../service/getApiClient';
3
+ import Wazo from '../../index';
4
+ import Room from '../Room';
5
+
6
+ jest.mock('../../../service/getApiClient');
7
+
8
+ describe('Participant', () => {
9
+ describe('ban', () => {
10
+ it('should throw an error when there is no meeting', async () => {
11
+ const room = new Room();
12
+ const participant = new Wazo.RemoteParticipant(room, {
13
+ call_id: 'id',
14
+ caller_id_name: 'Alice',
15
+ caller_id_number: 'number',
16
+ });
17
+
18
+ await expect(async () => participant.ban())
19
+ .rejects.toThrow('Attempting to ban a participant without a `meetingUuid`');
20
+ });
21
+
22
+ it('should call the api', async () => {
23
+ const number = '1234';
24
+ const meetingUuid = 'some-meeting-uuid';
25
+ const callId = 'some-call-id';
26
+ const room = new Room();
27
+ room.setMeetingUuid(meetingUuid);
28
+
29
+ const banMeetingParticipant = jest.fn();
30
+
31
+ getApiClient.mockImplementation(() => ({
32
+ calld: {
33
+ banMeetingParticipant,
34
+ },
35
+ }));
36
+
37
+ const participant = new Wazo.RemoteParticipant(room, {
38
+ call_id: callId,
39
+ caller_id_name: 'Alice',
40
+ caller_id_number: number,
41
+ });
42
+
43
+ participant.onBan = jest.fn();
44
+
45
+ await participant.ban();
46
+ expect(banMeetingParticipant).toHaveBeenCalledWith(meetingUuid, callId);
47
+ expect(participant.onBan).toHaveBeenCalledWith(true);
48
+ });
49
+
50
+ it('should call the api with a delay', async () => {
51
+ const number = '1234';
52
+ const meetingUuid = 'some-meeting-uuid';
53
+ const callId = 'some-call-id';
54
+ const someDelay = 2;
55
+ const room = new Room();
56
+ room.setMeetingUuid(meetingUuid);
57
+
58
+ const banMeetingParticipant = jest.fn();
59
+
60
+ getApiClient.mockImplementation(() => ({
61
+ calld: {
62
+ banMeetingParticipant,
63
+ },
64
+ }));
65
+
66
+ const participant = new Wazo.RemoteParticipant(room, {
67
+ call_id: callId,
68
+ caller_id_name: 'Alice',
69
+ caller_id_number: number,
70
+ });
71
+
72
+ participant.delay = jest.fn();
73
+ participant.onBan = jest.fn();
74
+
75
+ await participant.ban(someDelay);
76
+ expect(banMeetingParticipant).toHaveBeenCalledWith(meetingUuid, callId);
77
+ expect(participant.delay).toHaveBeenCalledWith(someDelay);
78
+ expect(participant.onBan).toHaveBeenCalledWith(true);
79
+ });
80
+ });
81
+ });
@@ -8,6 +8,7 @@ import type {
8
8
  import type { ChatMessageResponse } from '../domain/ChatMessage';
9
9
  import ChatRoom from '../domain/ChatRoom';
10
10
  import Meeting from '../domain/Meeting';
11
+ import type MeetingAuthorization from '../domain/MeetingAuthorization';
11
12
 
12
13
  type WebSocketBaseMessage = {
13
14
  op: string,
@@ -189,6 +190,10 @@ type AgentPaused = WebSocketBaseMessage & { data: any, name: 'agent_paused' };
189
190
  type AgentUnpaused = WebSocketBaseMessage & { data: any, name: 'agent_unpaused' };
190
191
 
191
192
  type MeetingUserProgress = WebSocketBaseMessage & { data: Meeting, name: 'meeting_user_progress' };
193
+ type MeetingAuthorizationPending = WebSocketBaseMessage & {
194
+ data: MeetingAuthorization,
195
+ name: 'meeting_authorization_pending'
196
+ };
192
197
 
193
198
  export type WebSocketMessage =
194
199
  | CallCreated
@@ -223,4 +228,5 @@ export type WebSocketMessage =
223
228
  | AgentStatusUpdate
224
229
  | AgentPaused
225
230
  | AgentUnpaused
226
- | MeetingUserProgress;
231
+ | MeetingUserProgress
232
+ | MeetingAuthorizationPending;
@@ -0,0 +1,35 @@
1
+ // @flow
2
+ import { convertKeysFromCamelToUnderscore } from '../object';
3
+
4
+ describe('object utils', () => {
5
+ describe('convertKeysFromCamelToUnderscore', () => {
6
+ it('should throw an error when input is not an object', () => {
7
+ const msg = 'Input is not an object';
8
+ expect(() => convertKeysFromCamelToUnderscore(true)).toThrow(msg);
9
+ expect(() => convertKeysFromCamelToUnderscore(null)).toThrow(msg);
10
+ expect(() => convertKeysFromCamelToUnderscore(undefined)).toThrow(msg);
11
+ expect(() => convertKeysFromCamelToUnderscore([])).toThrow(msg);
12
+ expect(() => convertKeysFromCamelToUnderscore('some-string')).toThrow(msg);
13
+ });
14
+
15
+ it('should convert camel-cased keys to underscore', () => {
16
+ let value = Math.random();
17
+ expect(convertKeysFromCamelToUnderscore({ LoremIpsum: value })).toEqual({ lorem_ipsum: value });
18
+ value = Math.random();
19
+ expect(convertKeysFromCamelToUnderscore({ loremIpsum: value })).toEqual({ lorem_ipsum: value });
20
+ value = Math.random();
21
+ expect(convertKeysFromCamelToUnderscore({ 'lorem-ipsum': value })).toEqual({ 'lorem-ipsum': value });
22
+ value = Math.random();
23
+ expect(convertKeysFromCamelToUnderscore({ L123: value })).toEqual({ l123: value });
24
+ });
25
+
26
+ it('should handle multi-leveled object', () => {
27
+ const value = Math.random();
28
+ expect(convertKeysFromCamelToUnderscore({ LoremIpsum: { DoloreSit: { Amet: value }, regular: 'reg' } }))
29
+ .toEqual({ lorem_ipsum: { dolore_sit: { amet: value }, regular: 'reg' } });
30
+
31
+ expect(convertKeysFromCamelToUnderscore({ LoremIpsum: { DoloreSit: { Amet: [value] }, regular: ['something'] } }))
32
+ .toEqual({ lorem_ipsum: { dolore_sit: { amet: [value] }, regular: ['something'] } });
33
+ });
34
+ });
35
+ });
@@ -0,0 +1,23 @@
1
+ import { camelToUnderscore } from '../string';
2
+
3
+ describe('string utils', () => {
4
+ describe('camelToUnderscore', () => {
5
+ it('', () => {
6
+ expect(() => camelToUnderscore(null)).toThrow('Input is not a string');
7
+ expect(() => camelToUnderscore(undefined)).toThrow('Input is not a string');
8
+ expect(() => camelToUnderscore({})).toThrow('Input is not a string');
9
+ expect(() => camelToUnderscore([])).toThrow('Input is not a string');
10
+ });
11
+
12
+ it('should convert camel-cased string to underscore', () => {
13
+ expect(camelToUnderscore('loremIpsum')).toEqual('lorem_ipsum');
14
+ expect(camelToUnderscore('lorem-ipsum')).toEqual('lorem-ipsum');
15
+ expect(camelToUnderscore('')).toEqual('');
16
+ });
17
+
18
+ it('should not prefix string with an underscore', () => {
19
+ expect(camelToUnderscore('LoremIpsum')).toEqual('lorem_ipsum');
20
+ expect(camelToUnderscore('L123')).toEqual('l123');
21
+ });
22
+ });
23
+ });
@@ -0,0 +1,15 @@
1
+ // @flow
2
+
3
+ import { camelToUnderscore } from './string';
4
+
5
+ export const convertKeysFromCamelToUnderscore = (args: Object) => {
6
+ if (!args || typeof args !== 'object' || Array.isArray(args)) {
7
+ throw new Error('Input is not an object');
8
+ }
9
+
10
+ return Object.keys(args).reduce((acc, key) => {
11
+ acc[camelToUnderscore(key)] = typeof args[key] === 'object' && !Array.isArray(args[key])
12
+ ? convertKeysFromCamelToUnderscore(args[key]) : args[key];
13
+ return acc;
14
+ }, {});
15
+ };
@@ -0,0 +1,8 @@
1
+ // @flow
2
+
3
+ export const camelToUnderscore = (key: string) => {
4
+ if (typeof key !== 'string') {
5
+ throw new Error('Input is not a string');
6
+ }
7
+ return key.charAt(0).toLowerCase() + key.substring(1).replace(/([A-Z])/g, '_$1').toLowerCase();
8
+ };
@@ -1070,7 +1070,7 @@ export default class WebRTCClient extends Emitter {
1070
1070
  });
1071
1071
  }
1072
1072
 
1073
- changeAudioInputDevice(id: string, session: ?Inviter, force: ?boolean) {
1073
+ async changeAudioInputDevice(id: string, session: ?Inviter, force: ?boolean) {
1074
1074
  const currentId = this.getAudioDeviceId();
1075
1075
  logger.info('setting audio input device', { id, currentId, session: !!session });
1076
1076
 
@@ -1078,33 +1078,50 @@ export default class WebRTCClient extends Emitter {
1078
1078
  return null;
1079
1079
  }
1080
1080
 
1081
+ // in order to handle an audio track change for default devices
1082
+ // we need the actual id of the device (not 'default')
1083
+ let deviceId = id;
1084
+ if (id === 'default' && navigator.mediaDevices) {
1085
+ const devices = await navigator.mediaDevices.enumerateDevices();
1086
+ const defaultDevice = devices.find(device => device.deviceId === 'default');
1087
+ if (defaultDevice) {
1088
+ const deviceLabel = defaultDevice.label.replace('Default - ', '');
1089
+ const targetDevice = devices.find(device => device.label === deviceLabel);
1090
+ if (targetDevice) {
1091
+ deviceId = targetDevice.deviceId;
1092
+ if (!force && deviceId === currentId) {
1093
+ return null;
1094
+ }
1095
+ }
1096
+ }
1097
+ }
1098
+
1081
1099
  // let's update the local audio value
1082
1100
  if (this.audio) {
1083
1101
  this.audio = {
1084
1102
  deviceId: {
1085
- exact: id,
1103
+ exact: deviceId,
1086
1104
  },
1087
1105
  };
1088
1106
  }
1089
1107
 
1090
- if (session) {
1108
+ if (session && navigator.mediaDevices) {
1091
1109
  const sdh = session.sessionDescriptionHandler;
1092
1110
  const pc = sdh.peerConnection;
1093
1111
 
1094
- // $FlowFixMe
1095
- return navigator.mediaDevices.getUserMedia({ audio: { deviceId: { exact: id } } }).then(async stream => {
1096
- const audioTrack = stream.getAudioTracks()[0];
1097
- const sender = pc && pc.getSenders().find(s => audioTrack && s && s.track && s.track.kind === audioTrack.kind);
1112
+ const constraints = { audio: { deviceId: { exact: deviceId } } };
1113
+ const stream = await navigator.mediaDevices.getUserMedia(constraints);
1114
+ const audioTrack = stream.getAudioTracks()[0];
1115
+ const sender = pc && pc.getSenders().find(s => audioTrack && s && s.track && s.track.kind === audioTrack.kind);
1098
1116
 
1099
- if (sender) {
1100
- if (sender.track) {
1101
- audioTrack.enabled = sender.track.enabled;
1102
- }
1103
- sender.replaceTrack(audioTrack);
1117
+ if (sender) {
1118
+ if (sender.track) {
1119
+ audioTrack.enabled = sender.track.enabled;
1104
1120
  }
1121
+ sender.replaceTrack(audioTrack);
1122
+ }
1105
1123
 
1106
- return stream;
1107
- });
1124
+ return stream;
1108
1125
  }
1109
1126
  }
1110
1127
 
@@ -1124,14 +1141,14 @@ export default class WebRTCClient extends Emitter {
1124
1141
  return null;
1125
1142
  }
1126
1143
 
1127
- // let's update the local video value
1128
- if (this.video) {
1129
- this.video = {
1130
- deviceId: {
1131
- exact: id,
1132
- },
1133
- };
1134
- }
1144
+ // let's make sure we don't lose other video constraints settings -- width, height, frameRate...
1145
+ const videoObject = typeof this.video === 'object' ? this.video : {};
1146
+ this.video = {
1147
+ ...videoObject,
1148
+ deviceId: {
1149
+ exact: id,
1150
+ },
1151
+ };
1135
1152
  }
1136
1153
 
1137
1154
  changeSessionVideoInputDevice(id: ?string, session: Inviter) {
@@ -94,6 +94,7 @@ export const AGENT_UNPAUSED = 'agent_unpaused';
94
94
  export const CONFERENCE_ADHOC_PARTICIPANT_LEFT = 'conference_adhoc_participant_left';
95
95
  export const CONFERENCE_ADHOC_DELETED = 'conference_adhoc_deleted';
96
96
  export const MEETING_USER_PROGRESS = 'meeting_user_progress';
97
+ export const MEETING_USER_GUEST_AUTHORIZATION_CREATED = 'meeting_user_guest_authorization_created';
97
98
 
98
99
  const BLACKLIST_EVENTS = [
99
100
  CHAT_MESSAGE_SENT,
@@ -448,6 +449,7 @@ WebSocketClient.eventLists = [
448
449
  CONFERENCE_ADHOC_PARTICIPANT_LEFT,
449
450
  CONFERENCE_ADHOC_DELETED,
450
451
  MEETING_USER_PROGRESS,
452
+ MEETING_USER_GUEST_AUTHORIZATION_CREATED,
451
453
  ];
452
454
 
453
455
  export default WebSocketClient;