@16x/webphone-sdk 3.2.6 → 3.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.2.8
4
+
5
+ 修复平台抖动后 dial 永久不可自愈(CALL_NOT_CONNECTED):
6
+
7
+ - `ReconnectManager` 新增 `isRecovered` 校验:recover() 成功返回不再被当作连接已恢复,未恢复按失败继续退避。
8
+ - `recoverInvalidSession` 重建失败不再 `reconnect.cancel()` 永久取消重连循环(3.2.7 事故主根因),保持封顶退避重试。
9
+ - `recoverConnection` 在会话被清空后改走 `performConnect` 全新建会话;在途通话超过 120s 宽限后强制终态(终态事件不可达时)。
10
+ - 媒体释放屏障增加 5s 超时,防止永不 settle 的释放操作锁死重连。
11
+
12
+ ## 3.2.7 - 2026-10-08
13
+
14
+ - Treat ICE probe timeouts as unknown instead of permanently marking servers unavailable; preserve
15
+ the original ICE pool for later re-probing.
16
+ - Keep ICE probing in the background so it does not block dialing. Use recently available ICE
17
+ servers for new calls with a five-minute cache, and re-probe after network recovery, SIP
18
+ reconnection, ICE failure, or cache expiry.
19
+ - Add bounded retry backoff and diagnostics for probe state, unknown/unavailable URL counts, cache
20
+ age, and the ICE configuration selected for the next call.
21
+
3
22
  ## 3.2.6 - 2026-10-07
4
23
 
5
24
  - Fix the multi-egress-office dial delay at its root: a dead STUN/TURN-UDP endpoint dragged JsSIP's
@@ -1 +1 @@
1
- const e=require("./redact-Bq26pObO.cjs");var t=`3.2.6`,n=class{capacity;entries=[];constructor(e){if(this.capacity=e,!Number.isInteger(e)||e<=0)throw RangeError(`RingBuffer capacity must be a positive integer`)}push(e){this.entries.length===this.capacity&&this.entries.shift(),this.entries.push(e)}toArray(){return[...this.entries]}},r=`@16x/webphone-sdk`,i=200,a=class{dependencies;logs=new n(i);now;constructor(e){this.dependencies=e,this.now=e.now??(()=>new Date)}log(t,n,r){this.logs.push({timestamp:this.now().toISOString(),level:t,message:e.t(n),...r===void 0?{}:{context:e.n(r)}})}async getReport(n={}){let i=n.includeRecentLogs!==!1,a=n.includeDeviceNames===!0,u=n.maskPhoneNumbers!==!1,d=this.dependencies.getSnapshot(),f=this.dependencies.getPermission===void 0?d.permission:await o(this.dependencies.getPermission,d.permission),p=a?await s(this.dependencies.getDeviceNames):void 0,m=l(c(this.dependencies.getUserAgent)),h={generatedAt:this.now().toISOString(),sdk:{name:r,version:t},browser:m.browser,os:m.os,...d,permission:f,recentLogs:i?this.logs.toArray():[],...p===void 0?{}:{deviceNames:p}};return e.n(h,{maskPhoneNumbers:u})}};async function o(e,t){try{return await e()}catch{return t}}async function s(e){if(e===void 0)return[];try{return[...await e()]}catch{return[]}}function c(e){if(e!==void 0)try{return e()}catch{return``}return typeof navigator>`u`?``:navigator.userAgent}function l(e){return{browser:u(e),os:d(e)}}function u(e){for(let[t,n]of[[`Edge`,/Edg\/([\d.]+)/],[`Chrome`,/(?:Chrome|CriOS)\/([\d.]+)/],[`Firefox`,/(?:Firefox|FxiOS)\/([\d.]+)/],[`Safari`,/Version\/([\d.]+).*Safari/]]){let r=e.match(n);if(r?.[1]!==void 0)return{name:t,version:r[1]}}return{name:`unknown`,version:``}}function d(e){let t=e.match(/(?:iPhone|iPad).*OS ([\d_]+)/);if(t?.[1]!==void 0)return{name:`iOS`,version:t[1].replaceAll(`_`,`.`)};let n=e.match(/Android ([\d.]+)/);if(n?.[1]!==void 0)return{name:`Android`,version:n[1]};let r=e.match(/Windows NT ([\d.]+)/);if(r?.[1]!==void 0)return{name:`Windows`,version:r[1]};let i=e.match(/Mac OS X ([\d_]+)/);return i?.[1]===void 0?/Linux/.test(e)?{name:`Linux`,version:``}:{name:`unknown`,version:``}:{name:`macOS`,version:i[1].replaceAll(`_`,`.`)}}Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return n}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return a}});
1
+ const e=require("./redact-Bq26pObO.cjs");var t=`3.2.8`,n=class{capacity;entries=[];constructor(e){if(this.capacity=e,!Number.isInteger(e)||e<=0)throw RangeError(`RingBuffer capacity must be a positive integer`)}push(e){this.entries.length===this.capacity&&this.entries.shift(),this.entries.push(e)}toArray(){return[...this.entries]}},r=`@16x/webphone-sdk`,i=200,a=class{dependencies;logs=new n(i);now;constructor(e){this.dependencies=e,this.now=e.now??(()=>new Date)}log(t,n,r){this.logs.push({timestamp:this.now().toISOString(),level:t,message:e.t(n),...r===void 0?{}:{context:e.n(r)}})}async getReport(n={}){let i=n.includeRecentLogs!==!1,a=n.includeDeviceNames===!0,u=n.maskPhoneNumbers!==!1,d=this.dependencies.getSnapshot(),f=this.dependencies.getPermission===void 0?d.permission:await o(this.dependencies.getPermission,d.permission),p=a?await s(this.dependencies.getDeviceNames):void 0,m=l(c(this.dependencies.getUserAgent)),h={generatedAt:this.now().toISOString(),sdk:{name:r,version:t},browser:m.browser,os:m.os,...d,permission:f,recentLogs:i?this.logs.toArray():[],...p===void 0?{}:{deviceNames:p}};return e.n(h,{maskPhoneNumbers:u})}};async function o(e,t){try{return await e()}catch{return t}}async function s(e){if(e===void 0)return[];try{return[...await e()]}catch{return[]}}function c(e){if(e!==void 0)try{return e()}catch{return``}return typeof navigator>`u`?``:navigator.userAgent}function l(e){return{browser:u(e),os:d(e)}}function u(e){for(let[t,n]of[[`Edge`,/Edg\/([\d.]+)/],[`Chrome`,/(?:Chrome|CriOS)\/([\d.]+)/],[`Firefox`,/(?:Firefox|FxiOS)\/([\d.]+)/],[`Safari`,/Version\/([\d.]+).*Safari/]]){let r=e.match(n);if(r?.[1]!==void 0)return{name:t,version:r[1]}}return{name:`unknown`,version:``}}function d(e){let t=e.match(/(?:iPhone|iPad).*OS ([\d_]+)/);if(t?.[1]!==void 0)return{name:`iOS`,version:t[1].replaceAll(`_`,`.`)};let n=e.match(/Android ([\d.]+)/);if(n?.[1]!==void 0)return{name:`Android`,version:n[1]};let r=e.match(/Windows NT ([\d.]+)/);if(r?.[1]!==void 0)return{name:`Windows`,version:r[1]};let i=e.match(/Mac OS X ([\d_]+)/);return i?.[1]===void 0?/Linux/.test(e)?{name:`Linux`,version:``}:{name:`unknown`,version:``}:{name:`macOS`,version:i[1].replaceAll(`_`,`.`)}}Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return n}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return a}});
@@ -1,6 +1,6 @@
1
1
  import { n as e, t } from "./redact-DB3CMbsK.js";
