@ipcom/asterisk-ari 0.0.156 → 0.0.158
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/cjs/index.cjs +690 -138
- package/dist/cjs/index.cjs.map +3 -3
- package/dist/esm/index.js +684 -132
- package/dist/esm/index.js.map +3 -3
- package/dist/types/ari-client/ariClient.d.ts +11 -1
- package/dist/types/ari-client/ariClient.d.ts.map +1 -1
- package/dist/types/ari-client/resources/baseResource.d.ts +6 -1
- package/dist/types/ari-client/resources/baseResource.d.ts.map +1 -1
- package/dist/types/ari-client/resources/bridges.d.ts +25 -1
- package/dist/types/ari-client/resources/bridges.d.ts.map +1 -1
- package/dist/types/ari-client/resources/channels.d.ts +12 -0
- package/dist/types/ari-client/resources/channels.d.ts.map +1 -1
- package/dist/types/ari-client/resources/playbacks.d.ts +34 -1
- package/dist/types/ari-client/resources/playbacks.d.ts.map +1 -1
- package/dist/types/ari-client/websocketClient.d.ts +49 -1
- package/dist/types/ari-client/websocketClient.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/esm/index.js
CHANGED
|
@@ -915,16 +915,6 @@ var Asterisk = class {
|
|
|
915
915
|
import { EventEmitter } from "events";
|
|
916
916
|
import { isAxiosError as isAxiosError2 } from "axios";
|
|
917
917
|
|
|
918
|
-
// src/ari-client/interfaces/events.types.ts
|
|
919
|
-
var bridgeEvents = [
|
|
920
|
-
"BridgeCreated",
|
|
921
|
-
"BridgeDestroyed",
|
|
922
|
-
"BridgeMerged",
|
|
923
|
-
"BridgeBlindTransfer",
|
|
924
|
-
"BridgeAttendedTransfer",
|
|
925
|
-
"BridgeVideoSourceChanged"
|
|
926
|
-
];
|
|
927
|
-
|
|
928
918
|
// src/ari-client/utils.ts
|
|
929
919
|
function toQueryParams2(options) {
|
|
930
920
|
return new URLSearchParams(
|
|
@@ -956,6 +946,8 @@ var BridgeInstance = class {
|
|
|
956
946
|
this.id = bridgeId || `bridge-${Date.now()}`;
|
|
957
947
|
}
|
|
958
948
|
eventEmitter = new EventEmitter();
|
|
949
|
+
listenersMap = /* @__PURE__ */ new Map();
|
|
950
|
+
// 🔹 Guarda listeners para remoção posterior
|
|
959
951
|
bridgeData = null;
|
|
960
952
|
id;
|
|
961
953
|
/**
|
|
@@ -985,12 +977,23 @@ var BridgeInstance = class {
|
|
|
985
977
|
if (!event) {
|
|
986
978
|
throw new Error("Event type is required");
|
|
987
979
|
}
|
|
980
|
+
const existingListeners = this.listenersMap.get(event) || [];
|
|
981
|
+
if (existingListeners.includes(listener)) {
|
|
982
|
+
console.warn(
|
|
983
|
+
`Listener j\xE1 registrado para evento ${event}, reutilizando.`
|
|
984
|
+
);
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
988
987
|
const wrappedListener = (data) => {
|
|
989
988
|
if ("bridge" in data && data.bridge?.id === this.id) {
|
|
990
989
|
listener(data);
|
|
991
990
|
}
|
|
992
991
|
};
|
|
993
992
|
this.eventEmitter.on(event, wrappedListener);
|
|
993
|
+
if (!this.listenersMap.has(event)) {
|
|
994
|
+
this.listenersMap.set(event, []);
|
|
995
|
+
}
|
|
996
|
+
this.listenersMap.get(event).push(wrappedListener);
|
|
994
997
|
}
|
|
995
998
|
/**
|
|
996
999
|
* Registers a one-time listener for specific bridge events.
|
|
@@ -1002,12 +1005,25 @@ var BridgeInstance = class {
|
|
|
1002
1005
|
if (!event) {
|
|
1003
1006
|
throw new Error("Event type is required");
|
|
1004
1007
|
}
|
|
1008
|
+
const eventKey = `${event}-${this.id}`;
|
|
1009
|
+
const existingListeners = this.listenersMap.get(eventKey) || [];
|
|
1010
|
+
if (existingListeners.includes(listener)) {
|
|
1011
|
+
console.warn(
|
|
1012
|
+
`One-time listener j\xE1 registrado para evento ${eventKey}, reutilizando.`
|
|
1013
|
+
);
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1005
1016
|
const wrappedListener = (data) => {
|
|
1006
1017
|
if ("bridge" in data && data.bridge?.id === this.id) {
|
|
1007
1018
|
listener(data);
|
|
1019
|
+
this.off(event, wrappedListener);
|
|
1008
1020
|
}
|
|
1009
1021
|
};
|
|
1010
1022
|
this.eventEmitter.once(event, wrappedListener);
|
|
1023
|
+
if (!this.listenersMap.has(eventKey)) {
|
|
1024
|
+
this.listenersMap.set(eventKey, []);
|
|
1025
|
+
}
|
|
1026
|
+
this.listenersMap.get(eventKey).push(wrappedListener);
|
|
1011
1027
|
}
|
|
1012
1028
|
/**
|
|
1013
1029
|
* Removes event listener(s) from the bridge.
|
|
@@ -1021,10 +1037,25 @@ var BridgeInstance = class {
|
|
|
1021
1037
|
}
|
|
1022
1038
|
if (listener) {
|
|
1023
1039
|
this.eventEmitter.off(event, listener);
|
|
1040
|
+
const storedListeners = this.listenersMap.get(event) || [];
|
|
1041
|
+
this.listenersMap.set(
|
|
1042
|
+
event,
|
|
1043
|
+
storedListeners.filter((l) => l !== listener)
|
|
1044
|
+
);
|
|
1024
1045
|
} else {
|
|
1025
1046
|
this.eventEmitter.removeAllListeners(event);
|
|
1047
|
+
this.listenersMap.delete(event);
|
|
1026
1048
|
}
|
|
1027
1049
|
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Cleans up the BridgeInstance, resetting its state and clearing resources.
|
|
1052
|
+
*/
|
|
1053
|
+
cleanup() {
|
|
1054
|
+
this.bridgeData = null;
|
|
1055
|
+
this.removeAllListeners();
|
|
1056
|
+
this.listenersMap.clear();
|
|
1057
|
+
console.log(`Bridge instance ${this.id} cleaned up`);
|
|
1058
|
+
}
|
|
1028
1059
|
/**
|
|
1029
1060
|
* Emits an event if it corresponds to the current bridge.
|
|
1030
1061
|
*
|
|
@@ -1043,6 +1074,16 @@ var BridgeInstance = class {
|
|
|
1043
1074
|
* Removes all event listeners from this bridge instance.
|
|
1044
1075
|
*/
|
|
1045
1076
|
removeAllListeners() {
|
|
1077
|
+
console.log(`Removing all event listeners for bridge ${this.id}`);
|
|
1078
|
+
this.listenersMap.forEach((listeners, event) => {
|
|
1079
|
+
listeners.forEach((listener) => {
|
|
1080
|
+
this.eventEmitter.off(
|
|
1081
|
+
event,
|
|
1082
|
+
listener
|
|
1083
|
+
);
|
|
1084
|
+
});
|
|
1085
|
+
});
|
|
1086
|
+
this.listenersMap.clear();
|
|
1046
1087
|
this.eventEmitter.removeAllListeners();
|
|
1047
1088
|
}
|
|
1048
1089
|
/**
|
|
@@ -1211,6 +1252,7 @@ var Bridges = class {
|
|
|
1211
1252
|
this.client = client;
|
|
1212
1253
|
}
|
|
1213
1254
|
bridgeInstances = /* @__PURE__ */ new Map();
|
|
1255
|
+
eventQueue = /* @__PURE__ */ new Map();
|
|
1214
1256
|
/**
|
|
1215
1257
|
* Creates or retrieves a Bridge instance.
|
|
1216
1258
|
*
|
|
@@ -1241,9 +1283,31 @@ var Bridges = class {
|
|
|
1241
1283
|
return this.bridgeInstances.get(id);
|
|
1242
1284
|
} catch (error) {
|
|
1243
1285
|
const message = getErrorMessage(error);
|
|
1286
|
+
console.warn(`Error creating/retrieving bridge instance:`, message);
|
|
1244
1287
|
throw new Error(`Failed to manage bridge instance: ${message}`);
|
|
1245
1288
|
}
|
|
1246
1289
|
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Removes all bridge instances and cleans up their resources.
|
|
1292
|
+
* This method ensures proper cleanup of all bridges and their associated listeners.
|
|
1293
|
+
*/
|
|
1294
|
+
remove() {
|
|
1295
|
+
const bridgeIds = Array.from(this.bridgeInstances.keys());
|
|
1296
|
+
for (const bridgeId of bridgeIds) {
|
|
1297
|
+
try {
|
|
1298
|
+
const instance = this.bridgeInstances.get(bridgeId);
|
|
1299
|
+
if (instance) {
|
|
1300
|
+
instance.cleanup();
|
|
1301
|
+
this.bridgeInstances.delete(bridgeId);
|
|
1302
|
+
console.log(`Bridge instance ${bridgeId} removed and cleaned up`);
|
|
1303
|
+
}
|
|
1304
|
+
} catch (error) {
|
|
1305
|
+
console.error(`Error cleaning up bridge ${bridgeId}:`, error);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
this.bridgeInstances.clear();
|
|
1309
|
+
console.log("All bridge instances have been removed and cleaned up");
|
|
1310
|
+
}
|
|
1247
1311
|
/**
|
|
1248
1312
|
* Removes a bridge instance from the collection of managed bridges.
|
|
1249
1313
|
*
|
|
@@ -1256,14 +1320,20 @@ var Bridges = class {
|
|
|
1256
1320
|
*/
|
|
1257
1321
|
removeBridgeInstance(bridgeId) {
|
|
1258
1322
|
if (!bridgeId) {
|
|
1259
|
-
throw new Error("ID
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1323
|
+
throw new Error("Bridge ID is required");
|
|
1324
|
+
}
|
|
1325
|
+
const instance = this.bridgeInstances.get(bridgeId);
|
|
1326
|
+
if (instance) {
|
|
1327
|
+
try {
|
|
1328
|
+
instance.cleanup();
|
|
1329
|
+
this.bridgeInstances.delete(bridgeId);
|
|
1330
|
+
console.log(`Bridge instance ${bridgeId} removed from memory`);
|
|
1331
|
+
} catch (error) {
|
|
1332
|
+
console.error(`Error removing bridge instance ${bridgeId}:`, error);
|
|
1333
|
+
throw error;
|
|
1334
|
+
}
|
|
1265
1335
|
} else {
|
|
1266
|
-
console.warn(`
|
|
1336
|
+
console.warn(`Attempt to remove non-existent instance: ${bridgeId}`);
|
|
1267
1337
|
}
|
|
1268
1338
|
}
|
|
1269
1339
|
/**
|
|
@@ -1280,25 +1350,51 @@ var Bridges = class {
|
|
|
1280
1350
|
*
|
|
1281
1351
|
* @remarks
|
|
1282
1352
|
* - If the event is invalid (null or undefined), a warning is logged and the function returns early.
|
|
1283
|
-
* - The function checks if the event is bridge-related and if the event
|
|
1353
|
+
* - The function checks if the event is bridge-related and if the event contains a valid bridge ID.
|
|
1284
1354
|
* - If a matching bridge instance is found, the event is emitted to that instance.
|
|
1285
1355
|
* - If no matching bridge instance is found, a warning is logged.
|
|
1286
1356
|
*/
|
|
1287
1357
|
propagateEventToBridge(event) {
|
|
1288
|
-
if (!event) {
|
|
1289
|
-
console.warn("
|
|
1358
|
+
if (!event || !("bridge" in event) || !event.bridge?.id) {
|
|
1359
|
+
console.warn("Invalid WebSocket event received");
|
|
1290
1360
|
return;
|
|
1291
1361
|
}
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1362
|
+
const key = `${event.type}-${event.bridge.id}`;
|
|
1363
|
+
const existing = this.eventQueue.get(key);
|
|
1364
|
+
if (existing) {
|
|
1365
|
+
clearTimeout(existing);
|
|
1366
|
+
}
|
|
1367
|
+
this.eventQueue.set(
|
|
1368
|
+
key,
|
|
1369
|
+
setTimeout(() => {
|
|
1370
|
+
const instance = this.bridgeInstances.get(event.bridge.id);
|
|
1371
|
+
if (instance) {
|
|
1372
|
+
instance.emitEvent(event);
|
|
1373
|
+
} else {
|
|
1374
|
+
console.warn(
|
|
1375
|
+
`No instance found for bridge ${event.bridge.id}. Event ignored.`
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
this.eventQueue.delete(key);
|
|
1379
|
+
}, 100)
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* Performs a cleanup of the Bridges instance, clearing all event queues and removing all bridge instances.
|
|
1384
|
+
*
|
|
1385
|
+
* This method is responsible for:
|
|
1386
|
+
* 1. Clearing all pending timeouts in the event queue.
|
|
1387
|
+
* 2. Removing all bridge instances managed by this Bridges object.
|
|
1388
|
+
*
|
|
1389
|
+
* It should be called when the Bridges instance is no longer needed or before reinitializing
|
|
1390
|
+
* to ensure all resources are properly released.
|
|
1391
|
+
*
|
|
1392
|
+
* @returns {void}
|
|
1393
|
+
*/
|
|
1394
|
+
cleanup() {
|
|
1395
|
+
this.eventQueue.forEach((timeout) => clearTimeout(timeout));
|
|
1396
|
+
this.eventQueue.clear();
|
|
1397
|
+
this.remove();
|
|
1302
1398
|
}
|
|
1303
1399
|
/**
|
|
1304
1400
|
* Lists all active bridges in the system.
|
|
@@ -1625,6 +1721,8 @@ var ChannelInstance = class {
|
|
|
1625
1721
|
}
|
|
1626
1722
|
eventEmitter = new EventEmitter2();
|
|
1627
1723
|
channelData = null;
|
|
1724
|
+
listenersMap = /* @__PURE__ */ new Map();
|
|
1725
|
+
// 🔹 Guarda listeners para remoção posterior
|
|
1628
1726
|
id;
|
|
1629
1727
|
/**
|
|
1630
1728
|
* Registers an event listener for specific channel events
|
|
@@ -1633,12 +1731,23 @@ var ChannelInstance = class {
|
|
|
1633
1731
|
if (!event) {
|
|
1634
1732
|
throw new Error("Event type is required");
|
|
1635
1733
|
}
|
|
1734
|
+
const existingListeners = this.listenersMap.get(event) || [];
|
|
1735
|
+
if (existingListeners.includes(listener)) {
|
|
1736
|
+
console.warn(
|
|
1737
|
+
`Listener j\xE1 registrado para evento ${event}, reutilizando.`
|
|
1738
|
+
);
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1636
1741
|
const wrappedListener = (data) => {
|
|
1637
1742
|
if ("channel" in data && data.channel?.id === this.id) {
|
|
1638
1743
|
listener(data);
|
|
1639
1744
|
}
|
|
1640
1745
|
};
|
|
1641
1746
|
this.eventEmitter.on(event, wrappedListener);
|
|
1747
|
+
if (!this.listenersMap.has(event)) {
|
|
1748
|
+
this.listenersMap.set(event, []);
|
|
1749
|
+
}
|
|
1750
|
+
this.listenersMap.get(event).push(wrappedListener);
|
|
1642
1751
|
}
|
|
1643
1752
|
/**
|
|
1644
1753
|
* Registers a one-time event listener
|
|
@@ -1647,12 +1756,25 @@ var ChannelInstance = class {
|
|
|
1647
1756
|
if (!event) {
|
|
1648
1757
|
throw new Error("Event type is required");
|
|
1649
1758
|
}
|
|
1759
|
+
const eventKey = `${event}-${this.id}`;
|
|
1760
|
+
const existingListeners = this.listenersMap.get(eventKey) || [];
|
|
1761
|
+
if (existingListeners.includes(listener)) {
|
|
1762
|
+
console.warn(
|
|
1763
|
+
`One-time listener j\xE1 registrado para evento ${eventKey}, reutilizando.`
|
|
1764
|
+
);
|
|
1765
|
+
return;
|
|
1766
|
+
}
|
|
1650
1767
|
const wrappedListener = (data) => {
|
|
1651
1768
|
if ("channel" in data && data.channel?.id === this.id) {
|
|
1652
1769
|
listener(data);
|
|
1770
|
+
this.off(event, wrappedListener);
|
|
1653
1771
|
}
|
|
1654
1772
|
};
|
|
1655
1773
|
this.eventEmitter.once(event, wrappedListener);
|
|
1774
|
+
if (!this.listenersMap.has(eventKey)) {
|
|
1775
|
+
this.listenersMap.set(eventKey, []);
|
|
1776
|
+
}
|
|
1777
|
+
this.listenersMap.get(eventKey).push(wrappedListener);
|
|
1656
1778
|
}
|
|
1657
1779
|
/**
|
|
1658
1780
|
* Removes event listener(s) for a specific WebSocket event type.
|
|
@@ -1669,10 +1791,25 @@ var ChannelInstance = class {
|
|
|
1669
1791
|
}
|
|
1670
1792
|
if (listener) {
|
|
1671
1793
|
this.eventEmitter.off(event, listener);
|
|
1794
|
+
const storedListeners = this.listenersMap.get(event) || [];
|
|
1795
|
+
this.listenersMap.set(
|
|
1796
|
+
event,
|
|
1797
|
+
storedListeners.filter((l) => l !== listener)
|
|
1798
|
+
);
|
|
1672
1799
|
} else {
|
|
1673
1800
|
this.eventEmitter.removeAllListeners(event);
|
|
1801
|
+
this.listenersMap.delete(event);
|
|
1674
1802
|
}
|
|
1675
1803
|
}
|
|
1804
|
+
/**
|
|
1805
|
+
* Cleans up the ChannelInstance, resetting its state and clearing resources.
|
|
1806
|
+
*/
|
|
1807
|
+
cleanup() {
|
|
1808
|
+
this.channelData = null;
|
|
1809
|
+
this.removeAllListeners();
|
|
1810
|
+
this.listenersMap.clear();
|
|
1811
|
+
console.log(`Channel instance ${this.id} cleaned up`);
|
|
1812
|
+
}
|
|
1676
1813
|
/**
|
|
1677
1814
|
* Emits an event if it matches the current channel
|
|
1678
1815
|
*/
|
|
@@ -1692,6 +1829,16 @@ var ChannelInstance = class {
|
|
|
1692
1829
|
* @return {void} This method does not return a value.
|
|
1693
1830
|
*/
|
|
1694
1831
|
removeAllListeners() {
|
|
1832
|
+
console.log(`Removing all event listeners for channel ${this.id}`);
|
|
1833
|
+
this.listenersMap.forEach((listeners, event) => {
|
|
1834
|
+
listeners.forEach((listener) => {
|
|
1835
|
+
this.eventEmitter.off(
|
|
1836
|
+
event,
|
|
1837
|
+
listener
|
|
1838
|
+
);
|
|
1839
|
+
});
|
|
1840
|
+
});
|
|
1841
|
+
this.listenersMap.clear();
|
|
1695
1842
|
this.eventEmitter.removeAllListeners();
|
|
1696
1843
|
}
|
|
1697
1844
|
/**
|
|
@@ -1980,6 +2127,7 @@ var Channels = class {
|
|
|
1980
2127
|
this.client = client;
|
|
1981
2128
|
}
|
|
1982
2129
|
channelInstances = /* @__PURE__ */ new Map();
|
|
2130
|
+
eventQueue = /* @__PURE__ */ new Map();
|
|
1983
2131
|
/**
|
|
1984
2132
|
* Creates or retrieves a ChannelInstance.
|
|
1985
2133
|
*
|
|
@@ -2015,6 +2163,32 @@ var Channels = class {
|
|
|
2015
2163
|
throw new Error(`Failed to manage channel instance: ${message}`);
|
|
2016
2164
|
}
|
|
2017
2165
|
}
|
|
2166
|
+
cleanup() {
|
|
2167
|
+
this.eventQueue.forEach((timeout) => clearTimeout(timeout));
|
|
2168
|
+
this.eventQueue.clear();
|
|
2169
|
+
this.remove();
|
|
2170
|
+
}
|
|
2171
|
+
/**
|
|
2172
|
+
* Removes all channel instances and cleans up their resources.
|
|
2173
|
+
* This method ensures proper cleanup of all channels and their associated listeners.
|
|
2174
|
+
*/
|
|
2175
|
+
remove() {
|
|
2176
|
+
const channelIds = Array.from(this.channelInstances.keys());
|
|
2177
|
+
for (const channelId of channelIds) {
|
|
2178
|
+
try {
|
|
2179
|
+
const instance = this.channelInstances.get(channelId);
|
|
2180
|
+
if (instance) {
|
|
2181
|
+
instance.cleanup();
|
|
2182
|
+
this.channelInstances.delete(channelId);
|
|
2183
|
+
console.log(`Channel instance ${channelId} removed and cleaned up`);
|
|
2184
|
+
}
|
|
2185
|
+
} catch (error) {
|
|
2186
|
+
console.error(`Error cleaning up channel ${channelId}:`, error);
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
this.channelInstances.clear();
|
|
2190
|
+
console.log("All channel instances have been removed and cleaned up");
|
|
2191
|
+
}
|
|
2018
2192
|
/**
|
|
2019
2193
|
* Retrieves the details of a specific channel.
|
|
2020
2194
|
*
|
|
@@ -2041,10 +2215,16 @@ var Channels = class {
|
|
|
2041
2215
|
if (!channelId) {
|
|
2042
2216
|
throw new Error("Channel ID is required");
|
|
2043
2217
|
}
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2218
|
+
const instance = this.channelInstances.get(channelId);
|
|
2219
|
+
if (instance) {
|
|
2220
|
+
try {
|
|
2221
|
+
instance.cleanup();
|
|
2222
|
+
this.channelInstances.delete(channelId);
|
|
2223
|
+
console.log(`Channel instance ${channelId} removed from memory`);
|
|
2224
|
+
} catch (error) {
|
|
2225
|
+
console.error(`Error removing channel instance ${channelId}:`, error);
|
|
2226
|
+
throw error;
|
|
2227
|
+
}
|
|
2048
2228
|
} else {
|
|
2049
2229
|
console.warn(`Attempt to remove non-existent instance: ${channelId}`);
|
|
2050
2230
|
}
|
|
@@ -2053,18 +2233,29 @@ var Channels = class {
|
|
|
2053
2233
|
* Propagates a WebSocket event to a specific channel.
|
|
2054
2234
|
*/
|
|
2055
2235
|
propagateEventToChannel(event) {
|
|
2056
|
-
if (!event) {
|
|
2236
|
+
if (!event || !("channel" in event) || !event.channel?.id) {
|
|
2057
2237
|
console.warn("Invalid WebSocket event received");
|
|
2058
2238
|
return;
|
|
2059
2239
|
}
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2240
|
+
const key = `${event.type}-${event.channel.id}`;
|
|
2241
|
+
const existing = this.eventQueue.get(key);
|
|
2242
|
+
if (existing) {
|
|
2243
|
+
clearTimeout(existing);
|
|
2244
|
+
}
|
|
2245
|
+
this.eventQueue.set(
|
|
2246
|
+
key,
|
|
2247
|
+
setTimeout(() => {
|
|
2248
|
+
const instance = this.channelInstances.get(event.channel.id);
|
|
2249
|
+
if (instance) {
|
|
2250
|
+
instance.emitEvent(event);
|
|
2251
|
+
} else {
|
|
2252
|
+
console.warn(
|
|
2253
|
+
`No instance found for channel ${event.channel.id}. Event ignored.`
|
|
2254
|
+
);
|
|
2255
|
+
}
|
|
2256
|
+
this.eventQueue.delete(key);
|
|
2257
|
+
}, 100)
|
|
2258
|
+
);
|
|
2068
2259
|
}
|
|
2069
2260
|
/**
|
|
2070
2261
|
* Initiates a new channel.
|
|
@@ -2545,6 +2736,8 @@ var PlaybackInstance = class {
|
|
|
2545
2736
|
this.id = playbackId;
|
|
2546
2737
|
}
|
|
2547
2738
|
eventEmitter = new EventEmitter3();
|
|
2739
|
+
listenersMap = /* @__PURE__ */ new Map();
|
|
2740
|
+
// 🔹 Guarda listeners para remoção posterior
|
|
2548
2741
|
playbackData = null;
|
|
2549
2742
|
id;
|
|
2550
2743
|
/**
|
|
@@ -2557,12 +2750,23 @@ var PlaybackInstance = class {
|
|
|
2557
2750
|
if (!event) {
|
|
2558
2751
|
throw new Error("Event type is required");
|
|
2559
2752
|
}
|
|
2753
|
+
const existingListeners = this.listenersMap.get(event) || [];
|
|
2754
|
+
if (existingListeners.includes(listener)) {
|
|
2755
|
+
console.warn(
|
|
2756
|
+
`Listener j\xE1 registrado para evento ${event}, reutilizando.`
|
|
2757
|
+
);
|
|
2758
|
+
return;
|
|
2759
|
+
}
|
|
2560
2760
|
const wrappedListener = (data) => {
|
|
2561
2761
|
if ("playback" in data && data.playback?.id === this.id) {
|
|
2562
2762
|
listener(data);
|
|
2563
2763
|
}
|
|
2564
2764
|
};
|
|
2565
2765
|
this.eventEmitter.on(event, wrappedListener);
|
|
2766
|
+
if (!this.listenersMap.has(event)) {
|
|
2767
|
+
this.listenersMap.set(event, []);
|
|
2768
|
+
}
|
|
2769
|
+
this.listenersMap.get(event).push(wrappedListener);
|
|
2566
2770
|
}
|
|
2567
2771
|
/**
|
|
2568
2772
|
* Registers a one-time event listener for a specific WebSocket event type.
|
|
@@ -2574,12 +2778,25 @@ var PlaybackInstance = class {
|
|
|
2574
2778
|
if (!event) {
|
|
2575
2779
|
throw new Error("Event type is required");
|
|
2576
2780
|
}
|
|
2781
|
+
const eventKey = `${event}-${this.id}`;
|
|
2782
|
+
const existingListeners = this.listenersMap.get(eventKey) || [];
|
|
2783
|
+
if (existingListeners.includes(listener)) {
|
|
2784
|
+
console.warn(
|
|
2785
|
+
`One-time listener j\xE1 registrado para evento ${eventKey}, reutilizando.`
|
|
2786
|
+
);
|
|
2787
|
+
return;
|
|
2788
|
+
}
|
|
2577
2789
|
const wrappedListener = (data) => {
|
|
2578
2790
|
if ("playback" in data && data.playback?.id === this.id) {
|
|
2579
2791
|
listener(data);
|
|
2792
|
+
this.off(event, wrappedListener);
|
|
2580
2793
|
}
|
|
2581
2794
|
};
|
|
2582
2795
|
this.eventEmitter.once(event, wrappedListener);
|
|
2796
|
+
if (!this.listenersMap.has(eventKey)) {
|
|
2797
|
+
this.listenersMap.set(eventKey, []);
|
|
2798
|
+
}
|
|
2799
|
+
this.listenersMap.get(eventKey).push(wrappedListener);
|
|
2583
2800
|
}
|
|
2584
2801
|
/**
|
|
2585
2802
|
* Removes event listener(s) for a specific WebSocket event type.
|
|
@@ -2593,10 +2810,25 @@ var PlaybackInstance = class {
|
|
|
2593
2810
|
}
|
|
2594
2811
|
if (listener) {
|
|
2595
2812
|
this.eventEmitter.off(event, listener);
|
|
2813
|
+
const storedListeners = this.listenersMap.get(event) || [];
|
|
2814
|
+
this.listenersMap.set(
|
|
2815
|
+
event,
|
|
2816
|
+
storedListeners.filter((l) => l !== listener)
|
|
2817
|
+
);
|
|
2596
2818
|
} else {
|
|
2597
2819
|
this.eventEmitter.removeAllListeners(event);
|
|
2820
|
+
this.listenersMap.delete(event);
|
|
2598
2821
|
}
|
|
2599
2822
|
}
|
|
2823
|
+
/**
|
|
2824
|
+
* Cleans up the PlaybackInstance, resetting its state and clearing resources.
|
|
2825
|
+
*/
|
|
2826
|
+
cleanup() {
|
|
2827
|
+
this.playbackData = null;
|
|
2828
|
+
this.removeAllListeners();
|
|
2829
|
+
this.listenersMap.clear();
|
|
2830
|
+
console.log(`Playback instance ${this.id} cleaned up`);
|
|
2831
|
+
}
|
|
2600
2832
|
/**
|
|
2601
2833
|
* Emits a WebSocket event if it matches the current playback instance.
|
|
2602
2834
|
*
|
|
@@ -2670,9 +2902,29 @@ var PlaybackInstance = class {
|
|
|
2670
2902
|
}
|
|
2671
2903
|
}
|
|
2672
2904
|
/**
|
|
2673
|
-
* Removes all event listeners
|
|
2905
|
+
* Removes all event listeners associated with this playback instance.
|
|
2906
|
+
* This method clears both the internal listener map and the event emitter.
|
|
2907
|
+
*
|
|
2908
|
+
* @remarks
|
|
2909
|
+
* This method performs the following actions:
|
|
2910
|
+
* 1. Logs a message indicating the removal of listeners.
|
|
2911
|
+
* 2. Iterates through all stored listeners and removes them from the event emitter.
|
|
2912
|
+
* 3. Clears the internal listener map.
|
|
2913
|
+
* 4. Removes all listeners from the event emitter.
|
|
2914
|
+
*
|
|
2915
|
+
* @returns {void} This method doesn't return a value.
|
|
2674
2916
|
*/
|
|
2675
2917
|
removeAllListeners() {
|
|
2918
|
+
console.log(`Removing all event listeners for playback ${this.id}`);
|
|
2919
|
+
this.listenersMap.forEach((listeners, event) => {
|
|
2920
|
+
listeners.forEach((listener) => {
|
|
2921
|
+
this.eventEmitter.off(
|
|
2922
|
+
event,
|
|
2923
|
+
listener
|
|
2924
|
+
);
|
|
2925
|
+
});
|
|
2926
|
+
});
|
|
2927
|
+
this.listenersMap.clear();
|
|
2676
2928
|
this.eventEmitter.removeAllListeners();
|
|
2677
2929
|
}
|
|
2678
2930
|
/**
|
|
@@ -2699,6 +2951,7 @@ var Playbacks = class {
|
|
|
2699
2951
|
this.client = client;
|
|
2700
2952
|
}
|
|
2701
2953
|
playbackInstances = /* @__PURE__ */ new Map();
|
|
2954
|
+
eventQueue = /* @__PURE__ */ new Map();
|
|
2702
2955
|
/**
|
|
2703
2956
|
* Gets or creates a playback instance
|
|
2704
2957
|
* @param {Object} [params] - Optional parameters for getting/creating a playback instance
|
|
@@ -2725,6 +2978,43 @@ var Playbacks = class {
|
|
|
2725
2978
|
throw new Error(`Failed to manage playback instance: ${message}`);
|
|
2726
2979
|
}
|
|
2727
2980
|
}
|
|
2981
|
+
/**
|
|
2982
|
+
* Cleans up resources associated with the Playbacks instance.
|
|
2983
|
+
* This method performs the following cleanup operations:
|
|
2984
|
+
* 1. Clears all pending timeouts in the event queue.
|
|
2985
|
+
* 2. Removes all playback instances.
|
|
2986
|
+
*
|
|
2987
|
+
* @remarks
|
|
2988
|
+
* This method should be called when the Playbacks instance is no longer needed
|
|
2989
|
+
* to ensure proper resource management and prevent memory leaks.
|
|
2990
|
+
*
|
|
2991
|
+
* @returns {void} This method doesn't return a value.
|
|
2992
|
+
*/
|
|
2993
|
+
cleanup() {
|
|
2994
|
+
this.eventQueue.forEach((timeout) => clearTimeout(timeout));
|
|
2995
|
+
this.eventQueue.clear();
|
|
2996
|
+
this.remove();
|
|
2997
|
+
}
|
|
2998
|
+
/**
|
|
2999
|
+
* Removes all playback instances and cleans up their resources.
|
|
3000
|
+
*/
|
|
3001
|
+
remove() {
|
|
3002
|
+
const playbackIds = Array.from(this.playbackInstances.keys());
|
|
3003
|
+
for (const playbackId of playbackIds) {
|
|
3004
|
+
try {
|
|
3005
|
+
const instance = this.playbackInstances.get(playbackId);
|
|
3006
|
+
if (instance) {
|
|
3007
|
+
instance.cleanup();
|
|
3008
|
+
this.playbackInstances.delete(playbackId);
|
|
3009
|
+
console.log(`Playback instance ${playbackId} removed and cleaned up`);
|
|
3010
|
+
}
|
|
3011
|
+
} catch (error) {
|
|
3012
|
+
console.error(`Error cleaning up playback ${playbackId}:`, error);
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
this.playbackInstances.clear();
|
|
3016
|
+
console.log("All playback instances have been removed and cleaned up");
|
|
3017
|
+
}
|
|
2728
3018
|
/**
|
|
2729
3019
|
* Removes a playback instance and cleans up its resources
|
|
2730
3020
|
* @param {string} playbackId - ID of the playback instance to remove
|
|
@@ -2734,10 +3024,16 @@ var Playbacks = class {
|
|
|
2734
3024
|
if (!playbackId) {
|
|
2735
3025
|
throw new Error("Playback ID is required");
|
|
2736
3026
|
}
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
3027
|
+
const instance = this.playbackInstances.get(playbackId);
|
|
3028
|
+
if (instance) {
|
|
3029
|
+
try {
|
|
3030
|
+
instance.cleanup();
|
|
3031
|
+
this.playbackInstances.delete(playbackId);
|
|
3032
|
+
console.log(`Playback instance ${playbackId} removed from memory`);
|
|
3033
|
+
} catch (error) {
|
|
3034
|
+
console.error(`Error removing playback instance ${playbackId}:`, error);
|
|
3035
|
+
throw error;
|
|
3036
|
+
}
|
|
2741
3037
|
} else {
|
|
2742
3038
|
console.warn(`Attempt to remove non-existent instance: ${playbackId}`);
|
|
2743
3039
|
}
|
|
@@ -2747,17 +3043,29 @@ var Playbacks = class {
|
|
|
2747
3043
|
* @param {WebSocketEvent} event - The WebSocket event to propagate
|
|
2748
3044
|
*/
|
|
2749
3045
|
propagateEventToPlayback(event) {
|
|
2750
|
-
if (!event) {
|
|
3046
|
+
if (!event || !("playback" in event) || !event.playback?.id) {
|
|
3047
|
+
console.warn("Invalid WebSocket event received");
|
|
2751
3048
|
return;
|
|
2752
3049
|
}
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
3050
|
+
const key = `${event.type}-${event.playback.id}`;
|
|
3051
|
+
const existing = this.eventQueue.get(key);
|
|
3052
|
+
if (existing) {
|
|
3053
|
+
clearTimeout(existing);
|
|
3054
|
+
}
|
|
3055
|
+
this.eventQueue.set(
|
|
3056
|
+
key,
|
|
3057
|
+
setTimeout(() => {
|
|
3058
|
+
const instance = this.playbackInstances.get(event.playback.id);
|
|
3059
|
+
if (instance) {
|
|
3060
|
+
instance.emitEvent(event);
|
|
3061
|
+
} else {
|
|
3062
|
+
console.warn(
|
|
3063
|
+
`No instance found for playback ${event.playback.id}. Event ignored.`
|
|
3064
|
+
);
|
|
3065
|
+
}
|
|
3066
|
+
this.eventQueue.delete(key);
|
|
3067
|
+
}, 100)
|
|
3068
|
+
);
|
|
2761
3069
|
}
|
|
2762
3070
|
/**
|
|
2763
3071
|
* Retrieves details of a specific playback
|
|
@@ -2895,9 +3203,57 @@ var WebSocketClient = class extends EventEmitter4 {
|
|
|
2895
3203
|
}
|
|
2896
3204
|
ws;
|
|
2897
3205
|
isReconnecting = false;
|
|
3206
|
+
isConnecting = false;
|
|
3207
|
+
// 🔹 Evita múltiplas conexões simultâneas
|
|
3208
|
+
shouldReconnect = true;
|
|
3209
|
+
// 🔹 Nova flag para impedir reconexão se for um fechamento intencional
|
|
2898
3210
|
maxReconnectAttempts = DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
2899
3211
|
reconnectionAttempts = 0;
|
|
2900
3212
|
lastWsUrl = "";
|
|
3213
|
+
eventQueue = /* @__PURE__ */ new Map();
|
|
3214
|
+
/**
|
|
3215
|
+
* Logs the current connection status of the WebSocket client at regular intervals.
|
|
3216
|
+
*
|
|
3217
|
+
* This method sets up an interval that logs various connection-related metrics every 60 seconds.
|
|
3218
|
+
* The logged information includes:
|
|
3219
|
+
* - The number of active connections (0 or 1)
|
|
3220
|
+
* - The current state of the WebSocket connection
|
|
3221
|
+
* - The number of reconnection attempts made
|
|
3222
|
+
* - The size of the event queue
|
|
3223
|
+
*
|
|
3224
|
+
* This can be useful for monitoring the health and status of the WebSocket connection over time.
|
|
3225
|
+
*
|
|
3226
|
+
* @private
|
|
3227
|
+
* @returns {void}
|
|
3228
|
+
*/
|
|
3229
|
+
logConnectionStatus() {
|
|
3230
|
+
setInterval(() => {
|
|
3231
|
+
console.log({
|
|
3232
|
+
connections: this.ws ? 1 : 0,
|
|
3233
|
+
state: this.getState(),
|
|
3234
|
+
reconnectAttempts: this.reconnectionAttempts,
|
|
3235
|
+
eventQueueSize: this.eventQueue.size
|
|
3236
|
+
});
|
|
3237
|
+
}, 6e4);
|
|
3238
|
+
}
|
|
3239
|
+
/**
|
|
3240
|
+
* Sets up a heartbeat mechanism for the WebSocket connection.
|
|
3241
|
+
*
|
|
3242
|
+
* This method creates an interval that sends a ping message every 30 seconds
|
|
3243
|
+
* to keep the connection alive. The heartbeat is automatically cleared when
|
|
3244
|
+
* the WebSocket connection is closed.
|
|
3245
|
+
*
|
|
3246
|
+
* @private
|
|
3247
|
+
* @returns {void}
|
|
3248
|
+
*/
|
|
3249
|
+
setupHeartbeat() {
|
|
3250
|
+
const interval = setInterval(() => {
|
|
3251
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
3252
|
+
this.ws.ping();
|
|
3253
|
+
}
|
|
3254
|
+
}, 3e4);
|
|
3255
|
+
this.ws.once("close", () => clearInterval(interval));
|
|
3256
|
+
}
|
|
2901
3257
|
backOffOptions = {
|
|
2902
3258
|
numOfAttempts: DEFAULT_MAX_RECONNECT_ATTEMPTS,
|
|
2903
3259
|
startingDelay: DEFAULT_STARTING_DELAY,
|
|
@@ -2924,21 +3280,28 @@ var WebSocketClient = class extends EventEmitter4 {
|
|
|
2924
3280
|
* @throws Will throw an error if the connection cannot be established.
|
|
2925
3281
|
*/
|
|
2926
3282
|
async connect() {
|
|
3283
|
+
if (this.isConnecting || this.isConnected()) {
|
|
3284
|
+
console.warn(
|
|
3285
|
+
"WebSocket is already connecting or connected. Skipping new connection."
|
|
3286
|
+
);
|
|
3287
|
+
return;
|
|
3288
|
+
}
|
|
3289
|
+
this.shouldReconnect = true;
|
|
3290
|
+
this.isConnecting = true;
|
|
2927
3291
|
const { baseUrl, username, password } = this.baseClient.getCredentials();
|
|
2928
3292
|
const protocol = baseUrl.startsWith("https") ? "wss" : "ws";
|
|
2929
3293
|
const normalizedHost = baseUrl.replace(/^https?:\/\//, "").replace(/\/ari$/, "");
|
|
2930
3294
|
const queryParams = new URLSearchParams();
|
|
2931
3295
|
queryParams.append("app", this.apps.join(","));
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
);
|
|
2936
|
-
} else {
|
|
2937
|
-
queryParams.append("subscribeAll", "true");
|
|
2938
|
-
}
|
|
3296
|
+
this.subscribedEvents?.forEach(
|
|
3297
|
+
(event) => queryParams.append("event", event)
|
|
3298
|
+
);
|
|
2939
3299
|
this.lastWsUrl = `${protocol}://${encodeURIComponent(username)}:${encodeURIComponent(password)}@${normalizedHost}/ari/events?${queryParams.toString()}`;
|
|
2940
|
-
|
|
2941
|
-
|
|
3300
|
+
try {
|
|
3301
|
+
await this.initializeWebSocket(this.lastWsUrl);
|
|
3302
|
+
} finally {
|
|
3303
|
+
this.isConnecting = false;
|
|
3304
|
+
}
|
|
2942
3305
|
}
|
|
2943
3306
|
/**
|
|
2944
3307
|
* Initializes a WebSocket connection with exponential backoff retry mechanism.
|
|
@@ -2960,8 +3323,8 @@ var WebSocketClient = class extends EventEmitter4 {
|
|
|
2960
3323
|
return new Promise((resolve, reject) => {
|
|
2961
3324
|
try {
|
|
2962
3325
|
this.ws = new WebSocket(wsUrl);
|
|
2963
|
-
this.ws.
|
|
2964
|
-
|
|
3326
|
+
this.ws.once("open", () => {
|
|
3327
|
+
this.setupHeartbeat();
|
|
2965
3328
|
if (this.isReconnecting) {
|
|
2966
3329
|
this.emit("reconnected", {
|
|
2967
3330
|
apps: this.apps,
|
|
@@ -2974,7 +3337,7 @@ var WebSocketClient = class extends EventEmitter4 {
|
|
|
2974
3337
|
resolve();
|
|
2975
3338
|
});
|
|
2976
3339
|
this.ws.on("message", (data) => this.handleMessage(data.toString()));
|
|
2977
|
-
this.ws.
|
|
3340
|
+
this.ws.once("close", (code) => {
|
|
2978
3341
|
console.warn(
|
|
2979
3342
|
`WebSocket disconnected with code ${code}. Attempting to reconnect...`
|
|
2980
3343
|
);
|
|
@@ -2982,7 +3345,7 @@ var WebSocketClient = class extends EventEmitter4 {
|
|
|
2982
3345
|
this.reconnect(this.lastWsUrl);
|
|
2983
3346
|
}
|
|
2984
3347
|
});
|
|
2985
|
-
this.ws.
|
|
3348
|
+
this.ws.once("error", (err) => {
|
|
2986
3349
|
console.error("WebSocket error:", err.message);
|
|
2987
3350
|
if (!this.isReconnecting) {
|
|
2988
3351
|
this.reconnect(this.lastWsUrl);
|
|
@@ -2995,6 +3358,34 @@ var WebSocketClient = class extends EventEmitter4 {
|
|
|
2995
3358
|
});
|
|
2996
3359
|
}, this.backOffOptions);
|
|
2997
3360
|
}
|
|
3361
|
+
getEventKey(event) {
|
|
3362
|
+
const ids = [];
|
|
3363
|
+
if ("channel" in event && event.channel?.id) ids.push(event.channel.id);
|
|
3364
|
+
if ("playback" in event && event.playback?.id) ids.push(event.playback.id);
|
|
3365
|
+
if ("bridge" in event && event.bridge?.id) ids.push(event.bridge.id);
|
|
3366
|
+
return `${event.type}-${ids.join("-")}`;
|
|
3367
|
+
}
|
|
3368
|
+
processEvent(event) {
|
|
3369
|
+
if (this.subscribedEvents?.length && !this.subscribedEvents.includes(event.type)) {
|
|
3370
|
+
return;
|
|
3371
|
+
}
|
|
3372
|
+
if ("channel" in event && event.channel?.id && this.ariClient) {
|
|
3373
|
+
const instanceChannel = this.ariClient.Channel(event.channel.id);
|
|
3374
|
+
instanceChannel.emitEvent(event);
|
|
3375
|
+
event.instanceChannel = instanceChannel;
|
|
3376
|
+
}
|
|
3377
|
+
if ("playback" in event && event.playback?.id && this.ariClient) {
|
|
3378
|
+
const instancePlayback = this.ariClient.Playback(event.playback.id);
|
|
3379
|
+
instancePlayback.emitEvent(event);
|
|
3380
|
+
event.instancePlayback = instancePlayback;
|
|
3381
|
+
}
|
|
3382
|
+
if ("bridge" in event && event.bridge?.id && this.ariClient) {
|
|
3383
|
+
const instanceBridge = this.ariClient.Bridge(event.bridge.id);
|
|
3384
|
+
instanceBridge.emitEvent(event);
|
|
3385
|
+
event.instanceBridge = instanceBridge;
|
|
3386
|
+
}
|
|
3387
|
+
this.emit(event.type, event);
|
|
3388
|
+
}
|
|
2998
3389
|
/**
|
|
2999
3390
|
* Handles incoming WebSocket messages by parsing and processing events.
|
|
3000
3391
|
*
|
|
@@ -3010,30 +3401,20 @@ var WebSocketClient = class extends EventEmitter4 {
|
|
|
3010
3401
|
handleMessage(rawMessage) {
|
|
3011
3402
|
try {
|
|
3012
3403
|
const event = JSON.parse(rawMessage);
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
const instanceChannel = this.ariClient.Channel(event.channel.id);
|
|
3018
|
-
instanceChannel.emitEvent(event);
|
|
3019
|
-
event.instanceChannel = instanceChannel;
|
|
3020
|
-
}
|
|
3021
|
-
if ("playback" in event && event.playback?.id && this.ariClient) {
|
|
3022
|
-
const instancePlayback = this.ariClient.Playback(event.playback.id);
|
|
3023
|
-
instancePlayback.emitEvent(event);
|
|
3024
|
-
event.instancePlayback = instancePlayback;
|
|
3025
|
-
}
|
|
3026
|
-
if ("bridge" in event && event.bridge?.id && this.ariClient) {
|
|
3027
|
-
const instanceBridge = this.ariClient.Bridge(event.bridge.id);
|
|
3028
|
-
instanceBridge.emitEvent(event);
|
|
3029
|
-
event.instanceBridge = instanceBridge;
|
|
3404
|
+
const key = this.getEventKey(event);
|
|
3405
|
+
const existing = this.eventQueue.get(key);
|
|
3406
|
+
if (existing) {
|
|
3407
|
+
clearTimeout(existing);
|
|
3030
3408
|
}
|
|
3031
|
-
this.
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3409
|
+
this.eventQueue.set(
|
|
3410
|
+
key,
|
|
3411
|
+
setTimeout(() => {
|
|
3412
|
+
this.processEvent(event);
|
|
3413
|
+
this.eventQueue.delete(key);
|
|
3414
|
+
}, 100)
|
|
3036
3415
|
);
|
|
3416
|
+
} catch (error) {
|
|
3417
|
+
console.error("Error processing WebSocket message:", error);
|
|
3037
3418
|
this.emit("error", new Error("Failed to decode WebSocket message"));
|
|
3038
3419
|
}
|
|
3039
3420
|
}
|
|
@@ -3049,21 +3430,26 @@ var WebSocketClient = class extends EventEmitter4 {
|
|
|
3049
3430
|
*
|
|
3050
3431
|
* @emits reconnectFailed - Emitted if all reconnection attempts fail.
|
|
3051
3432
|
*/
|
|
3052
|
-
reconnect(wsUrl) {
|
|
3433
|
+
async reconnect(wsUrl) {
|
|
3434
|
+
if (!this.shouldReconnect) {
|
|
3435
|
+
console.warn(
|
|
3436
|
+
"Reconnection skipped because WebSocket was intentionally closed."
|
|
3437
|
+
);
|
|
3438
|
+
return;
|
|
3439
|
+
}
|
|
3440
|
+
if (this.isReconnecting) {
|
|
3441
|
+
console.warn("J\xE1 h\xE1 uma tentativa de reconex\xE3o em andamento.");
|
|
3442
|
+
return;
|
|
3443
|
+
}
|
|
3053
3444
|
this.isReconnecting = true;
|
|
3054
3445
|
this.reconnectionAttempts++;
|
|
3055
|
-
console.log(
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
error instanceof Error ? error.message : "Unknown error"
|
|
3063
|
-
);
|
|
3064
|
-
this.emit("reconnectFailed", error);
|
|
3065
|
-
}
|
|
3066
|
-
);
|
|
3446
|
+
console.log(`Tentando reconex\xE3o #${this.reconnectionAttempts}...`);
|
|
3447
|
+
(0, import_exponential_backoff.backOff)(() => this.initializeWebSocket(wsUrl), this.backOffOptions).catch((error) => {
|
|
3448
|
+
console.error(`Falha ao reconectar: ${error.message}`);
|
|
3449
|
+
this.emit("reconnectFailed", error);
|
|
3450
|
+
}).finally(() => {
|
|
3451
|
+
this.isReconnecting = false;
|
|
3452
|
+
});
|
|
3067
3453
|
}
|
|
3068
3454
|
/**
|
|
3069
3455
|
* Closes the WebSocket connection if it exists.
|
|
@@ -3074,18 +3460,34 @@ var WebSocketClient = class extends EventEmitter4 {
|
|
|
3074
3460
|
*
|
|
3075
3461
|
* @throws {Error} Logs an error message if closing the WebSocket fails.
|
|
3076
3462
|
*/
|
|
3077
|
-
close() {
|
|
3463
|
+
async close() {
|
|
3464
|
+
if (!this.ws) {
|
|
3465
|
+
console.warn("No WebSocket connection to close");
|
|
3466
|
+
return;
|
|
3467
|
+
}
|
|
3468
|
+
console.log("Closing WebSocket connection.");
|
|
3469
|
+
this.shouldReconnect = false;
|
|
3470
|
+
this.eventQueue.forEach((timeout) => clearTimeout(timeout));
|
|
3471
|
+
this.eventQueue.clear();
|
|
3472
|
+
const closeTimeout = setTimeout(() => {
|
|
3473
|
+
if (this.ws && this.ws.readyState !== WebSocket.CLOSED) {
|
|
3474
|
+
this.ws.terminate();
|
|
3475
|
+
}
|
|
3476
|
+
}, 5e3);
|
|
3078
3477
|
try {
|
|
3079
|
-
|
|
3478
|
+
this.ws.removeAllListeners();
|
|
3479
|
+
await new Promise((resolve) => {
|
|
3480
|
+
this.ws.once("close", () => {
|
|
3481
|
+
clearTimeout(closeTimeout);
|
|
3482
|
+
resolve();
|
|
3483
|
+
});
|
|
3080
3484
|
this.ws.close();
|
|
3081
|
-
|
|
3082
|
-
console.log("WebSocket connection closed");
|
|
3083
|
-
}
|
|
3485
|
+
});
|
|
3084
3486
|
} catch (error) {
|
|
3085
|
-
console.error(
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
);
|
|
3487
|
+
console.error("Error closing WebSocket:", error);
|
|
3488
|
+
} finally {
|
|
3489
|
+
this.ws = void 0;
|
|
3490
|
+
this.emit("disconnected");
|
|
3089
3491
|
}
|
|
3090
3492
|
}
|
|
3091
3493
|
/**
|
|
@@ -3117,6 +3519,30 @@ var WebSocketClient = class extends EventEmitter4 {
|
|
|
3117
3519
|
getState() {
|
|
3118
3520
|
return this.ws?.readyState ?? WebSocket.CLOSED;
|
|
3119
3521
|
}
|
|
3522
|
+
/**
|
|
3523
|
+
* Cleans up the WebSocketClient instance, resetting its state and clearing resources.
|
|
3524
|
+
*
|
|
3525
|
+
* This method performs the following cleanup operations:
|
|
3526
|
+
* - Clears the event queue and cancels any pending timeouts.
|
|
3527
|
+
* - Stops any ongoing reconnection attempts.
|
|
3528
|
+
* - Clears the stored WebSocket URL.
|
|
3529
|
+
* - Resets the reconnection attempt counter.
|
|
3530
|
+
* - Removes all event listeners attached to this instance.
|
|
3531
|
+
*
|
|
3532
|
+
* This method is typically called when the WebSocketClient is no longer needed or
|
|
3533
|
+
* before reinitializing the client to ensure a clean slate.
|
|
3534
|
+
*
|
|
3535
|
+
* @returns {void} This method doesn't return a value.
|
|
3536
|
+
*/
|
|
3537
|
+
cleanup() {
|
|
3538
|
+
this.eventQueue.forEach((timeout) => clearTimeout(timeout));
|
|
3539
|
+
this.eventQueue.clear();
|
|
3540
|
+
this.shouldReconnect = false;
|
|
3541
|
+
this.isReconnecting = false;
|
|
3542
|
+
this.lastWsUrl = "";
|
|
3543
|
+
this.reconnectionAttempts = 0;
|
|
3544
|
+
this.removeAllListeners();
|
|
3545
|
+
}
|
|
3120
3546
|
};
|
|
3121
3547
|
|
|
3122
3548
|
// src/ari-client/ariClient.ts
|
|
@@ -3147,6 +3573,8 @@ var AriClient = class {
|
|
|
3147
3573
|
}
|
|
3148
3574
|
baseClient;
|
|
3149
3575
|
webSocketClient;
|
|
3576
|
+
eventListeners = /* @__PURE__ */ new Map();
|
|
3577
|
+
// Armazena os listeners para limpeza
|
|
3150
3578
|
channels;
|
|
3151
3579
|
endpoints;
|
|
3152
3580
|
applications;
|
|
@@ -3154,6 +3582,50 @@ var AriClient = class {
|
|
|
3154
3582
|
sounds;
|
|
3155
3583
|
asterisk;
|
|
3156
3584
|
bridges;
|
|
3585
|
+
async cleanup() {
|
|
3586
|
+
try {
|
|
3587
|
+
console.log("Starting ARI Client cleanup...");
|
|
3588
|
+
if (this.webSocketClient) {
|
|
3589
|
+
await this.closeWebSocket();
|
|
3590
|
+
}
|
|
3591
|
+
await Promise.all([
|
|
3592
|
+
// Cleanup de channels
|
|
3593
|
+
(async () => {
|
|
3594
|
+
try {
|
|
3595
|
+
this.channels.cleanup();
|
|
3596
|
+
} catch (error) {
|
|
3597
|
+
console.error("Error cleaning up channels:", error);
|
|
3598
|
+
}
|
|
3599
|
+
})(),
|
|
3600
|
+
// Cleanup de playbacks
|
|
3601
|
+
(async () => {
|
|
3602
|
+
try {
|
|
3603
|
+
this.playbacks.cleanup();
|
|
3604
|
+
} catch (error) {
|
|
3605
|
+
console.error("Error cleaning up playbacks:", error);
|
|
3606
|
+
}
|
|
3607
|
+
})(),
|
|
3608
|
+
// Cleanup de bridges
|
|
3609
|
+
(async () => {
|
|
3610
|
+
try {
|
|
3611
|
+
this.bridges.cleanup();
|
|
3612
|
+
} catch (error) {
|
|
3613
|
+
console.error("Error cleaning up bridges:", error);
|
|
3614
|
+
}
|
|
3615
|
+
})()
|
|
3616
|
+
]);
|
|
3617
|
+
this.eventListeners.forEach((listeners, event) => {
|
|
3618
|
+
listeners.forEach((listener) => {
|
|
3619
|
+
this.off(event, listener);
|
|
3620
|
+
});
|
|
3621
|
+
});
|
|
3622
|
+
this.eventListeners.clear();
|
|
3623
|
+
console.log("ARI Client cleanup completed successfully");
|
|
3624
|
+
} catch (error) {
|
|
3625
|
+
console.error("Error during ARI Client cleanup:", error);
|
|
3626
|
+
throw error;
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3157
3629
|
/**
|
|
3158
3630
|
* Initializes a WebSocket connection for receiving events.
|
|
3159
3631
|
*
|
|
@@ -3163,14 +3635,14 @@ var AriClient = class {
|
|
|
3163
3635
|
* @throws {Error} If connection fails or if WebSocket is already connected
|
|
3164
3636
|
*/
|
|
3165
3637
|
async connectWebSocket(apps, subscribedEvents) {
|
|
3166
|
-
if (!apps.length) {
|
|
3167
|
-
throw new Error("At least one application name is required");
|
|
3168
|
-
}
|
|
3169
|
-
if (this.webSocketClient) {
|
|
3170
|
-
console.warn("WebSocket is already connected");
|
|
3171
|
-
return;
|
|
3172
|
-
}
|
|
3173
3638
|
try {
|
|
3639
|
+
if (!apps.length) {
|
|
3640
|
+
throw new Error("At least one application name is required.");
|
|
3641
|
+
}
|
|
3642
|
+
if (this.webSocketClient) {
|
|
3643
|
+
await this.closeWebSocket();
|
|
3644
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
3645
|
+
}
|
|
3174
3646
|
this.webSocketClient = new WebSocketClient(
|
|
3175
3647
|
this.baseClient,
|
|
3176
3648
|
apps,
|
|
@@ -3178,13 +3650,37 @@ var AriClient = class {
|
|
|
3178
3650
|
this
|
|
3179
3651
|
);
|
|
3180
3652
|
await this.webSocketClient.connect();
|
|
3181
|
-
console.log("WebSocket connection established successfully");
|
|
3182
3653
|
} catch (error) {
|
|
3183
3654
|
console.error("Failed to establish WebSocket connection:", error);
|
|
3184
3655
|
this.webSocketClient = void 0;
|
|
3185
3656
|
throw error;
|
|
3186
3657
|
}
|
|
3187
3658
|
}
|
|
3659
|
+
/**
|
|
3660
|
+
* Destroys the ARI Client instance, cleaning up all resources and removing circular references.
|
|
3661
|
+
* This method should be called when the ARI Client is no longer needed to ensure proper cleanup.
|
|
3662
|
+
*
|
|
3663
|
+
* @returns {Promise<void>} A promise that resolves when the destruction process is complete.
|
|
3664
|
+
* @throws {Error} If an error occurs during the destruction process.
|
|
3665
|
+
*/
|
|
3666
|
+
async destroy() {
|
|
3667
|
+
try {
|
|
3668
|
+
console.log("Destroying ARI Client...");
|
|
3669
|
+
await this.cleanup();
|
|
3670
|
+
this.webSocketClient = void 0;
|
|
3671
|
+
this.channels = null;
|
|
3672
|
+
this.playbacks = null;
|
|
3673
|
+
this.bridges = null;
|
|
3674
|
+
this.endpoints = null;
|
|
3675
|
+
this.applications = null;
|
|
3676
|
+
this.sounds = null;
|
|
3677
|
+
this.asterisk = null;
|
|
3678
|
+
console.log("ARI Client destroyed successfully");
|
|
3679
|
+
} catch (error) {
|
|
3680
|
+
console.error("Error destroying ARI Client:", error);
|
|
3681
|
+
throw error;
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3188
3684
|
/**
|
|
3189
3685
|
* Registers an event listener for WebSocket events.
|
|
3190
3686
|
*
|
|
@@ -3196,8 +3692,14 @@ var AriClient = class {
|
|
|
3196
3692
|
if (!this.webSocketClient) {
|
|
3197
3693
|
throw new Error("WebSocket is not connected");
|
|
3198
3694
|
}
|
|
3695
|
+
const existingListeners = this.eventListeners.get(event) || [];
|
|
3696
|
+
if (existingListeners.includes(listener)) {
|
|
3697
|
+
console.warn(`Listener already registered for event ${event}, reusing.`);
|
|
3698
|
+
return;
|
|
3699
|
+
}
|
|
3199
3700
|
this.webSocketClient.on(event, listener);
|
|
3200
|
-
|
|
3701
|
+
existingListeners.push(listener);
|
|
3702
|
+
this.eventListeners.set(event, existingListeners);
|
|
3201
3703
|
}
|
|
3202
3704
|
/**
|
|
3203
3705
|
* Registers a one-time event listener for WebSocket events.
|
|
@@ -3210,7 +3712,19 @@ var AriClient = class {
|
|
|
3210
3712
|
if (!this.webSocketClient) {
|
|
3211
3713
|
throw new Error("WebSocket is not connected");
|
|
3212
3714
|
}
|
|
3213
|
-
this.
|
|
3715
|
+
const existingListeners = this.eventListeners.get(event) || [];
|
|
3716
|
+
if (existingListeners.includes(listener)) {
|
|
3717
|
+
console.warn(
|
|
3718
|
+
`One-time listener already registered for event ${event}, reusing.`
|
|
3719
|
+
);
|
|
3720
|
+
return;
|
|
3721
|
+
}
|
|
3722
|
+
const wrappedListener = (data) => {
|
|
3723
|
+
listener(data);
|
|
3724
|
+
this.off(event, wrappedListener);
|
|
3725
|
+
};
|
|
3726
|
+
this.webSocketClient.once(event, wrappedListener);
|
|
3727
|
+
this.eventListeners.set(event, [...existingListeners, wrappedListener]);
|
|
3214
3728
|
console.log(`One-time event listener registered for ${event}`);
|
|
3215
3729
|
}
|
|
3216
3730
|
/**
|
|
@@ -3225,19 +3739,57 @@ var AriClient = class {
|
|
|
3225
3739
|
return;
|
|
3226
3740
|
}
|
|
3227
3741
|
this.webSocketClient.off(event, listener);
|
|
3742
|
+
const existingListeners = this.eventListeners.get(event) || [];
|
|
3743
|
+
this.eventListeners.set(
|
|
3744
|
+
event,
|
|
3745
|
+
existingListeners.filter((l) => l !== listener)
|
|
3746
|
+
);
|
|
3228
3747
|
console.log(`Event listener removed for ${event}`);
|
|
3229
3748
|
}
|
|
3230
3749
|
/**
|
|
3231
3750
|
* Closes the WebSocket connection if one exists.
|
|
3232
3751
|
*/
|
|
3233
3752
|
closeWebSocket() {
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3753
|
+
return new Promise((resolve) => {
|
|
3754
|
+
if (!this.webSocketClient) {
|
|
3755
|
+
console.warn("No WebSocket connection to close");
|
|
3756
|
+
resolve();
|
|
3757
|
+
return;
|
|
3758
|
+
}
|
|
3759
|
+
console.log("Closing WebSocket connection and cleaning up listeners.");
|
|
3760
|
+
const closeTimeout = setTimeout(() => {
|
|
3761
|
+
if (this.webSocketClient) {
|
|
3762
|
+
this.webSocketClient.removeAllListeners();
|
|
3763
|
+
this.webSocketClient = void 0;
|
|
3764
|
+
}
|
|
3765
|
+
resolve();
|
|
3766
|
+
}, 5e3);
|
|
3767
|
+
this.eventListeners.forEach((listeners, event) => {
|
|
3768
|
+
listeners.forEach((listener) => {
|
|
3769
|
+
this.webSocketClient?.off(
|
|
3770
|
+
event,
|
|
3771
|
+
listener
|
|
3772
|
+
);
|
|
3773
|
+
});
|
|
3774
|
+
});
|
|
3775
|
+
this.eventListeners.clear();
|
|
3776
|
+
this.webSocketClient.once("close", () => {
|
|
3777
|
+
clearTimeout(closeTimeout);
|
|
3778
|
+
this.webSocketClient = void 0;
|
|
3779
|
+
console.log("WebSocket connection closed");
|
|
3780
|
+
resolve();
|
|
3781
|
+
});
|
|
3782
|
+
this.webSocketClient.close().then(() => {
|
|
3783
|
+
clearTimeout(closeTimeout);
|
|
3784
|
+
this.webSocketClient = void 0;
|
|
3785
|
+
resolve();
|
|
3786
|
+
}).catch((error) => {
|
|
3787
|
+
console.error("Error during WebSocket close:", error);
|
|
3788
|
+
clearTimeout(closeTimeout);
|
|
3789
|
+
this.webSocketClient = void 0;
|
|
3790
|
+
resolve();
|
|
3791
|
+
});
|
|
3792
|
+
});
|
|
3241
3793
|
}
|
|
3242
3794
|
/**
|
|
3243
3795
|
* Creates or retrieves a Channel instance.
|
|
@@ -3278,7 +3830,7 @@ var AriClient = class {
|
|
|
3278
3830
|
* @returns {boolean} True if WebSocket is connected, false otherwise
|
|
3279
3831
|
*/
|
|
3280
3832
|
isWebSocketConnected() {
|
|
3281
|
-
return !!this.webSocketClient;
|
|
3833
|
+
return !!this.webSocketClient && this.webSocketClient.isConnected();
|
|
3282
3834
|
}
|
|
3283
3835
|
};
|
|
3284
3836
|
export {
|