@voicenter-team/opensips-js 1.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. package/README.md +75 -0
  2. package/build/enum/call.event.listener.type.d.ts +7 -0
  3. package/build/enum/call.event.listener.type.js +10 -0
  4. package/build/enum/metric.keys.to.include.d.ts +2 -0
  5. package/build/enum/metric.keys.to.include.js +4 -0
  6. package/build/helpers/UA/index.d.ts +6 -0
  7. package/build/helpers/UA/index.js +9 -0
  8. package/build/helpers/audio.helper.d.ts +9 -0
  9. package/build/helpers/audio.helper.js +60 -0
  10. package/build/helpers/filter.helper.d.ts +2 -0
  11. package/build/helpers/filter.helper.js +14 -0
  12. package/build/helpers/time.helper.d.ts +16 -0
  13. package/build/helpers/time.helper.js +28 -0
  14. package/build/helpers/volume.helper.d.ts +2 -0
  15. package/build/helpers/volume.helper.js +76 -0
  16. package/build/helpers/webrtcmetrics/collector.d.ts +32 -0
  17. package/build/helpers/webrtcmetrics/collector.js +282 -0
  18. package/build/helpers/webrtcmetrics/engine.d.ts +20 -0
  19. package/build/helpers/webrtcmetrics/engine.js +164 -0
  20. package/build/helpers/webrtcmetrics/exporter.d.ts +116 -0
  21. package/build/helpers/webrtcmetrics/exporter.js +528 -0
  22. package/build/helpers/webrtcmetrics/extractor.d.ts +1 -0
  23. package/build/helpers/webrtcmetrics/extractor.js +976 -0
  24. package/build/helpers/webrtcmetrics/index.d.ts +63 -0
  25. package/build/helpers/webrtcmetrics/index.js +93 -0
  26. package/build/helpers/webrtcmetrics/metrics.d.ts +2 -0
  27. package/build/helpers/webrtcmetrics/metrics.js +8 -0
  28. package/build/helpers/webrtcmetrics/probe.d.ts +76 -0
  29. package/build/helpers/webrtcmetrics/probe.js +153 -0
  30. package/build/helpers/webrtcmetrics/utils/config.d.ts +12 -0
  31. package/build/helpers/webrtcmetrics/utils/config.js +28 -0
  32. package/build/helpers/webrtcmetrics/utils/helper.d.ts +13 -0
  33. package/build/helpers/webrtcmetrics/utils/helper.js +134 -0
  34. package/build/helpers/webrtcmetrics/utils/log.d.ts +7 -0
  35. package/build/helpers/webrtcmetrics/utils/log.js +71 -0
  36. package/build/helpers/webrtcmetrics/utils/models.d.ts +309 -0
  37. package/build/helpers/webrtcmetrics/utils/models.js +298 -0
  38. package/build/helpers/webrtcmetrics/utils/score.d.ts +4 -0
  39. package/build/helpers/webrtcmetrics/utils/score.js +235 -0
  40. package/build/helpers/webrtcmetrics/utils/shortUUId.d.ts +1 -0
  41. package/build/helpers/webrtcmetrics/utils/shortUUId.js +7 -0
  42. package/build/index.d.ts +170 -0
  43. package/build/index.js +849 -0
  44. package/package.json +61 -0
  45. package/src/types/declarations.d.ts +6 -0
  46. package/src/types/generic.d.ts +1 -0
  47. package/src/types/listeners.d.ts +42 -0
  48. package/src/types/rtc.d.ts +133 -0
  49. package/src/types/webrtcmetrics.d.ts +64 -0
