@wazo/sdk 0.39.0-beta.9 → 0.39.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/README.md +7 -7
- package/dist/wazo-sdk.js +12 -12
- package/esm/__tests__/api-client.test.js +7 -1
- package/esm/__tests__/web-rtc-client.test.js +10 -0
- package/esm/api/dird.js +2 -2
- package/esm/api-client.js +4 -0
- package/esm/checker/checks/ice-ipv4.js +2 -1
- package/esm/domain/Contact.js +15 -8
- package/esm/domain/Phone/WebRTCPhone.js +9 -11
- package/esm/domain/Session.js +24 -0
- package/esm/domain/__tests__/Contact.test.js +200 -48
- package/esm/lib/WazoSessionDescriptionHandler.js +14 -11
- package/esm/service/IssueReporter.js +12 -2
- package/esm/service/__tests__/IssueReporter.test.js +12 -0
- package/esm/service/getApiClient.js +9 -0
- package/esm/simple/Auth.js +40 -5
- package/esm/simple/Phone.js +3 -1
- package/esm/simple/Websocket.js +1 -0
- package/esm/simple/room/Stream.js +3 -3
- package/esm/types/api/dird.d.ts.map +1 -1
- package/esm/types/api-client.d.ts +3 -2
- package/esm/types/api-client.d.ts.map +1 -1
- package/esm/types/checker/checks/ice-ipv4.d.ts.map +1 -1
- package/esm/types/domain/Contact.d.ts +1 -1
- package/esm/types/domain/Contact.d.ts.map +1 -1
- package/esm/types/domain/Phone/WebRTCPhone.d.ts +1 -1
- package/esm/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
- package/esm/types/domain/Session.d.ts +2 -0
- package/esm/types/domain/Session.d.ts.map +1 -1
- package/esm/types/domain/types.d.ts +40 -14
- package/esm/types/domain/types.d.ts.map +1 -1
- package/esm/types/index.d.ts +2 -0
- package/esm/types/index.d.ts.map +1 -1
- package/esm/types/lib/WazoSessionDescriptionHandler.d.ts +1 -1
- package/esm/types/lib/WazoSessionDescriptionHandler.d.ts.map +1 -1
- package/esm/types/service/IssueReporter.d.ts +1 -0
- package/esm/types/service/IssueReporter.d.ts.map +1 -1
- package/esm/types/service/getApiClient.d.ts +2 -0
- package/esm/types/service/getApiClient.d.ts.map +1 -1
- package/esm/types/simple/Auth.d.ts.map +1 -1
- package/esm/types/simple/Phone.d.ts +4 -3
- package/esm/types/simple/Phone.d.ts.map +1 -1
- package/esm/types/simple/Websocket.d.ts.map +1 -1
- package/esm/types/simple/index.d.ts +1 -0
- package/esm/types/simple/index.d.ts.map +1 -1
- package/esm/types/utils/Emitter.d.ts +1 -1
- package/esm/types/utils/Heartbeat.d.ts +3 -2
- package/esm/types/utils/Heartbeat.d.ts.map +1 -1
- package/esm/types/utils/api-requester.d.ts +5 -1
- package/esm/types/utils/api-requester.d.ts.map +1 -1
- package/esm/types/web-rtc-client.d.ts +5 -29
- package/esm/types/web-rtc-client.d.ts.map +1 -1
- package/esm/types/websocket-client.d.ts +5 -1
- package/esm/types/websocket-client.d.ts.map +1 -1
- package/esm/utils/__tests__/api-requester.test.js +7 -2
- package/esm/utils/api-requester.js +23 -5
- package/esm/web-rtc-client.js +69 -40
- package/esm/websocket-client.js +9 -2
- package/lib/__tests__/api-client.test.js +7 -1
- package/lib/__tests__/web-rtc-client.test.js +16 -0
- package/lib/api/dird.js +2 -2
- package/lib/api-client.js +4 -0
- package/lib/checker/checks/ice-ipv4.js +2 -1
- package/lib/domain/Contact.js +15 -8
- package/lib/domain/Phone/WebRTCPhone.js +9 -11
- package/lib/domain/Session.js +23 -0
- package/lib/domain/__tests__/Contact.test.js +175 -50
- package/lib/lib/WazoSessionDescriptionHandler.js +15 -12
- package/lib/service/IssueReporter.js +12 -2
- package/lib/service/__tests__/IssueReporter.test.js +12 -0
- package/lib/service/getApiClient.js +12 -1
- package/lib/simple/Auth.js +34 -4
- package/lib/simple/Phone.js +3 -1
- package/lib/simple/Websocket.js +1 -0
- package/lib/simple/room/Stream.js +3 -3
- package/lib/types/api/dird.d.ts.map +1 -1
- package/lib/types/api-client.d.ts +3 -2
- package/lib/types/api-client.d.ts.map +1 -1
- package/lib/types/checker/checks/ice-ipv4.d.ts.map +1 -1
- package/lib/types/domain/Contact.d.ts +1 -1
- package/lib/types/domain/Contact.d.ts.map +1 -1
- package/lib/types/domain/Phone/WebRTCPhone.d.ts +1 -1
- package/lib/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
- package/lib/types/domain/Session.d.ts +2 -0
- package/lib/types/domain/Session.d.ts.map +1 -1
- package/lib/types/domain/types.d.ts +40 -14
- package/lib/types/domain/types.d.ts.map +1 -1
- package/lib/types/index.d.ts +2 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/lib/WazoSessionDescriptionHandler.d.ts +1 -1
- package/lib/types/lib/WazoSessionDescriptionHandler.d.ts.map +1 -1
- package/lib/types/service/IssueReporter.d.ts +1 -0
- package/lib/types/service/IssueReporter.d.ts.map +1 -1
- package/lib/types/service/getApiClient.d.ts +2 -0
- package/lib/types/service/getApiClient.d.ts.map +1 -1
- package/lib/types/simple/Auth.d.ts.map +1 -1
- package/lib/types/simple/Phone.d.ts +4 -3
- package/lib/types/simple/Phone.d.ts.map +1 -1
- package/lib/types/simple/Websocket.d.ts.map +1 -1
- package/lib/types/simple/index.d.ts +1 -0
- package/lib/types/simple/index.d.ts.map +1 -1
- package/lib/types/utils/Emitter.d.ts +1 -1
- package/lib/types/utils/Heartbeat.d.ts +3 -2
- package/lib/types/utils/Heartbeat.d.ts.map +1 -1
- package/lib/types/utils/api-requester.d.ts +5 -1
- package/lib/types/utils/api-requester.d.ts.map +1 -1
- package/lib/types/web-rtc-client.d.ts +5 -29
- package/lib/types/web-rtc-client.d.ts.map +1 -1
- package/lib/types/websocket-client.d.ts +5 -1
- package/lib/types/websocket-client.d.ts.map +1 -1
- package/lib/utils/__tests__/api-requester.test.js +7 -2
- package/lib/utils/api-requester.js +19 -5
- package/lib/web-rtc-client.js +67 -39
- package/lib/websocket-client.js +8 -2
- package/package.json +37 -35
- package/src/__tests__/api-client.test.ts +9 -1
- package/src/__tests__/web-rtc-client.test.ts +12 -0
- package/src/api/dird.ts +3 -2
- package/src/api-client.ts +8 -2
- package/src/checker/checks/ice-ipv4.ts +2 -1
- package/src/domain/Contact.ts +16 -8
- package/src/domain/Phone/WebRTCPhone.ts +11 -12
- package/src/domain/Session.ts +28 -0
- package/src/domain/__tests__/Contact.test.ts +213 -54
- package/src/domain/types.ts +42 -14
- package/src/lib/WazoSessionDescriptionHandler.ts +15 -11
- package/src/service/IssueReporter.ts +13 -2
- package/src/service/__tests__/IssueReporter.test.ts +14 -0
- package/src/service/getApiClient.ts +12 -0
- package/src/simple/Auth.ts +58 -6
- package/src/simple/Phone.ts +15 -6
- package/src/simple/Websocket.ts +1 -0
- package/src/simple/room/Stream.ts +3 -3
- package/src/types/global.d.ts +1 -0
- package/src/utils/Heartbeat.ts +2 -2
- package/src/utils/__tests__/api-requester.test.ts +8 -2
- package/src/utils/api-requester.ts +31 -4
- package/src/web-rtc-client.ts +84 -70
- package/src/websocket-client.ts +14 -1
package/src/api/dird.ts
CHANGED
|
@@ -5,6 +5,7 @@ import Contact from '../domain/Contact';
|
|
|
5
5
|
import type { NewContact } from '../domain/Contact';
|
|
6
6
|
import type { DirectorySource, DirectorySources } from '../domain/DirectorySource';
|
|
7
7
|
import type { Sources } from '../index';
|
|
8
|
+
import { ContactsResponse } from '../index';
|
|
8
9
|
|
|
9
10
|
const getContactPayload = (contact: NewContact | Contact) => ({
|
|
10
11
|
email: contact.email,
|
|
@@ -51,9 +52,9 @@ type ContactSearchQueryParams = {
|
|
|
51
52
|
uuid?: string;
|
|
52
53
|
} | null;
|
|
53
54
|
export default ((client: ApiRequester, baseUrl: string): DirD => ({
|
|
54
|
-
search: (context: string, term: string): Promise<Array<Contact>> => client.get(`${baseUrl}/directories/lookup/${context}`, {
|
|
55
|
+
search: (context: string, term: string, offset = 0, limit: (number | null) = null): Promise<Array<Contact>> => client.get(`${baseUrl}/directories/lookup/${context}`, {
|
|
55
56
|
term,
|
|
56
|
-
}).then(Contact.parseMany),
|
|
57
|
+
}).then((response: ContactsResponse) => Contact.parseMany(response, offset, limit)),
|
|
57
58
|
listPersonalContacts: (queryParams: ContactSearchQueryParams = null): Promise<Array<Contact>> => client.get(`${baseUrl}/personal`, queryParams).then((response: any) => Contact.parseManyPersonal(response.items)),
|
|
58
59
|
fetchPersonalContact: (contactUuid: string): Promise<Contact> => client.get(`${baseUrl}/personal/${contactUuid}`).then(Contact.parsePersonal),
|
|
59
60
|
addContact: (contact: NewContact): Promise<Contact> => client.post(`${baseUrl}/personal`, getContactPayload(contact)).then(Contact.parsePersonal),
|
package/src/api-client.ts
CHANGED
|
@@ -125,12 +125,12 @@ export default class ApiClient {
|
|
|
125
125
|
this.initializeEndpoints();
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
async forceRefreshToken() {
|
|
128
|
+
async forceRefreshToken(): Promise<string | null> {
|
|
129
129
|
logger.info('forcing refresh token, calling callback');
|
|
130
130
|
return this.refreshTokenCallback();
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
async refreshTokenCallback() {
|
|
133
|
+
async refreshTokenCallback(): Promise<string | null> {
|
|
134
134
|
logger.info('refresh token callback called', {
|
|
135
135
|
refreshToken: this.refreshToken,
|
|
136
136
|
refreshBackend: this.refreshBackend,
|
|
@@ -167,6 +167,8 @@ export default class ApiClient {
|
|
|
167
167
|
this.onRefreshTokenError(error);
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
|
+
|
|
171
|
+
return null;
|
|
170
172
|
}
|
|
171
173
|
|
|
172
174
|
setToken(token: string) {
|
|
@@ -181,6 +183,10 @@ export default class ApiClient {
|
|
|
181
183
|
this.refreshToken = refreshToken;
|
|
182
184
|
}
|
|
183
185
|
|
|
186
|
+
setRequestTimeout(requestTimeout: number) {
|
|
187
|
+
this.client.setRequestTimeout(requestTimeout);
|
|
188
|
+
}
|
|
189
|
+
|
|
184
190
|
setClientId(clientId: string | null | undefined) {
|
|
185
191
|
this.client.clientId = clientId;
|
|
186
192
|
}
|
|
@@ -16,7 +16,8 @@ export default {
|
|
|
16
16
|
name: 'Non IP v4 ice',
|
|
17
17
|
check: (server: string, session: any, externalAppConfig: Record<string, any>) => new Promise((resolve, reject) => {
|
|
18
18
|
if (typeof MediaStream === 'undefined') {
|
|
19
|
-
|
|
19
|
+
resolve('Skipped on node');
|
|
20
|
+
return;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
const mobile = isMobile();
|
package/src/domain/Contact.ts
CHANGED
|
@@ -326,12 +326,13 @@ export default class Contact {
|
|
|
326
326
|
return aLastName.localeCompare(bLastName);
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
-
static parseMany(response: ContactsResponse): Array<Contact> {
|
|
330
|
-
if (!response || !response.results) {
|
|
329
|
+
static parseMany(response: ContactsResponse, offset = 0, limit: (number | null) = null): Array<Contact> {
|
|
330
|
+
if (!response || !response.results || limit === 0) {
|
|
331
331
|
return [];
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
-
|
|
334
|
+
const results = limit !== null && limit > 0 ? response.results.slice(offset, limit) : offset > 0 ? response.results.slice(offset) : response.results;
|
|
335
|
+
return results.map(r => Contact.parse(r, response.column_types));
|
|
335
336
|
}
|
|
336
337
|
|
|
337
338
|
static manyGraphQlWithNumbersParser(numbers: string[]): (...args: Array<any>) => any {
|
|
@@ -444,13 +445,20 @@ export default class Contact {
|
|
|
444
445
|
address = `${postalAddress.street} ${postalAddress.city} ${postalAddress.postCode} ${postalAddress.country}`;
|
|
445
446
|
}
|
|
446
447
|
|
|
448
|
+
const firstName = plain.givenName || '';
|
|
449
|
+
const lastName = plain.familyName || '';
|
|
450
|
+
const companyName = plain.company || '';
|
|
451
|
+
const isCompanyAccount = !firstName && !lastName;
|
|
452
|
+
const name = isCompanyAccount ? companyName : `${firstName} ${lastName}`;
|
|
453
|
+
|
|
447
454
|
return new Contact({
|
|
448
|
-
name
|
|
455
|
+
name,
|
|
449
456
|
number: plain.phoneNumbers.length ? plain.phoneNumbers[0].number : '',
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
457
|
+
// Make numbers unique
|
|
458
|
+
numbers: [...new Map(plain.phoneNumbers.map(item => [item.number, item])).values()].map((item, idx) => ({
|
|
459
|
+
label: idx === 0 ? 'primary' : 'secondary',
|
|
460
|
+
number: item.number,
|
|
461
|
+
})),
|
|
454
462
|
email: plain.emailAddresses.length ? plain.emailAddresses[0].email : '',
|
|
455
463
|
emails: plain.emailAddresses.length ? [{
|
|
456
464
|
label: 'primary',
|
|
@@ -382,8 +382,6 @@ export default class WebRTCPhone extends Emitter implements Phone {
|
|
|
382
382
|
delete this.callSessions[sipSessionId];
|
|
383
383
|
this.eventEmitter.emit(ON_CALL_CANCELED, callSession, elsewhere);
|
|
384
384
|
};
|
|
385
|
-
} else if (sipSession instanceof Invitation) {
|
|
386
|
-
console.warn('sipSession._onCancel not found, please update the wazo SDK accordingly');
|
|
387
385
|
}
|
|
388
386
|
|
|
389
387
|
sipSession.stateChange.addListener((newState: SessionState) => {
|
|
@@ -453,11 +451,6 @@ export default class WebRTCPhone extends Emitter implements Phone {
|
|
|
453
451
|
return this.eventEmitter.emit(ON_AUDIO_STREAM, stream);
|
|
454
452
|
}
|
|
455
453
|
|
|
456
|
-
// not sure this does anything
|
|
457
|
-
if (kind === 'video') {
|
|
458
|
-
event.track.enabled = false;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
454
|
return this.eventEmitter.emit(ON_VIDEO_STREAM, stream, event.track.id, event, sipSession);
|
|
462
455
|
};
|
|
463
456
|
|
|
@@ -509,7 +502,7 @@ export default class WebRTCPhone extends Emitter implements Phone {
|
|
|
509
502
|
return screenShareStream;
|
|
510
503
|
}
|
|
511
504
|
|
|
512
|
-
async stopScreenSharing(restoreLocalStream = true, callSession?: CallSession | null | undefined): Promise<OutgoingInviteRequest | void> {
|
|
505
|
+
async stopScreenSharing(restoreLocalStream = true, callSession?: CallSession | null | undefined): Promise<OutgoingInviteRequest | void | null> {
|
|
513
506
|
if (!this.currentScreenShare) {
|
|
514
507
|
return;
|
|
515
508
|
}
|
|
@@ -517,11 +510,13 @@ export default class WebRTCPhone extends Emitter implements Phone {
|
|
|
517
510
|
logger.info('WebRTC phone - stop screen sharing', {
|
|
518
511
|
restoreLocalStream,
|
|
519
512
|
});
|
|
520
|
-
let reinvited: OutgoingInviteRequest | void;
|
|
513
|
+
let reinvited: OutgoingInviteRequest | null | void = null;
|
|
521
514
|
|
|
522
515
|
try {
|
|
523
516
|
if (this.currentScreenShare.stream) {
|
|
524
|
-
this.currentScreenShare.stream.getTracks()
|
|
517
|
+
this.currentScreenShare.stream.getTracks()
|
|
518
|
+
.filter((track: MediaStreamTrack) => track.enabled)
|
|
519
|
+
.forEach((track: MediaStreamTrack) => track.stop());
|
|
525
520
|
}
|
|
526
521
|
|
|
527
522
|
if (restoreLocalStream) {
|
|
@@ -552,6 +547,7 @@ export default class WebRTCPhone extends Emitter implements Phone {
|
|
|
552
547
|
screensharing: false,
|
|
553
548
|
}) : null);
|
|
554
549
|
this.currentScreenShare = null;
|
|
550
|
+
|
|
555
551
|
return reinvited;
|
|
556
552
|
}
|
|
557
553
|
|
|
@@ -1369,12 +1365,15 @@ export default class WebRTCPhone extends Emitter implements Phone {
|
|
|
1369
1365
|
logger.info('WebRTC close');
|
|
1370
1366
|
|
|
1371
1367
|
try {
|
|
1372
|
-
await Promise.race([
|
|
1368
|
+
await Promise.race([
|
|
1369
|
+
this.unregister(),
|
|
1370
|
+
new Promise((resolve, reject) => setTimeout(() => reject(new Error('Unregister, timed out')), 3000)),
|
|
1371
|
+
]);
|
|
1373
1372
|
} catch (e: any) {
|
|
1374
1373
|
logger.error('WebRTC close, unregister error', e);
|
|
1375
1374
|
}
|
|
1376
1375
|
|
|
1377
|
-
this.client.close(force);
|
|
1376
|
+
await this.client.close(force);
|
|
1378
1377
|
this.unbind();
|
|
1379
1378
|
this.incomingSessions = [];
|
|
1380
1379
|
this.currentSipSession = undefined;
|
package/src/domain/Session.ts
CHANGED
|
@@ -72,6 +72,8 @@ type SessionArguments = {
|
|
|
72
72
|
export default class Session {
|
|
73
73
|
acl: string[];
|
|
74
74
|
|
|
75
|
+
contact?: Contact;
|
|
76
|
+
|
|
75
77
|
token: string;
|
|
76
78
|
|
|
77
79
|
refreshToken: string | null | undefined;
|
|
@@ -198,6 +200,32 @@ export default class Session {
|
|
|
198
200
|
return this.profile && (this.profile.sipLines || []).find(sipLine => sipLine && !sipLine.isWebRtc());
|
|
199
201
|
}
|
|
200
202
|
|
|
203
|
+
getContact(): Contact {
|
|
204
|
+
if (this.contact) {
|
|
205
|
+
return this.contact;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const { email, firstName, lastName, mobileNumber, state, status } = this.profile || {};
|
|
209
|
+
const number = this.primaryNumber() || '';
|
|
210
|
+
const numbers = [{ label: 'primary', number }];
|
|
211
|
+
|
|
212
|
+
if (mobileNumber) {
|
|
213
|
+
numbers.push({ label: 'mobile', number: mobileNumber });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return new Contact({
|
|
217
|
+
uuid: this.uuid || '',
|
|
218
|
+
email,
|
|
219
|
+
name: `${firstName} ${lastName}`,
|
|
220
|
+
number,
|
|
221
|
+
numbers,
|
|
222
|
+
state: state || '',
|
|
223
|
+
status,
|
|
224
|
+
backend: 'wazo',
|
|
225
|
+
source: 'default',
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
201
229
|
primaryContext(): string {
|
|
202
230
|
if (this.engineVersion) {
|
|
203
231
|
if (this.hasEngineVersionGte(MINIMUM_WAZO_ENGINE_VERSION_FOR_DEFAULT_CONTEXT)) {
|
|
@@ -1,5 +1,138 @@
|
|
|
1
1
|
import Contact from '../Contact';
|
|
2
2
|
|
|
3
|
+
const genericContactResponse = {
|
|
4
|
+
column_types: [
|
|
5
|
+
'name',
|
|
6
|
+
'number',
|
|
7
|
+
'callable',
|
|
8
|
+
'voicemail',
|
|
9
|
+
'favorite',
|
|
10
|
+
'email',
|
|
11
|
+
'personal',
|
|
12
|
+
],
|
|
13
|
+
term: 'a',
|
|
14
|
+
column_headers: [
|
|
15
|
+
'Nom',
|
|
16
|
+
'Num\u00e9ro',
|
|
17
|
+
'Mobile',
|
|
18
|
+
'Bo\u00eete vocale',
|
|
19
|
+
'Favoris',
|
|
20
|
+
'E-mail',
|
|
21
|
+
'Personnel',
|
|
22
|
+
],
|
|
23
|
+
results: [],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const contact1 = {
|
|
27
|
+
source: 'internal',
|
|
28
|
+
column_values: [
|
|
29
|
+
'Jonathan Lessard',
|
|
30
|
+
'8020',
|
|
31
|
+
'06800880',
|
|
32
|
+
null,
|
|
33
|
+
false,
|
|
34
|
+
'jonathan@example.com',
|
|
35
|
+
false,
|
|
36
|
+
],
|
|
37
|
+
backend: 'wazo',
|
|
38
|
+
relations: {
|
|
39
|
+
user_id: 8,
|
|
40
|
+
xivo_id: '6cd695d2-cdb9-4444-8b2d-27425ab85fa8',
|
|
41
|
+
agent_id: null,
|
|
42
|
+
endpoint_id: 8,
|
|
43
|
+
user_uuid: 'a14dd6d6-547c-434d-bd5c-e882b5b83b54',
|
|
44
|
+
source_entry_id: '8',
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const contact2 = {
|
|
49
|
+
source: 'internal',
|
|
50
|
+
column_values: [
|
|
51
|
+
'John Doe',
|
|
52
|
+
'8021',
|
|
53
|
+
'06800881',
|
|
54
|
+
null,
|
|
55
|
+
false,
|
|
56
|
+
'john@example.com',
|
|
57
|
+
false,
|
|
58
|
+
],
|
|
59
|
+
backend: 'wazo',
|
|
60
|
+
relations: {
|
|
61
|
+
user_id: 9,
|
|
62
|
+
xivo_id: '6cd695d2-cdb9-4444-8b2d-27425ab85fa9',
|
|
63
|
+
agent_id: null,
|
|
64
|
+
endpoint_id: 9,
|
|
65
|
+
user_uuid: 'a14dd6d6-547c-434d-bd5c-e882b5b83b55',
|
|
66
|
+
source_entry_id: '9',
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const parsedContact1 = new Contact({
|
|
71
|
+
name: 'Jonathan Lessard',
|
|
72
|
+
number: '8020',
|
|
73
|
+
numbers: [
|
|
74
|
+
{
|
|
75
|
+
label: 'primary',
|
|
76
|
+
number: '8020',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
label: 'secondary',
|
|
80
|
+
number: '06800880',
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
favorited: false,
|
|
84
|
+
email: 'jonathan@example.com',
|
|
85
|
+
emails: [
|
|
86
|
+
{
|
|
87
|
+
label: 'primary',
|
|
88
|
+
email: 'jonathan@example.com',
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
address: '',
|
|
92
|
+
entreprise: '',
|
|
93
|
+
birthday: '',
|
|
94
|
+
note: '',
|
|
95
|
+
endpointId: 8,
|
|
96
|
+
personal: false,
|
|
97
|
+
source: 'internal',
|
|
98
|
+
sourceId: '8',
|
|
99
|
+
uuid: 'a14dd6d6-547c-434d-bd5c-e882b5b83b54',
|
|
100
|
+
backend: 'wazo',
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const parsedContact2 = new Contact({
|
|
104
|
+
name: 'John Doe',
|
|
105
|
+
number: '8021',
|
|
106
|
+
numbers: [
|
|
107
|
+
{
|
|
108
|
+
label: 'primary',
|
|
109
|
+
number: '8021',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
label: 'secondary',
|
|
113
|
+
number: '06800881',
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
favorited: false,
|
|
117
|
+
email: 'john@example.com',
|
|
118
|
+
emails: [
|
|
119
|
+
{
|
|
120
|
+
label: 'primary',
|
|
121
|
+
email: 'john@example.com',
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
address: '',
|
|
125
|
+
entreprise: '',
|
|
126
|
+
birthday: '',
|
|
127
|
+
note: '',
|
|
128
|
+
endpointId: 9,
|
|
129
|
+
personal: false,
|
|
130
|
+
source: 'internal',
|
|
131
|
+
sourceId: '9',
|
|
132
|
+
uuid: 'a14dd6d6-547c-434d-bd5c-e882b5b83b55',
|
|
133
|
+
backend: 'wazo',
|
|
134
|
+
});
|
|
135
|
+
|
|
3
136
|
describe('Contact domain', () => {
|
|
4
137
|
it('is extern when given no uuid', () => {
|
|
5
138
|
const boba = new Contact({});
|
|
@@ -132,10 +265,7 @@ describe('Contact domain', () => {
|
|
|
132
265
|
const contact = new Contact({
|
|
133
266
|
name: 'John Smith',
|
|
134
267
|
});
|
|
135
|
-
const {
|
|
136
|
-
firstName,
|
|
137
|
-
lastName,
|
|
138
|
-
} = contact.separateName();
|
|
268
|
+
const { firstName, lastName } = contact.separateName();
|
|
139
269
|
expect(firstName).toBe('John');
|
|
140
270
|
expect(lastName).toBe('Smith');
|
|
141
271
|
});
|
|
@@ -143,10 +273,7 @@ describe('Contact domain', () => {
|
|
|
143
273
|
const contact = new Contact({
|
|
144
274
|
name: 'John Smith',
|
|
145
275
|
});
|
|
146
|
-
const {
|
|
147
|
-
firstName,
|
|
148
|
-
lastName,
|
|
149
|
-
} = contact.separateName();
|
|
276
|
+
const { firstName, lastName } = contact.separateName();
|
|
150
277
|
expect(firstName).toBe('John');
|
|
151
278
|
expect(lastName).toBe('Smith');
|
|
152
279
|
});
|
|
@@ -172,67 +299,99 @@ describe('Contact domain', () => {
|
|
|
172
299
|
});
|
|
173
300
|
it('can parse a plain contact to domain', () => {
|
|
174
301
|
const response = {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
column_headers: ['Nom', 'Num\u00e9ro', 'Mobile', 'Bo\u00eete vocale', 'Favoris', 'E-mail', 'Personnel'],
|
|
178
|
-
results: [{
|
|
179
|
-
source: 'internal',
|
|
180
|
-
column_values: ['Jonathan Lessard', '8020', '06800880', null, false, 'contact@nexapp.ca', false],
|
|
181
|
-
backend: 'wazo',
|
|
182
|
-
relations: {
|
|
183
|
-
user_id: 8,
|
|
184
|
-
xivo_id: '6cd695d2-cdb9-4444-8b2d-27425ab85fa8',
|
|
185
|
-
agent_id: null,
|
|
186
|
-
endpoint_id: 8,
|
|
187
|
-
user_uuid: 'a14dd6d6-547c-434d-bd5c-e882b5b83b54',
|
|
188
|
-
source_entry_id: '8',
|
|
189
|
-
},
|
|
190
|
-
}],
|
|
302
|
+
...genericContactResponse,
|
|
303
|
+
results: [contact1],
|
|
191
304
|
};
|
|
192
305
|
const contact = Contact.parse(response.results[0], response.column_types);
|
|
193
|
-
expect(contact).toEqual(
|
|
194
|
-
name: 'Jonathan Lessard',
|
|
195
|
-
number: '8020',
|
|
196
|
-
numbers: [{
|
|
197
|
-
label: 'primary',
|
|
198
|
-
number: '8020',
|
|
199
|
-
}, {
|
|
200
|
-
label: 'secondary',
|
|
201
|
-
number: '06800880',
|
|
202
|
-
}],
|
|
203
|
-
favorited: false,
|
|
204
|
-
email: 'contact@nexapp.ca',
|
|
205
|
-
emails: [{
|
|
206
|
-
label: 'primary',
|
|
207
|
-
email: 'contact@nexapp.ca',
|
|
208
|
-
}],
|
|
209
|
-
address: '',
|
|
210
|
-
entreprise: '',
|
|
211
|
-
birthday: '',
|
|
212
|
-
note: '',
|
|
213
|
-
endpointId: 8,
|
|
214
|
-
personal: false,
|
|
215
|
-
source: 'internal',
|
|
216
|
-
sourceId: '8',
|
|
217
|
-
uuid: 'a14dd6d6-547c-434d-bd5c-e882b5b83b54',
|
|
218
|
-
backend: 'wazo',
|
|
219
|
-
}));
|
|
306
|
+
expect(contact).toEqual(parsedContact1);
|
|
220
307
|
});
|
|
221
308
|
it('should merge 2 contact', () => {
|
|
222
|
-
const
|
|
309
|
+
const noBirthdayContact = new Contact({
|
|
223
310
|
uuid: 'uuid-12345',
|
|
224
311
|
lastActivity: 'yesterday',
|
|
225
312
|
// @ts-expect-error
|
|
226
313
|
birthday: null,
|
|
227
314
|
});
|
|
228
|
-
const
|
|
315
|
+
const noLastActivityContact = new Contact({
|
|
229
316
|
uuid: 'uuid-12345',
|
|
230
317
|
// @ts-expect-error
|
|
231
318
|
lastActivity: null,
|
|
232
319
|
birthday: 'tomorrow',
|
|
233
320
|
});
|
|
234
|
-
const result =
|
|
321
|
+
const result = noBirthdayContact.merge(noLastActivityContact);
|
|
235
322
|
expect(result.lastActivity).toBe('yesterday');
|
|
236
323
|
expect(result.birthday).toBe('tomorrow');
|
|
237
324
|
});
|
|
325
|
+
it('returns all contacts when parsing many contacts with no limit', () => {
|
|
326
|
+
const response = {
|
|
327
|
+
...genericContactResponse,
|
|
328
|
+
results: [contact1, contact2],
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const contacts = Contact.parseMany(response);
|
|
332
|
+
expect(contacts).toEqual([parsedContact1, parsedContact2]);
|
|
333
|
+
});
|
|
334
|
+
it('returns all contacts when parsing many contacts with a higher limit than number of results', () => {
|
|
335
|
+
const response = {
|
|
336
|
+
...genericContactResponse,
|
|
337
|
+
results: [contact1, contact2],
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const contacts = Contact.parseMany(response, 0, 1000);
|
|
341
|
+
expect(contacts).toEqual([parsedContact1, parsedContact2]);
|
|
342
|
+
});
|
|
343
|
+
it('returns limited contacts when parsing many contacts with a smaller limit than number of results', () => {
|
|
344
|
+
const response = {
|
|
345
|
+
...genericContactResponse,
|
|
346
|
+
results: [contact1],
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const contacts = Contact.parseMany(response, 0, 1);
|
|
350
|
+
expect(contacts).toEqual([parsedContact1]);
|
|
351
|
+
});
|
|
352
|
+
it('returns all contacts when parsing many contacts with same limit than number of results', () => {
|
|
353
|
+
const response = {
|
|
354
|
+
...genericContactResponse,
|
|
355
|
+
results: [contact1, contact2],
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const contacts = Contact.parseMany(response, 0, 2);
|
|
359
|
+
expect(contacts).toEqual([parsedContact1, parsedContact2]);
|
|
360
|
+
});
|
|
361
|
+
it('returns an empty array when parsing many contact with limit of 0', () => {
|
|
362
|
+
const response = {
|
|
363
|
+
...genericContactResponse,
|
|
364
|
+
results: [contact1, contact2],
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const contacts = Contact.parseMany(response, 0, 0);
|
|
368
|
+
expect(contacts).toEqual([]);
|
|
369
|
+
});
|
|
370
|
+
it('returns all contacts when parsing many contacts with a negative limit', () => {
|
|
371
|
+
const response = {
|
|
372
|
+
...genericContactResponse,
|
|
373
|
+
results: [contact1, contact2],
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
const contacts = Contact.parseMany(response, 0, -1);
|
|
377
|
+
expect(contacts).toEqual([parsedContact1, parsedContact2]);
|
|
378
|
+
});
|
|
379
|
+
it('returns all contacts when parsing many contacts with a null limit', () => {
|
|
380
|
+
const response = {
|
|
381
|
+
...genericContactResponse,
|
|
382
|
+
results: [contact1, contact2],
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
const contacts = Contact.parseMany(response, 0, null);
|
|
386
|
+
expect(contacts).toEqual([parsedContact1, parsedContact2]);
|
|
387
|
+
});
|
|
388
|
+
it('returns all contacts after the first one when parsing many contacts with an offset of 1', () => {
|
|
389
|
+
const response = {
|
|
390
|
+
...genericContactResponse,
|
|
391
|
+
results: [contact1, contact2],
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const contacts = Contact.parseMany(response, 1, null);
|
|
395
|
+
expect(contacts).toEqual([parsedContact2]);
|
|
396
|
+
});
|
|
238
397
|
});
|
package/src/domain/types.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import type { Session, UserAgentOptions as sipJsUserAgentOptions } from 'sip.js';
|
|
2
|
+
import { SessionDescriptionHandlerFactoryOptions } from 'sip.js/lib/platform/web';
|
|
3
|
+
import WazoSessionDescriptionHandler from '../lib/WazoSessionDescriptionHandler';
|
|
4
|
+
|
|
1
5
|
// @TODO: stand-in for empty object types. Was `type Something = {};` in JS
|
|
2
6
|
type TodoObject = object;
|
|
3
7
|
|
|
@@ -202,20 +206,6 @@ export type ListCallNodesResponse = {
|
|
|
202
206
|
};
|
|
203
207
|
export type GetTenantResponse = TodoObject;
|
|
204
208
|
export type GetUserResponse = TodoObject;
|
|
205
|
-
export type WebRtcConfig = {
|
|
206
|
-
id: number;
|
|
207
|
-
host: string;
|
|
208
|
-
line: {
|
|
209
|
-
id: number;
|
|
210
|
-
links: Array<Link>;
|
|
211
|
-
};
|
|
212
|
-
links: Array<Link>;
|
|
213
|
-
options: Array<Array<string>>;
|
|
214
|
-
secret: string;
|
|
215
|
-
tenant_uuid: string;
|
|
216
|
-
type: string;
|
|
217
|
-
username: string;
|
|
218
|
-
};
|
|
219
209
|
export type CTITransfer = {
|
|
220
210
|
'flow': string;
|
|
221
211
|
'id': string;
|
|
@@ -225,3 +215,41 @@ export type CTITransfer = {
|
|
|
225
215
|
'status': string;
|
|
226
216
|
'transferred_call': string;
|
|
227
217
|
};
|
|
218
|
+
export type UserAgentOptions = sipJsUserAgentOptions & {
|
|
219
|
+
peerConnectionOptions?: Record<string, any>,
|
|
220
|
+
sessionDescriptionHandlerFactory: (session: Session, options: SessionDescriptionHandlerFactoryOptions) => WazoSessionDescriptionHandler,
|
|
221
|
+
};
|
|
222
|
+
export type UserAgentConfigOverrides = Partial<UserAgentOptions & {
|
|
223
|
+
traceSip: any,
|
|
224
|
+
}>;
|
|
225
|
+
export type MediaConfig = {
|
|
226
|
+
audio: Record<string, any> | boolean;
|
|
227
|
+
video?: Record<string, any> | boolean;
|
|
228
|
+
localVideo?: Record<string, any> | boolean;
|
|
229
|
+
};
|
|
230
|
+
export type WebRtcConfig = {
|
|
231
|
+
displayName?: string;
|
|
232
|
+
host: string;
|
|
233
|
+
port?: number;
|
|
234
|
+
websocketSip?: string;
|
|
235
|
+
authorizationUser?: string;
|
|
236
|
+
password?: string;
|
|
237
|
+
uri?: string;
|
|
238
|
+
media?: MediaConfig;
|
|
239
|
+
iceCheckingTimeout?: number;
|
|
240
|
+
log?: Record<string, any>;
|
|
241
|
+
audioOutputDeviceId?: string;
|
|
242
|
+
audioOutputVolume?: number;
|
|
243
|
+
userAgentString?: string;
|
|
244
|
+
heartbeatDelay?: number;
|
|
245
|
+
heartbeatTimeout?: number;
|
|
246
|
+
maxHeartbeats?: number;
|
|
247
|
+
skipRegister?: boolean;
|
|
248
|
+
userUuid?: string,
|
|
249
|
+
}; // @see https://github.com/onsip/SIP.js/blob/master/src/Web/Simple.js
|
|
250
|
+
|
|
251
|
+
export type ConnectionOptions = WebRtcConfig & {
|
|
252
|
+
uaConfigOverrides: UserAgentConfigOverrides,
|
|
253
|
+
audioDeviceOutput: string,
|
|
254
|
+
audioDeviceRing: string
|
|
255
|
+
};
|
|
@@ -208,20 +208,24 @@ class WazoSessionDescriptionHandler extends SessionDescriptionHandler {
|
|
|
208
208
|
return;
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
// Closing senders via getLocalStreams
|
|
211
|
+
// Closing senders via getLocalStreams, `getLocalStreams` is deprecated, we have to check if it exists.
|
|
212
212
|
// @ts-ignore
|
|
213
|
-
this.peerConnection?.getLocalStreams
|
|
214
|
-
|
|
215
|
-
|
|
213
|
+
if (this.peerConnection?.getLocalStreams) {
|
|
214
|
+
// @ts-ignore
|
|
215
|
+
this.peerConnection?.getLocalStreams().forEach(stream => {
|
|
216
|
+
stream.getTracks()
|
|
217
|
+
.filter((track: MediaStreamTrack) => track.enabled)
|
|
218
|
+
.forEach((track: MediaStreamTrack) => track.stop());
|
|
216
219
|
});
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
220
|
+
|
|
221
|
+
// Closing receivers via getRemoteStreams
|
|
222
|
+
// @ts-ignore
|
|
223
|
+
this.peerConnection?.getRemoteStreams().forEach(stream => {
|
|
224
|
+
stream.getTracks()
|
|
225
|
+
.filter((track: MediaStreamTrack) => track.enabled)
|
|
226
|
+
.forEach((track: MediaStreamTrack) => track.stop());
|
|
223
227
|
});
|
|
224
|
-
}
|
|
228
|
+
}
|
|
225
229
|
|
|
226
230
|
if (this._dataChannel) {
|
|
227
231
|
this._dataChannel.close();
|
|
@@ -14,7 +14,7 @@ const ERROR = 'error';
|
|
|
14
14
|
const CONSOLE_METHODS = [INFO, LOG, WARN, ERROR];
|
|
15
15
|
const LOG_LEVELS = [TRACE, DEBUG, INFO, LOG, WARN, ERROR];
|
|
16
16
|
const CATEGORY_PREFIX = 'logger-category=';
|
|
17
|
-
const MAX_REMOTE_RETRY =
|
|
17
|
+
const MAX_REMOTE_RETRY = 10;
|
|
18
18
|
|
|
19
19
|
const addLevelsTo = (instance: Record<string, any>, withMethods = false) => {
|
|
20
20
|
instance.TRACE = TRACE;
|
|
@@ -350,6 +350,15 @@ class IssueReporter {
|
|
|
350
350
|
}
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
+
_computeRetryDelay(attempt: number, initial = 1000, maxWait = 50000) {
|
|
354
|
+
// min wait is initial(attempt = 0)
|
|
355
|
+
// later retries are randomly distributed between initial wait and exponential
|
|
356
|
+
const base = 1.5;
|
|
357
|
+
const wait = Math.min(initial * base ** attempt, maxWait);
|
|
358
|
+
const jitterWait = Math.max(initial, Math.random() * wait);
|
|
359
|
+
return jitterWait;
|
|
360
|
+
}
|
|
361
|
+
|
|
353
362
|
_sendDebugToGrafana(payload: string | Record<string, any> | Record<string, any>[], retry = 0) {
|
|
354
363
|
if (!this.remoteClientConfiguration || retry >= MAX_REMOTE_RETRY) {
|
|
355
364
|
return;
|
|
@@ -376,6 +385,8 @@ class IssueReporter {
|
|
|
376
385
|
// @ts-ignore
|
|
377
386
|
e.skipSendToRemote = true;
|
|
378
387
|
this.log('error', this._makeCategory('grafana'), 'Sending log to grafana, error', e);
|
|
388
|
+
// wait at least 1 second, at most 50 seconds
|
|
389
|
+
const wait = this._computeRetryDelay(retry, 1000, 50000);
|
|
379
390
|
setTimeout(() => {
|
|
380
391
|
if (Array.isArray(payload)) {
|
|
381
392
|
payload = payload.map(message => this._writeRetryCount(message, retry + 1));
|
|
@@ -384,7 +395,7 @@ class IssueReporter {
|
|
|
384
395
|
}
|
|
385
396
|
|
|
386
397
|
this._sendDebugToGrafana(payload, retry + 1);
|
|
387
|
-
},
|
|
398
|
+
}, wait);
|
|
388
399
|
});
|
|
389
400
|
}
|
|
390
401
|
|