@ipcom/asterisk-ari 0.0.156 → 0.0.157
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 +692 -136
- package/dist/cjs/index.cjs.map +3 -3
- package/dist/esm/index.js +686 -130
- 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/cjs/index.cjs
CHANGED
|
@@ -936,16 +936,6 @@ var Asterisk = class {
|
|
|
936
936
|
var import_events = require("events");
|
|
937
937
|
var import_axios2 = require("axios");
|
|
938
938
|
|
|
939
|
-
// src/ari-client/interfaces/events.types.ts
|
|
940
|
-
var bridgeEvents = [
|
|
941
|
-
"BridgeCreated",
|
|
942
|
-
"BridgeDestroyed",
|
|
943
|
-
"BridgeMerged",
|
|
944
|
-
"BridgeBlindTransfer",
|
|
945
|
-
"BridgeAttendedTransfer",
|
|
946
|
-
"BridgeVideoSourceChanged"
|
|
947
|
-
];
|
|
948
|
-
|
|
949
939
|
// src/ari-client/utils.ts
|
|
950
940
|
function toQueryParams2(options) {
|
|
951
941
|
return new URLSearchParams(
|
|
@@ -977,6 +967,8 @@ var BridgeInstance = class {
|
|
|
977
967
|
this.id = bridgeId || `bridge-${Date.now()}`;
|
|
978
968
|
}
|
|
979
969
|
eventEmitter = new import_events.EventEmitter();
|
|
970
|
+
listenersMap = /* @__PURE__ */ new Map();
|
|
971
|
+
// 🔹 Guarda listeners para remoção posterior
|
|
980
972
|
bridgeData = null;
|
|
981
973
|
id;
|
|
982
974
|
/**
|
|
@@ -1006,12 +998,23 @@ var BridgeInstance = class {
|
|
|
1006
998
|
if (!event) {
|
|
1007
999
|
throw new Error("Event type is required");
|
|
1008
1000
|
}
|
|
1001
|
+
const existingListeners = this.listenersMap.get(event) || [];
|
|
1002
|
+
if (existingListeners.includes(listener)) {
|
|
1003
|
+
console.warn(
|
|
1004
|
+
`Listener j\xE1 registrado para evento ${event}, reutilizando.`
|
|
1005
|
+
);
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1009
1008
|
const wrappedListener = (data) => {
|
|
1010
1009
|
if ("bridge" in data && data.bridge?.id === this.id) {
|
|
1011
1010
|
listener(data);
|
|
1012
1011
|
}
|
|
1013
1012
|
};
|
|
1014
1013
|
this.eventEmitter.on(event, wrappedListener);
|
|
1014
|
+
if (!this.listenersMap.has(event)) {
|
|
1015
|
+
this.listenersMap.set(event, []);
|
|
1016
|
+
}
|
|
1017
|
+
this.listenersMap.get(event).push(wrappedListener);
|
|
1015
1018
|
}
|
|
1016
1019
|
/**
|
|
1017
1020
|
* Registers a one-time listener for specific bridge events.
|
|
@@ -1023,12 +1026,25 @@ var BridgeInstance = class {
|
|
|
1023
1026
|
if (!event) {
|
|
1024
1027
|
throw new Error("Event type is required");
|
|
1025
1028
|
}
|
|
1029
|
+
const eventKey = `${event}-${this.id}`;
|
|
1030
|
+
const existingListeners = this.listenersMap.get(eventKey) || [];
|
|
1031
|
+
if (existingListeners.includes(listener)) {
|
|
1032
|
+
console.warn(
|
|
1033
|
+
`One-time listener j\xE1 registrado para evento ${eventKey}, reutilizando.`
|
|
1034
|
+
);
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1026
1037
|
const wrappedListener = (data) => {
|
|
1027
1038
|
if ("bridge" in data && data.bridge?.id === this.id) {
|
|
1028
1039
|
listener(data);
|
|
1040
|
+
this.off(event, wrappedListener);
|
|
1029
1041
|
}
|
|
1030
1042
|
};
|
|
1031
1043
|
this.eventEmitter.once(event, wrappedListener);
|
|
1044
|
+
if (!this.listenersMap.has(eventKey)) {
|
|
1045
|
+
this.listenersMap.set(eventKey, []);
|
|
1046
|
+
}
|
|
1047
|
+
this.listenersMap.get(eventKey).push(wrappedListener);
|
|
1032
1048
|
}
|
|
1033
1049
|
/**
|
|
1034
1050
|
* Removes event listener(s) from the bridge.
|
|
@@ -1042,10 +1058,25 @@ var BridgeInstance = class {
|
|
|
1042
1058
|
}
|
|
1043
1059
|
if (listener) {
|
|
1044
1060
|
this.eventEmitter.off(event, listener);
|
|
1061
|
+
const storedListeners = this.listenersMap.get(event) || [];
|
|
1062
|
+
this.listenersMap.set(
|
|
1063
|
+
event,
|
|
1064
|
+
storedListeners.filter((l) => l !== listener)
|
|
1065
|
+
);
|
|
1045
1066
|
} else {
|
|
1046
1067
|
this.eventEmitter.removeAllListeners(event);
|
|
1068
|
+
this.listenersMap.delete(event);
|
|
1047
1069
|
}
|
|
1048
1070
|
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Cleans up the BridgeInstance, resetting its state and clearing resources.
|
|
1073
|
+
*/
|
|
1074
|
+
cleanup() {
|
|
1075
|
+
this.bridgeData = null;
|
|
1076
|
+
this.removeAllListeners();
|
|
1077
|
+
this.listenersMap.clear();
|
|
1078
|
+
console.log(`Bridge instance ${this.id} cleaned up`);
|
|
1079
|
+
}
|
|
1049
1080
|
/**
|
|
1050
1081
|
* Emits an event if it corresponds to the current bridge.
|
|
1051
1082
|
*
|
|
@@ -1064,6 +1095,16 @@ var BridgeInstance = class {
|
|
|
1064
1095
|
* Removes all event listeners from this bridge instance.
|
|
1065
1096
|
*/
|
|
1066
1097
|
removeAllListeners() {
|
|
1098
|
+
console.log(`Removing all event listeners for bridge ${this.id}`);
|
|
1099
|
+
this.listenersMap.forEach((listeners, event) => {
|
|
1100
|
+
listeners.forEach((listener) => {
|
|
1101
|
+
this.eventEmitter.off(
|
|
1102
|
+
event,
|
|
1103
|
+
listener
|
|
1104
|
+
);
|
|
1105
|
+
});
|
|
1106
|
+
});
|
|
1107
|
+
this.listenersMap.clear();
|
|
1067
1108
|
this.eventEmitter.removeAllListeners();
|
|
1068
1109
|
}
|
|
1069
1110
|
/**
|
|
@@ -1232,6 +1273,7 @@ var Bridges = class {
|
|
|
1232
1273
|
this.client = client;
|
|
1233
1274
|
}
|
|
1234
1275
|
bridgeInstances = /* @__PURE__ */ new Map();
|
|
1276
|
+
eventQueue = /* @__PURE__ */ new Map();
|
|
1235
1277
|
/**
|
|
1236
1278
|
* Creates or retrieves a Bridge instance.
|
|
1237
1279
|
*
|
|
@@ -1262,9 +1304,31 @@ var Bridges = class {
|
|
|
1262
1304
|
return this.bridgeInstances.get(id);
|
|
1263
1305
|
} catch (error) {
|
|
1264
1306
|
const message = getErrorMessage(error);
|
|
1307
|
+
console.warn(`Error creating/retrieving bridge instance:`, message);
|
|
1265
1308
|
throw new Error(`Failed to manage bridge instance: ${message}`);
|
|
1266
1309
|
}
|
|
1267
1310
|
}
|
|
1311
|
+
/**
|
|
1312
|
+
* Removes all bridge instances and cleans up their resources.
|
|
1313
|
+
* This method ensures proper cleanup of all bridges and their associated listeners.
|
|
1314
|
+
*/
|
|
1315
|
+
remove() {
|
|
1316
|
+
const bridgeIds = Array.from(this.bridgeInstances.keys());
|
|
1317
|
+
for (const bridgeId of bridgeIds) {
|
|
1318
|
+
try {
|
|
1319
|
+
const instance = this.bridgeInstances.get(bridgeId);
|
|
1320
|
+
if (instance) {
|
|
1321
|
+
instance.cleanup();
|
|
1322
|
+
this.bridgeInstances.delete(bridgeId);
|
|
1323
|
+
console.log(`Bridge instance ${bridgeId} removed and cleaned up`);
|
|
1324
|
+
}
|
|
1325
|
+
} catch (error) {
|
|
1326
|
+
console.error(`Error cleaning up bridge ${bridgeId}:`, error);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
this.bridgeInstances.clear();
|
|
1330
|
+
console.log("All bridge instances have been removed and cleaned up");
|
|
1331
|
+
}
|
|
1268
1332
|
/**
|
|
1269
1333
|
* Removes a bridge instance from the collection of managed bridges.
|
|
1270
1334
|
*
|
|
@@ -1277,14 +1341,20 @@ var Bridges = class {
|
|
|
1277
1341
|
*/
|
|
1278
1342
|
removeBridgeInstance(bridgeId) {
|
|
1279
1343
|
if (!bridgeId) {
|
|
1280
|
-
throw new Error("ID
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1344
|
+
throw new Error("Bridge ID is required");
|
|
1345
|
+
}
|
|
1346
|
+
const instance = this.bridgeInstances.get(bridgeId);
|
|
1347
|
+
if (instance) {
|
|
1348
|
+
try {
|
|
1349
|
+
instance.cleanup();
|
|
1350
|
+
this.bridgeInstances.delete(bridgeId);
|
|
1351
|
+
console.log(`Bridge instance ${bridgeId} removed from memory`);
|
|
1352
|
+
} catch (error) {
|
|
1353
|
+
console.error(`Error removing bridge instance ${bridgeId}:`, error);
|
|
1354
|
+
throw error;
|
|
1355
|
+
}
|
|
1286
1356
|
} else {
|
|
1287
|
-
console.warn(`
|
|
1357
|
+
console.warn(`Attempt to remove non-existent instance: ${bridgeId}`);
|
|
1288
1358
|
}
|
|
1289
1359
|
}
|
|
1290
1360
|
/**
|
|
@@ -1301,25 +1371,51 @@ var Bridges = class {
|
|
|
1301
1371
|
*
|
|
1302
1372
|
* @remarks
|
|
1303
1373
|
* - If the event is invalid (null or undefined), a warning is logged and the function returns early.
|
|
1304
|
-
* - The function checks if the event is bridge-related and if the event
|
|
1374
|
+
* - The function checks if the event is bridge-related and if the event contains a valid bridge ID.
|
|
1305
1375
|
* - If a matching bridge instance is found, the event is emitted to that instance.
|
|
1306
1376
|
* - If no matching bridge instance is found, a warning is logged.
|
|
1307
1377
|
*/
|
|
1308
1378
|
propagateEventToBridge(event) {
|
|
1309
|
-
if (!event) {
|
|
1310
|
-
console.warn("
|
|
1379
|
+
if (!event || !("bridge" in event) || !event.bridge?.id) {
|
|
1380
|
+
console.warn("Invalid WebSocket event received");
|
|
1311
1381
|
return;
|
|
1312
1382
|
}
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1383
|
+
const key = `${event.type}-${event.bridge.id}`;
|
|
1384
|
+
const existing = this.eventQueue.get(key);
|
|
1385
|
+
if (existing) {
|
|
1386
|
+
clearTimeout(existing);
|
|
1387
|
+
}
|
|
1388
|
+
this.eventQueue.set(
|
|
1389
|
+
key,
|
|
1390
|
+
setTimeout(() => {
|
|
1391
|
+
const instance = this.bridgeInstances.get(event.bridge.id);
|
|
1392
|
+
if (instance) {
|
|
1393
|
+
instance.emitEvent(event);
|
|
1394
|
+
} else {
|
|
1395
|
+
console.warn(
|
|
1396
|
+
`No instance found for bridge ${event.bridge.id}. Event ignored.`
|
|
1397
|
+
);
|
|
1398
|
+
}
|
|
1399
|
+
this.eventQueue.delete(key);
|
|
1400
|
+
}, 100)
|
|
1401
|
+
);
|
|
1402
|
+
}
|
|
1403
|
+
/**
|
|
1404
|
+
* Performs a cleanup of the Bridges instance, clearing all event queues and removing all bridge instances.
|
|
1405
|
+
*
|
|
1406
|
+
* This method is responsible for:
|
|
1407
|
+
* 1. Clearing all pending timeouts in the event queue.
|
|
1408
|
+
* 2. Removing all bridge instances managed by this Bridges object.
|
|
1409
|
+
*
|
|
1410
|
+
* It should be called when the Bridges instance is no longer needed or before reinitializing
|
|
1411
|
+
* to ensure all resources are properly released.
|
|
1412
|
+
*
|
|
1413
|
+
* @returns {void}
|
|
1414
|
+
*/
|
|
1415
|
+
cleanup() {
|
|
1416
|
+
this.eventQueue.forEach((timeout) => clearTimeout(timeout));
|
|
1417
|
+
this.eventQueue.clear();
|
|
1418
|
+
this.remove();
|
|
1323
1419
|
}
|
|
1324
1420
|
/**
|
|
1325
1421
|
* Lists all active bridges in the system.
|
|
@@ -1580,7 +1676,7 @@ var Bridges = class {
|
|
|
1580
1676
|
};
|
|
1581
1677
|
|
|
1582
1678
|
// src/ari-client/resources/channels.ts
|
|
1583
|
-
var
|
|
1679
|
+
var import_events2 = require("events");
|
|
1584
1680
|
var import_axios3 = require("axios");
|
|
1585
1681
|
|
|
1586
1682
|
// node_modules/uuid/dist/esm/stringify.js
|
|
@@ -1644,8 +1740,10 @@ var ChannelInstance = class {
|
|
|
1644
1740
|
this.baseClient = baseClient;
|
|
1645
1741
|
this.id = channelId || `channel-${Date.now()}`;
|
|
1646
1742
|
}
|
|
1647
|
-
eventEmitter = new
|
|
1743
|
+
eventEmitter = new import_events2.EventEmitter();
|
|
1648
1744
|
channelData = null;
|
|
1745
|
+
listenersMap = /* @__PURE__ */ new Map();
|
|
1746
|
+
// 🔹 Guarda listeners para remoção posterior
|
|
1649
1747
|
id;
|
|
1650
1748
|
/**
|
|
1651
1749
|
* Registers an event listener for specific channel events
|
|
@@ -1654,12 +1752,23 @@ var ChannelInstance = class {
|
|
|
1654
1752
|
if (!event) {
|
|
1655
1753
|
throw new Error("Event type is required");
|
|
1656
1754
|
}
|
|
1755
|
+
const existingListeners = this.listenersMap.get(event) || [];
|
|
1756
|
+
if (existingListeners.includes(listener)) {
|
|
1757
|
+
console.warn(
|
|
1758
|
+
`Listener j\xE1 registrado para evento ${event}, reutilizando.`
|
|
1759
|
+
);
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1657
1762
|
const wrappedListener = (data) => {
|
|
1658
1763
|
if ("channel" in data && data.channel?.id === this.id) {
|
|
1659
1764
|
listener(data);
|
|
1660
1765
|
}
|
|
1661
1766
|
};
|
|
1662
1767
|
this.eventEmitter.on(event, wrappedListener);
|
|
1768
|
+
if (!this.listenersMap.has(event)) {
|
|
1769
|
+
this.listenersMap.set(event, []);
|
|
1770
|
+
}
|
|
1771
|
+
this.listenersMap.get(event).push(wrappedListener);
|
|
1663
1772
|
}
|
|
1664
1773
|
/**
|
|
1665
1774
|
* Registers a one-time event listener
|
|
@@ -1668,12 +1777,25 @@ var ChannelInstance = class {
|
|
|
1668
1777
|
if (!event) {
|
|
1669
1778
|
throw new Error("Event type is required");
|
|
1670
1779
|
}
|
|
1780
|
+
const eventKey = `${event}-${this.id}`;
|
|
1781
|
+
const existingListeners = this.listenersMap.get(eventKey) || [];
|
|
1782
|
+
if (existingListeners.includes(listener)) {
|
|
1783
|
+
console.warn(
|
|
1784
|
+
`One-time listener j\xE1 registrado para evento ${eventKey}, reutilizando.`
|
|
1785
|
+
);
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1671
1788
|
const wrappedListener = (data) => {
|
|
1672
1789
|
if ("channel" in data && data.channel?.id === this.id) {
|
|
1673
1790
|
listener(data);
|
|
1791
|
+
this.off(event, wrappedListener);
|
|
1674
1792
|
}
|
|
1675
1793
|
};
|
|
1676
1794
|
this.eventEmitter.once(event, wrappedListener);
|
|
1795
|
+
if (!this.listenersMap.has(eventKey)) {
|
|
1796
|
+
this.listenersMap.set(eventKey, []);
|
|
1797
|
+
}
|
|
1798
|
+
this.listenersMap.get(eventKey).push(wrappedListener);
|
|
1677
1799
|
}
|
|
1678
1800
|
/**
|
|
1679
1801
|
* Removes event listener(s) for a specific WebSocket event type.
|
|
@@ -1690,10 +1812,25 @@ var ChannelInstance = class {
|
|
|
1690
1812
|
}
|
|
1691
1813
|
if (listener) {
|
|
1692
1814
|
this.eventEmitter.off(event, listener);
|
|
1815
|
+
const storedListeners = this.listenersMap.get(event) || [];
|
|
1816
|
+
this.listenersMap.set(
|
|
1817
|
+
event,
|
|
1818
|
+
storedListeners.filter((l) => l !== listener)
|
|
1819
|
+
);
|
|
1693
1820
|
} else {
|
|
1694
1821
|
this.eventEmitter.removeAllListeners(event);
|
|
1822
|
+
this.listenersMap.delete(event);
|
|
1695
1823
|
}
|
|
1696
1824
|
}
|
|
1825
|
+
/**
|
|
1826
|
+
* Cleans up the ChannelInstance, resetting its state and clearing resources.
|
|
1827
|
+
*/
|
|
1828
|
+
cleanup() {
|
|
1829
|
+
this.channelData = null;
|
|
1830
|
+
this.removeAllListeners();
|
|
1831
|
+
this.listenersMap.clear();
|
|
1832
|
+
console.log(`Channel instance ${this.id} cleaned up`);
|
|
1833
|
+
}
|
|
1697
1834
|
/**
|
|
1698
1835
|
* Emits an event if it matches the current channel
|
|
1699
1836
|
*/
|
|
@@ -1713,6 +1850,16 @@ var ChannelInstance = class {
|
|
|
1713
1850
|
* @return {void} This method does not return a value.
|
|
1714
1851
|
*/
|
|
1715
1852
|
removeAllListeners() {
|
|
1853
|
+
console.log(`Removing all event listeners for channel ${this.id}`);
|
|
1854
|
+
this.listenersMap.forEach((listeners, event) => {
|
|
1855
|
+
listeners.forEach((listener) => {
|
|
1856
|
+
this.eventEmitter.off(
|
|
1857
|
+
event,
|
|
1858
|
+
listener
|
|
1859
|
+
);
|
|
1860
|
+
});
|
|
1861
|
+
});
|
|
1862
|
+
this.listenersMap.clear();
|
|
1716
1863
|
this.eventEmitter.removeAllListeners();
|
|
1717
1864
|
}
|
|
1718
1865
|
/**
|
|
@@ -2001,6 +2148,7 @@ var Channels = class {
|
|
|
2001
2148
|
this.client = client;
|
|
2002
2149
|
}
|
|
2003
2150
|
channelInstances = /* @__PURE__ */ new Map();
|
|
2151
|
+
eventQueue = /* @__PURE__ */ new Map();
|
|
2004
2152
|
/**
|
|
2005
2153
|
* Creates or retrieves a ChannelInstance.
|
|
2006
2154
|
*
|
|
@@ -2036,6 +2184,32 @@ var Channels = class {
|
|
|
2036
2184
|
throw new Error(`Failed to manage channel instance: ${message}`);
|
|
2037
2185
|
}
|
|
2038
2186
|
}
|
|
2187
|
+
cleanup() {
|
|
2188
|
+
this.eventQueue.forEach((timeout) => clearTimeout(timeout));
|
|
2189
|
+
this.eventQueue.clear();
|
|
2190
|
+
this.remove();
|
|
2191
|
+
}
|
|
2192
|
+
/**
|
|
2193
|
+
* Removes all channel instances and cleans up their resources.
|
|
2194
|
+
* This method ensures proper cleanup of all channels and their associated listeners.
|
|
2195
|
+
*/
|
|
2196
|
+
remove() {
|
|
2197
|
+
const channelIds = Array.from(this.channelInstances.keys());
|
|
2198
|
+
for (const channelId of channelIds) {
|
|
2199
|
+
try {
|
|
2200
|
+
const instance = this.channelInstances.get(channelId);
|
|
2201
|
+
if (instance) {
|
|
2202
|
+
instance.cleanup();
|
|
2203
|
+
this.channelInstances.delete(channelId);
|
|
2204
|
+
console.log(`Channel instance ${channelId} removed and cleaned up`);
|
|
2205
|
+
}
|
|
2206
|
+
} catch (error) {
|
|
2207
|
+
console.error(`Error cleaning up channel ${channelId}:`, error);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
this.channelInstances.clear();
|
|
2211
|
+
console.log("All channel instances have been removed and cleaned up");
|
|
2212
|
+
}
|
|
2039
2213
|
/**
|
|
2040
2214
|
* Retrieves the details of a specific channel.
|
|
2041
2215
|
*
|
|
@@ -2062,10 +2236,16 @@ var Channels = class {
|
|
|
2062
2236
|
if (!channelId) {
|
|
2063
2237
|
throw new Error("Channel ID is required");
|
|
2064
2238
|
}
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2239
|
+
const instance = this.channelInstances.get(channelId);
|
|
2240
|
+
if (instance) {
|
|
2241
|
+
try {
|
|
2242
|
+
instance.cleanup();
|
|
2243
|
+
this.channelInstances.delete(channelId);
|
|
2244
|
+
console.log(`Channel instance ${channelId} removed from memory`);
|
|
2245
|
+
} catch (error) {
|
|
2246
|
+
console.error(`Error removing channel instance ${channelId}:`, error);
|
|
2247
|
+
throw error;
|
|
2248
|
+
}
|
|
2069
2249
|
} else {
|
|
2070
2250
|
console.warn(`Attempt to remove non-existent instance: ${channelId}`);
|
|
2071
2251
|
}
|
|
@@ -2074,18 +2254,29 @@ var Channels = class {
|
|
|
2074
2254
|
* Propagates a WebSocket event to a specific channel.
|
|
2075
2255
|
*/
|
|
2076
2256
|
propagateEventToChannel(event) {
|
|
2077
|
-
if (!event) {
|
|
2257
|
+
if (!event || !("channel" in event) || !event.channel?.id) {
|
|
2078
2258
|
console.warn("Invalid WebSocket event received");
|
|
2079
2259
|
return;
|
|
2080
2260
|
}
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2261
|
+
const key = `${event.type}-${event.channel.id}`;
|
|
2262
|
+
const existing = this.eventQueue.get(key);
|
|
2263
|
+
if (existing) {
|
|
2264
|
+
clearTimeout(existing);
|
|
2265
|
+
}
|
|
2266
|
+
this.eventQueue.set(
|
|
2267
|
+
key,
|
|
2268
|
+
setTimeout(() => {
|
|
2269
|
+
const instance = this.channelInstances.get(event.channel.id);
|
|
2270
|
+
if (instance) {
|
|
2271
|
+
instance.emitEvent(event);
|
|
2272
|
+
} else {
|
|
2273
|
+
console.warn(
|
|
2274
|
+
`No instance found for channel ${event.channel.id}. Event ignored.`
|
|
2275
|
+
);
|
|
2276
|
+
}
|
|
2277
|
+
this.eventQueue.delete(key);
|
|
2278
|
+
}, 100)
|
|
2279
|
+
);
|
|
2089
2280
|
}
|
|
2090
2281
|
/**
|
|
2091
2282
|
* Initiates a new channel.
|
|
@@ -2540,7 +2731,7 @@ var Endpoints = class {
|
|
|
2540
2731
|
};
|
|
2541
2732
|
|
|
2542
2733
|
// src/ari-client/resources/playbacks.ts
|
|
2543
|
-
var
|
|
2734
|
+
var import_events3 = require("events");
|
|
2544
2735
|
var import_axios4 = require("axios");
|
|
2545
2736
|
var getErrorMessage3 = (error) => {
|
|
2546
2737
|
if ((0, import_axios4.isAxiosError)(error)) {
|
|
@@ -2565,7 +2756,9 @@ var PlaybackInstance = class {
|
|
|
2565
2756
|
this.playbackId = playbackId;
|
|
2566
2757
|
this.id = playbackId;
|
|
2567
2758
|
}
|
|
2568
|
-
eventEmitter = new
|
|
2759
|
+
eventEmitter = new import_events3.EventEmitter();
|
|
2760
|
+
listenersMap = /* @__PURE__ */ new Map();
|
|
2761
|
+
// 🔹 Guarda listeners para remoção posterior
|
|
2569
2762
|
playbackData = null;
|
|
2570
2763
|
id;
|
|
2571
2764
|
/**
|
|
@@ -2578,12 +2771,23 @@ var PlaybackInstance = class {
|
|
|
2578
2771
|
if (!event) {
|
|
2579
2772
|
throw new Error("Event type is required");
|
|
2580
2773
|
}
|
|
2774
|
+
const existingListeners = this.listenersMap.get(event) || [];
|
|
2775
|
+
if (existingListeners.includes(listener)) {
|
|
2776
|
+
console.warn(
|
|
2777
|
+
`Listener j\xE1 registrado para evento ${event}, reutilizando.`
|
|
2778
|
+
);
|
|
2779
|
+
return;
|
|
2780
|
+
}
|
|
2581
2781
|
const wrappedListener = (data) => {
|
|
2582
2782
|
if ("playback" in data && data.playback?.id === this.id) {
|
|
2583
2783
|
listener(data);
|
|
2584
2784
|
}
|
|
2585
2785
|
};
|
|
2586
2786
|
this.eventEmitter.on(event, wrappedListener);
|
|
2787
|
+
if (!this.listenersMap.has(event)) {
|
|
2788
|
+
this.listenersMap.set(event, []);
|
|
2789
|
+
}
|
|
2790
|
+
this.listenersMap.get(event).push(wrappedListener);
|
|
2587
2791
|
}
|
|
2588
2792
|
/**
|
|
2589
2793
|
* Registers a one-time event listener for a specific WebSocket event type.
|
|
@@ -2595,12 +2799,25 @@ var PlaybackInstance = class {
|
|
|
2595
2799
|
if (!event) {
|
|
2596
2800
|
throw new Error("Event type is required");
|
|
2597
2801
|
}
|
|
2802
|
+
const eventKey = `${event}-${this.id}`;
|
|
2803
|
+
const existingListeners = this.listenersMap.get(eventKey) || [];
|
|
2804
|
+
if (existingListeners.includes(listener)) {
|
|
2805
|
+
console.warn(
|
|
2806
|
+
`One-time listener j\xE1 registrado para evento ${eventKey}, reutilizando.`
|
|
2807
|
+
);
|
|
2808
|
+
return;
|
|
2809
|
+
}
|
|
2598
2810
|
const wrappedListener = (data) => {
|
|
2599
2811
|
if ("playback" in data && data.playback?.id === this.id) {
|
|
2600
2812
|
listener(data);
|
|
2813
|
+
this.off(event, wrappedListener);
|
|
2601
2814
|
}
|
|
2602
2815
|
};
|
|
2603
2816
|
this.eventEmitter.once(event, wrappedListener);
|
|
2817
|
+
if (!this.listenersMap.has(eventKey)) {
|
|
2818
|
+
this.listenersMap.set(eventKey, []);
|
|
2819
|
+
}
|
|
2820
|
+
this.listenersMap.get(eventKey).push(wrappedListener);
|
|
2604
2821
|
}
|
|
2605
2822
|
/**
|
|
2606
2823
|
* Removes event listener(s) for a specific WebSocket event type.
|
|
@@ -2614,10 +2831,25 @@ var PlaybackInstance = class {
|
|
|
2614
2831
|
}
|
|
2615
2832
|
if (listener) {
|
|
2616
2833
|
this.eventEmitter.off(event, listener);
|
|
2834
|
+
const storedListeners = this.listenersMap.get(event) || [];
|
|
2835
|
+
this.listenersMap.set(
|
|
2836
|
+
event,
|
|
2837
|
+
storedListeners.filter((l) => l !== listener)
|
|
2838
|
+
);
|
|
2617
2839
|
} else {
|
|
2618
2840
|
this.eventEmitter.removeAllListeners(event);
|
|
2841
|
+
this.listenersMap.delete(event);
|
|
2619
2842
|
}
|
|
2620
2843
|
}
|
|
2844
|
+
/**
|
|
2845
|
+
* Cleans up the PlaybackInstance, resetting its state and clearing resources.
|
|
2846
|
+
*/
|
|
2847
|
+
cleanup() {
|
|
2848
|
+
this.playbackData = null;
|
|
2849
|
+
this.removeAllListeners();
|
|
2850
|
+
this.listenersMap.clear();
|
|
2851
|
+
console.log(`Playback instance ${this.id} cleaned up`);
|
|
2852
|
+
}
|
|
2621
2853
|
/**
|
|
2622
2854
|
* Emits a WebSocket event if it matches the current playback instance.
|
|
2623
2855
|
*
|
|
@@ -2691,9 +2923,29 @@ var PlaybackInstance = class {
|
|
|
2691
2923
|
}
|
|
2692
2924
|
}
|
|
2693
2925
|
/**
|
|
2694
|
-
* Removes all event listeners
|
|
2926
|
+
* Removes all event listeners associated with this playback instance.
|
|
2927
|
+
* This method clears both the internal listener map and the event emitter.
|
|
2928
|
+
*
|
|
2929
|
+
* @remarks
|
|
2930
|
+
* This method performs the following actions:
|
|
2931
|
+
* 1. Logs a message indicating the removal of listeners.
|
|
2932
|
+
* 2. Iterates through all stored listeners and removes them from the event emitter.
|
|
2933
|
+
* 3. Clears the internal listener map.
|
|
2934
|
+
* 4. Removes all listeners from the event emitter.
|
|
2935
|
+
*
|
|
2936
|
+
* @returns {void} This method doesn't return a value.
|
|
2695
2937
|
*/
|
|
2696
2938
|
removeAllListeners() {
|
|
2939
|
+
console.log(`Removing all event listeners for playback ${this.id}`);
|
|
2940
|
+
this.listenersMap.forEach((listeners, event) => {
|
|
2941
|
+
listeners.forEach((listener) => {
|
|
2942
|
+
this.eventEmitter.off(
|
|
2943
|
+
event,
|
|
2944
|
+
listener
|
|
2945
|
+
);
|
|
2946
|
+
});
|
|
2947
|
+
});
|
|
2948
|
+
this.listenersMap.clear();
|
|
2697
2949
|
this.eventEmitter.removeAllListeners();
|
|
2698
2950
|
}
|
|
2699
2951
|
/**
|
|
@@ -2720,6 +2972,7 @@ var Playbacks = class {
|
|
|
2720
2972
|
this.client = client;
|
|
2721
2973
|
}
|
|
2722
2974
|
playbackInstances = /* @__PURE__ */ new Map();
|
|
2975
|
+
eventQueue = /* @__PURE__ */ new Map();
|
|
2723
2976
|
/**
|
|
2724
2977
|
* Gets or creates a playback instance
|
|
2725
2978
|
* @param {Object} [params] - Optional parameters for getting/creating a playback instance
|
|
@@ -2746,6 +2999,43 @@ var Playbacks = class {
|
|
|
2746
2999
|
throw new Error(`Failed to manage playback instance: ${message}`);
|
|
2747
3000
|
}
|
|
2748
3001
|
}
|
|
3002
|
+
/**
|
|
3003
|
+
* Cleans up resources associated with the Playbacks instance.
|
|
3004
|
+
* This method performs the following cleanup operations:
|
|
3005
|
+
* 1. Clears all pending timeouts in the event queue.
|
|
3006
|
+
* 2. Removes all playback instances.
|
|
3007
|
+
*
|
|
3008
|
+
* @remarks
|
|
3009
|
+
* This method should be called when the Playbacks instance is no longer needed
|
|
3010
|
+
* to ensure proper resource management and prevent memory leaks.
|
|
3011
|
+
*
|
|
3012
|
+
* @returns {void} This method doesn't return a value.
|
|
3013
|
+
*/
|
|
3014
|
+
cleanup() {
|
|
3015
|
+
this.eventQueue.forEach((timeout) => clearTimeout(timeout));
|
|
3016
|
+
this.eventQueue.clear();
|
|
3017
|
+
this.remove();
|
|
3018
|
+
}
|
|
3019
|
+
/**
|
|
3020
|
+
* Removes all playback instances and cleans up their resources.
|
|
3021
|
+
*/
|
|
3022
|
+
remove() {
|
|
3023
|
+
const playbackIds = Array.from(this.playbackInstances.keys());
|
|
3024
|
+
for (const playbackId of playbackIds) {
|
|
3025
|
+
try {
|
|
3026
|
+
const instance = this.playbackInstances.get(playbackId);
|
|
3027
|
+
if (instance) {
|
|
3028
|
+
instance.cleanup();
|
|
3029
|
+
this.playbackInstances.delete(playbackId);
|
|
3030
|
+
console.log(`Playback instance ${playbackId} removed and cleaned up`);
|
|
3031
|
+
}
|
|
3032
|
+
} catch (error) {
|
|
3033
|
+
console.error(`Error cleaning up playback ${playbackId}:`, error);
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
this.playbackInstances.clear();
|
|
3037
|
+
console.log("All playback instances have been removed and cleaned up");
|
|
3038
|
+
}
|
|
2749
3039
|
/**
|
|
2750
3040
|
* Removes a playback instance and cleans up its resources
|
|
2751
3041
|
* @param {string} playbackId - ID of the playback instance to remove
|
|
@@ -2755,10 +3045,16 @@ var Playbacks = class {
|
|
|
2755
3045
|
if (!playbackId) {
|
|
2756
3046
|
throw new Error("Playback ID is required");
|
|
2757
3047
|
}
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
3048
|
+
const instance = this.playbackInstances.get(playbackId);
|
|
3049
|
+
if (instance) {
|
|
3050
|
+
try {
|
|
3051
|
+
instance.cleanup();
|
|
3052
|
+
this.playbackInstances.delete(playbackId);
|
|
3053
|
+
console.log(`Playback instance ${playbackId} removed from memory`);
|
|
3054
|
+
} catch (error) {
|
|
3055
|
+
console.error(`Error removing playback instance ${playbackId}:`, error);
|
|
3056
|
+
throw error;
|
|
3057
|
+
}
|
|
2762
3058
|
} else {
|
|
2763
3059
|
console.warn(`Attempt to remove non-existent instance: ${playbackId}`);
|
|
2764
3060
|
}
|
|
@@ -2768,17 +3064,29 @@ var Playbacks = class {
|
|
|
2768
3064
|
* @param {WebSocketEvent} event - The WebSocket event to propagate
|
|
2769
3065
|
*/
|
|
2770
3066
|
propagateEventToPlayback(event) {
|
|
2771
|
-
if (!event) {
|
|
3067
|
+
if (!event || !("playback" in event) || !event.playback?.id) {
|
|
3068
|
+
console.warn("Invalid WebSocket event received");
|
|
2772
3069
|
return;
|
|
2773
3070
|
}
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
3071
|
+
const key = `${event.type}-${event.playback.id}`;
|
|
3072
|
+
const existing = this.eventQueue.get(key);
|
|
3073
|
+
if (existing) {
|
|
3074
|
+
clearTimeout(existing);
|
|
3075
|
+
}
|
|
3076
|
+
this.eventQueue.set(
|
|
3077
|
+
key,
|
|
3078
|
+
setTimeout(() => {
|
|
3079
|
+
const instance = this.playbackInstances.get(event.playback.id);
|
|
3080
|
+
if (instance) {
|
|
3081
|
+
instance.emitEvent(event);
|
|
3082
|
+
} else {
|
|
3083
|
+
console.warn(
|
|
3084
|
+
`No instance found for playback ${event.playback.id}. Event ignored.`
|
|
3085
|
+
);
|
|
3086
|
+
}
|
|
3087
|
+
this.eventQueue.delete(key);
|
|
3088
|
+
}, 100)
|
|
3089
|
+
);
|
|
2782
3090
|
}
|
|
2783
3091
|
/**
|
|
2784
3092
|
* Retrieves details of a specific playback
|
|
@@ -2884,13 +3192,13 @@ var Sounds = class {
|
|
|
2884
3192
|
};
|
|
2885
3193
|
|
|
2886
3194
|
// src/ari-client/websocketClient.ts
|
|
2887
|
-
var
|
|
3195
|
+
var import_events4 = require("events");
|
|
2888
3196
|
var import_exponential_backoff = __toESM(require_backoff(), 1);
|
|
2889
3197
|
var import_ws = __toESM(require("ws"), 1);
|
|
2890
3198
|
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 30;
|
|
2891
3199
|
var DEFAULT_STARTING_DELAY = 500;
|
|
2892
3200
|
var DEFAULT_MAX_DELAY = 1e4;
|
|
2893
|
-
var WebSocketClient = class extends
|
|
3201
|
+
var WebSocketClient = class extends import_events4.EventEmitter {
|
|
2894
3202
|
/**
|
|
2895
3203
|
* Creates a new WebSocketClient instance.
|
|
2896
3204
|
*
|
|
@@ -2916,9 +3224,57 @@ var WebSocketClient = class extends import_events5.EventEmitter {
|
|
|
2916
3224
|
}
|
|
2917
3225
|
ws;
|
|
2918
3226
|
isReconnecting = false;
|
|
3227
|
+
isConnecting = false;
|
|
3228
|
+
// 🔹 Evita múltiplas conexões simultâneas
|
|
3229
|
+
shouldReconnect = true;
|
|
3230
|
+
// 🔹 Nova flag para impedir reconexão se for um fechamento intencional
|
|
2919
3231
|
maxReconnectAttempts = DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
2920
3232
|
reconnectionAttempts = 0;
|
|
2921
3233
|
lastWsUrl = "";
|
|
3234
|
+
eventQueue = /* @__PURE__ */ new Map();
|
|
3235
|
+
/**
|
|
3236
|
+
* Logs the current connection status of the WebSocket client at regular intervals.
|
|
3237
|
+
*
|
|
3238
|
+
* This method sets up an interval that logs various connection-related metrics every 60 seconds.
|
|
3239
|
+
* The logged information includes:
|
|
3240
|
+
* - The number of active connections (0 or 1)
|
|
3241
|
+
* - The current state of the WebSocket connection
|
|
3242
|
+
* - The number of reconnection attempts made
|
|
3243
|
+
* - The size of the event queue
|
|
3244
|
+
*
|
|
3245
|
+
* This can be useful for monitoring the health and status of the WebSocket connection over time.
|
|
3246
|
+
*
|
|
3247
|
+
* @private
|
|
3248
|
+
* @returns {void}
|
|
3249
|
+
*/
|
|
3250
|
+
logConnectionStatus() {
|
|
3251
|
+
setInterval(() => {
|
|
3252
|
+
console.log({
|
|
3253
|
+
connections: this.ws ? 1 : 0,
|
|
3254
|
+
state: this.getState(),
|
|
3255
|
+
reconnectAttempts: this.reconnectionAttempts,
|
|
3256
|
+
eventQueueSize: this.eventQueue.size
|
|
3257
|
+
});
|
|
3258
|
+
}, 6e4);
|
|
3259
|
+
}
|
|
3260
|
+
/**
|
|
3261
|
+
* Sets up a heartbeat mechanism for the WebSocket connection.
|
|
3262
|
+
*
|
|
3263
|
+
* This method creates an interval that sends a ping message every 30 seconds
|
|
3264
|
+
* to keep the connection alive. The heartbeat is automatically cleared when
|
|
3265
|
+
* the WebSocket connection is closed.
|
|
3266
|
+
*
|
|
3267
|
+
* @private
|
|
3268
|
+
* @returns {void}
|
|
3269
|
+
*/
|
|
3270
|
+
setupHeartbeat() {
|
|
3271
|
+
const interval = setInterval(() => {
|
|
3272
|
+
if (this.ws?.readyState === import_ws.default.OPEN) {
|
|
3273
|
+
this.ws.ping();
|
|
3274
|
+
}
|
|
3275
|
+
}, 3e4);
|
|
3276
|
+
this.ws.once("close", () => clearInterval(interval));
|
|
3277
|
+
}
|
|
2922
3278
|
backOffOptions = {
|
|
2923
3279
|
numOfAttempts: DEFAULT_MAX_RECONNECT_ATTEMPTS,
|
|
2924
3280
|
startingDelay: DEFAULT_STARTING_DELAY,
|
|
@@ -2945,21 +3301,29 @@ var WebSocketClient = class extends import_events5.EventEmitter {
|
|
|
2945
3301
|
* @throws Will throw an error if the connection cannot be established.
|
|
2946
3302
|
*/
|
|
2947
3303
|
async connect() {
|
|
3304
|
+
if (this.isConnecting || this.isConnected()) {
|
|
3305
|
+
console.warn(
|
|
3306
|
+
"WebSocket is already connecting or connected. Skipping new connection."
|
|
3307
|
+
);
|
|
3308
|
+
return;
|
|
3309
|
+
}
|
|
3310
|
+
this.shouldReconnect = true;
|
|
3311
|
+
this.isConnecting = true;
|
|
2948
3312
|
const { baseUrl, username, password } = this.baseClient.getCredentials();
|
|
2949
3313
|
const protocol = baseUrl.startsWith("https") ? "wss" : "ws";
|
|
2950
3314
|
const normalizedHost = baseUrl.replace(/^https?:\/\//, "").replace(/\/ari$/, "");
|
|
2951
3315
|
const queryParams = new URLSearchParams();
|
|
2952
3316
|
queryParams.append("app", this.apps.join(","));
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
);
|
|
2957
|
-
} else {
|
|
2958
|
-
queryParams.append("subscribeAll", "true");
|
|
2959
|
-
}
|
|
3317
|
+
this.subscribedEvents?.forEach(
|
|
3318
|
+
(event) => queryParams.append("event", event)
|
|
3319
|
+
);
|
|
2960
3320
|
this.lastWsUrl = `${protocol}://${encodeURIComponent(username)}:${encodeURIComponent(password)}@${normalizedHost}/ari/events?${queryParams.toString()}`;
|
|
2961
3321
|
console.log("Connecting to WebSocket...");
|
|
2962
|
-
|
|
3322
|
+
try {
|
|
3323
|
+
await this.initializeWebSocket(this.lastWsUrl);
|
|
3324
|
+
} finally {
|
|
3325
|
+
this.isConnecting = false;
|
|
3326
|
+
}
|
|
2963
3327
|
}
|
|
2964
3328
|
/**
|
|
2965
3329
|
* Initializes a WebSocket connection with exponential backoff retry mechanism.
|
|
@@ -2981,8 +3345,9 @@ var WebSocketClient = class extends import_events5.EventEmitter {
|
|
|
2981
3345
|
return new Promise((resolve, reject) => {
|
|
2982
3346
|
try {
|
|
2983
3347
|
this.ws = new import_ws.default(wsUrl);
|
|
2984
|
-
this.ws.
|
|
3348
|
+
this.ws.once("open", () => {
|
|
2985
3349
|
console.log("WebSocket connection established successfully");
|
|
3350
|
+
this.setupHeartbeat();
|
|
2986
3351
|
if (this.isReconnecting) {
|
|
2987
3352
|
this.emit("reconnected", {
|
|
2988
3353
|
apps: this.apps,
|
|
@@ -2995,7 +3360,7 @@ var WebSocketClient = class extends import_events5.EventEmitter {
|
|
|
2995
3360
|
resolve();
|
|
2996
3361
|
});
|
|
2997
3362
|
this.ws.on("message", (data) => this.handleMessage(data.toString()));
|
|
2998
|
-
this.ws.
|
|
3363
|
+
this.ws.once("close", (code) => {
|
|
2999
3364
|
console.warn(
|
|
3000
3365
|
`WebSocket disconnected with code ${code}. Attempting to reconnect...`
|
|
3001
3366
|
);
|
|
@@ -3003,7 +3368,7 @@ var WebSocketClient = class extends import_events5.EventEmitter {
|
|
|
3003
3368
|
this.reconnect(this.lastWsUrl);
|
|
3004
3369
|
}
|
|
3005
3370
|
});
|
|
3006
|
-
this.ws.
|
|
3371
|
+
this.ws.once("error", (err) => {
|
|
3007
3372
|
console.error("WebSocket error:", err.message);
|
|
3008
3373
|
if (!this.isReconnecting) {
|
|
3009
3374
|
this.reconnect(this.lastWsUrl);
|
|
@@ -3016,6 +3381,34 @@ var WebSocketClient = class extends import_events5.EventEmitter {
|
|
|
3016
3381
|
});
|
|
3017
3382
|
}, this.backOffOptions);
|
|
3018
3383
|
}
|
|
3384
|
+
getEventKey(event) {
|
|
3385
|
+
const ids = [];
|
|
3386
|
+
if ("channel" in event && event.channel?.id) ids.push(event.channel.id);
|
|
3387
|
+
if ("playback" in event && event.playback?.id) ids.push(event.playback.id);
|
|
3388
|
+
if ("bridge" in event && event.bridge?.id) ids.push(event.bridge.id);
|
|
3389
|
+
return `${event.type}-${ids.join("-")}`;
|
|
3390
|
+
}
|
|
3391
|
+
processEvent(event) {
|
|
3392
|
+
if (this.subscribedEvents?.length && !this.subscribedEvents.includes(event.type)) {
|
|
3393
|
+
return;
|
|
3394
|
+
}
|
|
3395
|
+
if ("channel" in event && event.channel?.id && this.ariClient) {
|
|
3396
|
+
const instanceChannel = this.ariClient.Channel(event.channel.id);
|
|
3397
|
+
instanceChannel.emitEvent(event);
|
|
3398
|
+
event.instanceChannel = instanceChannel;
|
|
3399
|
+
}
|
|
3400
|
+
if ("playback" in event && event.playback?.id && this.ariClient) {
|
|
3401
|
+
const instancePlayback = this.ariClient.Playback(event.playback.id);
|
|
3402
|
+
instancePlayback.emitEvent(event);
|
|
3403
|
+
event.instancePlayback = instancePlayback;
|
|
3404
|
+
}
|
|
3405
|
+
if ("bridge" in event && event.bridge?.id && this.ariClient) {
|
|
3406
|
+
const instanceBridge = this.ariClient.Bridge(event.bridge.id);
|
|
3407
|
+
instanceBridge.emitEvent(event);
|
|
3408
|
+
event.instanceBridge = instanceBridge;
|
|
3409
|
+
}
|
|
3410
|
+
this.emit(event.type, event);
|
|
3411
|
+
}
|
|
3019
3412
|
/**
|
|
3020
3413
|
* Handles incoming WebSocket messages by parsing and processing events.
|
|
3021
3414
|
*
|
|
@@ -3031,30 +3424,20 @@ var WebSocketClient = class extends import_events5.EventEmitter {
|
|
|
3031
3424
|
handleMessage(rawMessage) {
|
|
3032
3425
|
try {
|
|
3033
3426
|
const event = JSON.parse(rawMessage);
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
const instanceChannel = this.ariClient.Channel(event.channel.id);
|
|
3039
|
-
instanceChannel.emitEvent(event);
|
|
3040
|
-
event.instanceChannel = instanceChannel;
|
|
3427
|
+
const key = this.getEventKey(event);
|
|
3428
|
+
const existing = this.eventQueue.get(key);
|
|
3429
|
+
if (existing) {
|
|
3430
|
+
clearTimeout(existing);
|
|
3041
3431
|
}
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
const instanceBridge = this.ariClient.Bridge(event.bridge.id);
|
|
3049
|
-
instanceBridge.emitEvent(event);
|
|
3050
|
-
event.instanceBridge = instanceBridge;
|
|
3051
|
-
}
|
|
3052
|
-
this.emit(event.type, event);
|
|
3053
|
-
} catch (error) {
|
|
3054
|
-
console.error(
|
|
3055
|
-
"Error processing WebSocket message:",
|
|
3056
|
-
error instanceof Error ? error.message : "Unknown error"
|
|
3432
|
+
this.eventQueue.set(
|
|
3433
|
+
key,
|
|
3434
|
+
setTimeout(() => {
|
|
3435
|
+
this.processEvent(event);
|
|
3436
|
+
this.eventQueue.delete(key);
|
|
3437
|
+
}, 100)
|
|
3057
3438
|
);
|
|
3439
|
+
} catch (error) {
|
|
3440
|
+
console.error("Error processing WebSocket message:", error);
|
|
3058
3441
|
this.emit("error", new Error("Failed to decode WebSocket message"));
|
|
3059
3442
|
}
|
|
3060
3443
|
}
|
|
@@ -3070,21 +3453,26 @@ var WebSocketClient = class extends import_events5.EventEmitter {
|
|
|
3070
3453
|
*
|
|
3071
3454
|
* @emits reconnectFailed - Emitted if all reconnection attempts fail.
|
|
3072
3455
|
*/
|
|
3073
|
-
reconnect(wsUrl) {
|
|
3456
|
+
async reconnect(wsUrl) {
|
|
3457
|
+
if (!this.shouldReconnect) {
|
|
3458
|
+
console.warn(
|
|
3459
|
+
"Reconnection skipped because WebSocket was intentionally closed."
|
|
3460
|
+
);
|
|
3461
|
+
return;
|
|
3462
|
+
}
|
|
3463
|
+
if (this.isReconnecting) {
|
|
3464
|
+
console.warn("J\xE1 h\xE1 uma tentativa de reconex\xE3o em andamento.");
|
|
3465
|
+
return;
|
|
3466
|
+
}
|
|
3074
3467
|
this.isReconnecting = true;
|
|
3075
3468
|
this.reconnectionAttempts++;
|
|
3076
|
-
console.log(
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
error instanceof Error ? error.message : "Unknown error"
|
|
3084
|
-
);
|
|
3085
|
-
this.emit("reconnectFailed", error);
|
|
3086
|
-
}
|
|
3087
|
-
);
|
|
3469
|
+
console.log(`Tentando reconex\xE3o #${this.reconnectionAttempts}...`);
|
|
3470
|
+
(0, import_exponential_backoff.backOff)(() => this.initializeWebSocket(wsUrl), this.backOffOptions).catch((error) => {
|
|
3471
|
+
console.error(`Falha ao reconectar: ${error.message}`);
|
|
3472
|
+
this.emit("reconnectFailed", error);
|
|
3473
|
+
}).finally(() => {
|
|
3474
|
+
this.isReconnecting = false;
|
|
3475
|
+
});
|
|
3088
3476
|
}
|
|
3089
3477
|
/**
|
|
3090
3478
|
* Closes the WebSocket connection if it exists.
|
|
@@ -3095,18 +3483,34 @@ var WebSocketClient = class extends import_events5.EventEmitter {
|
|
|
3095
3483
|
*
|
|
3096
3484
|
* @throws {Error} Logs an error message if closing the WebSocket fails.
|
|
3097
3485
|
*/
|
|
3098
|
-
close() {
|
|
3486
|
+
async close() {
|
|
3487
|
+
if (!this.ws) {
|
|
3488
|
+
console.warn("No WebSocket connection to close");
|
|
3489
|
+
return;
|
|
3490
|
+
}
|
|
3491
|
+
console.log("Closing WebSocket connection.");
|
|
3492
|
+
this.shouldReconnect = false;
|
|
3493
|
+
this.eventQueue.forEach((timeout) => clearTimeout(timeout));
|
|
3494
|
+
this.eventQueue.clear();
|
|
3495
|
+
const closeTimeout = setTimeout(() => {
|
|
3496
|
+
if (this.ws && this.ws.readyState !== import_ws.default.CLOSED) {
|
|
3497
|
+
this.ws.terminate();
|
|
3498
|
+
}
|
|
3499
|
+
}, 5e3);
|
|
3099
3500
|
try {
|
|
3100
|
-
|
|
3501
|
+
this.ws.removeAllListeners();
|
|
3502
|
+
await new Promise((resolve) => {
|
|
3503
|
+
this.ws.once("close", () => {
|
|
3504
|
+
clearTimeout(closeTimeout);
|
|
3505
|
+
resolve();
|
|
3506
|
+
});
|
|
3101
3507
|
this.ws.close();
|
|
3102
|
-
|
|
3103
|
-
console.log("WebSocket connection closed");
|
|
3104
|
-
}
|
|
3508
|
+
});
|
|
3105
3509
|
} catch (error) {
|
|
3106
|
-
console.error(
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
);
|
|
3510
|
+
console.error("Error closing WebSocket:", error);
|
|
3511
|
+
} finally {
|
|
3512
|
+
this.ws = void 0;
|
|
3513
|
+
this.emit("disconnected");
|
|
3110
3514
|
}
|
|
3111
3515
|
}
|
|
3112
3516
|
/**
|
|
@@ -3138,6 +3542,30 @@ var WebSocketClient = class extends import_events5.EventEmitter {
|
|
|
3138
3542
|
getState() {
|
|
3139
3543
|
return this.ws?.readyState ?? import_ws.default.CLOSED;
|
|
3140
3544
|
}
|
|
3545
|
+
/**
|
|
3546
|
+
* Cleans up the WebSocketClient instance, resetting its state and clearing resources.
|
|
3547
|
+
*
|
|
3548
|
+
* This method performs the following cleanup operations:
|
|
3549
|
+
* - Clears the event queue and cancels any pending timeouts.
|
|
3550
|
+
* - Stops any ongoing reconnection attempts.
|
|
3551
|
+
* - Clears the stored WebSocket URL.
|
|
3552
|
+
* - Resets the reconnection attempt counter.
|
|
3553
|
+
* - Removes all event listeners attached to this instance.
|
|
3554
|
+
*
|
|
3555
|
+
* This method is typically called when the WebSocketClient is no longer needed or
|
|
3556
|
+
* before reinitializing the client to ensure a clean slate.
|
|
3557
|
+
*
|
|
3558
|
+
* @returns {void} This method doesn't return a value.
|
|
3559
|
+
*/
|
|
3560
|
+
cleanup() {
|
|
3561
|
+
this.eventQueue.forEach((timeout) => clearTimeout(timeout));
|
|
3562
|
+
this.eventQueue.clear();
|
|
3563
|
+
this.shouldReconnect = false;
|
|
3564
|
+
this.isReconnecting = false;
|
|
3565
|
+
this.lastWsUrl = "";
|
|
3566
|
+
this.reconnectionAttempts = 0;
|
|
3567
|
+
this.removeAllListeners();
|
|
3568
|
+
}
|
|
3141
3569
|
};
|
|
3142
3570
|
|
|
3143
3571
|
// src/ari-client/ariClient.ts
|
|
@@ -3168,6 +3596,8 @@ var AriClient = class {
|
|
|
3168
3596
|
}
|
|
3169
3597
|
baseClient;
|
|
3170
3598
|
webSocketClient;
|
|
3599
|
+
eventListeners = /* @__PURE__ */ new Map();
|
|
3600
|
+
// Armazena os listeners para limpeza
|
|
3171
3601
|
channels;
|
|
3172
3602
|
endpoints;
|
|
3173
3603
|
applications;
|
|
@@ -3175,6 +3605,50 @@ var AriClient = class {
|
|
|
3175
3605
|
sounds;
|
|
3176
3606
|
asterisk;
|
|
3177
3607
|
bridges;
|
|
3608
|
+
async cleanup() {
|
|
3609
|
+
try {
|
|
3610
|
+
console.log("Starting ARI Client cleanup...");
|
|
3611
|
+
if (this.webSocketClient) {
|
|
3612
|
+
await this.closeWebSocket();
|
|
3613
|
+
}
|
|
3614
|
+
await Promise.all([
|
|
3615
|
+
// Cleanup de channels
|
|
3616
|
+
(async () => {
|
|
3617
|
+
try {
|
|
3618
|
+
this.channels.cleanup();
|
|
3619
|
+
} catch (error) {
|
|
3620
|
+
console.error("Error cleaning up channels:", error);
|
|
3621
|
+
}
|
|
3622
|
+
})(),
|
|
3623
|
+
// Cleanup de playbacks
|
|
3624
|
+
(async () => {
|
|
3625
|
+
try {
|
|
3626
|
+
this.playbacks.cleanup();
|
|
3627
|
+
} catch (error) {
|
|
3628
|
+
console.error("Error cleaning up playbacks:", error);
|
|
3629
|
+
}
|
|
3630
|
+
})(),
|
|
3631
|
+
// Cleanup de bridges
|
|
3632
|
+
(async () => {
|
|
3633
|
+
try {
|
|
3634
|
+
this.bridges.cleanup();
|
|
3635
|
+
} catch (error) {
|
|
3636
|
+
console.error("Error cleaning up bridges:", error);
|
|
3637
|
+
}
|
|
3638
|
+
})()
|
|
3639
|
+
]);
|
|
3640
|
+
this.eventListeners.forEach((listeners, event) => {
|
|
3641
|
+
listeners.forEach((listener) => {
|
|
3642
|
+
this.off(event, listener);
|
|
3643
|
+
});
|
|
3644
|
+
});
|
|
3645
|
+
this.eventListeners.clear();
|
|
3646
|
+
console.log("ARI Client cleanup completed successfully");
|
|
3647
|
+
} catch (error) {
|
|
3648
|
+
console.error("Error during ARI Client cleanup:", error);
|
|
3649
|
+
throw error;
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3178
3652
|
/**
|
|
3179
3653
|
* Initializes a WebSocket connection for receiving events.
|
|
3180
3654
|
*
|
|
@@ -3184,14 +3658,14 @@ var AriClient = class {
|
|
|
3184
3658
|
* @throws {Error} If connection fails or if WebSocket is already connected
|
|
3185
3659
|
*/
|
|
3186
3660
|
async connectWebSocket(apps, subscribedEvents) {
|
|
3187
|
-
if (!apps.length) {
|
|
3188
|
-
throw new Error("At least one application name is required");
|
|
3189
|
-
}
|
|
3190
|
-
if (this.webSocketClient) {
|
|
3191
|
-
console.warn("WebSocket is already connected");
|
|
3192
|
-
return;
|
|
3193
|
-
}
|
|
3194
3661
|
try {
|
|
3662
|
+
if (!apps.length) {
|
|
3663
|
+
throw new Error("At least one application name is required.");
|
|
3664
|
+
}
|
|
3665
|
+
if (this.webSocketClient) {
|
|
3666
|
+
await this.closeWebSocket();
|
|
3667
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
3668
|
+
}
|
|
3195
3669
|
this.webSocketClient = new WebSocketClient(
|
|
3196
3670
|
this.baseClient,
|
|
3197
3671
|
apps,
|
|
@@ -3199,13 +3673,38 @@ var AriClient = class {
|
|
|
3199
3673
|
this
|
|
3200
3674
|
);
|
|
3201
3675
|
await this.webSocketClient.connect();
|
|
3202
|
-
console.log("WebSocket connection established successfully");
|
|
3676
|
+
console.log("WebSocket connection established successfully.");
|
|
3203
3677
|
} catch (error) {
|
|
3204
3678
|
console.error("Failed to establish WebSocket connection:", error);
|
|
3205
3679
|
this.webSocketClient = void 0;
|
|
3206
3680
|
throw error;
|
|
3207
3681
|
}
|
|
3208
3682
|
}
|
|
3683
|
+
/**
|
|
3684
|
+
* Destroys the ARI Client instance, cleaning up all resources and removing circular references.
|
|
3685
|
+
* This method should be called when the ARI Client is no longer needed to ensure proper cleanup.
|
|
3686
|
+
*
|
|
3687
|
+
* @returns {Promise<void>} A promise that resolves when the destruction process is complete.
|
|
3688
|
+
* @throws {Error} If an error occurs during the destruction process.
|
|
3689
|
+
*/
|
|
3690
|
+
async destroy() {
|
|
3691
|
+
try {
|
|
3692
|
+
console.log("Destroying ARI Client...");
|
|
3693
|
+
await this.cleanup();
|
|
3694
|
+
this.webSocketClient = void 0;
|
|
3695
|
+
this.channels = null;
|
|
3696
|
+
this.playbacks = null;
|
|
3697
|
+
this.bridges = null;
|
|
3698
|
+
this.endpoints = null;
|
|
3699
|
+
this.applications = null;
|
|
3700
|
+
this.sounds = null;
|
|
3701
|
+
this.asterisk = null;
|
|
3702
|
+
console.log("ARI Client destroyed successfully");
|
|
3703
|
+
} catch (error) {
|
|
3704
|
+
console.error("Error destroying ARI Client:", error);
|
|
3705
|
+
throw error;
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3209
3708
|
/**
|
|
3210
3709
|
* Registers an event listener for WebSocket events.
|
|
3211
3710
|
*
|
|
@@ -3217,8 +3716,15 @@ var AriClient = class {
|
|
|
3217
3716
|
if (!this.webSocketClient) {
|
|
3218
3717
|
throw new Error("WebSocket is not connected");
|
|
3219
3718
|
}
|
|
3719
|
+
const existingListeners = this.eventListeners.get(event) || [];
|
|
3720
|
+
if (existingListeners.includes(listener)) {
|
|
3721
|
+
console.warn(`Listener already registered for event ${event}, reusing.`);
|
|
3722
|
+
return;
|
|
3723
|
+
}
|
|
3220
3724
|
this.webSocketClient.on(event, listener);
|
|
3221
|
-
|
|
3725
|
+
existingListeners.push(listener);
|
|
3726
|
+
this.eventListeners.set(event, existingListeners);
|
|
3727
|
+
console.log(`Event listener successfully registered for ${event}`);
|
|
3222
3728
|
}
|
|
3223
3729
|
/**
|
|
3224
3730
|
* Registers a one-time event listener for WebSocket events.
|
|
@@ -3231,7 +3737,19 @@ var AriClient = class {
|
|
|
3231
3737
|
if (!this.webSocketClient) {
|
|
3232
3738
|
throw new Error("WebSocket is not connected");
|
|
3233
3739
|
}
|
|
3234
|
-
this.
|
|
3740
|
+
const existingListeners = this.eventListeners.get(event) || [];
|
|
3741
|
+
if (existingListeners.includes(listener)) {
|
|
3742
|
+
console.warn(
|
|
3743
|
+
`One-time listener already registered for event ${event}, reusing.`
|
|
3744
|
+
);
|
|
3745
|
+
return;
|
|
3746
|
+
}
|
|
3747
|
+
const wrappedListener = (data) => {
|
|
3748
|
+
listener(data);
|
|
3749
|
+
this.off(event, wrappedListener);
|
|
3750
|
+
};
|
|
3751
|
+
this.webSocketClient.once(event, wrappedListener);
|
|
3752
|
+
this.eventListeners.set(event, [...existingListeners, wrappedListener]);
|
|
3235
3753
|
console.log(`One-time event listener registered for ${event}`);
|
|
3236
3754
|
}
|
|
3237
3755
|
/**
|
|
@@ -3246,19 +3764,57 @@ var AriClient = class {
|
|
|
3246
3764
|
return;
|
|
3247
3765
|
}
|
|
3248
3766
|
this.webSocketClient.off(event, listener);
|
|
3767
|
+
const existingListeners = this.eventListeners.get(event) || [];
|
|
3768
|
+
this.eventListeners.set(
|
|
3769
|
+
event,
|
|
3770
|
+
existingListeners.filter((l) => l !== listener)
|
|
3771
|
+
);
|
|
3249
3772
|
console.log(`Event listener removed for ${event}`);
|
|
3250
3773
|
}
|
|
3251
3774
|
/**
|
|
3252
3775
|
* Closes the WebSocket connection if one exists.
|
|
3253
3776
|
*/
|
|
3254
3777
|
closeWebSocket() {
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3778
|
+
return new Promise((resolve) => {
|
|
3779
|
+
if (!this.webSocketClient) {
|
|
3780
|
+
console.warn("No WebSocket connection to close");
|
|
3781
|
+
resolve();
|
|
3782
|
+
return;
|
|
3783
|
+
}
|
|
3784
|
+
console.log("Closing WebSocket connection and cleaning up listeners.");
|
|
3785
|
+
const closeTimeout = setTimeout(() => {
|
|
3786
|
+
if (this.webSocketClient) {
|
|
3787
|
+
this.webSocketClient.removeAllListeners();
|
|
3788
|
+
this.webSocketClient = void 0;
|
|
3789
|
+
}
|
|
3790
|
+
resolve();
|
|
3791
|
+
}, 5e3);
|
|
3792
|
+
this.eventListeners.forEach((listeners, event) => {
|
|
3793
|
+
listeners.forEach((listener) => {
|
|
3794
|
+
this.webSocketClient?.off(
|
|
3795
|
+
event,
|
|
3796
|
+
listener
|
|
3797
|
+
);
|
|
3798
|
+
});
|
|
3799
|
+
});
|
|
3800
|
+
this.eventListeners.clear();
|
|
3801
|
+
this.webSocketClient.once("close", () => {
|
|
3802
|
+
clearTimeout(closeTimeout);
|
|
3803
|
+
this.webSocketClient = void 0;
|
|
3804
|
+
console.log("WebSocket connection closed");
|
|
3805
|
+
resolve();
|
|
3806
|
+
});
|
|
3807
|
+
this.webSocketClient.close().then(() => {
|
|
3808
|
+
clearTimeout(closeTimeout);
|
|
3809
|
+
this.webSocketClient = void 0;
|
|
3810
|
+
resolve();
|
|
3811
|
+
}).catch((error) => {
|
|
3812
|
+
console.error("Error during WebSocket close:", error);
|
|
3813
|
+
clearTimeout(closeTimeout);
|
|
3814
|
+
this.webSocketClient = void 0;
|
|
3815
|
+
resolve();
|
|
3816
|
+
});
|
|
3817
|
+
});
|
|
3262
3818
|
}
|
|
3263
3819
|
/**
|
|
3264
3820
|
* Creates or retrieves a Channel instance.
|
|
@@ -3299,7 +3855,7 @@ var AriClient = class {
|
|
|
3299
3855
|
* @returns {boolean} True if WebSocket is connected, false otherwise
|
|
3300
3856
|
*/
|
|
3301
3857
|
isWebSocketConnected() {
|
|
3302
|
-
return !!this.webSocketClient;
|
|
3858
|
+
return !!this.webSocketClient && this.webSocketClient.isConnected();
|
|
3303
3859
|
}
|
|
3304
3860
|
};
|
|
3305
3861
|
// Annotate the CommonJS export names for ESM import in node:
|