@wazo/sdk 0.48.3 → 0.49.1

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/dist/index.js CHANGED
@@ -79,6 +79,7 @@ __webpack_require__.d(__webpack_exports__, {
79
79
  WazoApiClient: ()=>ApiClient,
80
80
  BadResponse: ()=>BadResponse,
81
81
  CTIPhone: ()=>CTIPhone,
82
+ RefreshTokenError: ()=>RefreshTokenError,
82
83
  CallSession: ()=>CallSession,
83
84
  Session: ()=>Session,
84
85
  setOnRefreshTokenError: ()=>setOnRefreshTokenError,
@@ -97,7 +98,7 @@ __webpack_require__.d(__webpack_exports__, {
97
98
  Contact: ()=>Contact,
98
99
  setCurrentServer: ()=>setCurrentServer,
99
100
  parsePhoneNumber: ()=>parsePhoneNumber,
100
- SessionDescriptionHandler: ()=>WazoSessionDescriptionHandler,
101
+ SessionDescriptionHandler: ()=>lib_WazoSessionDescriptionHandler,
101
102
  default: ()=>src_0,
102
103
  SFUNotAvailableError: ()=>SFUNotAvailableError,
103
104
  Incall: ()=>Incall,
@@ -4039,6 +4040,14 @@ const auth = (client, baseUrl)=>({
4039
4040
  getProviderAuthUrl: (userUuid, provider)=>client.post(`${baseUrl}/users/${userUuid}/external/${provider}`, {}),
4040
4041
  deleteProviderToken: (userUuid, provider)=>client.delete(`${baseUrl}/users/${userUuid}/external/${provider}`)
4041
4042
  });
4043
+ class RefreshTokenError extends Error {
4044
+ reason;
4045
+ constructor(reason){
4046
+ super(`Token refresh failed: ${reason}`);
4047
+ this.name = 'RefreshTokenError';
4048
+ this.reason = reason;
4049
+ }
4050
+ }
4042
4051
  const base_api_client_logger = IssueReporter ? IssueReporter.loggerFor('api') : console;
4043
4052
  class BaseApiClient {
4044
4053
  client;
@@ -4052,6 +4061,9 @@ class BaseApiClient {
4052
4061
  refreshDomainName;
4053
4062
  isMobile;
4054
4063
  fetchOptions;
4064
+ refreshTokenErrorNotified = false;
4065
+ refreshTokenPromise = null;
4066
+ refreshTokenGeneration = 0;
4055
4067
  constructor({ server, agent = null, refreshToken, clientId, isMobile = false, fetchOptions }){
4056
4068
  this.updateParameters({
4057
4069
  server,
@@ -4081,6 +4093,17 @@ class BaseApiClient {
4081
4093
  return this.refreshTokenCallback();
4082
4094
  }
4083
4095
  async refreshTokenCallback() {
4096
+ if (this.refreshTokenPromise) return this.refreshTokenPromise;
4097
+ const generation = this.refreshTokenGeneration;
4098
+ const promise = this.performTokenRefresh(generation);
4099
+ this.refreshTokenPromise = promise;
4100
+ try {
4101
+ return await promise;
4102
+ } finally{
4103
+ if (this.refreshTokenPromise === promise) this.refreshTokenPromise = null;
4104
+ }
4105
+ }
4106
+ async performTokenRefresh(generation) {
4084
4107
  base_api_client_logger.info('refresh token callback called', {
4085
4108
  refreshToken: obfuscateToken(this.refreshToken),
4086
4109
  refreshBackend: this.refreshBackend,
@@ -4089,22 +4112,57 @@ class BaseApiClient {
4089
4112
  refreshExpiration: this.refreshExpiration,
4090
4113
  isMobile: this.isMobile
4091
4114
  });
4092
- if (!this.refreshToken) return null;
4115
+ if (!this.refreshToken) {
4116
+ this.notifyRefreshTokenError(new RefreshTokenError('no_refresh_token'), 'no_refresh_token', generation);
4117
+ return null;
4118
+ }
4093
4119
  try {
4094
4120
  const session = await this.auth.refreshToken(this.refreshToken, this.refreshBackend, this.refreshExpiration, this.isMobile, this.refreshTenantId, this.refreshDomainName);
4095
- if (!session) return null;
4121
+ if (!session) {
4122
+ this.notifyRefreshTokenError(new RefreshTokenError('empty_session'), 'empty_session', generation);
4123
+ return null;
4124
+ }
4125
+ if (generation !== this.refreshTokenGeneration) {
4126
+ base_api_client_logger.info('ignoring stale token refresh success');
4127
+ return null;
4128
+ }
4096
4129
  base_api_client_logger.info('token refreshed', {
4097
4130
  token: obfuscateToken(session.token)
4098
4131
  });
4099
- if (this.onRefreshToken) this.onRefreshToken(session.token, session);
4132
+ this.refreshTokenErrorNotified = false;
4133
+ this.refreshTokenGeneration += 1;
4100
4134
  this.setToken(session.token);
4135
+ if (this.onRefreshToken) try {
4136
+ this.onRefreshToken(session.token, session);
4137
+ } catch (e) {
4138
+ base_api_client_logger.error('onRefreshToken handler failed', e);
4139
+ }
4101
4140
  return session.token;
4102
4141
  } catch (error) {
4103
4142
  base_api_client_logger.error('token refresh, error', error);
4104
- if (this.onRefreshTokenError) this.onRefreshTokenError(error);
4143
+ this.notifyRefreshTokenError(error, void 0, generation);
4105
4144
  }
4106
4145
  return null;
4107
4146
  }
4147
+ notifyRefreshTokenError(error, reason, generation = this.refreshTokenGeneration) {
4148
+ if (generation !== this.refreshTokenGeneration) {
4149
+ base_api_client_logger.info('ignoring stale token refresh failure', {
4150
+ reason
4151
+ });
4152
+ return;
4153
+ }
4154
+ if (reason) base_api_client_logger.error('token refresh failed', {
4155
+ reason
4156
+ });
4157
+ if (!this.onRefreshTokenError) return;
4158
+ if (this.refreshTokenErrorNotified) return;
4159
+ this.refreshTokenErrorNotified = true;
4160
+ try {
4161
+ this.onRefreshTokenError(error);
4162
+ } catch (e) {
4163
+ base_api_client_logger.error('onRefreshTokenError handler failed', e);
4164
+ }
4165
+ }
4108
4166
  setToken(token) {
4109
4167
  this.client.setToken(token);
4110
4168
  }
@@ -4112,7 +4170,16 @@ class BaseApiClient {
4112
4170
  this.client.setTenant(tenant);
4113
4171
  }
4114
4172
  setRefreshToken(refreshToken) {
4173
+ const changed = refreshToken !== this.refreshToken;
4115
4174
  this.refreshToken = refreshToken;
4175
+ if (refreshToken) {
4176
+ this.refreshTokenErrorNotified = false;
4177
+ if (changed) {
4178
+ this.refreshTokenGeneration += 1;
4179
+ this.refreshTokenPromise = null;
4180
+ this.client.refreshTokenPromise = null;
4181
+ }
4182
+ }
4116
4183
  }
4117
4184
  setRequestTimeout(requestTimeout) {
4118
4185
  this.client.setRequestTimeout(requestTimeout);
@@ -4870,7 +4937,7 @@ const wazoMediaStreamFactory = (constraints)=>{
4870
4937
  if (constraints.screen && !constraints.desktop) return navigator.mediaDevices.getDisplayMedia.call(navigator.mediaDevices, constraints);
4871
4938
  return navigator.mediaDevices.getUserMedia.call(navigator.mediaDevices, constraints);
4872
4939
  };
4873
- class WazoSessionDescriptionHandler_WazoSessionDescriptionHandler extends session_description_handler_namespaceObject.SessionDescriptionHandler {
4940
+ class WazoSessionDescriptionHandler extends session_description_handler_namespaceObject.SessionDescriptionHandler {
4874
4941
  gatheredCandidates;
4875
4942
  eventEmitter;
4876
4943
  isWeb;
@@ -5172,7 +5239,7 @@ class WazoSessionDescriptionHandler_WazoSessionDescriptionHandler extends sessio
5172
5239
  return keysA.every((key, i)=>keysB[i] === key && this._constraintsEqual(a[key], b[key]));
5173
5240
  }
5174
5241
  }
5175
- const WazoSessionDescriptionHandler = WazoSessionDescriptionHandler_WazoSessionDescriptionHandler;
5242
+ const lib_WazoSessionDescriptionHandler = WazoSessionDescriptionHandler;
5176
5243
  const lastIndexOf = (array, method)=>{
5177
5244
  const start = array.length - 1;
5178
5245
  for(let i = start; i >= 0; i--)if (method(array[i])) return i;
@@ -5242,8 +5309,12 @@ const toAssertedIdentity = (identity)=>{
5242
5309
  };
5243
5310
  const MAX_REGISTER_TRIES = 5;
5244
5311
  const NO_ANSWER_TIMEOUT = 86400;
5312
+ let liveClientCount = 0;
5245
5313
  class WebRTCClient extends Emitter {
5246
5314
  clientId;
5315
+ _countedAsLive;
5316
+ _closed;
5317
+ lastTransportMessageAt;
5247
5318
  config;
5248
5319
  uaConfigOverrides;
5249
5320
  userAgent;
@@ -5314,11 +5385,32 @@ class WebRTCClient extends Emitter {
5314
5385
  web_rtc_client_logger.info('sdk webrtc constructor', {
5315
5386
  clientId: this.clientId
5316
5387
  });
5388
+ liveClientCount += 1;
5389
+ this._countedAsLive = true;
5390
+ this._closed = false;
5391
+ this.lastTransportMessageAt = null;
5392
+ if (liveClientCount > 1) web_rtc_client_logger.info('sdk webrtc multiple clients alive', {
5393
+ liveClientCount,
5394
+ clientId: this.clientId
5395
+ });
5317
5396
  this.uaConfigOverrides = uaConfigOverrides;
5318
5397
  this.config = config;
5319
5398
  this.skipRegister = config.skipRegister;
5320
5399
  this._buildConfig(config, session).then((newConfig)=>{
5321
5400
  this.config = newConfig;
5401
+ if (this._closed) {
5402
+ web_rtc_client_logger.info('sdk webrtc constructor, client closed during config build, skipping UA creation', {
5403
+ clientId: this.clientId
5404
+ });
5405
+ return;
5406
+ }
5407
+ if (this.userAgent && !session) {
5408
+ web_rtc_client_logger.info('sdk webrtc constructor, userAgent already created, skipping', {
5409
+ clientId: this.clientId
5410
+ });
5411
+ return;
5412
+ }
5413
+ if (this.registerer) this._cleanupRegister();
5322
5414
  this.userAgent = this.createUserAgent(uaConfigOverrides);
5323
5415
  });
5324
5416
  this.audioOutputDeviceId = config.audioDeviceOutput;
@@ -5377,6 +5469,19 @@ class WebRTCClient extends Emitter {
5377
5469
  this.audio = media.audio;
5378
5470
  }
5379
5471
  createUserAgent(uaConfigOverrides) {
5472
+ this.lastTransportMessageAt = null;
5473
+ if (this.userAgent) {
5474
+ web_rtc_client_logger.warn('sdk webrtc, stopping existing UA before creating a new one', {
5475
+ clientId: this.clientId
5476
+ });
5477
+ this.userAgent.stop().catch((error)=>{
5478
+ web_rtc_client_logger.error('sdk webrtc, error stopping previous UA', {
5479
+ error: error?.message,
5480
+ clientId: this.clientId
5481
+ });
5482
+ });
5483
+ this.userAgent = null;
5484
+ }
5380
5485
  const uaOptions = this._createUaOptions(uaConfigOverrides);
5381
5486
  web_rtc_client_logger.info('sdk webrtc, creating UA', {
5382
5487
  uaOptions: {
@@ -5406,6 +5511,7 @@ class WebRTCClient extends Emitter {
5406
5511
  web_rtc_client_logger.warn('Transport connect error', e);
5407
5512
  });
5408
5513
  ua.transport.onMessage = (rawMessage)=>{
5514
+ this.lastTransportMessageAt = Date.now();
5409
5515
  const message = parser_namespaceObject.Parser.parseMessage(rawMessage, ua.transport.logger);
5410
5516
  ua.onTransportMessage(rawMessage);
5411
5517
  this.eventEmitter.emit(MESSAGE, message);
@@ -5424,6 +5530,16 @@ class WebRTCClient extends Emitter {
5424
5530
  isRegistered() {
5425
5531
  return Boolean(this.registerer && this.registerer.state === registerer_state_namespaceObject.RegistererState.Registered);
5426
5532
  }
5533
+ getLastTransportMessageAt() {
5534
+ return this.lastTransportMessageAt;
5535
+ }
5536
+ isTransportSuspect() {
5537
+ if (!this.isConnected()) return true;
5538
+ if (Object.keys(this.sipSessions).length > 0) return false;
5539
+ const threshold = this.config.transportSilenceThresholdMs;
5540
+ if (!threshold || !this.lastTransportMessageAt) return false;
5541
+ return Date.now() - this.lastTransportMessageAt > threshold;
5542
+ }
5427
5543
  onConnect() {
5428
5544
  web_rtc_client_logger.info('sdk webrtc connected', {
5429
5545
  method: 'delegate.onConnect',
@@ -5447,6 +5563,23 @@ class WebRTCClient extends Emitter {
5447
5563
  this.eventEmitter.emit(UNREGISTERED);
5448
5564
  }
5449
5565
  }
5566
+ async _isRegistrationHealthy() {
5567
+ if (!this.isTransportSuspect()) return true;
5568
+ web_rtc_client_logger.warn('sdk webrtc registered but transport suspect, re-registering', {
5569
+ clientId: this.clientId,
5570
+ connected: this.isConnected(),
5571
+ lastTransportMessageAt: this.lastTransportMessageAt
5572
+ });
5573
+ this._cleanupRegister();
5574
+ if (this.isConnected()) {
5575
+ this.connectionPromise = this._disconnectTransport(true);
5576
+ await this.connectionPromise;
5577
+ this.connectionPromise = null;
5578
+ const transport = this.userAgent?.transport;
5579
+ if (transport && transport.state !== transport_state_namespaceObject.TransportState.Disconnected) transport.transitionState(transport_state_namespaceObject.TransportState.Disconnected);
5580
+ }
5581
+ return false;
5582
+ }
5450
5583
  async register(tries = 0) {
5451
5584
  const logInfo = {
5452
5585
  clientId: this.clientId,
@@ -5459,6 +5592,11 @@ class WebRTCClient extends Emitter {
5459
5592
  skipRegister: this.skipRegister
5460
5593
  };
5461
5594
  this.forceClosed = false;
5595
+ this._closed = false;
5596
+ if (!this._countedAsLive) {
5597
+ this._countedAsLive = true;
5598
+ liveClientCount += 1;
5599
+ }
5462
5600
  if (this.skipRegister) {
5463
5601
  web_rtc_client_logger.info('sdk webrtc skip register...', logInfo);
5464
5602
  return Promise.resolve();
@@ -5468,15 +5606,19 @@ class WebRTCClient extends Emitter {
5468
5606
  web_rtc_client_logger.info('sdk webrtc recreating User Agent');
5469
5607
  this.userAgent = this.createUserAgent(this.uaConfigOverrides);
5470
5608
  }
5471
- if (!this.userAgent || this.isRegistered()) {
5472
- web_rtc_client_logger.info('sdk webrtc registering aborted, already registered or no UA can be created');
5473
- return Promise.resolve();
5609
+ if (!this.userAgent) {
5610
+ web_rtc_client_logger.info('sdk webrtc registering aborted, no UA can be created');
5611
+ return;
5612
+ }
5613
+ if (this.isRegistered() && await this._isRegistrationHealthy()) {
5614
+ web_rtc_client_logger.info('sdk webrtc registering aborted, already registered');
5615
+ return;
5474
5616
  }
5475
5617
  if (this.connectionPromise || this.registerer?.waiting) {
5476
5618
  web_rtc_client_logger.info('sdk webrtc registering aborted due to a registration in progress.', {
5477
5619
  clientId: this.clientId
5478
5620
  });
5479
- return Promise.resolve();
5621
+ return;
5480
5622
  }
5481
5623
  const registerOptions = this._isWeb() ? {} : {
5482
5624
  extraContactHeaderParams: [
@@ -5577,7 +5719,6 @@ class WebRTCClient extends Emitter {
5577
5719
  web_rtc_client_logger.info('sdk webrtc unregistered', {
5578
5720
  clientId: this.clientId
5579
5721
  });
5580
- if (this.registerer) this.registerer.stateChange.addListener(onRegisterStateChange);
5581
5722
  this._cleanupRegister();
5582
5723
  resolve();
5583
5724
  }
@@ -5771,6 +5912,11 @@ class WebRTCClient extends Emitter {
5771
5912
  force
5772
5913
  });
5773
5914
  this.forceClosed = force;
5915
+ this._closed = true;
5916
+ if (this._countedAsLive) {
5917
+ this._countedAsLive = false;
5918
+ liveClientCount = Math.max(0, liveClientCount - 1);
5919
+ }
5774
5920
  this._cleanupMedia();
5775
5921
  this.connectionPromise = null;
5776
5922
  Object.values(this.audioElements).forEach((audioElement)=>{
@@ -6698,7 +6844,7 @@ class WebRTCClient extends Emitter {
6698
6844
  ...options.peerConnectionConfiguration || {}
6699
6845
  }
6700
6846
  };
6701
- return new WazoSessionDescriptionHandler(uaLogger, wazoMediaStreamFactory, sdhOptions, isWeb, session);
6847
+ return new lib_WazoSessionDescriptionHandler(uaLogger, wazoMediaStreamFactory, sdhOptions, isWeb, session);
6702
6848
  },
6703
6849
  transportOptions: {
6704
6850
  traceSip: uaOptionsOverrides?.traceSip || false,
@@ -8659,6 +8805,15 @@ class WebRTCPhone extends Emitter {
8659
8805
  isRegistered() {
8660
8806
  return this.client && this.client.isRegistered();
8661
8807
  }
8808
+ isConnected() {
8809
+ return Boolean(this.client?.isConnected());
8810
+ }
8811
+ isTransportSuspect() {
8812
+ return !this.client || this.client.isTransportSuspect();
8813
+ }
8814
+ getLastTransportMessageAt() {
8815
+ return this.client?.getLastTransportMessageAt() ?? null;
8816
+ }
8662
8817
  enableRinging() {
8663
8818
  WebRTCPhone_logger.info('WebRTC enable ringing');
8664
8819
  this.ringingEnabled = true;
@@ -8978,8 +9133,9 @@ class WebRTCPhone extends Emitter {
8978
9133
  const { state } = sipSession || {};
8979
9134
  const sessionId = this.getSipSessionId(sipSession);
8980
9135
  fromSession = fromSession || this.callSessions[sessionId];
9136
+ const headerCallId = sipSession?.request?.getHeader?.('X-Wazo-Call-ID');
8981
9137
  const callSession = new CallSession({
8982
- callId: fromSession && fromSession.callId,
9138
+ callId: fromSession?.callId || headerCallId || '',
8983
9139
  sipCallId: sessionId,
8984
9140
  sipStatus: state,
8985
9141
  displayName: identity ? identity.displayName || number : number,
@@ -9614,6 +9770,7 @@ const protocolDebugMessages = [
9614
9770
  class Phone_Phone extends Emitter {
9615
9771
  client;
9616
9772
  phone;
9773
+ connectInFlight;
9617
9774
  session;
9618
9775
  sipLine;
9619
9776
  SessionState;
@@ -9683,10 +9840,37 @@ class Phone_Phone extends Emitter {
9683
9840
  this.sipLine = sipLine || this.getPrimaryWebRtcLine();
9684
9841
  if (!this.sipLine) throw new Error('Sorry, no sip lines found for this user');
9685
9842
  options.userUuid = session.uuid || '';
9686
- this.connectWithCredentials(server, this.sipLine, session.displayName(), options);
9843
+ return this.connectWithCredentials(server, this.sipLine, session.displayName(), options);
9687
9844
  }
9688
- connectWithCredentials(server, sipLine, displayName, rawOptions = {}) {
9845
+ async connectWithCredentials(server, sipLine, displayName, rawOptions = {}) {
9689
9846
  if (this.phone) return;
9847
+ if (this.connectInFlight) {
9848
+ Phone_logger.info('connectWithCredentials: connection already in progress, reusing it and ignoring this call\'s arguments', {
9849
+ server,
9850
+ sipLineId: sipLine?.id
9851
+ });
9852
+ return this.connectInFlight;
9853
+ }
9854
+ this.connectInFlight = this._connectWithCredentials(server, sipLine, displayName, rawOptions).finally(()=>{
9855
+ this.connectInFlight = null;
9856
+ });
9857
+ return this.connectInFlight;
9858
+ }
9859
+ async _connectWithCredentials(server, sipLine, displayName, rawOptions = {}) {
9860
+ if (this.client) {
9861
+ Phone_logger.warn('connectWithCredentials: closing previous client before creating a new one');
9862
+ try {
9863
+ await this.client.close();
9864
+ } catch (error) {
9865
+ Phone_logger.error('connectWithCredentials: error while closing previous client', {
9866
+ message: error?.message
9867
+ });
9868
+ }
9869
+ if (this.phone) {
9870
+ Phone_logger.info('connectWithCredentials: phone connected while closing previous client, keeping it');
9871
+ return;
9872
+ }
9873
+ }
9690
9874
  const [host, port = 443] = server.split(':');
9691
9875
  const options = rawOptions;
9692
9876
  options.media = options.media || {
@@ -9723,6 +9907,24 @@ class Phone_Phone extends Emitter {
9723
9907
  }
9724
9908
  this.phone = null;
9725
9909
  }
9910
+ async reset() {
9911
+ Phone_logger.info('phone reset', {
9912
+ phone: !!this.phone,
9913
+ client: !!this.client
9914
+ });
9915
+ await this.disconnect().catch((error)=>{
9916
+ Phone_logger.error('reset: error while disconnecting phone', {
9917
+ message: error?.message
9918
+ });
9919
+ });
9920
+ if (this.client) await this.client.close().catch((error)=>{
9921
+ Phone_logger.error('reset: error while closing client', {
9922
+ message: error?.message
9923
+ });
9924
+ });
9925
+ this.phone = null;
9926
+ this.client = null;
9927
+ }
9726
9928
  async call(extension, withCamera = false, rawSipLine = null, audioOnly = false, conference = false) {
9727
9929
  if (!this.phone) return;
9728
9930
  const sipLine = rawSipLine || this.getPrimaryWebRtcLine();