@flashphoner/websdk 2.0.244 → 2.0.246

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flashphoner/websdk",
3
- "version": "2.0.244",
3
+ "version": "2.0.246",
4
4
  "description": "Official Flashphoner WebCallServer WebSDK package",
5
5
  "main": "./src/flashphoner-core.js",
6
6
  "types": "./src/flashphoner-core.d.ts",
@@ -0,0 +1,237 @@
1
+ 'use strict';
2
+
3
+ function getClientHints(navigator) {
4
+ let {userAgent} = navigator;
5
+ let mobile, platform = '', platformVersion = '', architecture = '', bitness = '', model = '', uaFullVersion = '', fullVersionList = [];
6
+ let platformInfo = userAgent;
7
+ let found = false;
8
+ let versionInfo = userAgent.replace(/\(([^)]+)\)?/g, ($0, $1) => {
9
+ if (!found) {
10
+ platformInfo = $1;
11
+ found = true;
12
+ }
13
+ return '';
14
+ });
15
+ let items = versionInfo.match(/(\S+)\/(\S+)/g);
16
+ let webview = false;
17
+ // detect mobile
18
+ mobile = userAgent.indexOf('Mobile') !== -1;
19
+ let m;
20
+ let m2;
21
+ // detect platform
22
+ if ((m = /Windows NT (\d+(\.\d+)*)/.exec(platformInfo)) !== null) {
23
+ platform = 'Windows';
24
+ // see https://docs.microsoft.com/en-us/microsoft-edge/web-platform/how-to-detect-win11
25
+ let nt2win = {
26
+ '6.1': '0.1', // win-7
27
+ '6.2': '0.2', // win-8
28
+ '6.3': '0.3', // win-8.1
29
+ '10.0': '10.0', // win-10
30
+ '11.0': '13.0', // win-11
31
+ };
32
+ let ver = nt2win[m[1]];
33
+ if (ver)
34
+ platformVersion = padVersion(ver, 3);
35
+ if ((m2 = /\b(WOW64|Win64|x64)\b/.exec(platformInfo)) !== null) {
36
+ architecture = 'x86';
37
+ bitness = '64';
38
+ }
39
+ } else if ((m = /Android (\d+(\.\d+)*)/.exec(platformInfo)) !== null) {
40
+ platform = 'Android';
41
+ platformVersion = padVersion(m[1]);
42
+ if ((m2 = /Linux (\w+)/.exec(navigator.platform)) !== null) {
43
+ if (m2[1]) {
44
+ m2 = parseArch(m2[1]);
45
+ architecture = m2[0];
46
+ bitness = m2[1];
47
+ }
48
+ }
49
+ } else if ((m = /(iPhone|iPod touch); CPU iPhone OS (\d+(_\d+)*)/.exec(platformInfo)) !== null) {
50
+ // see special notes at https://www.whatismybrowser.com/guides/the-latest-user-agent/safari
51
+ platform = 'iOS';
52
+ platformVersion = padVersion(m[2].replace(/_/g, '.'));
53
+ } else if ((m = /(iPad); CPU OS (\d+(_\d+)*)/.exec(platformInfo)) !== null) {
54
+ platform = 'iOS';
55
+ platformVersion = padVersion(m[2].replace(/_/g, '.'));
56
+ } else if ((m = /Macintosh; (Intel|\w+) Mac OS X (\d+([_.]\d+)*)/.exec(platformInfo)) !== null) {
57
+ platform = 'macOS';
58
+ platformVersion = padVersion(m[2].replace(/_/g, '.'));
59
+ } else if ((m = /Linux/.exec(platformInfo)) !== null) {
60
+ platform = 'Linux';
61
+ platformVersion = '';
62
+ // TODO
63
+ } else if ((m = /CrOS (\w+) (\d+(\.\d+)*)/.exec(platformInfo)) !== null) {
64
+ platform = 'Chrome OS';
65
+ platformVersion = padVersion(m[2]);
66
+ m2 = parseArch(m[1]);
67
+ architecture = m2[0];
68
+ bitness = m2[1];
69
+ }
70
+ if (!platform) {
71
+ platform = 'Unknown';
72
+ }
73
+ // detect fullVersionList / brands
74
+ let notABrand = {brand: ' Not;A Brand', version: '99.0.0.0'};
75
+ if ((m = /Chrome\/(\d+(\.\d+)*)/.exec(versionInfo)) !== null && navigator.vendor === 'Google Inc.') {
76
+ fullVersionList.push({brand: 'Chromium', version: padVersion(m[1], 4)});
77
+ if ((m2 = /(Edge?)\/(\d+(\.\d+)*)/.exec(versionInfo)) !== null) {
78
+ let identBrandMap = {
79
+ 'Edge': 'Microsoft Edge',
80
+ 'Edg': 'Microsoft Edge',
81
+ };
82
+ let brand = identBrandMap[m[1]];
83
+ fullVersionList.push({brand: brand, version: padVersion(m2[2], 4)});
84
+ } else {
85
+ fullVersionList.push({brand: 'Google Chrome', version: padVersion(m[1], 4)});
86
+ }
87
+ if (/\bwv\b/.exec(platformInfo)) {
88
+ webview = true;
89
+ }
90
+ } else if ((m = /AppleWebKit\/(\d+(\.\d+)*)/.exec(versionInfo)) !== null && navigator.vendor === 'Apple Computer, Inc.') {
91
+ fullVersionList.push({brand: 'WebKit', version: padVersion(m[1])});
92
+ if (platform === 'iOS' && (m2 = /(CriOS|EdgiOS|FxiOS|Version)\/(\d+(\.\d+)*)/.exec(versionInfo)) != null) {
93
+ let identBrandMap = { // no
94
+ 'CriOS': 'Google Chrome',
95
+ 'EdgiOS': 'Microsoft Edge',
96
+ 'FxiOS': 'Mozilla Firefox',
97
+ 'Version': 'Apple Safari',
98
+ };
99
+ let brand = identBrandMap[m2[1]];
100
+ fullVersionList.push({brand, version: padVersion(m2[2])});
101
+ if (items.findIndex((s) => s.startsWith('Safari/')) === -1) {
102
+ webview = true;
103
+ }
104
+ }
105
+ } else if ((m = /Firefox\/(\d+(\.\d+)*)/.exec(versionInfo)) !== null) {
106
+ fullVersionList.push({brand: 'Firefox', version: padVersion(m[1])});
107
+ } else {
108
+ fullVersionList.push(notABrand);
109
+ }
110
+ uaFullVersion = fullVersionList.length > 0 ? fullVersionList[fullVersionList.length - 1] : '';
111
+ let brands = fullVersionList.map((b) => {
112
+ let pos = b.version.indexOf('.');
113
+ let version = pos === -1 ? b.version : b.version.slice(0, pos);
114
+ return {brand: b.brand, version};
115
+ });
116
+ // TODO detect architecture, bitness and model
117
+ return {
118
+ mobile,
119
+ platform,
120
+ brands,
121
+ platformVersion,
122
+ architecture,
123
+ bitness,
124
+ model,
125
+ uaFullVersion,
126
+ fullVersionList,
127
+ webview
128
+ };
129
+ }
130
+
131
+ function parseArch(arch) {
132
+ switch (arch) {
133
+ case 'x86_64':
134
+ case 'x64':
135
+ return ['x86', '64'];
136
+ case 'x86_32':
137
+ case 'x86':
138
+ return ['x86', ''];
139
+ case 'armv6l':
140
+ case 'armv7l':
141
+ case 'armv8l':
142
+ return [arch, ''];
143
+ case 'aarch64':
144
+ return ['arm', '64'];
145
+ default:
146
+ return ['', ''];
147
+ }
148
+ }
149
+ function padVersion(ver, minSegs = 3) {
150
+ let parts = ver.split('.');
151
+ let len = parts.length;
152
+ if (len < minSegs) {
153
+ for (let i = 0, lenToPad = minSegs - len; i < lenToPad; i += 1) {
154
+ parts.push('0');
155
+ }
156
+ return parts.join('.');
157
+ }
158
+ return ver;
159
+ }
160
+
161
+ class NavigatorUAData {
162
+ constructor(navigator) {
163
+ this._ch = getClientHints(navigator);
164
+ Object.defineProperties(this, {
165
+ _ch: {enumerable: false},
166
+ });
167
+ }
168
+ get mobile() {
169
+ return this._ch.mobile;
170
+ }
171
+ get platform() {
172
+ return this._ch.platform;
173
+ }
174
+ get brands() {
175
+ return this._ch.brands;
176
+ }
177
+ getHighEntropyValues(hints) {
178
+ return new Promise((resolve, reject) => {
179
+ if (!Array.isArray(hints)) {
180
+ throw new TypeError('argument hints is not an array');
181
+ }
182
+ let hintSet = new Set(hints);
183
+ let data = this._ch;
184
+ let obj = {
185
+ mobile: data.mobile,
186
+ platform: data.platform,
187
+ brands: data.brands,
188
+ };
189
+ if (hintSet.has('architecture'))
190
+ obj.architecture = data.architecture;
191
+ if (hintSet.has('bitness'))
192
+ obj.bitness = data.bitness;
193
+ if (hintSet.has('model'))
194
+ obj.model = data.model;
195
+ if (hintSet.has('platformVersion'))
196
+ obj.platformVersion = data.platformVersion;
197
+ if (hintSet.has('uaFullVersion'))
198
+ obj.uaFullVersion = data.uaFullVersion;
199
+ if (hintSet.has('fullVersionList'))
200
+ obj.fullVersionList = data.fullVersionList;
201
+ resolve(obj);
202
+ });
203
+ }
204
+ toJSON() {
205
+ let data = this._ch;
206
+ return {
207
+ mobile: data.mobile,
208
+ brands: data.brands,
209
+ };
210
+ }
211
+ }
212
+ Object.defineProperty(NavigatorUAData.prototype, Symbol.toStringTag, {
213
+ enumerable: false,
214
+ configurable: true,
215
+ writable: false,
216
+ value: 'NavigatorUAData'
217
+ });
218
+
219
+ async function getClientInfo(navigator, keys) {
220
+ let info = {};
221
+ if (location.protocol === 'https:') {
222
+ if (!keys) {
223
+ keys = ['brands', 'mobile', 'platform', 'platformVersion', 'architecture', 'bitness', 'model', 'fullVersionList'];
224
+ }
225
+ if (!navigator.userAgentData) {
226
+ let customUAData = new NavigatorUAData(navigator);
227
+ info = await customUAData.getHighEntropyValues(keys);
228
+ } else {
229
+ info = await navigator.userAgentData.getHighEntropyValues(keys);
230
+ }
231
+ }
232
+ return info;
233
+ }
234
+
235
+ module.exports = {
236
+ getClientInfo
237
+ };
@@ -4,6 +4,7 @@ const { v1: uuid_v1 } = require('uuid');
4
4
  const constants = require("./constants");
