@exotel-npm-dev/exotel-ai-assist 1.0.0 → 1.0.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/controller/index.js +38 -11
- package/dist/index.js +38 -11
- package/dist/react/index.js +38 -11
- package/dist/types/index.d.ts +1 -0
- package/package.json +1 -1
package/dist/controller/index.js
CHANGED
|
@@ -398,13 +398,21 @@ class BroadcastChannelTransport {
|
|
|
398
398
|
if (this.socket) {
|
|
399
399
|
this.socket.close(1000, "reconnecting");
|
|
400
400
|
}
|
|
401
|
-
// authToken is passed as a subprotocol so it is sent in the
|
|
402
|
-
// Sec-WebSocket-Protocol header — the only way to include auth
|
|
403
|
-
// credentials in a browser WebSocket handshake.
|
|
404
401
|
this.socket = new WebSocket(url);
|
|
405
402
|
this.socket.onopen = () => {
|
|
406
403
|
var _a;
|
|
407
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
|
+
this.socket.send(JSON.stringify({
|
|
413
|
+
type: "auth",
|
|
414
|
+
access_token: authToken
|
|
415
|
+
}));
|
|
408
416
|
const msg = {
|
|
409
417
|
type: "CONNECTED"
|
|
410
418
|
};
|
|
@@ -429,11 +437,12 @@ class BroadcastChannelTransport {
|
|
|
429
437
|
(_a = this.handler) === null || _a === void 0 ? void 0 : _a.call(this, msg);
|
|
430
438
|
this.channel.postMessage(msg);
|
|
431
439
|
};
|
|
432
|
-
this.socket.onclose =
|
|
440
|
+
this.socket.onclose = ev => {
|
|
433
441
|
var _a;
|
|
434
442
|
this.socketConnected = false;
|
|
435
443
|
const msg = {
|
|
436
|
-
type: "DISCONNECTED"
|
|
444
|
+
type: "DISCONNECTED",
|
|
445
|
+
code: ev.code
|
|
437
446
|
};
|
|
438
447
|
(_a = this.handler) === null || _a === void 0 ? void 0 : _a.call(this, msg);
|
|
439
448
|
this.channel.postMessage(msg);
|
|
@@ -1534,12 +1543,14 @@ class Utils {
|
|
|
1534
1543
|
}
|
|
1535
1544
|
static constructQueryParams(params) {
|
|
1536
1545
|
// 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.
|
|
1537
1548
|
const INTERNAL_KEYS = new Set(["authToken", "accountId", "wssBaseUrl", "reconnectInterval", "maxReconnectAttempts"]);
|
|
1538
|
-
//
|
|
1549
|
+
// Stream-key resolution params sent as query parameters.
|
|
1539
1550
|
// accountId is embedded in the URL path — never sent as a query param.
|
|
1540
|
-
const
|
|
1551
|
+
const pairs = [];
|
|
1541
1552
|
if (params.call_sid !== undefined && params.call_sid !== null) {
|
|
1542
|
-
|
|
1553
|
+
pairs.push(["call_sid", String(params.call_sid)]);
|
|
1543
1554
|
}
|
|
1544
1555
|
// Extra caller-supplied params, excluding internals and already-mapped keys.
|
|
1545
1556
|
const extra = Object.entries(params).filter(([k, v]) => !INTERNAL_KEYS.has(k) && k !== "call_sid" && v !== undefined && v !== null).map(([k, v]) => [k, String(v)]);
|
|
@@ -1549,7 +1560,7 @@ class Utils {
|
|
|
1549
1560
|
// array as a plain object, turning index 0 into a key and the inner
|
|
1550
1561
|
// array's toString() (comma-joined) into the value.
|
|
1551
1562
|
const query = new URLSearchParams();
|
|
1552
|
-
[...
|
|
1563
|
+
[...pairs, ...extra].forEach(([k, v]) => query.append(k, v));
|
|
1553
1564
|
return query.toString();
|
|
1554
1565
|
}
|
|
1555
1566
|
/**
|
|
@@ -1645,7 +1656,11 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
1645
1656
|
case "DISCONNECTED":
|
|
1646
1657
|
this._setStatus("disconnected");
|
|
1647
1658
|
this.emit("onCallEnd");
|
|
1648
|
-
|
|
1659
|
+
// Close codes 4001 (auth_failed) and 4002 (auth_timeout) are permanent
|
|
1660
|
+
// auth failures — reconnecting with the same token would fail again.
|
|
1661
|
+
if (msg.code !== 4001 && msg.code !== 4002) {
|
|
1662
|
+
this._scheduleReconnect();
|
|
1663
|
+
}
|
|
1649
1664
|
break;
|
|
1650
1665
|
case "ERROR":
|
|
1651
1666
|
this._setStatus("error");
|
|
@@ -1657,14 +1672,26 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
1657
1672
|
}
|
|
1658
1673
|
}
|
|
1659
1674
|
_handleServerPayload(raw) {
|
|
1675
|
+
var _a;
|
|
1660
1676
|
let parsed;
|
|
1661
1677
|
try {
|
|
1662
1678
|
parsed = JSON.parse(raw);
|
|
1663
|
-
} catch (
|
|
1679
|
+
} catch (_b) {
|
|
1664
1680
|
this.emit("error", new Error(`Failed to parse server message: ${raw}`));
|
|
1665
1681
|
return;
|
|
1666
1682
|
}
|
|
1667
1683
|
this.emit("raw", parsed);
|
|
1684
|
+
// Auth error messages arrive before the server closes the connection.
|
|
1685
|
+
// Treat both as fatal — the server will follow up with a 4001/4002 close.
|
|
1686
|
+
const msgType = parsed.type;
|
|
1687
|
+
if (msgType === "auth_failed" || msgType === "auth_timeout") {
|
|
1688
|
+
const detail = (_a = parsed.error) !== null && _a !== void 0 ? _a : msgType;
|
|
1689
|
+
const err = new Error(detail);
|
|
1690
|
+
err.code = msgType.toUpperCase();
|
|
1691
|
+
this._setStatus("error");
|
|
1692
|
+
this.emit("error", err);
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1668
1695
|
if (parsed.config) {
|
|
1669
1696
|
this.emit("botConfig", parsed.config);
|
|
1670
1697
|
}
|
package/dist/index.js
CHANGED
|
@@ -19816,13 +19816,21 @@ class BroadcastChannelTransport {
|
|
|
19816
19816
|
if (this.socket) {
|
|
19817
19817
|
this.socket.close(1000, "reconnecting");
|
|
19818
19818
|
}
|
|
19819
|
-
// authToken is passed as a subprotocol so it is sent in the
|
|
19820
|
-
// Sec-WebSocket-Protocol header — the only way to include auth
|
|
19821
|
-
// credentials in a browser WebSocket handshake.
|
|
19822
19819
|
this.socket = new WebSocket(url);
|
|
19823
19820
|
this.socket.onopen = () => {
|
|
19824
19821
|
var _a;
|
|
19825
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
|
+
this.socket.send(JSON.stringify({
|
|
19831
|
+
type: "auth",
|
|
19832
|
+
access_token: authToken
|
|
19833
|
+
}));
|
|
19826
19834
|
const msg = {
|
|
19827
19835
|
type: "CONNECTED"
|
|
19828
19836
|
};
|
|
@@ -19847,11 +19855,12 @@ class BroadcastChannelTransport {
|
|
|
19847
19855
|
(_a = this.handler) === null || _a === void 0 ? void 0 : _a.call(this, msg);
|
|
19848
19856
|
this.channel.postMessage(msg);
|
|
19849
19857
|
};
|
|
19850
|
-
this.socket.onclose =
|
|
19858
|
+
this.socket.onclose = ev => {
|
|
19851
19859
|
var _a;
|
|
19852
19860
|
this.socketConnected = false;
|
|
19853
19861
|
const msg = {
|
|
19854
|
-
type: "DISCONNECTED"
|
|
19862
|
+
type: "DISCONNECTED",
|
|
19863
|
+
code: ev.code
|
|
19855
19864
|
};
|
|
19856
19865
|
(_a = this.handler) === null || _a === void 0 ? void 0 : _a.call(this, msg);
|
|
19857
19866
|
this.channel.postMessage(msg);
|
|
@@ -20952,12 +20961,14 @@ class Utils {
|
|
|
20952
20961
|
}
|
|
20953
20962
|
static constructQueryParams(params) {
|
|
20954
20963
|
// 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.
|
|
20955
20966
|
const INTERNAL_KEYS = new Set(["authToken", "accountId", "wssBaseUrl", "reconnectInterval", "maxReconnectAttempts"]);
|
|
20956
|
-
//
|
|
20967
|
+
// Stream-key resolution params sent as query parameters.
|
|
20957
20968
|
// accountId is embedded in the URL path — never sent as a query param.
|
|
20958
|
-
const
|
|
20969
|
+
const pairs = [];
|
|
20959
20970
|
if (params.call_sid !== undefined && params.call_sid !== null) {
|
|
20960
|
-
|
|
20971
|
+
pairs.push(["call_sid", String(params.call_sid)]);
|
|
20961
20972
|
}
|
|
20962
20973
|
// Extra caller-supplied params, excluding internals and already-mapped keys.
|
|
20963
20974
|
const extra = Object.entries(params).filter(([k, v]) => !INTERNAL_KEYS.has(k) && k !== "call_sid" && v !== undefined && v !== null).map(([k, v]) => [k, String(v)]);
|
|
@@ -20967,7 +20978,7 @@ class Utils {
|
|
|
20967
20978
|
// array as a plain object, turning index 0 into a key and the inner
|
|
20968
20979
|
// array's toString() (comma-joined) into the value.
|
|
20969
20980
|
const query = new URLSearchParams();
|
|
20970
|
-
[...
|
|
20981
|
+
[...pairs, ...extra].forEach(([k, v]) => query.append(k, v));
|
|
20971
20982
|
return query.toString();
|
|
20972
20983
|
}
|
|
20973
20984
|
/**
|
|
@@ -21063,7 +21074,11 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
21063
21074
|
case "DISCONNECTED":
|
|
21064
21075
|
this._setStatus("disconnected");
|
|
21065
21076
|
this.emit("onCallEnd");
|
|
21066
|
-
|
|
21077
|
+
// Close codes 4001 (auth_failed) and 4002 (auth_timeout) are permanent
|
|
21078
|
+
// auth failures — reconnecting with the same token would fail again.
|
|
21079
|
+
if (msg.code !== 4001 && msg.code !== 4002) {
|
|
21080
|
+
this._scheduleReconnect();
|
|
21081
|
+
}
|
|
21067
21082
|
break;
|
|
21068
21083
|
case "ERROR":
|
|
21069
21084
|
this._setStatus("error");
|
|
@@ -21075,14 +21090,26 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
21075
21090
|
}
|
|
21076
21091
|
}
|
|
21077
21092
|
_handleServerPayload(raw) {
|
|
21093
|
+
var _a;
|
|
21078
21094
|
let parsed;
|
|
21079
21095
|
try {
|
|
21080
21096
|
parsed = JSON.parse(raw);
|
|
21081
|
-
} catch (
|
|
21097
|
+
} catch (_b) {
|
|
21082
21098
|
this.emit("error", new Error(`Failed to parse server message: ${raw}`));
|
|
21083
21099
|
return;
|
|
21084
21100
|
}
|
|
21085
21101
|
this.emit("raw", parsed);
|
|
21102
|
+
// Auth error messages arrive before the server closes the connection.
|
|
21103
|
+
// Treat both as fatal — the server will follow up with a 4001/4002 close.
|
|
21104
|
+
const msgType = parsed.type;
|
|
21105
|
+
if (msgType === "auth_failed" || msgType === "auth_timeout") {
|
|
21106
|
+
const detail = (_a = parsed.error) !== null && _a !== void 0 ? _a : msgType;
|
|
21107
|
+
const err = new Error(detail);
|
|
21108
|
+
err.code = msgType.toUpperCase();
|
|
21109
|
+
this._setStatus("error");
|
|
21110
|
+
this.emit("error", err);
|
|
21111
|
+
return;
|
|
21112
|
+
}
|
|
21086
21113
|
if (parsed.config) {
|
|
21087
21114
|
this.emit("botConfig", parsed.config);
|
|
21088
21115
|
}
|
package/dist/react/index.js
CHANGED
|
@@ -5102,13 +5102,21 @@ class BroadcastChannelTransport {
|
|
|
5102
5102
|
if (this.socket) {
|
|
5103
5103
|
this.socket.close(1000, "reconnecting");
|
|
5104
5104
|
}
|
|
5105
|
-
// authToken is passed as a subprotocol so it is sent in the
|
|
5106
|
-
// Sec-WebSocket-Protocol header — the only way to include auth
|
|
5107
|
-
// credentials in a browser WebSocket handshake.
|
|
5108
5105
|
this.socket = new WebSocket(url);
|
|
5109
5106
|
this.socket.onopen = () => {
|
|
5110
5107
|
var _a;
|
|
5111
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
|
+
this.socket.send(JSON.stringify({
|
|
5117
|
+
type: "auth",
|
|
5118
|
+
access_token: authToken
|
|
5119
|
+
}));
|
|
5112
5120
|
const msg = {
|
|
5113
5121
|
type: "CONNECTED"
|
|
5114
5122
|
};
|
|
@@ -5133,11 +5141,12 @@ class BroadcastChannelTransport {
|
|
|
5133
5141
|
(_a = this.handler) === null || _a === void 0 ? void 0 : _a.call(this, msg);
|
|
5134
5142
|
this.channel.postMessage(msg);
|
|
5135
5143
|
};
|
|
5136
|
-
this.socket.onclose =
|
|
5144
|
+
this.socket.onclose = ev => {
|
|
5137
5145
|
var _a;
|
|
5138
5146
|
this.socketConnected = false;
|
|
5139
5147
|
const msg = {
|
|
5140
|
-
type: "DISCONNECTED"
|
|
5148
|
+
type: "DISCONNECTED",
|
|
5149
|
+
code: ev.code
|
|
5141
5150
|
};
|
|
5142
5151
|
(_a = this.handler) === null || _a === void 0 ? void 0 : _a.call(this, msg);
|
|
5143
5152
|
this.channel.postMessage(msg);
|
|
@@ -6238,12 +6247,14 @@ class Utils {
|
|
|
6238
6247
|
}
|
|
6239
6248
|
static constructQueryParams(params) {
|
|
6240
6249
|
// 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.
|
|
6241
6252
|
const INTERNAL_KEYS = new Set(["authToken", "accountId", "wssBaseUrl", "reconnectInterval", "maxReconnectAttempts"]);
|
|
6242
|
-
//
|
|
6253
|
+
// Stream-key resolution params sent as query parameters.
|
|
6243
6254
|
// accountId is embedded in the URL path — never sent as a query param.
|
|
6244
|
-
const
|
|
6255
|
+
const pairs = [];
|
|
6245
6256
|
if (params.call_sid !== undefined && params.call_sid !== null) {
|
|
6246
|
-
|
|
6257
|
+
pairs.push(["call_sid", String(params.call_sid)]);
|
|
6247
6258
|
}
|
|
6248
6259
|
// Extra caller-supplied params, excluding internals and already-mapped keys.
|
|
6249
6260
|
const extra = Object.entries(params).filter(([k, v]) => !INTERNAL_KEYS.has(k) && k !== "call_sid" && v !== undefined && v !== null).map(([k, v]) => [k, String(v)]);
|
|
@@ -6253,7 +6264,7 @@ class Utils {
|
|
|
6253
6264
|
// array as a plain object, turning index 0 into a key and the inner
|
|
6254
6265
|
// array's toString() (comma-joined) into the value.
|
|
6255
6266
|
const query = new URLSearchParams();
|
|
6256
|
-
[...
|
|
6267
|
+
[...pairs, ...extra].forEach(([k, v]) => query.append(k, v));
|
|
6257
6268
|
return query.toString();
|
|
6258
6269
|
}
|
|
6259
6270
|
/**
|
|
@@ -6349,7 +6360,11 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
6349
6360
|
case "DISCONNECTED":
|
|
6350
6361
|
this._setStatus("disconnected");
|
|
6351
6362
|
this.emit("onCallEnd");
|
|
6352
|
-
|
|
6363
|
+
// Close codes 4001 (auth_failed) and 4002 (auth_timeout) are permanent
|
|
6364
|
+
// auth failures — reconnecting with the same token would fail again.
|
|
6365
|
+
if (msg.code !== 4001 && msg.code !== 4002) {
|
|
6366
|
+
this._scheduleReconnect();
|
|
6367
|
+
}
|
|
6353
6368
|
break;
|
|
6354
6369
|
case "ERROR":
|
|
6355
6370
|
this._setStatus("error");
|
|
@@ -6361,14 +6376,26 @@ class ExotelAIAssistController extends EventEmitter {
|
|
|
6361
6376
|
}
|
|
6362
6377
|
}
|
|
6363
6378
|
_handleServerPayload(raw) {
|
|
6379
|
+
var _a;
|
|
6364
6380
|
let parsed;
|
|
6365
6381
|
try {
|
|
6366
6382
|
parsed = JSON.parse(raw);
|
|
6367
|
-
} catch (
|
|
6383
|
+
} catch (_b) {
|
|
6368
6384
|
this.emit("error", new Error(`Failed to parse server message: ${raw}`));
|
|
6369
6385
|
return;
|
|
6370
6386
|
}
|
|
6371
6387
|
this.emit("raw", parsed);
|
|
6388
|
+
// Auth error messages arrive before the server closes the connection.
|
|
6389
|
+
// Treat both as fatal — the server will follow up with a 4001/4002 close.
|
|
6390
|
+
const msgType = parsed.type;
|
|
6391
|
+
if (msgType === "auth_failed" || msgType === "auth_timeout") {
|
|
6392
|
+
const detail = (_a = parsed.error) !== null && _a !== void 0 ? _a : msgType;
|
|
6393
|
+
const err = new Error(detail);
|
|
6394
|
+
err.code = msgType.toUpperCase();
|
|
6395
|
+
this._setStatus("error");
|
|
6396
|
+
this.emit("error", err);
|
|
6397
|
+
return;
|
|
6398
|
+
}
|
|
6372
6399
|
if (parsed.config) {
|
|
6373
6400
|
this.emit("botConfig", parsed.config);
|
|
6374
6401
|
}
|
package/dist/types/index.d.ts
CHANGED
package/package.json
CHANGED