package/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # Getting started
2
+ ## Installation
3
+ Using npm:
4
+ ```shell
5
+ $ npm i @voicenter-team/opensips-js
6
+ ```
7
+
8
+ ## Usage
9
+ Firstly lets import the library and create the OpenSIPS instance:
10
+ ```javascript
11
+ import OpenSIPSJS from '@voicenter-team/opensips-js'
12
+
13
+ const openSIPSJS = new OpenSIPSJS({
14
+ configuration: {
15
+ session_timers: false,
16
+ uri: 'sip:extension_user@domain',
17
+ password: 'password',
18
+ },
19
+ socketInterfaces: [ 'wss://domain' ],
20
+ sipDomain: 'domain',
21
+ sipOptions: {
22
+ session_timers: false,
23
+ extraHeaders: [ 'X-Bar: bar' ],
24
+ pcConfig: {},
25
+ },
26
+ })
27
+ ```
28
+
29
+ Then you will be able to call next methods on openSIPSJS instance:
30
+
31
+ ### Methods
32
+ - `async setMediaDevices(setDefaults: Boolean = false)` - will set up media devices
33
+ - `async setMicrophone(deviceId: String)` - set passed device as input device for calls
34
+ - `async setSpeaker(deviceId: String)` - set passed device as output device for calls
35
+ - `async setCurrentActiveRoomId(roomId: Number)` - move to the room
36
+ - `doCallHold({callId: Number, toHold: Boolean, automatic: Boolean})` - hold/unhold call by id
37
+ - `doCall(target: String, addToCurrentRoom: Boolean)` - call to the target. If addToCurrentRoom is true then the call will be added to the user's current room
38
+ - `callTerminate(callId: String)` - terminate call
39
+ - `callTransfer({callId: String, target: String})` - transfer call to target
40
+ - `callMerge(roomId: Number)` - merge calls in specific room
41
+ - `callAnswer(callId: String)` - answer the call
42
+ - `setMetricsConfig(config: WebrtcMetricsConfigType)` - set the metric config (used for audio quality indicator)
43
+ - `doMute(muted: Boolean)` - set the agent muteness
44
+ - `setDND(value: Boolean)` - set the agent "Do not disturb" status
45
+ - `async callChangeRoom({callId: String, roomId: Number})` - move call to the room
46
+ - `callMove({callId: String, roomId: Number})` - Same as callChangeRoom. Move call to the specific room
47
+ - `subscribe({type: String, listener: function})` - subscribe to an event. Available events: `new_call`, `ended`, `progress`, `failed`, `confirmed`
48
+ - `removeIListener(type: String)` - remove event listener
49
+
50
+ WebrtcMetricsConfigType
51
+
52
+ | Parameter | Type |
53
+ |----------------|------------------------|
54
+ | `refreshEvery` | `number \| undefined` |
55
+ | `startAfter` | `number \| undefined` |
56
+ | `startAfter` | `number \| undefined` |
57
+ | `verbose` | `boolean \| undefined` |
58
+ | `pname` | `string \| undefined` |
59
+ | `cid` | `string \| undefined` |
60
+ | `uid` | `string \| undefined` |
61
+ | `record` | `boolean \| undefined` |
62
+ | `ticket` | `boolean \| undefined` |
63
+
64
+ Also there are next public fields on openSIPSJS instance:
65
+ ### Fields
66
+ - `getActiveRooms: { [key: number]: IRoom }` - returns an object of active rooms where key is room id and value is room data
67
+ - `sipDomain: String` - returns sip domain
68
+ - `sipOptions: Object` - returns sip options
69
+ - `getInputDeviceList: []` - returns list of input devices
70
+ - `getOutputDeviceList: []` - returns list of output devices
71
+ - `currentActiveRoomId: Number` - returns current active room id
72
+ - `selectedInputDevice: String` - returns current selected input device id
73
+ - `selectedOutputDevice: String` - returns current selected output device id
74
+ - `isDND: Boolean` - returns if the agent is in "Do not disturb" status
75
+ - `isMuted: Boolean` - returns if the agent is muted
@@ -0,0 +1,7 @@
1
+ export declare const CALL_EVENT_LISTENER_TYPE: {
2
+ NEW_CALL: string;
3
+ CALL_CONFIRMED: string;
4
+ CALL_FAILED: string;
5
+ CALL_PROGRESS: string;
6
+ CALL_ENDED: string;
7
+ };
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CALL_EVENT_LISTENER_TYPE = void 0;
4
+ exports.CALL_EVENT_LISTENER_TYPE = {
5
+ NEW_CALL: 'new_call',
6
+ CALL_CONFIRMED: 'confirmed',
7
+ CALL_FAILED: 'failed',
8
+ CALL_PROGRESS: 'progress',
9
+ CALL_ENDED: 'ended'
10
+ };
@@ -0,0 +1,2 @@
1
+ import { ProbeMetricInType } from '@/types/webrtcmetrics';
2
+ export declare const METRIC_KEYS_TO_INCLUDE: (keyof ProbeMetricInType)[];
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.METRIC_KEYS_TO_INCLUDE = void 0;
4
+ exports.METRIC_KEYS_TO_INCLUDE = ['mos_in', 'codec_in', 'delta_KBytes_in', 'delta_kbs_in', 'delta_jitter_ms_in', 'delta_packets_lost_in'];
@@ -0,0 +1,6 @@
1
+ import { UA } from 'jssip';
2
+ import { CallOptionsExtended } from '@/types/rtc';
3
+ import { RTCSession } from 'jssip/lib/RTCSession';
4
+ export default class UAExtended extends UA {
5
+ call(target: string, options?: CallOptionsExtended): RTCSession;
6
+ }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const jssip_1 = require("jssip");
4
+ class UAExtended extends jssip_1.UA {
5
+ call(target, options) {
6
+ return super.call(target, options);
7
+ }
8
+ }
9
+ exports.default = UAExtended;
@@ -0,0 +1,9 @@
1
+ import { ICall, MediaEvent } from '@/types/rtc';
2
+ import { Writeable } from '@/types/generic';
3
+ type ICallKey = keyof ICall;
4
+ declare const CALL_KEYS_TO_INCLUDE: Array<ICallKey>;
5
+ export type ICallSimplified = Writeable<Pick<ICall, typeof CALL_KEYS_TO_INCLUDE[number]>>;
6
+ export declare function simplifyCallObject(call: ICall): ICallSimplified;
7
+ export declare function processAudioVolume(stream: MediaStream, volume: number): MediaStream;
8
+ export declare function syncStream(event: MediaEvent, call: ICall, outputDevice: string, volume: number): void;
9
+ export {};
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.syncStream = exports.processAudioVolume = exports.simplifyCallObject = void 0;
4
+ const CALL_KEYS_TO_INCLUDE = [
5
+ 'roomId',
6
+ '_audioMuted',
7
+ '_cancel_reason',
8
+ '_contact',
9
+ 'direction',
10
+ '_end_time',
11
+ '_eventsCount',
12
+ '_from_tag',
13
+ '_id',
14
+ '_is_canceled',
15
+ '_is_confirmed',
16
+ '_late_sdp',
17
+ '_localHold',
18
+ '_videoMuted',
19
+ 'status',
20
+ 'start_time',
21
+ '_remote_identity',
22
+ 'audioTag',
23
+ //'audioQuality',
24
+ 'isOnHold',
25
+ //'originalStream',
26
+ 'localMuted'
27
+ ];
28
+ function simplifyCallObject(call) {
29
+ const simplified = {};
30
+ CALL_KEYS_TO_INCLUDE.forEach(key => {
31
+ if (call[key] !== undefined) {
32
+ simplified[key] = call[key];
33
+ }
34
+ });
35
+ simplified.localHold = call._localHold;
36
+ return simplified;
37
+ }
38
+ exports.simplifyCallObject = simplifyCallObject;
39
+ function processAudioVolume(stream, volume) {
40
+ const audioContext = new AudioContext();
41
+ const audioSource = audioContext.createMediaStreamSource(stream);
42
+ const audioDestination = audioContext.createMediaStreamDestination();
43
+ const gainNode = audioContext.createGain();
44
+ audioSource.connect(gainNode);
45
+ gainNode.connect(audioDestination);
46
+ gainNode.gain.value = volume;
47
+ return audioDestination.stream;
48
+ }
49
+ exports.processAudioVolume = processAudioVolume;
50
+ function syncStream(event, call, outputDevice, volume) {
51
+ const audio = document.createElement('audio');
52
+ audio.id = call._id;
53
+ audio.className = 'audioTag';
54
+ audio.srcObject = event.stream;
55
+ audio.setSinkId(outputDevice);
56
+ audio.volume = volume;
57
+ audio.play();
58
+ call.audioTag = audio;
59
+ }
60
+ exports.syncStream = syncStream;
@@ -0,0 +1,2 @@
1
+ import { ProbeMetricInType } from '@/types/webrtcmetrics';
2
+ export declare function filterObjectKeys(fullObj: ProbeMetricInType, keys: Array<keyof ProbeMetricInType>): ProbeMetricInType;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.filterObjectKeys = void 0;
4
+ function filterObjectKeys(fullObj, keys) {
5
+ return Object.keys(fullObj)
6
+ .filter((key) => keys.includes(key))
7
+ .reduce((obj, key) => {
8
+ const k = key;
9
+ //const o = obj as ProbeMetricInType
10
+ //o[k] = fullObj[k] //as ProbeMetricInType[keyof ProbeMetricInType]
11
+ return Object.assign(Object.assign({}, obj), { [k]: fullObj[k] });
12
+ }, {});
13
+ }
14
+ exports.filterObjectKeys = filterObjectKeys;
@@ -0,0 +1,16 @@
1
+ export interface ITimeData {
2
+ callId: string;
3
+ hours: number;
4
+ minutes: number;
5
+ seconds: number;
6
+ formatted: string;
7
+ }
8
+ export type TempTimeData = Omit<ITimeData, 'callId'> & {
9
+ callId: string | undefined;
10
+ };
11
+ export declare function setupTime(time: TempTimeData): {
12
+ seconds: number;
13
+ minutes: number;
14
+ hours: number;
15
+ formatted: string;
16
+ };
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setupTime = void 0;
4
+ function formatTime(time) {
5
+ return time < 10 ? `0${time}` : `${time}`;
6
+ }
7
+ function setupTime(time) {
8
+ let hours = time.hours || 0;
9
+ let minutes = time.minutes || 0;
10
+ let seconds = time.seconds || 0;
11
+ seconds++;
12
+ if (seconds === 60) {
13
+ seconds = 0;
14
+ minutes++;
15
+ if (minutes === 60) {
16
+ minutes = 0;
17
+ hours++;
18
+ }
19
+ }
20
+ const formatted = `${formatTime(hours)}:${formatTime(minutes)}:${formatTime(seconds)}`;
21
+ return {
22
+ seconds,
23
+ minutes,
24
+ hours,
25
+ formatted
26
+ };
27
+ }
28
+ exports.setupTime = setupTime;
@@ -0,0 +1,2 @@
1
+ export declare const runIndicator: (stream: MediaStream, deviceId: string) => void;
2
+ export declare const clearVolumeInterval: () => void;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.clearVolumeInterval = exports.runIndicator = void 0;
4
+ const height = 20;
5
+ const lineWidth = 4;
6
+ let interval = undefined;
7
+ const runIndicator = (stream, deviceId) => {
8
+ if (stream && stream.getTracks().length) {
9
+ //console.log('RUN INDICATOR IF')
10
+ getVolumeLevelBar(stream, deviceId);
11
+ }
12
+ else {
13
+ //console.log('RUN INDICATOR ELSE')
14
+ (0, exports.clearVolumeInterval)();
15
+ }
16
+ };
17
+ exports.runIndicator = runIndicator;
18
+ const clearVolumeInterval = () => {
19
+ clearInterval(interval);
20
+ };
21
+ exports.clearVolumeInterval = clearVolumeInterval;
22
+ const getMaxSmallIndicatorHeight = (value) => {
23
+ const halfLineHeight = height / 4;
24
+ return value < halfLineHeight ? value : halfLineHeight;
25
+ };
26
+ const getVolumeLevelBar = (stream, deviceId) => {
27
+ //console.log('IN GET VOLUME LEVEL BAR')
28
+ //console.log('TRACKS LENGTH', stream.getTracks().length)
29
+ clearInterval(interval);
30
+ const audioContext = new AudioContext();
31
+ const analyser = audioContext.createAnalyser();
32
+ const microphone = audioContext.createMediaStreamSource(stream);
33
+ const javascriptNode = audioContext.createScriptProcessor(2048, 1, 1);
34
+ analyser.smoothingTimeConstant = 0.8;
35
+ analyser.fftSize = 1024;
36
+ microphone.connect(analyser);
37
+ analyser.connect(javascriptNode);
38
+ javascriptNode.connect(audioContext.destination);
39
+ const canvas = document.getElementById(`canvas-${deviceId}`);
40
+ if (!canvas) {
41
+ return;
42
+ }
43
+ const indicatorWidth = lineWidth * 5;
44
+ const halfLineHeight = height / 2;
45
+ canvas.setAttribute('width', `${indicatorWidth}`);
46
+ canvas.setAttribute('height', `${height}`);
47
+ const canvasContext = canvas.getContext('2d');
48
+ interval = setInterval(() => {
49
+ if (!canvasContext) {
50
+ return;
51
+ }
52
+ const array = new Uint8Array(analyser.frequencyBinCount);
53
+ analyser.getByteFrequencyData(array);
54
+ let values = 0;
55
+ const length = array.length;
56
+ for (let i = 0; i < length; i++) {
57
+ values += (array[i]);
58
+ }
59
+ const average = values / length;
60
+ //console.log('average', average)
61
+ canvasContext.fillStyle = 'blue'; //getComputedStyle(document.body).getPropertyValue('--primary-actions')
62
+ const halfValue = average / 2;
63
+ canvasContext.clearRect(0, halfLineHeight, lineWidth, halfLineHeight);
64
+ canvasContext.fillRect(0, halfLineHeight, lineWidth, getMaxSmallIndicatorHeight(halfValue));
65
+ canvasContext.clearRect(0, halfLineHeight, lineWidth, -halfLineHeight);
66
+ canvasContext.fillRect(0, halfLineHeight, lineWidth, 0 - getMaxSmallIndicatorHeight(halfValue));
67
+ canvasContext.clearRect(lineWidth * 2, halfLineHeight, lineWidth, halfLineHeight);
68
+ canvasContext.fillRect(lineWidth * 2, halfLineHeight, lineWidth, average);
69
+ canvasContext.clearRect(lineWidth * 2, halfLineHeight, lineWidth, -halfLineHeight);
70
+ canvasContext.fillRect(lineWidth * 2, halfLineHeight, lineWidth, 0 - average);
71
+ canvasContext.clearRect(lineWidth * 4, halfLineHeight, lineWidth, halfLineHeight);
72
+ canvasContext.fillRect(lineWidth * 4, halfLineHeight, lineWidth, getMaxSmallIndicatorHeight(halfValue));
73
+ canvasContext.clearRect(lineWidth * 4, halfLineHeight, lineWidth, -halfLineHeight);
74
+ canvasContext.fillRect(lineWidth * 4, halfLineHeight, lineWidth, 0 - getMaxSmallIndicatorHeight(halfValue));
75
+ }, 200);
76
+ };
@@ -0,0 +1,32 @@
1
+ export default class Collector {
2
+ constructor(cfg: any, refProbeId: any);
3
+ _callbacks: {
4
+ onreport: null;
5
+ onticket: null;
6
+ };
7
+ _id: string;
8
+ _moduleName: string;
9
+ _probeId: any;
10
+ _config: any;
11
+ _exporter: Exporter;
12
+ _state: string;
13
+ analyze(stats: any, previousReport: any, beforeLastReport: any, referenceReport: any): any;
14
+ takeReferenceStats(): Promise<any>;
15
+ collectStats(): Promise<any>;
16
+ start(): Promise<void>;
17
+ set state(arg: string);
18
+ get state(): string;
19
+ _startedTime: Date | undefined;
20
+ mute(): Promise<void>;
21
+ unmute(): Promise<void>;
22
+ stop(forced: any): Promise<void>;
23
+ _stoppedTime: Date | undefined;
24
+ registerCallback(name: any, callback: any, context: any): void;
25
+ unregisterCallback(name: any): void;
26
+ fireOnReport(report: any): void;
27
+ fireOnTicket(ticket: any): void;
28
+ updateConfig(config: any): void;
29
+ addCustomEvent(at: any, category: any, name: any, description: any): void;
30
+ registerToPCEvents(): Promise<void>;
31
+ }
32
+ import Exporter from "./exporter";
@@ -0,0 +1,282 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const exporter_1 = __importDefault(require("./exporter"));
16
+ const extractor_1 = require("./extractor");
17
+ const score_1 = require("./utils/score");
18
+ const models_1 = require("./utils/models");
19
+ const helper_1 = require("./utils/helper");
20
+ const log_1 = require("./utils/log");
21
+ class Collector {
22
+ constructor(cfg, refProbeId) {
23
+ this._callbacks = {
24
+ onreport: null,
25
+ onticket: null,
26
+ };
27
+ this._id = (0, helper_1.createCollectorId)();
28
+ this._moduleName = this._id;
29
+ this._probeId = refProbeId;
30
+ this._config = cfg;
31
+ this._exporter = new exporter_1.default(cfg);
32
+ this._state = models_1.COLLECTOR_STATE.IDLE;
33
+ this.registerToPCEvents();
34
+ (0, log_1.info)(this._moduleName, `new collector created for probe ${this._probeId}`);
35
+ }
36
+ analyze(stats, previousReport, beforeLastReport, referenceReport) {
37
+ const getDefaultSSRCMetric = (kind, reportType) => {
38
+ if (kind === models_1.VALUE.AUDIO) {
39
+ if (reportType === models_1.TYPE.INBOUND_RTP) {
40
+ return Object.assign({}, models_1.defaultAudioMetricIn);
41
+ }
42
+ return Object.assign({}, models_1.defaultAudioMetricOut);
43
+ }
44
+ if (reportType === models_1.TYPE.INBOUND_RTP) {
45
+ return Object.assign({}, models_1.defaultVideoMetricIn);
46
+ }
47
+ return Object.assign({}, models_1.defaultVideoMetricOut);
48
+ };
49
+ const report = (0, models_1.getDefaultMetric)(previousReport);
50
+ report.pname = this._config.pname;
51
+ report.call_id = this._config.cid;
52
+ report.user_id = this._config.uid;
53
+ report.count = previousReport ? previousReport.count + 1 : 1;
54
+ let timestamp = null;
55
+ stats.forEach((stat) => {
56
+ if (!timestamp && stat.timestamp) {
57
+ timestamp = stat.timestamp;
58
+ }
59
+ const values = (0, extractor_1.extract)(stat, report, report.pname, referenceReport);
60
+ values.forEach((data) => {
61
+ if (data.value && data.type) {
62
+ if (data.ssrc) {
63
+ let ssrcReport = report[data.type][data.ssrc];
64
+ if (!ssrcReport) {
65
+ ssrcReport = getDefaultSSRCMetric(data.type, stat.type);
66
+ ssrcReport.ssrc = data.ssrc;
67
+ report[data.type][data.ssrc] = (ssrcReport);
68
+ }
69
+ Object.keys(data.value).forEach((key) => {
70
+ ssrcReport[key] = data.value[key];
71
+ });
72
+ }
73
+ else {
74
+ Object.keys(data.value).forEach((key) => {
75
+ report[data.type][key] = data.value[key];
76
+ });
77
+ }
78
+ }
79
+ });
80
+ });
81
+ report.timestamp = timestamp;
82
+ Object.keys(report[models_1.VALUE.AUDIO]).forEach((key) => {
83
+ const ssrcReport = report[models_1.VALUE.AUDIO][key];
84
+ if (ssrcReport.direction === models_1.DIRECTION.INBOUND) {
85
+ ssrcReport.mos_emodel_in = (0, score_1.computeEModelMOS)(report, models_1.VALUE.AUDIO, previousReport, beforeLastReport, ssrcReport.ssrc);
86
+ ssrcReport.mos_in = (0, score_1.computeMOS)(report, models_1.VALUE.AUDIO, previousReport, beforeLastReport, ssrcReport.ssrc);
87
+ }
88
+ else {
89
+ ssrcReport.mos_emodel_out = (0, score_1.computeEModelMOSForOutgoing)(report, models_1.VALUE.AUDIO, previousReport, beforeLastReport, ssrcReport.ssrc);
90
+ ssrcReport.mos_out = (0, score_1.computeMOSForOutgoing)(report, models_1.VALUE.AUDIO, previousReport, beforeLastReport, ssrcReport.ssrc);
91
+ }
92
+ });
93
+ return report;
94
+ }
95
+ takeReferenceStats() {
96
+ return __awaiter(this, void 0, void 0, function* () {
97
+ return new Promise((resolve, reject) => {
98
+ const preWaitTime = Date.now();
99
+ setTimeout(() => __awaiter(this, void 0, void 0, function* () {
100
+ try {
101
+ const waitTime = Date.now() - preWaitTime;
102
+ const preTime = Date.now();
103
+ const reports = yield this._config.pc.getStats();
104
+ const referenceReport = this.analyze(reports, null, null, null);
105
+ const postTime = Date.now();
106
+ referenceReport.experimental.time_to_measure_ms = postTime - preTime;
107
+ referenceReport.experimental.time_to_wait_ms = waitTime;
108
+ this._exporter.saveReferenceReport(referenceReport);
109
+ (0, log_1.debug)(this._moduleName, `got reference report for probe ${this._probeId}`);
110
+ resolve();
111
+ }
112
+ catch (err) {
113
+ reject(err);
114
+ }
115
+ }), this._config.startAfter);
116
+ });
117
+ });
118
+ }
119
+ collectStats() {
120
+ return __awaiter(this, void 0, void 0, function* () {
121
+ try {
122
+ if (this._state !== models_1.COLLECTOR_STATE.RUNNING || !this._config.pc) {
123
+ (0, log_1.debug)(this._moduleName, `report discarded (too late) for probe ${this._probeId}`);
124
+ return null;
125
+ }
126
+ // Take into account last report in case no report have been generated (eg: candidate-pair)
127
+ const preTime = Date.now();
128
+ const reports = yield this._config.pc.getStats();
129
+ const report = this.analyze(reports, this._exporter.getLastReport(), this._exporter.getBeforeLastReport(), this._exporter.getReferenceReport());
130
+ const postTime = Date.now();
131
+ report.experimental.time_to_measure_ms = postTime - preTime;
132
+ this._exporter.addReport(report);
133
+ (0, log_1.debug)(this._moduleName, `got report for probe ${this._probeId}#${this._exporter.getReportsNumber() + 1}`);
134
+ this.fireOnReport(report);
135
+ return report;
136
+ }
137
+ catch (err) {
138
+ (0, log_1.error)(this._moduleName, `got error ${err}`);
139
+ return null;
140
+ }
141
+ });
142
+ }
143
+ start() {
144
+ return __awaiter(this, void 0, void 0, function* () {
145
+ (0, log_1.debug)(this._moduleName, "starting");
146
+ this.state = models_1.COLLECTOR_STATE.RUNNING;
147
+ this._startedTime = this._exporter.start();
148
+ (0, log_1.debug)(this._moduleName, "started");
149
+ });
150
+ }
151
+ mute() {
152
+ return __awaiter(this, void 0, void 0, function* () {
153
+ this.state = models_1.COLLECTOR_STATE.MUTED;
154
+ (0, log_1.debug)(this._moduleName, "muted");
155
+ });
156
+ }
157
+ unmute() {
158
+ return __awaiter(this, void 0, void 0, function* () {
159
+ this.state = models_1.COLLECTOR_STATE.RUNNING;
160
+ (0, log_1.debug)(this._moduleName, "unmuted");
161
+ });
162
+ }
163
+ stop(forced) {
164
+ return __awaiter(this, void 0, void 0, function* () {
165
+ (0, log_1.debug)(this._moduleName, `stopping${forced ? " by watchdog" : ""}...`);
166
+ this._stoppedTime = this._exporter.stop();
167
+ this.state = models_1.COLLECTOR_STATE.IDLE;
168
+ if (this._config.ticket) {
169
+ const { ticket } = this._exporter;
170
+ this.fireOnTicket(ticket);
171
+ }
172
+ this._exporter.reset();
173
+ (0, log_1.debug)(this._moduleName, "stopped");
174
+ });
175
+ }
176
+ registerCallback(name, callback, context) {
177
+ if (name in this._callbacks) {
178
+ this._callbacks[name] = {
179
+ callback,
180
+ context,
181
+ };
182
+ (0, log_1.debug)(this._moduleName, `registered callback '${name}'`);
183
+ }
184
+ else {
185
+ (0, log_1.error)(this._moduleName, `can't register callback for '${name}' - not found`);
186
+ }
187
+ }
188
+ unregisterCallback(name) {
189
+ if (name in this._callbacks) {
190
+ this._callbacks[name] = null;
191
+ delete this._callbacks[name];
192
+ (0, log_1.debug)(this._moduleName, `unregistered callback '${name}'`);
193
+ }
194
+ else {
195
+ (0, log_1.error)(this._moduleName, `can't unregister callback for '${name}' - not found`);
196
+ }
197
+ }
198
+ fireOnReport(report) {
199
+ if (this._callbacks.onreport) {
200
+ (0, helper_1.call)(this._callbacks.onreport.callback, this._callbacks.onreport.context, report);
201
+ }
202
+ }
203
+ fireOnTicket(ticket) {
204
+ if (this._callbacks.onticket) {
205
+ (0, helper_1.call)(this._callbacks.onticket.callback, this._callbacks.onticket.context, ticket);
206
+ }
207
+ }
208
+ updateConfig(config) {
209
+ this._config = config;
210
+ this._exporter.updateConfig(config);
211
+ }
212
+ get state() {
213
+ return this._state;
214
+ }
215
+ set state(newState) {
216
+ this._state = newState;
217
+ (0, log_1.debug)(this._moduleName, `state changed to ${newState}`);
218
+ }
219
+ addCustomEvent(at, category, name, description) {
220
+ this._exporter.addCustomEvent({
221
+ at: typeof at === "object" ? at.toJSON() : at,
222
+ category,
223
+ name,
224
+ description,
225
+ });
226
+ }
227
+ registerToPCEvents() {
228
+ return __awaiter(this, void 0, void 0, function* () {
229
+ const { pc } = this._config;
230
+ navigator.mediaDevices.ondevicechange = () => __awaiter(this, void 0, void 0, function* () {
231
+ try {
232
+ const devices = yield navigator.mediaDevices.enumerateDevices();
233
+ this.addCustomEvent(new Date().toJSON(), "device", `${devices.length} devices found`, "Media Devices state");
234
+ // eslint-disable-next-line no-empty
235
+ }
236
+ catch (err) {
237
+ (0, log_1.error)(this._moduleName, "can't get devices");
238
+ }
239
+ });
240
+ if (pc) {
241
+ pc.oniceconnectionstatechange = () => {
242
+ const value = pc.iceConnectionState;
243
+ if (value === models_1.ICE_CONNECTION_STATE.CONNECTED ||
244
+ value === models_1.ICE_CONNECTION_STATE.COMPLETED) {
245
+ this.addCustomEvent(new Date().toJSON(), "call", value, "ICE connection state");
246
+ }
247
+ else if (value === models_1.ICE_CONNECTION_STATE.DISCONNECTED ||
248
+ value === models_1.ICE_CONNECTION_STATE.FAILED) {
249
+ this.addCustomEvent(new Date().toJSON(), "call", value, "ICE connection state");
250
+ }
251
+ else if (value === models_1.ICE_CONNECTION_STATE.CLOSED) {
252
+ this.addCustomEvent(new Date().toJSON(), "call", "ended", "ICE connection state");
253
+ }
254
+ };
255
+ pc.onicegatheringstatechange = () => {
256
+ const value = pc.iceGatheringState;
257
+ this.addCustomEvent(new Date().toJSON(), "call", value, "ICE gathering state");
258
+ };
259
+ pc.ontrack = (e) => {
260
+ this.addCustomEvent(new Date().toJSON(), "call", `${e.track.kind}track`, "MediaStreamTrack received");
261
+ };
262
+ pc.onnegotiationneeded = () => {
263
+ this.addCustomEvent(new Date().toJSON(), "call", "negotiation", "Media changed");
264
+ };
265
+ const receivers = pc.getReceivers();
266
+ if (receivers && receivers.length > 0) {
267
+ const receiver = receivers[0];
268
+ const { transport } = receiver;
269
+ if (transport) {
270
+ const { iceTransport } = transport;
271
+ if (iceTransport) {
272
+ iceTransport.onselectedcandidatepairchange = () => {
273
+ this.addCustomEvent(new Date().toJSON(), "call", "transport", "Candidates Pair changed");
274
+ };
275
+ }
276
+ }
277
+ }
278
+ }
279
+ });
280
+ }
281
+ }
282
+ exports.default = Collector;