2
2
  //#region src/version.ts
3
- var n = "3.2.6", r = class {
3
+ var n = "3.2.8", r = class {
4
4
  capacity;
5
5
  entries = [];
6
6
  constructor(e) {
@@ -42,56 +42,100 @@ function t(e) {
42
42
  }
43
43
  //#endregion
44
44
  //#region src/sip/iceServerQuality.ts
45
- var n = 2e3;
46
- function r(e) {
45
+ var n = 2e3, r = [
46
+ 5e3,
47
+ 15e3,
48
+ 6e4
49
+ ];
50
+ function i() {
51
+ return {
52
+ runs: 0,
53
+ lastMs: null,
54
+ udpOk: !1,
55
+ tcpRelayOk: !1,
56
+ convergedServers: null,
57
+ unchanged: null,
58
+ status: "idle",
59
+ lastProbeAt: null,
60
+ cacheAgeMs: null,
61
+ unknownCount: 0,
62
+ unavailableCount: 0,
63
+ dialMode: "original"
64
+ };
65
+ }
66
+ function a(e) {
47
67
  if (!e) return "";
48
68
  let t = String(e.type ?? "");
49
69
  return t === "" ? / typ (\w+)/.exec(String(e.candidate ?? ""))?.[1] ?? "" : t;
50
70
  }
51
- function i(e, t = {}) {
52
- let i = Number.isFinite(t.timeoutMs) && t.timeoutMs > 0 ? t.timeoutMs : n;
53
- if (typeof RTCPeerConnection > "u" || !Array.isArray(e)) return Promise.resolve([]);
54
- let a = [];
71
+ function o(e) {
72
+ return /^turns:/i.test(e) ? "tls" : /transport=tcp/i.test(e) ? "tcp" : "udp";
73
+ }
74
+ function s(e, t) {
75
+ let n = t.flatMap((e) => (Array.isArray(e.urls) ? e.urls : [e.urls]).map((t) => [String(t), e])), r = new Map(n), i = [];
76
+ for (let t of e) {
77
+ let e = Array.isArray(t.urls) ? t.urls : [t.urls];
78
+ for (let t of e) {
79
+ let e = r.get(String(t));
80
+ e && i.push({
81
+ ...e,
82
+ urls: [t]
83
+ });
84
+ }
85
+ }
86
+ return i;
87
+ }
88
+ function c(e, t = {}) {
89
+ let r = Number.isFinite(t.timeoutMs) && t.timeoutMs > 0 ? t.timeoutMs : n;
90
+ if (!Array.isArray(e)) return Promise.resolve([]);
91
+ let i = [];
55
92
  for (let t of e) {
56
93
  if (!t) continue;
57
94
  let e = Array.isArray(t.urls) ? t.urls : [t.urls];
58
95
  for (let n of e) {
59
96
  let e = String(n ?? "").trim();
60
- /^(stun|turn|turns):/i.test(e) && a.push({
97
+ /^(stun|turn|turns):/i.test(e) && i.push({
61
98
  url: e,
62
99
  entry: t
63
100
  });
64
101
  }
65
102
  }
66
- return Promise.all(a.map((e) => new Promise((t) => {
67
- let n = !1, a, o = (r) => {
103
+ return Promise.all(i.map((e) => new Promise((t) => {
104
+ let n = !1, i = !1, s, c = o(e.url), l = (r, i) => {
68
105
  if (!n) {
69
- n = !0, clearTimeout(s);
106
+ n = !0, clearTimeout(u);
70
107
  try {
71
- a?.close();
108
+ s?.close();
72
109
  } catch {}
73
110
  t({
74
111
  url: e.url,
75
- ok: r,
76
- tcp: /transport=tcp/i.test(e.url) || /^turns:/i.test(e.url)
112
+ status: r,
113
+ transport: c,
114
+ reason: i
77
115
  });
78
116
  }
79
- }, s = setTimeout(() => o(!1), i);
117
+ }, u = setTimeout(() => l(i ? "unavailable" : "unknown", i ? "server-error" : "timeout"), r);
80
118
  try {
119
+ if (typeof RTCPeerConnection > "u") {
120
+ l("unknown", "local-error");
121
+ return;
122
+ }
81
123
  let t = { urls: [e.url] };
82
- e.entry.username && (t.username = e.entry.username), e.entry.credential && (t.credential = e.entry.credential), a = new RTCPeerConnection({ iceServers: [t] }), a.createDataChannel("webphone-ice-probe"), a.onicecandidate = (e) => {
124
+ e.entry.username && (t.username = e.entry.username), e.entry.credential && (t.credential = e.entry.credential), s = new RTCPeerConnection({ iceServers: [t] }), s.createDataChannel("webphone-ice-probe"), s.onicecandidate = (e) => {
83
125
  if (!e || !e.candidate) return;
84
- let t = r(e.candidate);
85
- (t === "srflx" || t === "relay") && o(!0);
86
- }, a.createOffer().then((e) => a?.setLocalDescription(e)).catch(() => o(!1));
126
+ let t = a(e.candidate);
127
+ (t === "srflx" || t === "relay") && l("available", "candidate");
128
+ }, s.onicecandidateerror = (t) => {
129
+ t?.url && t.url === e.url && (i = !0);
130
+ }, s.createOffer().then((e) => s?.setLocalDescription(e)).catch(() => l("unknown", "local-error"));
87
131
  } catch {
88
- o(!1);
132
+ l("unknown", "local-error");
89
133
  }
90
134
  })));
91
135
  }
92
- function a(e, t) {
136
+ function l(e, t) {
93
137
  let n = /* @__PURE__ */ new Map();
94
- for (let e of t) e && e.url && n.set(String(e.url), e.ok === !0);
138
+ for (let e of t) e && e.url && n.set(String(e.url), e.status === "available");
95
139
  let r = [];
96
140
  for (let t of e ?? []) {
97
141
  if (!t) continue;
@@ -105,77 +149,147 @@ function a(e, t) {
105
149
  }
106
150
  return r;
107
151
  }
108
- var o = class {
109
- convergedList;
152
+ var u = class {
153
+ availableList;
154
+ lastGoodList;
155
+ lastGoodAt;
110
156
  lastResults = [];
111
- snapshotState = {
112
- runs: 0,
113
- lastMs: null,
114
- udpOk: !1,
115
- tcpRelayOk: !1,
116
- convergedServers: null,
117
- unchanged: null
118
- };
157
+ snapshotState = i();
119
158
  running = !1;
120
159
  seq = 0;
121
- converged(e) {
122
- return this.convergedList ?? e;
160
+ consecutiveFailures = 0;
161
+ lastAttemptAt;
162
+ retryTimer;
163
+ pendingRequest;
164
+ serversForDial(e, t = Date.now()) {
165
+ if (!e) return;
166
+ if (!this.lastResults.length) return this.setDialMode("original"), e;
167
+ let n = this.lastGoodAt === void 0 ? null : Math.max(0, t - this.lastGoodAt);
168
+ this.snapshotState = {
169
+ ...this.snapshotState,
170
+ cacheAgeMs: n
171
+ };
172
+ let r = n !== null && n > 3e5, i = this.snapshotState.lastProbeAt === null ? null : Math.max(0, t - this.snapshotState.lastProbeAt);
173
+ if (r || i !== null && i > 3e5) return this.snapshotState = {
174
+ ...this.snapshotState,
175
+ status: "stale",
176
+ cacheAgeMs: n ?? i
177
+ }, this.assess(e, "ttl-expired"), this.availableList && this.availableList.length > 0 ? (this.setDialMode("original"), e) : (this.setDialMode("host-only"), []);
178
+ if (!r && this.availableList && this.availableList.length > 0) return this.setDialMode("known-available"), this.availableList.map((e) => ({
179
+ ...e,
180
+ urls: Array.isArray(e.urls) ? [...e.urls] : e.urls
181
+ }));
182
+ if (this.lastGoodList && n !== null && n <= 3e5) {
183
+ let t = s(this.lastGoodList, e);
184
+ if (t.length > 0) return this.setDialMode("last-known-good"), t;
185
+ }
186
+ return this.setDialMode("host-only"), [];
123
187
  }
124
188
  snapshot() {
125
189
  return this.snapshotState;
126
190
  }
127
191
  reset() {
128
- this.seq++, this.running = !1, this.convergedList = void 0, this.lastResults = [], this.snapshotState = {
129
- runs: 0,
130
- lastMs: null,
131
- udpOk: !1,
132
- tcpRelayOk: !1,
133
- convergedServers: null,
134
- unchanged: null
135
- };
192
+ this.retryTimer !== void 0 && clearTimeout(this.retryTimer), this.retryTimer = void 0, this.pendingRequest = void 0, this.seq++, this.running = !1, this.availableList = void 0, this.lastGoodList = void 0, this.lastGoodAt = void 0, this.lastResults = [], this.consecutiveFailures = 0, this.lastAttemptAt = void 0, this.snapshotState = i();
193
+ }
194
+ cancelInFlight() {
195
+ this.seq++, this.running = !1, this.retryTimer !== void 0 && clearTimeout(this.retryTimer), this.retryTimer = void 0, this.pendingRequest = void 0;
136
196
  }
137
- assess(e, t = {}) {
138
- if (e === void 0 || e.length === 0) return;
139
- let n = e.map((e) => ({
197
+ assess(e, t = "initial-registration") {
198
+ if (e === void 0) return;
199
+ if (e.length === 0) {
200
+ this.reset();
201
+ return;
202
+ }
203
+ if (this.running) {
204
+ t !== "initial-registration" && (this.pendingRequest = {
205
+ servers: e,
206
+ trigger: t
207
+ });
208
+ return;
209
+ }
210
+ let n = Date.now();
211
+ if (t !== "initial-registration" && this.consecutiveFailures > 0 && this.lastAttemptAt !== void 0) {
212
+ let i = r[Math.min(this.consecutiveFailures - 1, r.length - 1)] - (n - this.lastAttemptAt);
213
+ if (i > 0) {
214
+ this.pendingRequest = {
215
+ servers: e,
216
+ trigger: t
217
+ }, this.retryTimer === void 0 && (this.retryTimer = setTimeout(() => {
218
+ this.retryTimer = void 0;
219
+ let e = this.pendingRequest;
220
+ this.pendingRequest = void 0, e && this.assess(e.servers, e.trigger);
221
+ }, i));
222
+ return;
223
+ }
224
+ }
225
+ let i = e.map((e) => ({
140
226
  ...e,
141
227
  urls: Array.isArray(e.urls) ? [...e.urls] : [e.urls]
142
228
  }));
143
- if (typeof RTCPeerConnection > "u" || !n.some((e) => (Array.isArray(e.urls) ? e.urls : [e.urls]).some((e) => /^(stun|turn|turns):/i.test(String(e ?? ""))))) return;
144
- let r = ++this.seq;
145
- this.running = !0;
229
+ if (!i.some((e) => (Array.isArray(e.urls) ? e.urls : [e.urls]).some((e) => /^(stun|turn|turns):/i.test(String(e ?? ""))))) return;
230
+ let a = ++this.seq;
231
+ this.running = !0, this.lastAttemptAt = n, this.availableList = void 0, this.snapshotState = {
232
+ ...this.snapshotState,
233
+ status: t === "ttl-expired" ? "stale" : "probing"
234
+ };
146
235
  let o = Date.now();
147
- i(n, t).then((e) => {
148
- if (r !== this.seq) return;
236
+ c(i).then((e) => {
237
+ if (a !== this.seq) return;
149
238
  this.lastResults = e;
150
- let t = n, i = a(t, e);
151
- this.convergedList = i, this.snapshotState = {
239
+ let t = i, n = l(t, e);
240
+ this.availableList = n.length > 0 ? n : void 0, this.availableList && (this.lastGoodList = n.map((e) => ({
241
+ ...e,
242
+ urls: Array.isArray(e.urls) ? [...e.urls] : e.urls
243
+ })), this.lastGoodAt = Date.now());
244
+ let s = e.filter((e) => e.status === "unknown").length, c = e.filter((e) => e.status === "unavailable").length;
245
+ this.consecutiveFailures = n.length === 0 ? this.consecutiveFailures + 1 : 0, n.length === 0 && this.consecutiveFailures <= r.length && this.pendingRequest === void 0 && (this.pendingRequest = {
246
+ servers: i,
247
+ trigger: "retry-backoff"
248
+ }), this.snapshotState = {
152
249
  runs: this.snapshotState.runs + 1,
153
250
  lastMs: Date.now() - o,
154
- udpOk: e.some((e) => e.ok && !e.tcp),
155
- tcpRelayOk: e.some((e) => e.ok && e.tcp),
156
- convergedServers: i.length,
157
- unchanged: JSON.stringify(i) === JSON.stringify(t)
251
+ udpOk: e.some((e) => e.status === "available" && e.transport === "udp"),
252
+ tcpRelayOk: e.some((e) => e.status === "available" && (e.transport === "tcp" || e.transport === "tls")),
253
+ convergedServers: n.length,
254
+ unchanged: JSON.stringify(n) === JSON.stringify(t),
255
+ status: s + c > 0 ? "degraded" : "healthy",
256
+ lastProbeAt: Date.now(),
257
+ cacheAgeMs: 0,
258
+ unknownCount: s,
259
+ unavailableCount: c,
260
+ dialMode: n.length > 0 ? "known-available" : "host-only"
158
261
  };
159
262
  }).catch(() => {
160
263
  this.seq;
161
264
  }).finally(() => {
162
- r === this.seq && (this.running = !1);
265
+ if (a !== this.seq) return;
266
+ this.running = !1;
267
+ let e = this.pendingRequest;
268
+ this.pendingRequest = void 0, e && this.assess(e.servers, e.trigger);
163
269
  });
164
270
  }
165
271
  isRunning() {
166
272
  return this.running;
167
273
  }
168
- }, s = 3e3;
169
- function c(e) {
274
+ setDialMode(e) {
275
+ let t = this.snapshotState.status;
276
+ this.snapshotState = {
277
+ ...this.snapshotState,
278
+ dialMode: e,
279
+ status: t === "healthy" && e === "host-only" ? "stale" : t
280
+ };
281
+ }
282
+ }, d = 3e3;
283
+ function f(e) {
170
284
  if (!e) return "";
171
285
  let t = String(e.type ?? "");
172
286
  return t === "" ? / typ (\w+)/.exec(String(e.candidate ?? ""))?.[1] ?? "" : t;
173
287
  }
174
- function l(e, t = {}) {
288
+ function p(e, t = {}) {
175
289
  if (typeof e.on != "function") return () => void 0;
176
- let n = Number.isFinite(t.timeoutMs) && t.timeoutMs > 0 ? t.timeoutMs : s, r = !1, i, a = () => {
290
+ let n = Number.isFinite(t.timeoutMs) && t.timeoutMs > 0 ? t.timeoutMs : d, r = !1, i, a = () => {
177
291
  if (!r) {
178
- r = !0, clearTimeout(l);
292
+ r = !0, clearTimeout(s);
179
293
  try {
180
294
  i?.();
181
295
  } catch {}
@@ -184,13 +298,13 @@ function l(e, t = {}) {
184
298
  let t = e;
185
299
  if (!t || typeof t.ready != "function") return;
186
300
  i = t.ready;
187
- let n = c(t.candidate);
301
+ let n = f(t.candidate);
188
302
  (n === "srflx" || n === "relay") && a();
189
303
  };
190
304
  e.on("icecandidate", o);
191
- let l = setTimeout(a, n);
305
+ let s = setTimeout(a, n);
192
306
  return () => {
193
- clearTimeout(l);
307
+ clearTimeout(s);
194
308
  try {
195
309
  e.off?.("icecandidate", o);
196
310
  } catch {}
@@ -198,12 +312,12 @@ function l(e, t = {}) {
198
312
  }
199
313
  //#endregion
200
314
  //#region src/sip/JsSipAdapter.ts
201
- var u = 300, d = 25, f = 2, p = "", m = 120;
202
- function h(e) {
315
+ var m = 300, h = 25, g = 2, _ = "", v = 120;
316
+ function y(e) {
203
317
  let t = Math.floor(Number(e.sip.registerExpires));
204
- return Number.isFinite(t) && t > 0 ? t : u;
318
+ return Number.isFinite(t) && t > 0 ? t : m;
205
319
  }
206
- var g = class {
320
+ var b = class {
207
321
  factory;
208
322
  transportBaseUrl;
209
323
  keepaliveSeconds;
@@ -220,8 +334,8 @@ var g = class {
220
334
  keepaliveBusy = !1;
221
335
  registerExpiresSeconds = 0;
222
336
  connectionHandlers = /* @__PURE__ */ new Set();
223
- iceQuality = new o();
224
- constructor(e, t, n = d, r = p, i = !1) {
337
+ iceQuality = new u();
338
+ constructor(e, t, n = h, r = _, i = !1) {
225
339
  this.factory = e, this.transportBaseUrl = t, this.keepaliveSeconds = n, this.agentTerminal = r, this.sessionTimers = i;
226
340
  }
227
341
  terminalHeaders() {
@@ -231,7 +345,7 @@ var g = class {
231
345
  if (this.closeWarmPc(), typeof RTCPeerConnection < "u" && this.iceServers !== void 0) try {
232
346
  this.warmPc = new RTCPeerConnection({
233
347
  iceServers: [...this.iceServers],
234
- iceCandidatePoolSize: f
348
+ iceCandidatePoolSize: g
235
349
  });
236
350
  } catch {
237
351
  this.warmPc = void 0;
@@ -260,28 +374,30 @@ var g = class {
260
374
  this.keepaliveTimer !== void 0 && clearInterval(this.keepaliveTimer), this.keepaliveTimer = void 0;
261
375
  }
262
376
  connect(t) {
263
- this.assertNotDisposed(), this.disconnect(this.registrationError("SIP connection replaced"));
377
+ this.assertNotDisposed();
378
+ let n = this.iceQuality.snapshot().runs > 0;
379
+ this.disconnect(this.registrationError("SIP connection replaced"), !n);
264
380
  try {
265
381
  let e = this.factory.createUa({
266
- sockets: b(this.factory.createSocket(this.withoutTicket(this.resolveWssUrl(t.transport.wssUrl)), t.transport.ticket)),
382
+ sockets: w(this.factory.createSocket(this.withoutTicket(this.resolveWssUrl(t.transport.wssUrl)), t.transport.ticket)),
267
383
  uri: t.sip.uri,
268
384
  password: t.sip.registerTicket,
269
385
  registrar_server: t.sip.registrar,
270
386
  register: !0,
271
- register_expires: h(t),
387
+ register_expires: y(t),
272
388
  session_timers: this.sessionTimers,
273
389
  ...this.agentTerminal === "" ? {} : { user_agent: this.agentTerminal }
274
390
  });
275
- return this.ua = e, this.registerExpiresSeconds = h(t), this.iceServers = t.iceServers, this.uaListeners = [], this.startRegistration(e);
391
+ return this.ua = e, this.registerExpiresSeconds = y(t), this.iceServers = t.iceServers, this.uaListeners = [], this.startRegistration(e);
276
392
  } catch (t) {
277
393
  let n = t instanceof e && t.code === "CONFIG_INVALID" ? t : this.transportError(t);
278
394
  return this.disconnect(n), Promise.reject(n);
279
395
  }
280
396
  }
281
- disconnect(e = this.registrationError("SIP connection disconnected")) {
282
- this.stopKeepalive(), this.closeWarmPc(), this.iceQuality.reset();
283
- let t = this.pendingConnection;
284
- this.pendingConnection = void 0, this.ua !== void 0 && (this.removeUaListeners(this.ua), this.ua.stop()), this.ua = void 0, this.uaListeners = void 0, this.iceServers = void 0, t?.reject(e);
397
+ disconnect(e = this.registrationError("SIP connection disconnected"), t = !0) {
398
+ this.stopKeepalive(), this.closeWarmPc(), t ? this.iceQuality.reset() : this.iceQuality.cancelInFlight();
399
+ let n = this.pendingConnection;
400
+ this.pendingConnection = void 0, this.ua !== void 0 && (this.removeUaListeners(this.ua), this.ua.stop()), this.ua = void 0, this.uaListeners = void 0, this.iceServers = void 0, n?.reject(e);
285
401
  }
286
402
  dial(t, n) {
287
403
  let r = this.requireUa();
@@ -292,20 +408,20 @@ var g = class {
292
408
  audio: !0,
293
409
  video: !1
294
410
  },
295
- sessionTimersExpires: m,
411
+ sessionTimersExpires: v,
296
412
  pcConfig: {
297
413
  iceServers: this.dialIceServers(),
298
- iceCandidatePoolSize: f
414
+ iceCandidatePoolSize: g
299
415
  }
300
416
  } : {
301
417
  mediaConstraints: {
302
418
  audio: !0,
303
419
  video: !1
304
420
  },
305
- sessionTimersExpires: m,
421
+ sessionTimersExpires: v,
306
422
  pcConfig: {
307
423
  iceServers: [...n.iceServers],
308
- iceCandidatePoolSize: f
424
+ iceCandidatePoolSize: g
309
425
  },
310
426
  extraHeaders: [
311
427
  ...this.terminalHeaders(),
@@ -313,7 +429,7 @@ var g = class {
313
429
  ...n.type === "extension" ? ["X-XCall-Destination-Type: extension"] : []
314
430
  ]
315
431
  };
316
- return new _(r.call(t, e));
432
+ return this.createSessionHandle(r.call(t, e));
317
433
  } catch (t) {
318
434
  throw new e("CALL_OPERATION_NOT_ALLOWED", "call", !1, { cause: t });
319
435
  }
@@ -330,12 +446,14 @@ var g = class {
330
446
  this.disposed || (this.disposed = !0, this.disconnect(), this.incomingHandlers = void 0, this.connectionHandlers.clear());
331
447
  }
332
448
  dialIceServers() {
333
- let e = this.iceQuality.converged(this.iceServers);
334
- return e === void 0 ? void 0 : [...e];
449
+ return this.iceQuality.serversForDial(this.iceServers);
335
450
  }
336
451
  iceQualitySnapshot() {
337
452
  return this.iceQuality.snapshot();
338
453
  }
454
+ reassessIceServers(e = "network-online") {
455
+ this.iceQuality.assess(this.iceServers, e);
456
+ }
339
457
  getResourceCounts() {
340
458
  return {
341
459
  listeners: (this.uaListeners?.length ?? 0) + (this.incomingHandlers?.size ?? 0) + this.connectionHandlers.size,
@@ -343,12 +461,15 @@ var g = class {
343
461
  };
344
462
  }
345
463
  handleIncoming(e) {
346
- if (!v(e)) return;
464
+ if (!S(e)) return;
347
465
  let t = e.session;
348
466
  if (t === void 0) return;
349
- let n = new _(t);
467
+ let n = this.createSessionHandle(t);
350
468
  this.incomingHandlers?.forEach((e) => e(n));
351
469
  }
470
+ createSessionHandle(e) {
471
+ return new x(e, () => this.reassessIceServers("ice-failed"));
472
+ }
352
473
  startRegistration(e) {
353
474
  let t = !0, n = !1, r = new Promise((t, n) => {
354
475
  this.pendingConnection = {
@@ -372,7 +493,7 @@ var g = class {
372
493
  }), this.listen(e, "disconnected", (t) => {
373
494
  if (!this.isCurrentUa(e)) return;
374
495
  let n = this.transportError(t);
375
- this.connectionHandlers.forEach((e) => e("disconnected", n)), this.disconnect(n);
496
+ this.connectionHandlers.forEach((e) => e("disconnected", n)), this.disconnect(n, !1);
376
497
  }), this.listen(e, "newRTCSession", (t) => {
377
498
  this.isCurrentUa(e) && this.handleIncoming(t);
378
499
  });
@@ -400,14 +521,17 @@ var g = class {
400
521
  }
401
522
  resolveConnection(e) {
402
523
  let t = this.pendingConnection;
403
- t?.ua === e && (this.pendingConnection = void 0, t.resolve(), this.warmIceCandidatePool(), this.iceQuality.assess(this.iceServers), this.startKeepalive(e, this.registerExpiresSeconds), this.connectionHandlers.forEach((e) => e("registered")));
524
+ if (t?.ua !== e) return;
525
+ this.pendingConnection = void 0, t.resolve(), this.warmIceCandidatePool();
526
+ let n = this.iceQuality.snapshot().runs > 0 ? "sip-reconnected" : "initial-registration";
527
+ this.iceQuality.assess(this.iceServers, n), this.startKeepalive(e, this.registerExpiresSeconds), this.connectionHandlers.forEach((e) => e("registered"));
404
528
  }
405
529
  resolveWssUrl(e) {
406
530
  let t = this.transportBaseUrl?.trim() === "" || this.transportBaseUrl === void 0 ? typeof location > "u" ? "http://localhost" : location.origin : this.transportBaseUrl, n = e.startsWith("/") ? new URL(e, t) : new URL(e);
407
531
  return n.protocol === "http:" && (n.protocol = "ws:"), n.protocol === "https:" && (n.protocol = "wss:"), this.assertAllowedWssUrl(n), n.toString();
408
532
  }
409
533
  assertAllowedWssUrl(t) {
410
- if (t.protocol !== "wss:" && !(t.protocol === "ws:" && x(t.hostname))) throw new e("CONFIG_INVALID", "configuration", !1, { cause: t.origin });
534
+ if (t.protocol !== "wss:" && !(t.protocol === "ws:" && T(t.hostname))) throw new e("CONFIG_INVALID", "configuration", !1, { cause: t.origin });
411
535
  }
412
536
  withoutTicket(e) {
413
537
  let t = new URL(e);
@@ -429,7 +553,7 @@ var g = class {
429
553
  registrationFailureCause(e) {
430
554
  return typeof e == "object" && e && "cause" in e ? e.cause : e;
431
555
  }
432
- }, _ = class {
556
+ }, x = class {
433
557
  session;
434
558
  pendingHoldReject;
435
559
  mediaRegistration;
@@ -438,14 +562,15 @@ var g = class {
438
562
  mediaWatchStarted = !1;
439
563
  sessionUnsubscribeFromPeerConnection;
440
564
  detachIceGatheringTimeout;
441
- constructor(e) {
442
- this.session = e, this.detachIceGatheringTimeout = l(e);
565
+ detachIceFailureWatch;
566
+ constructor(e, t) {
567
+ this.session = e, this.detachIceGatheringTimeout = p(e), t !== void 0 && this.watchIceFailure(t);
443
568
  }
444
569
  get id() {
445
570
  return this.session.id;
446
571
  }
447
572
  get from() {
448
- return this.session.from ?? S(this.session.remote_identity);
573
+ return this.session.from ?? E(this.session.remote_identity);
449
574
  }
450
575
  getMediaRegistration() {
451
576
  let e = this.session.connection;
@@ -456,12 +581,24 @@ var g = class {
456
581
  mediaConstraints: e.mediaConstraints,
457
582
  pcConfig: {
458
583
  iceServers: [...e.pcConfig.iceServers],
459
- iceCandidatePoolSize: f
584
+ iceCandidatePoolSize: g
460
585
  }
461
586
  });
462
587
  }
463
588
  terminate() {
464
- this.detachIceGatheringTimeout?.(), this.detachIceGatheringTimeout = void 0, this.pendingHoldReject?.(/* @__PURE__ */ Error("SIP session terminated")), this.session.terminate();
589
+ this.detachIceGatheringTimeout?.(), this.detachIceGatheringTimeout = void 0, this.detachIceFailureWatch?.(), this.detachIceFailureWatch = void 0, this.pendingHoldReject?.(/* @__PURE__ */ Error("SIP session terminated")), this.session.terminate();
590
+ }
591
+ watchIceFailure(e) {
592
+ let t, n = () => {
593
+ t?.iceConnectionState === "failed" && e();
594
+ }, r = (e) => {
595
+ e && e !== t && (t?.removeEventListener("iceconnectionstatechange", n), t = e, t.addEventListener("iceconnectionstatechange", n), n());
596
+ }, i = (e) => {
597
+ r(e?.peerconnection);
598
+ }, a = () => {
599
+ this.session.off("peerconnection", i), this.session.off("ended", a), this.session.off("failed", a), t?.removeEventListener("iceconnectionstatechange", n), t = void 0;
600
+ }, o = this.session.connection;
601
+ o != null && r(o), this.session.on("peerconnection", i), this.session.on("ended", a), this.session.on("failed", a), this.detachIceFailureWatch = a;
465
602
  }
466
603
  hold() {
467
604
  return this.changeHoldState("hold");
@@ -547,22 +684,22 @@ var g = class {
547
684
  });
548
685
  }
549
686
  };
550
- function v(e) {
687
+ function S(e) {
551
688
  if (typeof e != "object" || !e) return !1;
552
689
  let t = e;
553
- return t.originator === "remote" && y(t.session);
690
+ return t.originator === "remote" && C(t.session);
554
691
  }
555
- function y(e) {
692
+ function C(e) {
556
693
  return typeof e == "object" && !!e && "id" in e;
557
694
  }
558
- function b(e) {
695
+ function w(e) {
559
696
  return typeof e == "object" && e && (e.via_transport = "WSS"), e;
560
697
  }
561
- function x(e) {
698
+ function T(e) {
562
699
  let t = e.replace(/^\[|\]$/g, "").toLowerCase();
563
700
  return t === "localhost" || t === "127.0.0.1" || t === "::1" || t === "0:0:0:0:0:0:0:1";
564
701
  }
565
- function S(e) {
702
+ function E(e) {
566
703
  let t = e?.uri;
567
704
  if (t == null) return;
568
705
  if (typeof t.user == "string" && t.user !== "") return t.user;
@@ -570,4 +707,4 @@ function S(e) {
570
707
  return n.match(/^sips?:([^@;>]+)/i)?.[1] ?? (n === "" ? void 0 : n);
571
708
  }
572
709
  //#endregion
573
- export { g as JsSipAdapter };
710
+ export { b as JsSipAdapter };