5
5
  const util = require('./util');
6
6
  const LoggerObject = require('./util').logger;
7
+ const clientInfo = require('./client-info');
7
8
  const Promise = require('promise-polyfill');
8
9
  const KalmanFilter = require('kalmanjs');
9
10
  const browserDetails = require('webrtc-adapter').default.browserDetails;
@@ -11,6 +12,7 @@ const LOG_PREFIX = "core";
11
12
  var coreLogger;
12
13
  var loggerConf = {push: false, severity: "INFO"};
13
14
  var isUsingTemasysPlugin = false;
15
+ var clientUAData;
14
16
 
15
17
  /**
16
18
  * @namespace Flashphoner
@@ -62,7 +64,7 @@ var disableConnectionQualityCalculation;
62
64
  * @throws {Error} Error if none of MediaProviders available
63
65
  * @memberof Flashphoner
64
66
  */
65
- var init = function (options) {
67
+ var init = async function (options) {
66
68
  if (!initialized) {
67
69
  if (!options) {
68
70
  options = {};
@@ -190,8 +192,12 @@ var init = function (options) {
190
192
  if (!waitingTemasys && options.mediaProvidersReadyCallback) {
191
193
  options.mediaProvidersReadyCallback(Object.keys(MediaProvider));
192
194
  }
195
+
193
196
  coreLogger.info(LOG_PREFIX, "Initialized");
194
197
  initialized = true;
198
+
199
+ clientUAData = await clientInfo.getClientInfo(window.navigator);
200
+ coreLogger.info(LOG_PREFIX, "Client system data: " + JSON.stringify(clientUAData));
195
201
  }
196
202
  };
197
203
 
@@ -572,13 +578,16 @@ var createSession = function (options) {
572
578
  appKey: appKey,
573
579
  mediaProviders: Object.keys(MediaProvider),
574
580
  keepAlive: keepAlive,
575
- authToken:authToken,
581
+ authToken: authToken,
576
582
  clientVersion: "2.0",
577
583
  clientOSVersion: window.navigator.appVersion,
578
584
  clientBrowserVersion: window.navigator.userAgent,
579
585
  msePacketizationVersion: 2,
580
586
  custom: options.custom
581
587
  };
588
+ if (clientUAData) {
589
+ cConfig.clientInfo = clientUAData;
590
+ }
582
591
  if (sipConfig) {
583
592
  util.copyObjectPropsToAnotherObject(sipConfig, cConfig);
584
593
  }
@@ -1009,6 +1018,8 @@ var createSession = function (options) {
1009
1018
  stripCodecs: stripCodecs
1010
1019
  });
1011
1020
  }).then(function (offer) {
1021
+ // Get local media info to send in publishStream message
1022
+ let localMediaInfo = collectLocalMediaInfo(MediaProvider[mediaProvider], localDisplay);
1012
1023
  send("call", {
1013
1024
  callId: id_,
1014
1025
  incoming: false,
@@ -1021,7 +1032,8 @@ var createSession = function (options) {
1021
1032
  caller: login,
1022
1033
  callee: callee_,
1023
1034
  custom: options.custom,
1024
- visibleName: visibleName_
1035
+ visibleName: visibleName_,
1036
+ localMediaInfo: localMediaInfo
1025
1037
  });
1026
1038
  });
1027
1039
  }).catch(function (error) {
@@ -1155,6 +1167,8 @@ var createSession = function (options) {
1155
1167
  });
1156
1168
  }).then(function (sdp) {
1157
1169
  if (status_ != CALL_STATUS.FINISH && status_ != CALL_STATUS.FAILED) {
1170
+ // Get local media info to send in publishStream message
1171
+ let localMediaInfo = collectLocalMediaInfo(MediaProvider[mediaProvider], localDisplay);
1158
1172
  send("answer", {
1159
1173
  callId: id_,
1160
1174
  incoming: true,
@@ -1166,7 +1180,8 @@ var createSession = function (options) {
1166
1180
  sipSDP: sipSDP,
1167
1181
  caller: cConfig.login,
1168
1182
  callee: callee_,
1169
- custom: options.custom
1183
+ custom: options.custom,
1184
+ localMediaInfo: localMediaInfo
1170
1185
  });
1171
1186
  } else {
1172
1187
  hangup();
@@ -2006,6 +2021,7 @@ var createSession = function (options) {
2006
2021
  }
2007
2022
  return;
2008
2023
  }
2024
+
2009
2025
  //create mediaProvider connection
2010
2026
  MediaProvider[mediaProvider].createConnection({
2011
2027
  id: id_,
@@ -2028,6 +2044,8 @@ var createSession = function (options) {
2028
2044
  });
2029
2045
  }).then(function (offer) {
2030
2046
  logger.debug(LOG_PREFIX, "Offer SDP:\n" + offer.sdp);
2047
+ // Get local media info to send in publishStream message
2048
+ let localMediaInfo = collectLocalMediaInfo(MediaProvider[mediaProvider], display);
2031
2049
  //publish stream with offer sdp to server
2032
2050
  send("publishStream", {
2033
2051
  mediaSessionId: id_,
@@ -2046,7 +2064,8 @@ var createSession = function (options) {
2046
2064
  rtmpUrl: rtmpUrl,
2047
2065
  constraints: constraints,
2048
2066
  transport: transportType,
2049
- cvoExtension: cvoExtension
2067
+ cvoExtension: cvoExtension,
2068
+ localMediaInfo: localMediaInfo
2050
2069
  });
2051
2070
  });
2052
2071
  }).catch(function (error) {
@@ -2856,6 +2875,65 @@ var createSession = function (options) {
2856
2875
  return sessionLogger;
2857
2876
  };
2858
2877
 
2878
+ const collectLocalMediaInfo = function (mediaProvider, display) {
2879
+ // Get devices available
2880
+ let videoCams = mediaProvider.videoCams || [];
2881
+ let mics = mediaProvider.mics || [];
2882
+
2883
+ if (videoCams.length) {
2884
+ logger.info(LOG_PREFIX, "Video inputs available: " + JSON.stringify(videoCams));
2885
+ }
2886
+ if (mics.length) {
2887
+ logger.info(LOG_PREFIX, "Audio inputs available: " + JSON.stringify(mics));
2888
+ }
2889
+
2890
+ // Get track labels to identify publishing device
2891
+ let audioTracks = [];
2892
+ let videoTracks = [];
2893
+ let localVideo;
2894
+ if (mediaProvider.getCacheInstance) {
2895
+ localVideo = mediaProvider.getCacheInstance(display);
2896
+ }
2897
+ if (!localVideo && mediaProvider.getVideoElement) {
2898
+ localVideo = mediaProvider.getVideoElement(display);
2899
+ }
2900
+ if (localVideo) {
2901
+ localVideo.srcObject.getAudioTracks().forEach((track) => {
2902
+ let device = track.label;
2903
+ if (device === "MediaStreamAudioDestinationNode" && mediaProvider.getAudioSourceDevice) {
2904
+ device = mediaProvider.getAudioSourceDevice();
2905
+ }
2906
+ audioTracks.push({
2907
+ trackId: track.id,
2908
+ device: device
2909
+ });
2910
+ });
2911
+ localVideo.srcObject.getVideoTracks().forEach((track) => {
2912
+ videoTracks.push({
2913
+ trackId: track.id,
2914
+ device: track.label
2915
+ });
2916
+ });
2917
+ }
2918
+ if (videoTracks.length) {
2919
+ logger.info(LOG_PREFIX, "Video tracks captured: " + JSON.stringify(videoTracks));
2920
+ }
2921
+ if (audioTracks.length) {
2922
+ logger.info(LOG_PREFIX, "Audio tracks captured: " + JSON.stringify(audioTracks));
2923
+ }
2924
+
2925
+ return {
2926
+ devices: {
2927
+ video: videoCams,
2928
+ audio: mics
2929
+ },
2930
+ tracks: {
2931
+ video: videoTracks,
2932
+ audio: audioTracks
2933
+ }
2934
+ };
2935
+ }
2936
+
2859
2937
  //export Session
2860
2938
  session.id = id;
2861
2939
  session.status = status;
@@ -19,6 +19,8 @@ var validBrowsers = ["firefox", "chrome", "safari"];
19
19
  var videoCams = [];
20
20
  // list of presented audio input devices
21
21
  var mics = [];
22
+ // current audio source device label
23
+ let audioSourceDevice = "";
22
24
 
23
25
  var createConnection = function (options) {
24
26
  return new Promise(function (resolve, reject) {
@@ -955,10 +957,14 @@ var getMediaAccess = function (constraints, display, disableConstraintsNormaliza
955
957
  // WCS-2933, fix mobile streaming issues, gather info about available devices before streaming, but not during
956
958
  listDevices(false).then((devices) => {
957
959
  devices.video.forEach(function (device) {
958
- videoCams.push(device);
960
+ if (!videoCams.find((cam) => device.id === cam.id)) {
961
+ videoCams.push(device);
962
+ }
959
963
  })
960
964
  devices.audio.forEach(function (device) {
961
- mics.push(device);
965
+ if (!mics.find((mic) => device.id === mic.id)) {
966
+ mics.push(device);
967
+ }
962
968
  })
963
969
  navigator.getUserMedia(constraints, function (stream) {
964
970
  loadVideo(display, stream, screenShare, requestAudioConstraints, resolve, constraints, useCanvas);
@@ -1188,6 +1194,7 @@ var createGainNode = function (stream) {
1188
1194
  source.connect(gainNode);
1189
1195
  gainNode.connect(destination);
1190
1196
  var sourceAudioTrack = stream.getAudioTracks()[0];
1197
+ audioSourceDevice = sourceAudioTrack.label;
1191
1198
  gainNode.sourceAudioTrack = sourceAudioTrack;
1192
1199
  gainNode.release = function () {
1193
1200
  this.sourceAudioTrack.stop();
@@ -1198,6 +1205,10 @@ var createGainNode = function (stream) {
1198
1205
  return gainNode;
1199
1206
  };
1200
1207
 
1208
+ const getAudioSourceDevice = function () {
1209
+ return audioSourceDevice;
1210
+ }
1211
+
1201
1212
  //Fix to set screen resolution for screen sharing in Firefox
1202
1213
  var setScreenResolution = function (video, stream, constraints) {
1203
1214
  var newHeight;
@@ -1314,6 +1325,22 @@ function getCacheInstance(display) {
1314
1325
  }
1315
1326
  }
1316
1327
 
1328
+ function getVideoElement(display) {
1329
+ if (display) {
1330
+ for (const child of display.children) {
1331
+ if (child.tagName.toLowerCase() === "video") {
1332
+ return child;
1333
+ } else {
1334
+ let grandchild = getVideoElement(child);
1335
+ if (grandchild) {
1336
+ return grandchild;
1337
+ }
1338
+ }
1339
+ }
1340
+ }
1341
+ return null;
1342
+ }
1343
+
1317
1344
  function createVideoElement(useControls = false) {
1318
1345
  let video = document.createElement('video');
1319
1346
  // Prepare video tag to auto play and add specific Safari tweaks #WCS-2425
@@ -1440,8 +1467,8 @@ var listDevices = function (labels, kind, deviceConstraints) {
1440
1467
  return;
1441
1468
  }
1442
1469
  navigator.getUserMedia(constraints, function (stream) {
1443
- navigator.mediaDevices.enumerateDevices().then(function (devicesWithLabales) {
1444
- resolve(getList(devicesWithLabales));
1470
+ navigator.mediaDevices.enumerateDevices().then(function (devicesWithLabels) {
1471
+ resolve(getList(devicesWithLabels));
1445
1472
  stream.getTracks().forEach(function (track) {
1446
1473
  track.stop();
1447
1474
  });
@@ -1581,5 +1608,10 @@ module.exports = {
1581
1608
  logger = configuration.logger;
1582
1609
  createMicGainNode = (typeof configuration.createMicGainNode !== 'undefined') ? configuration.createMicGainNode : true;
1583
1610
  logger.info(LOG_PREFIX, "Initialized");
1584
- }
1611
+ },
1612
+ videoCams: videoCams,
1613
+ mics: mics,
1614
+ getAudioSourceDevice: getAudioSourceDevice,
1615
+ getCacheInstance: getCacheInstance,
1616
+ getVideoElement: getVideoElement
1585
1617
  };