@exotel-npm-dev/exotel-ai-assist 1.0.1 → 1.0.3
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/controller/index.d.ts +5 -5
- package/dist/controller/index.js +55 -53
- package/dist/index.js +56 -54
- package/dist/react/index.js +56 -54
- package/package.json +1 -1
|
@@ -4,11 +4,12 @@ export declare class ExotelAIAssistController extends EventEmitter<ControllerEve
|
|
|
4
4
|
private params;
|
|
5
5
|
private transport;
|
|
6
6
|
private status;
|
|
7
|
+
private destroyed;
|
|
8
|
+
private connectionEstablished;
|
|
7
9
|
private reconnectAttempt;
|
|
8
10
|
private reconnectTimer;
|
|
9
|
-
private
|
|
10
|
-
private readonly
|
|
11
|
-
private readonly maxReconnectAttempts;
|
|
11
|
+
private static readonly MAX_RECONNECT_ATTEMPTS;
|
|
12
|
+
private static readonly RECONNECT_BASE_MS;
|
|
12
13
|
constructor(params: ExotelAIAssistParams);
|
|
13
14
|
connect(): void;
|
|
14
15
|
disconnect(): void;
|
|
@@ -17,9 +18,8 @@ export declare class ExotelAIAssistController extends EventEmitter<ControllerEve
|
|
|
17
18
|
getStatus(): ConnectionStatus;
|
|
18
19
|
private _ensureTransport;
|
|
19
20
|
private _handleTransportMessage;
|
|
20
|
-
private _handleServerPayload;
|
|
21
21
|
private _scheduleReconnect;
|
|
22
22
|
private _clearReconnectTimer;
|
|
23
|
+
private _handleServerPayload;
|
|
23
24
|
private _setStatus;
|
|
24
|
-
private _log;
|
|
25
25
|
}
|
package/dist/controller/index.js
CHANGED
|
@@ -402,13 +402,6 @@ class BroadcastChannelTransport {
|
|
|
402
402
|
this.socket.onopen = () => {
|
|
403
403
|
var _a;
|
|
404
404
|
this.socketConnected = true;
|
|
405
|
-
// First-message auth protocol: send the JWT before any other message.
|
|
406
|
-
// The server does not send an auth_required prompt; the client is
|
|
407
|
-
// responsible for sending this immediately after the socket opens.
|
|
408
|
-
console.log('Sending token', {
|
|
409
|
-
type: "auth",
|
|
410
|
-
access_token: authToken
|
|
411
|
-
});
|
|
412
405
|
this.socket.send(JSON.stringify({
|
|
413
406
|
type: "auth",
|
|
414
407
|
access_token: authToken
|
|
@@ -1543,10 +1536,8 @@ class Utils {
|
|
|
1543
1536
|
}
|
|
1544
1537
|
static constructQueryParams(params) {
|
|
1545
1538
|
// Keys consumed by the library — never forwarded to the server.
|
|
1546
|
-
// authToken is intentionally excluded: authentication is done via the
|
|
1547
|
-
// first WebSocket text message (first-message auth protocol), not the URL.
|
|
1548
1539
|
const INTERNAL_KEYS = new Set(["authToken", "accountId", "wssBaseUrl", "reconnectInterval", "maxReconnectAttempts"]);
|
|
1549
|
-
//
|
|
1540
|
+
// Mandatory server params remapped to the names the API expects.
|
|
1550
1541
|
// accountId is embedded in the URL path — never sent as a query param.
|
|
1551
1542
|
const pairs = [];
|
|
1552
1543
|
if (params.call_sid !== undefined && params.call_sid !== null) {
|
|
@@ -1581,20 +1572,25 @@ Utils.MAX_EXTRA_QUERY_PARAMS = 3;
|
|
|
1581
1572
|
|
|
1582
1573
|
class ExotelAIAssistController extends EventEmitter {
|
|
1583
1574
|
constructor(params) {
|
|
1584
|
-
var _a, _b;
|
|
1585
1575
|
super();
|
|
1586
1576
|
this.transport = null;
|
|
1587
1577
|
this.status = "idle";
|
|
1578
|
+
this.destroyed = false;
|
|
1579
|
+
// True once the WebSocket's onopen fires (CONNECTED received).
|
|
1580
|
+
// Reset to false before every new connection attempt.
|
|
1581
|
+
// This is the gate for the retry decision:
|
|
1582
|
+
// connectionEstablished === false → connection never opened → retry
|
|
1583
|
+
// connectionEstablished === true → server ended a live connection → destroy
|
|
1584
|
+
this.connectionEstablished = false;
|
|
1588
1585
|
this.reconnectAttempt = 0;
|
|
1589
1586
|
this.reconnectTimer = null;
|
|
1590
|
-
this.destroyed = false;
|
|
1591
1587
|
this.params = Object.assign({}, params);
|
|
1592
|
-
this.reconnectInterval = (_a = params.reconnectInterval) !== null && _a !== void 0 ? _a : 3000;
|
|
1593
|
-
this.maxReconnectAttempts = (_b = params.maxReconnectAttempts) !== null && _b !== void 0 ? _b : 5;
|
|
1594
1588
|
}
|
|
1595
1589
|
connect() {
|
|
1596
1590
|
if (this.destroyed) return;
|
|
1597
1591
|
this._clearReconnectTimer();
|
|
1592
|
+
this.connectionEstablished = false;
|
|
1593
|
+
this.reconnectAttempt = 0;
|
|
1598
1594
|
this._setStatus("connecting");
|
|
1599
1595
|
this._ensureTransport();
|
|
1600
1596
|
this.transport.connect(Utils.buildWsUrl(this.params), this.params.authToken);
|
|
@@ -1609,14 +1605,12 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
1609
1605
|
var _a, _b;
|
|
1610
1606
|
const prev = this.params;
|
|
1611
1607
|
this.params = Object.assign(Object.assign({}, prev), patch);
|
|
1612
|
-
const
|
|
1613
|
-
const prevHash = Utils.hash(prev);
|
|
1614
|
-
const hashChanged = hash !== prevHash;
|
|
1608
|
+
const hashChanged = Utils.hash(this.params) !== Utils.hash(prev);
|
|
1615
1609
|
if (hashChanged) {
|
|
1616
|
-
this.reconnectAttempt = 0;
|
|
1617
1610
|
this._clearReconnectTimer();
|
|
1618
|
-
|
|
1619
|
-
|
|
1611
|
+
this.connectionEstablished = false;
|
|
1612
|
+
this.reconnectAttempt = 0;
|
|
1613
|
+
// Tear down the current transport and start fresh with the new params.
|
|
1620
1614
|
(_a = this.transport) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
1621
1615
|
this.transport = null;
|
|
1622
1616
|
this._setStatus("connecting");
|
|
@@ -1631,6 +1625,7 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
1631
1625
|
}
|
|
1632
1626
|
destroy() {
|
|
1633
1627
|
var _a;
|
|
1628
|
+
if (this.destroyed) return;
|
|
1634
1629
|
this.destroyed = true;
|
|
1635
1630
|
this._clearReconnectTimer();
|
|
1636
1631
|
(_a = this.transport) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
@@ -1649,21 +1644,27 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
1649
1644
|
var _a;
|
|
1650
1645
|
switch (msg.type) {
|
|
1651
1646
|
case "CONNECTED":
|
|
1647
|
+
this.connectionEstablished = true;
|
|
1652
1648
|
this.reconnectAttempt = 0;
|
|
1653
1649
|
this._setStatus("connected");
|
|
1654
1650
|
this.emit("onCallStart");
|
|
1655
1651
|
break;
|
|
1656
1652
|
case "DISCONNECTED":
|
|
1657
|
-
this.
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1653
|
+
if (this.connectionEstablished) {
|
|
1654
|
+
// Server ended an already-established connection (call ended, stream
|
|
1655
|
+
// not found, auth failure, etc.) — do not retry.
|
|
1656
|
+
this._setStatus("disconnected");
|
|
1657
|
+
this.emit("onCallEnd");
|
|
1658
|
+
this.destroy();
|
|
1659
|
+
} else {
|
|
1660
|
+
// WebSocket never opened — network/DNS/refused error.
|
|
1661
|
+
// Retry up to MAX_RECONNECT_ATTEMPTS then give up.
|
|
1662
1662
|
this._scheduleReconnect();
|
|
1663
1663
|
}
|
|
1664
1664
|
break;
|
|
1665
1665
|
case "ERROR":
|
|
1666
|
-
|
|
1666
|
+
// onerror fires before onclose; DISCONNECTED will arrive right after
|
|
1667
|
+
// and drive the retry / destroy decision. Just surface the error here.
|
|
1667
1668
|
this.emit("error", new Error((_a = msg.message) !== null && _a !== void 0 ? _a : "Unknown transport error"));
|
|
1668
1669
|
break;
|
|
1669
1670
|
case "MESSAGE":
|
|
@@ -1671,6 +1672,32 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
1671
1672
|
break;
|
|
1672
1673
|
}
|
|
1673
1674
|
}
|
|
1675
|
+
_scheduleReconnect() {
|
|
1676
|
+
if (this.destroyed) return;
|
|
1677
|
+
if (this.reconnectAttempt >= ExotelAIAssistController.MAX_RECONNECT_ATTEMPTS) {
|
|
1678
|
+
const err = new Error("Connection failed after maximum retries");
|
|
1679
|
+
err.code = "MAX_RECONNECT_EXCEEDED";
|
|
1680
|
+
this._setStatus("error");
|
|
1681
|
+
this.emit("error", err);
|
|
1682
|
+
this.destroy();
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
const delay = Math.min(ExotelAIAssistController.RECONNECT_BASE_MS * Math.pow(2, this.reconnectAttempt), 30000);
|
|
1686
|
+
this.reconnectAttempt += 1;
|
|
1687
|
+
this._setStatus("connecting");
|
|
1688
|
+
this.reconnectTimer = setTimeout(() => {
|
|
1689
|
+
var _a;
|
|
1690
|
+
if (this.destroyed) return;
|
|
1691
|
+
this.connectionEstablished = false;
|
|
1692
|
+
(_a = this.transport) === null || _a === void 0 ? void 0 : _a.connect(Utils.buildWsUrl(this.params), this.params.authToken);
|
|
1693
|
+
}, delay);
|
|
1694
|
+
}
|
|
1695
|
+
_clearReconnectTimer() {
|
|
1696
|
+
if (this.reconnectTimer !== null) {
|
|
1697
|
+
clearTimeout(this.reconnectTimer);
|
|
1698
|
+
this.reconnectTimer = null;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1674
1701
|
_handleServerPayload(raw) {
|
|
1675
1702
|
var _a;
|
|
1676
1703
|
let parsed;
|
|
@@ -1733,38 +1760,13 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
1733
1760
|
}
|
|
1734
1761
|
}
|
|
1735
1762
|
}
|
|
1736
|
-
_scheduleReconnect() {
|
|
1737
|
-
if (this.destroyed) return;
|
|
1738
|
-
if (this.status === "error") return;
|
|
1739
|
-
if (this.reconnectAttempt >= this.maxReconnectAttempts) {
|
|
1740
|
-
const err = new Error("MAX_RECONNECT_EXCEEDED");
|
|
1741
|
-
err.code = "MAX_RECONNECT_EXCEEDED";
|
|
1742
|
-
this.emit("error", err);
|
|
1743
|
-
return;
|
|
1744
|
-
}
|
|
1745
|
-
const delay = Math.min(this.reconnectInterval * Math.pow(2, this.reconnectAttempt), 30000);
|
|
1746
|
-
this.reconnectTimer = setTimeout(() => {
|
|
1747
|
-
var _a;
|
|
1748
|
-
if (this.destroyed) return;
|
|
1749
|
-
this.reconnectAttempt += 1;
|
|
1750
|
-
this._setStatus("connecting");
|
|
1751
|
-
(_a = this.transport) === null || _a === void 0 ? void 0 : _a.connect(Utils.buildWsUrl(this.params), this.params.authToken);
|
|
1752
|
-
}, delay);
|
|
1753
|
-
}
|
|
1754
|
-
_clearReconnectTimer() {
|
|
1755
|
-
if (this.reconnectTimer !== null) {
|
|
1756
|
-
clearTimeout(this.reconnectTimer);
|
|
1757
|
-
this.reconnectTimer = null;
|
|
1758
|
-
}
|
|
1759
|
-
}
|
|
1760
1763
|
_setStatus(status) {
|
|
1761
1764
|
if (this.status === status) return;
|
|
1762
1765
|
this.status = status;
|
|
1763
1766
|
this.emit("statusChange", status);
|
|
1764
1767
|
}
|
|
1765
|
-
_log(...args) {
|
|
1766
|
-
console.log("[ExotelAIAssist]", ...args);
|
|
1767
|
-
}
|
|
1768
1768
|
}
|
|
1769
|
+
ExotelAIAssistController.MAX_RECONNECT_ATTEMPTS = 5;
|
|
1770
|
+
ExotelAIAssistController.RECONNECT_BASE_MS = 3000;
|
|
1769
1771
|
|
|
1770
1772
|
exports.ExotelAIAssistController = ExotelAIAssistController;
|
package/dist/index.js
CHANGED
|
@@ -19820,13 +19820,6 @@ class BroadcastChannelTransport {
|
|
|
19820
19820
|
this.socket.onopen = () => {
|
|
19821
19821
|
var _a;
|
|
19822
19822
|
this.socketConnected = true;
|
|
19823
|
-
// First-message auth protocol: send the JWT before any other message.
|
|
19824
|
-
// The server does not send an auth_required prompt; the client is
|
|
19825
|
-
// responsible for sending this immediately after the socket opens.
|
|
19826
|
-
console.log('Sending token', {
|
|
19827
|
-
type: "auth",
|
|
19828
|
-
access_token: authToken
|
|
19829
|
-
});
|
|
19830
19823
|
this.socket.send(JSON.stringify({
|
|
19831
19824
|
type: "auth",
|
|
19832
19825
|
access_token: authToken
|
|
@@ -20961,10 +20954,8 @@ class Utils {
|
|
|
20961
20954
|
}
|
|
20962
20955
|
static constructQueryParams(params) {
|
|
20963
20956
|
// Keys consumed by the library — never forwarded to the server.
|
|
20964
|
-
// authToken is intentionally excluded: authentication is done via the
|
|
20965
|
-
// first WebSocket text message (first-message auth protocol), not the URL.
|
|
20966
20957
|
const INTERNAL_KEYS = new Set(["authToken", "accountId", "wssBaseUrl", "reconnectInterval", "maxReconnectAttempts"]);
|
|
20967
|
-
//
|
|
20958
|
+
// Mandatory server params remapped to the names the API expects.
|
|
20968
20959
|
// accountId is embedded in the URL path — never sent as a query param.
|
|
20969
20960
|
const pairs = [];
|
|
20970
20961
|
if (params.call_sid !== undefined && params.call_sid !== null) {
|
|
@@ -20999,20 +20990,25 @@ Utils.MAX_EXTRA_QUERY_PARAMS = 3;
|
|
|
20999
20990
|
|
|
21000
20991
|
class ExotelAIAssistController extends EventEmitter {
|
|
21001
20992
|
constructor(params) {
|
|
21002
|
-
var _a, _b;
|
|
21003
20993
|
super();
|
|
21004
20994
|
this.transport = null;
|
|
21005
20995
|
this.status = "idle";
|
|
20996
|
+
this.destroyed = false;
|
|
20997
|
+
// True once the WebSocket's onopen fires (CONNECTED received).
|
|
20998
|
+
// Reset to false before every new connection attempt.
|
|
20999
|
+
// This is the gate for the retry decision:
|
|
21000
|
+
// connectionEstablished === false → connection never opened → retry
|
|
21001
|
+
// connectionEstablished === true → server ended a live connection → destroy
|
|
21002
|
+
this.connectionEstablished = false;
|
|
21006
21003
|
this.reconnectAttempt = 0;
|
|
21007
21004
|
this.reconnectTimer = null;
|
|
21008
|
-
this.destroyed = false;
|
|
21009
21005
|
this.params = Object.assign({}, params);
|
|
21010
|
-
this.reconnectInterval = (_a = params.reconnectInterval) !== null && _a !== void 0 ? _a : 3000;
|
|
21011
|
-
this.maxReconnectAttempts = (_b = params.maxReconnectAttempts) !== null && _b !== void 0 ? _b : 5;
|
|
21012
21006
|
}
|
|
21013
21007
|
connect() {
|
|
21014
21008
|
if (this.destroyed) return;
|
|
21015
21009
|
this._clearReconnectTimer();
|
|
21010
|
+
this.connectionEstablished = false;
|
|
21011
|
+
this.reconnectAttempt = 0;
|
|
21016
21012
|
this._setStatus("connecting");
|
|
21017
21013
|
this._ensureTransport();
|
|
21018
21014
|
this.transport.connect(Utils.buildWsUrl(this.params), this.params.authToken);
|
|
@@ -21027,14 +21023,12 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
21027
21023
|
var _a, _b;
|
|
21028
21024
|
const prev = this.params;
|
|
21029
21025
|
this.params = Object.assign(Object.assign({}, prev), patch);
|
|
21030
|
-
const
|
|
21031
|
-
const prevHash = Utils.hash(prev);
|
|
21032
|
-
const hashChanged = hash !== prevHash;
|
|
21026
|
+
const hashChanged = Utils.hash(this.params) !== Utils.hash(prev);
|
|
21033
21027
|
if (hashChanged) {
|
|
21034
|
-
this.reconnectAttempt = 0;
|
|
21035
21028
|
this._clearReconnectTimer();
|
|
21036
|
-
|
|
21037
|
-
|
|
21029
|
+
this.connectionEstablished = false;
|
|
21030
|
+
this.reconnectAttempt = 0;
|
|
21031
|
+
// Tear down the current transport and start fresh with the new params.
|
|
21038
21032
|
(_a = this.transport) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
21039
21033
|
this.transport = null;
|
|
21040
21034
|
this._setStatus("connecting");
|
|
@@ -21049,6 +21043,7 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
21049
21043
|
}
|
|
21050
21044
|
destroy() {
|
|
21051
21045
|
var _a;
|
|
21046
|
+
if (this.destroyed) return;
|
|
21052
21047
|
this.destroyed = true;
|
|
21053
21048
|
this._clearReconnectTimer();
|
|
21054
21049
|
(_a = this.transport) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
@@ -21067,21 +21062,27 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
21067
21062
|
var _a;
|
|
21068
21063
|
switch (msg.type) {
|
|
21069
21064
|
case "CONNECTED":
|
|
21065
|
+
this.connectionEstablished = true;
|
|
21070
21066
|
this.reconnectAttempt = 0;
|
|
21071
21067
|
this._setStatus("connected");
|
|
21072
21068
|
this.emit("onCallStart");
|
|
21073
21069
|
break;
|
|
21074
21070
|
case "DISCONNECTED":
|
|
21075
|
-
this.
|
|
21076
|
-
|
|
21077
|
-
|
|
21078
|
-
|
|
21079
|
-
|
|
21071
|
+
if (this.connectionEstablished) {
|
|
21072
|
+
// Server ended an already-established connection (call ended, stream
|
|
21073
|
+
// not found, auth failure, etc.) — do not retry.
|
|
21074
|
+
this._setStatus("disconnected");
|
|
21075
|
+
this.emit("onCallEnd");
|
|
21076
|
+
this.destroy();
|
|
21077
|
+
} else {
|
|
21078
|
+
// WebSocket never opened — network/DNS/refused error.
|
|
21079
|
+
// Retry up to MAX_RECONNECT_ATTEMPTS then give up.
|
|
21080
21080
|
this._scheduleReconnect();
|
|
21081
21081
|
}
|
|
21082
21082
|
break;
|
|
21083
21083
|
case "ERROR":
|
|
21084
|
-
|
|
21084
|
+
// onerror fires before onclose; DISCONNECTED will arrive right after
|
|
21085
|
+
// and drive the retry / destroy decision. Just surface the error here.
|
|
21085
21086
|
this.emit("error", new Error((_a = msg.message) !== null && _a !== void 0 ? _a : "Unknown transport error"));
|
|
21086
21087
|
break;
|
|
21087
21088
|
case "MESSAGE":
|
|
@@ -21089,6 +21090,32 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
21089
21090
|
break;
|
|
21090
21091
|
}
|
|
21091
21092
|
}
|
|
21093
|
+
_scheduleReconnect() {
|
|
21094
|
+
if (this.destroyed) return;
|
|
21095
|
+
if (this.reconnectAttempt >= ExotelAIAssistController.MAX_RECONNECT_ATTEMPTS) {
|
|
21096
|
+
const err = new Error("Connection failed after maximum retries");
|
|
21097
|
+
err.code = "MAX_RECONNECT_EXCEEDED";
|
|
21098
|
+
this._setStatus("error");
|
|
21099
|
+
this.emit("error", err);
|
|
21100
|
+
this.destroy();
|
|
21101
|
+
return;
|
|
21102
|
+
}
|
|
21103
|
+
const delay = Math.min(ExotelAIAssistController.RECONNECT_BASE_MS * Math.pow(2, this.reconnectAttempt), 30000);
|
|
21104
|
+
this.reconnectAttempt += 1;
|
|
21105
|
+
this._setStatus("connecting");
|
|
21106
|
+
this.reconnectTimer = setTimeout(() => {
|
|
21107
|
+
var _a;
|
|
21108
|
+
if (this.destroyed) return;
|
|
21109
|
+
this.connectionEstablished = false;
|
|
21110
|
+
(_a = this.transport) === null || _a === void 0 ? void 0 : _a.connect(Utils.buildWsUrl(this.params), this.params.authToken);
|
|
21111
|
+
}, delay);
|
|
21112
|
+
}
|
|
21113
|
+
_clearReconnectTimer() {
|
|
21114
|
+
if (this.reconnectTimer !== null) {
|
|
21115
|
+
clearTimeout(this.reconnectTimer);
|
|
21116
|
+
this.reconnectTimer = null;
|
|
21117
|
+
}
|
|
21118
|
+
}
|
|
21092
21119
|
_handleServerPayload(raw) {
|
|
21093
21120
|
var _a;
|
|
21094
21121
|
let parsed;
|
|
@@ -21151,39 +21178,14 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
21151
21178
|
}
|
|
21152
21179
|
}
|
|
21153
21180
|
}
|
|
21154
|
-
_scheduleReconnect() {
|
|
21155
|
-
if (this.destroyed) return;
|
|
21156
|
-
if (this.status === "error") return;
|
|
21157
|
-
if (this.reconnectAttempt >= this.maxReconnectAttempts) {
|
|
21158
|
-
const err = new Error("MAX_RECONNECT_EXCEEDED");
|
|
21159
|
-
err.code = "MAX_RECONNECT_EXCEEDED";
|
|
21160
|
-
this.emit("error", err);
|
|
21161
|
-
return;
|
|
21162
|
-
}
|
|
21163
|
-
const delay = Math.min(this.reconnectInterval * Math.pow(2, this.reconnectAttempt), 30000);
|
|
21164
|
-
this.reconnectTimer = setTimeout(() => {
|
|
21165
|
-
var _a;
|
|
21166
|
-
if (this.destroyed) return;
|
|
21167
|
-
this.reconnectAttempt += 1;
|
|
21168
|
-
this._setStatus("connecting");
|
|
21169
|
-
(_a = this.transport) === null || _a === void 0 ? void 0 : _a.connect(Utils.buildWsUrl(this.params), this.params.authToken);
|
|
21170
|
-
}, delay);
|
|
21171
|
-
}
|
|
21172
|
-
_clearReconnectTimer() {
|
|
21173
|
-
if (this.reconnectTimer !== null) {
|
|
21174
|
-
clearTimeout(this.reconnectTimer);
|
|
21175
|
-
this.reconnectTimer = null;
|
|
21176
|
-
}
|
|
21177
|
-
}
|
|
21178
21181
|
_setStatus(status) {
|
|
21179
21182
|
if (this.status === status) return;
|
|
21180
21183
|
this.status = status;
|
|
21181
21184
|
this.emit("statusChange", status);
|
|
21182
21185
|
}
|
|
21183
|
-
_log(...args) {
|
|
21184
|
-
console.log("[ExotelAIAssist]", ...args);
|
|
21185
|
-
}
|
|
21186
21186
|
}
|
|
21187
|
+
ExotelAIAssistController.MAX_RECONNECT_ATTEMPTS = 5;
|
|
21188
|
+
ExotelAIAssistController.RECONNECT_BASE_MS = 3000;
|
|
21187
21189
|
|
|
21188
21190
|
function useExotelAIAssist(params) {
|
|
21189
21191
|
const controllerRef = reactExports.useRef(null);
|
|
@@ -21284,7 +21286,7 @@ function styleInject(css, ref) {
|
|
|
21284
21286
|
}
|
|
21285
21287
|
}
|
|
21286
21288
|
|
|
21287
|
-
var css_248z = ".oa-panel{background:#fff;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif}.oa-panel,.oa-theme-root{display:flex;flex-direction:column;height:100%;min-height:0;width:100%}.oa-theme-root{position:relative}.oa-header-title{color:#111827;font-size:16px;font-weight:600;line-height:20px;margin:0}.oa-tabs{width:100%}.oa-tabs-content{
|
|
21289
|
+
var css_248z = ".oa-panel{background:#fff;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif}.oa-panel,.oa-theme-root{display:flex;flex-direction:column;height:100%;min-height:0;width:100%}.oa-theme-root{position:relative}.oa-header-title{color:#111827;font-size:16px;font-weight:600;line-height:20px;margin:0}.oa-tabs{width:100%}.oa-tabs-content{flex:1;min-height:0;overflow:hidden}.oa-tabs-content[data-state=inactive]{display:none!important}.oa-tabs-content[data-state=active]{display:flex;flex-direction:column}.oa-tabs-list{border-bottom:1px solid #e5e7eb;display:flex;gap:20px;padding:0}.oa-tabs-trigger{align-items:center;background:transparent;border:none;border-radius:0;color:#6b7280;cursor:pointer;display:inline-flex;font-size:15px;font-weight:500;gap:6px;outline:none;padding:10px 0;position:relative}.oa-tabs-trigger:hover{color:#374151}.oa-tabs-trigger[data-state=active]{color:#2563eb}.oa-tabs-trigger[data-state=active]:after{background:#2563eb;border-radius:2px;bottom:-1px;content:\"\";height:2px;left:0;position:absolute;right:0}.oa-suggestion-card{border:1px solid rgba(17,24,39,.04);border-radius:12px;padding:12px}.oa-suggestion-card--recent-wrapper{animation:oa-suggestion-enter .4s ease-out,oa-border-shimmer 3s ease-in-out .4s infinite;background:linear-gradient(90deg,#7c3aed,#e11d48,#7c3aed);background-size:200% 100%;border-radius:12px;padding:2px}.oa-suggestion-card--recent-wrapper .oa-suggestion-card--recent{border:none}.oa-suggestion-card--recent{animation:none;background:linear-gradient(90deg,#f5f3ff 0,#fef3f2 50%,#fff7ed);border-radius:9px}.oa-suggestion-card--older{background:#f5f5f5;border:1px solid #e5e7eb}.oa-suggestion-card--older .oa-suggestion-text{color:#252525}@keyframes oa-suggestion-enter{0%{opacity:.3;transform:scale(.97)}to{opacity:1;transform:scale(1)}}@keyframes oa-border-shimmer{0%,to{background-position:0 50%}50%{background-position:100% 50%}}.oa-suggestion-text{color:#111827;font-size:13px;line-height:18px;white-space:pre-wrap}.oa-copy-icon{align-items:center;background:#fff;border:1px solid #e5e7eb;border-radius:8px;color:#6b7280;cursor:pointer;display:inline-flex;height:32px;justify-content:center;min-height:32px;min-width:32px;padding:0;width:32px}.oa-copy-icon:hover{background:#f9fafb;color:#374151}@keyframes oa-toast-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}.oa-muted{color:#6b7280}";
|
|
21288
21290
|
styleInject(css_248z);
|
|
21289
21291
|
|
|
21290
21292
|
function Sparkle() {
|
package/dist/react/index.js
CHANGED
|
@@ -5106,13 +5106,6 @@ class BroadcastChannelTransport {
|
|
|
5106
5106
|
this.socket.onopen = () => {
|
|
5107
5107
|
var _a;
|
|
5108
5108
|
this.socketConnected = true;
|
|
5109
|
-
// First-message auth protocol: send the JWT before any other message.
|
|
5110
|
-
// The server does not send an auth_required prompt; the client is
|
|
5111
|
-
// responsible for sending this immediately after the socket opens.
|
|
5112
|
-
console.log('Sending token', {
|
|
5113
|
-
type: "auth",
|
|
5114
|
-
access_token: authToken
|
|
5115
|
-
});
|
|
5116
5109
|
this.socket.send(JSON.stringify({
|
|
5117
5110
|
type: "auth",
|
|
5118
5111
|
access_token: authToken
|
|
@@ -6247,10 +6240,8 @@ class Utils {
|
|
|
6247
6240
|
}
|
|
6248
6241
|
static constructQueryParams(params) {
|
|
6249
6242
|
// Keys consumed by the library — never forwarded to the server.
|
|
6250
|
-
// authToken is intentionally excluded: authentication is done via the
|
|
6251
|
-
// first WebSocket text message (first-message auth protocol), not the URL.
|
|
6252
6243
|
const INTERNAL_KEYS = new Set(["authToken", "accountId", "wssBaseUrl", "reconnectInterval", "maxReconnectAttempts"]);
|
|
6253
|
-
//
|
|
6244
|
+
// Mandatory server params remapped to the names the API expects.
|
|
6254
6245
|
// accountId is embedded in the URL path — never sent as a query param.
|
|
6255
6246
|
const pairs = [];
|
|
6256
6247
|
if (params.call_sid !== undefined && params.call_sid !== null) {
|
|
@@ -6285,20 +6276,25 @@ Utils.MAX_EXTRA_QUERY_PARAMS = 3;
|
|
|
6285
6276
|
|
|
6286
6277
|
class ExotelAIAssistController extends EventEmitter {
|
|
6287
6278
|
constructor(params) {
|
|
6288
|
-
var _a, _b;
|
|
6289
6279
|
super();
|
|
6290
6280
|
this.transport = null;
|
|
6291
6281
|
this.status = "idle";
|
|
6282
|
+
this.destroyed = false;
|
|
6283
|
+
// True once the WebSocket's onopen fires (CONNECTED received).
|
|
6284
|
+
// Reset to false before every new connection attempt.
|
|
6285
|
+
// This is the gate for the retry decision:
|
|
6286
|
+
// connectionEstablished === false → connection never opened → retry
|
|
6287
|
+
// connectionEstablished === true → server ended a live connection → destroy
|
|
6288
|
+
this.connectionEstablished = false;
|
|
6292
6289
|
this.reconnectAttempt = 0;
|
|
6293
6290
|
this.reconnectTimer = null;
|
|
6294
|
-
this.destroyed = false;
|
|
6295
6291
|
this.params = Object.assign({}, params);
|
|
6296
|
-
this.reconnectInterval = (_a = params.reconnectInterval) !== null && _a !== void 0 ? _a : 3000;
|
|
6297
|
-
this.maxReconnectAttempts = (_b = params.maxReconnectAttempts) !== null && _b !== void 0 ? _b : 5;
|
|
6298
6292
|
}
|
|
6299
6293
|
connect() {
|
|
6300
6294
|
if (this.destroyed) return;
|
|
6301
6295
|
this._clearReconnectTimer();
|
|
6296
|
+
this.connectionEstablished = false;
|
|
6297
|
+
this.reconnectAttempt = 0;
|
|
6302
6298
|
this._setStatus("connecting");
|
|
6303
6299
|
this._ensureTransport();
|
|
6304
6300
|
this.transport.connect(Utils.buildWsUrl(this.params), this.params.authToken);
|
|
@@ -6313,14 +6309,12 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
6313
6309
|
var _a, _b;
|
|
6314
6310
|
const prev = this.params;
|
|
6315
6311
|
this.params = Object.assign(Object.assign({}, prev), patch);
|
|
6316
|
-
const
|
|
6317
|
-
const prevHash = Utils.hash(prev);
|
|
6318
|
-
const hashChanged = hash !== prevHash;
|
|
6312
|
+
const hashChanged = Utils.hash(this.params) !== Utils.hash(prev);
|
|
6319
6313
|
if (hashChanged) {
|
|
6320
|
-
this.reconnectAttempt = 0;
|
|
6321
6314
|
this._clearReconnectTimer();
|
|
6322
|
-
|
|
6323
|
-
|
|
6315
|
+
this.connectionEstablished = false;
|
|
6316
|
+
this.reconnectAttempt = 0;
|
|
6317
|
+
// Tear down the current transport and start fresh with the new params.
|
|
6324
6318
|
(_a = this.transport) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
6325
6319
|
this.transport = null;
|
|
6326
6320
|
this._setStatus("connecting");
|
|
@@ -6335,6 +6329,7 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
6335
6329
|
}
|
|
6336
6330
|
destroy() {
|
|
6337
6331
|
var _a;
|
|
6332
|
+
if (this.destroyed) return;
|
|
6338
6333
|
this.destroyed = true;
|
|
6339
6334
|
this._clearReconnectTimer();
|
|
6340
6335
|
(_a = this.transport) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
@@ -6353,21 +6348,27 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
6353
6348
|
var _a;
|
|
6354
6349
|
switch (msg.type) {
|
|
6355
6350
|
case "CONNECTED":
|
|
6351
|
+
this.connectionEstablished = true;
|
|
6356
6352
|
this.reconnectAttempt = 0;
|
|
6357
6353
|
this._setStatus("connected");
|
|
6358
6354
|
this.emit("onCallStart");
|
|
6359
6355
|
break;
|
|
6360
6356
|
case "DISCONNECTED":
|
|
6361
|
-
this.
|
|
6362
|
-
|
|
6363
|
-
|
|
6364
|
-
|
|
6365
|
-
|
|
6357
|
+
if (this.connectionEstablished) {
|
|
6358
|
+
// Server ended an already-established connection (call ended, stream
|
|
6359
|
+
// not found, auth failure, etc.) — do not retry.
|
|
6360
|
+
this._setStatus("disconnected");
|
|
6361
|
+
this.emit("onCallEnd");
|
|
6362
|
+
this.destroy();
|
|
6363
|
+
} else {
|
|
6364
|
+
// WebSocket never opened — network/DNS/refused error.
|
|
6365
|
+
// Retry up to MAX_RECONNECT_ATTEMPTS then give up.
|
|
6366
6366
|
this._scheduleReconnect();
|
|
6367
6367
|
}
|
|
6368
6368
|
break;
|
|
6369
6369
|
case "ERROR":
|
|
6370
|
-
|
|
6370
|
+
// onerror fires before onclose; DISCONNECTED will arrive right after
|
|
6371
|
+
// and drive the retry / destroy decision. Just surface the error here.
|
|
6371
6372
|
this.emit("error", new Error((_a = msg.message) !== null && _a !== void 0 ? _a : "Unknown transport error"));
|
|
6372
6373
|
break;
|
|
6373
6374
|
case "MESSAGE":
|
|
@@ -6375,6 +6376,32 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
6375
6376
|
break;
|
|
6376
6377
|
}
|
|
6377
6378
|
}
|
|
6379
|
+
_scheduleReconnect() {
|
|
6380
|
+
if (this.destroyed) return;
|
|
6381
|
+
if (this.reconnectAttempt >= ExotelAIAssistController.MAX_RECONNECT_ATTEMPTS) {
|
|
6382
|
+
const err = new Error("Connection failed after maximum retries");
|
|
6383
|
+
err.code = "MAX_RECONNECT_EXCEEDED";
|
|
6384
|
+
this._setStatus("error");
|
|
6385
|
+
this.emit("error", err);
|
|
6386
|
+
this.destroy();
|
|
6387
|
+
return;
|
|
6388
|
+
}
|
|
6389
|
+
const delay = Math.min(ExotelAIAssistController.RECONNECT_BASE_MS * Math.pow(2, this.reconnectAttempt), 30000);
|
|
6390
|
+
this.reconnectAttempt += 1;
|
|
6391
|
+
this._setStatus("connecting");
|
|
6392
|
+
this.reconnectTimer = setTimeout(() => {
|
|
6393
|
+
var _a;
|
|
6394
|
+
if (this.destroyed) return;
|
|
6395
|
+
this.connectionEstablished = false;
|
|
6396
|
+
(_a = this.transport) === null || _a === void 0 ? void 0 : _a.connect(Utils.buildWsUrl(this.params), this.params.authToken);
|
|
6397
|
+
}, delay);
|
|
6398
|
+
}
|
|
6399
|
+
_clearReconnectTimer() {
|
|
6400
|
+
if (this.reconnectTimer !== null) {
|
|
6401
|
+
clearTimeout(this.reconnectTimer);
|
|
6402
|
+
this.reconnectTimer = null;
|
|
6403
|
+
}
|
|
6404
|
+
}
|
|
6378
6405
|
_handleServerPayload(raw) {
|
|
6379
6406
|
var _a;
|
|
6380
6407
|
let parsed;
|
|
@@ -6437,39 +6464,14 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
6437
6464
|
}
|
|
6438
6465
|
}
|
|
6439
6466
|
}
|
|
6440
|
-
_scheduleReconnect() {
|
|
6441
|
-
if (this.destroyed) return;
|
|
6442
|
-
if (this.status === "error") return;
|
|
6443
|
-
if (this.reconnectAttempt >= this.maxReconnectAttempts) {
|
|
6444
|
-
const err = new Error("MAX_RECONNECT_EXCEEDED");
|
|
6445
|
-
err.code = "MAX_RECONNECT_EXCEEDED";
|
|
6446
|
-
this.emit("error", err);
|
|
6447
|
-
return;
|
|
6448
|
-
}
|
|
6449
|
-
const delay = Math.min(this.reconnectInterval * Math.pow(2, this.reconnectAttempt), 30000);
|
|
6450
|
-
this.reconnectTimer = setTimeout(() => {
|
|
6451
|
-
var _a;
|
|
6452
|
-
if (this.destroyed) return;
|
|
6453
|
-
this.reconnectAttempt += 1;
|
|
6454
|
-
this._setStatus("connecting");
|
|
6455
|
-
(_a = this.transport) === null || _a === void 0 ? void 0 : _a.connect(Utils.buildWsUrl(this.params), this.params.authToken);
|
|
6456
|
-
}, delay);
|
|
6457
|
-
}
|
|
6458
|
-
_clearReconnectTimer() {
|
|
6459
|
-
if (this.reconnectTimer !== null) {
|
|
6460
|
-
clearTimeout(this.reconnectTimer);
|
|
6461
|
-
this.reconnectTimer = null;
|
|
6462
|
-
}
|
|
6463
|
-
}
|
|
6464
6467
|
_setStatus(status) {
|
|
6465
6468
|
if (this.status === status) return;
|
|
6466
6469
|
this.status = status;
|
|
6467
6470
|
this.emit("statusChange", status);
|
|
6468
6471
|
}
|
|
6469
|
-
_log(...args) {
|
|
6470
|
-
console.log("[ExotelAIAssist]", ...args);
|
|
6471
|
-
}
|
|
6472
6472
|
}
|
|
6473
|
+
ExotelAIAssistController.MAX_RECONNECT_ATTEMPTS = 5;
|
|
6474
|
+
ExotelAIAssistController.RECONNECT_BASE_MS = 3000;
|
|
6473
6475
|
|
|
6474
6476
|
function useExotelAIAssist(params) {
|
|
6475
6477
|
const controllerRef = React.useRef(null);
|
|
@@ -6570,7 +6572,7 @@ function styleInject(css, ref) {
|
|
|
6570
6572
|
}
|
|
6571
6573
|
}
|
|
6572
6574
|
|
|
6573
|
-
var css_248z = ".oa-panel{background:#fff;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif}.oa-panel,.oa-theme-root{display:flex;flex-direction:column;height:100%;min-height:0;width:100%}.oa-theme-root{position:relative}.oa-header-title{color:#111827;font-size:16px;font-weight:600;line-height:20px;margin:0}.oa-tabs{width:100%}.oa-tabs-content{
|
|
6575
|
+
var css_248z = ".oa-panel{background:#fff;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif}.oa-panel,.oa-theme-root{display:flex;flex-direction:column;height:100%;min-height:0;width:100%}.oa-theme-root{position:relative}.oa-header-title{color:#111827;font-size:16px;font-weight:600;line-height:20px;margin:0}.oa-tabs{width:100%}.oa-tabs-content{flex:1;min-height:0;overflow:hidden}.oa-tabs-content[data-state=inactive]{display:none!important}.oa-tabs-content[data-state=active]{display:flex;flex-direction:column}.oa-tabs-list{border-bottom:1px solid #e5e7eb;display:flex;gap:20px;padding:0}.oa-tabs-trigger{align-items:center;background:transparent;border:none;border-radius:0;color:#6b7280;cursor:pointer;display:inline-flex;font-size:15px;font-weight:500;gap:6px;outline:none;padding:10px 0;position:relative}.oa-tabs-trigger:hover{color:#374151}.oa-tabs-trigger[data-state=active]{color:#2563eb}.oa-tabs-trigger[data-state=active]:after{background:#2563eb;border-radius:2px;bottom:-1px;content:\"\";height:2px;left:0;position:absolute;right:0}.oa-suggestion-card{border:1px solid rgba(17,24,39,.04);border-radius:12px;padding:12px}.oa-suggestion-card--recent-wrapper{animation:oa-suggestion-enter .4s ease-out,oa-border-shimmer 3s ease-in-out .4s infinite;background:linear-gradient(90deg,#7c3aed,#e11d48,#7c3aed);background-size:200% 100%;border-radius:12px;padding:2px}.oa-suggestion-card--recent-wrapper .oa-suggestion-card--recent{border:none}.oa-suggestion-card--recent{animation:none;background:linear-gradient(90deg,#f5f3ff 0,#fef3f2 50%,#fff7ed);border-radius:9px}.oa-suggestion-card--older{background:#f5f5f5;border:1px solid #e5e7eb}.oa-suggestion-card--older .oa-suggestion-text{color:#252525}@keyframes oa-suggestion-enter{0%{opacity:.3;transform:scale(.97)}to{opacity:1;transform:scale(1)}}@keyframes oa-border-shimmer{0%,to{background-position:0 50%}50%{background-position:100% 50%}}.oa-suggestion-text{color:#111827;font-size:13px;line-height:18px;white-space:pre-wrap}.oa-copy-icon{align-items:center;background:#fff;border:1px solid #e5e7eb;border-radius:8px;color:#6b7280;cursor:pointer;display:inline-flex;height:32px;justify-content:center;min-height:32px;min-width:32px;padding:0;width:32px}.oa-copy-icon:hover{background:#f9fafb;color:#374151}@keyframes oa-toast-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}.oa-muted{color:#6b7280}";
|
|
6574
6576
|
styleInject(css_248z);
|
|
6575
6577
|
|
|
6576
6578
|
function Sparkle() {
|
package/package.json
CHANGED