@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.
@@ -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 da bridge \xE9 obrigat\xF3rio");
1281
- }
1282
- if (this.bridgeInstances.has(bridgeId)) {
1283
- const instance = this.bridgeInstances.get(bridgeId);
1284
- instance?.removeAllListeners();
1285
- this.bridgeInstances.delete(bridgeId);
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(`Tentativa de remover inst\xE2ncia inexistente: ${bridgeId}`);
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 type is included in the predefined bridge events.
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("Evento WebSocket inv\xE1lido recebido");
1379
+ if (!event || !("bridge" in event) || !event.bridge?.id) {
1380
+ console.warn("Invalid WebSocket event received");
1311
1381
  return;
1312
1382
  }
1313
- if ("bridge" in event && event.bridge?.id && bridgeEvents.includes(event.type)) {
1314
- const instance = this.bridgeInstances.get(event.bridge.id);
1315
- if (instance) {
1316
- instance.emitEvent(event);
1317
- } else {
1318
- console.warn(
1319
- `Nenhuma inst\xE2ncia encontrada para bridge ${event.bridge.id}`
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 import_events3 = require("events");
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 import_events3.EventEmitter();
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
- if (this.channelInstances.has(channelId)) {
2066
- const instance = this.channelInstances.get(channelId);
2067
- instance?.removeAllListeners();
2068
- this.channelInstances.delete(channelId);
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
- if ("channel" in event && event.channel?.id) {
2082
- const instance = this.channelInstances.get(event.channel.id);
2083
- if (instance) {
2084
- instance.emitEvent(event);
2085
- } else {
2086
- console.warn(`No instance found for channel ${event.channel.id}`);
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 import_events4 = require("events");
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 import_events4.EventEmitter();
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 from this playback instance.
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
- if (this.playbackInstances.has(playbackId)) {
2759
- const instance = this.playbackInstances.get(playbackId);
2760
- instance?.removeAllListeners();
2761
- this.playbackInstances.delete(playbackId);
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
- if ("playback" in event && event.playback?.id) {
2775
- const instance = this.playbackInstances.get(event.playback.id);
2776
- if (instance) {
2777
- instance.emitEvent(event);
2778
- } else {
2779
- console.warn(`No instance found for playback ${event.playback.id}`);
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 import_events5 = require("events");
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 import_events5.EventEmitter {
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,28 @@ 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
- if (this.subscribedEvents?.length) {
2954
- this.subscribedEvents.forEach(
2955
- (event) => queryParams.append("event", event)
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
- console.log("Connecting to WebSocket...");
2962
- return this.initializeWebSocket(this.lastWsUrl);
3321
+ try {
3322
+ await this.initializeWebSocket(this.lastWsUrl);
3323
+ } finally {
3324
+ this.isConnecting = false;
3325
+ }
2963
3326
  }
2964
3327
  /**
2965
3328
  * Initializes a WebSocket connection with exponential backoff retry mechanism.
@@ -2981,8 +3344,8 @@ var WebSocketClient = class extends import_events5.EventEmitter {
2981
3344
  return new Promise((resolve, reject) => {
2982
3345
  try {
2983
3346
  this.ws = new import_ws.default(wsUrl);
2984
- this.ws.on("open", () => {
2985
- console.log("WebSocket connection established successfully");
3347
+ this.ws.once("open", () => {
3348
+ this.setupHeartbeat();
2986
3349
  if (this.isReconnecting) {
2987
3350
  this.emit("reconnected", {
2988
3351
  apps: this.apps,
@@ -2995,7 +3358,7 @@ var WebSocketClient = class extends import_events5.EventEmitter {
2995
3358
  resolve();
2996
3359
  });
2997
3360
  this.ws.on("message", (data) => this.handleMessage(data.toString()));
2998
- this.ws.on("close", (code) => {
3361
+ this.ws.once("close", (code) => {
2999
3362
  console.warn(
3000
3363
  `WebSocket disconnected with code ${code}. Attempting to reconnect...`
3001
3364
  );
@@ -3003,7 +3366,7 @@ var WebSocketClient = class extends import_events5.EventEmitter {
3003
3366
  this.reconnect(this.lastWsUrl);
3004
3367
  }
3005
3368
  });
3006
- this.ws.on("error", (err) => {
3369
+ this.ws.once("error", (err) => {
3007
3370
  console.error("WebSocket error:", err.message);
3008
3371
  if (!this.isReconnecting) {
3009
3372
  this.reconnect(this.lastWsUrl);
@@ -3016,6 +3379,34 @@ var WebSocketClient = class extends import_events5.EventEmitter {
3016
3379
  });
3017
3380
  }, this.backOffOptions);
3018
3381
  }
3382
+ getEventKey(event) {
3383
+ const ids = [];
3384
+ if ("channel" in event && event.channel?.id) ids.push(event.channel.id);
3385
+ if ("playback" in event && event.playback?.id) ids.push(event.playback.id);
3386
+ if ("bridge" in event && event.bridge?.id) ids.push(event.bridge.id);
3387
+ return `${event.type}-${ids.join("-")}`;
3388
+ }
3389
+ processEvent(event) {
3390
+ if (this.subscribedEvents?.length && !this.subscribedEvents.includes(event.type)) {
3391
+ return;
3392
+ }
3393
+ if ("channel" in event && event.channel?.id && this.ariClient) {
3394
+ const instanceChannel = this.ariClient.Channel(event.channel.id);
3395
+ instanceChannel.emitEvent(event);
3396
+ event.instanceChannel = instanceChannel;
3397
+ }
3398
+ if ("playback" in event && event.playback?.id && this.ariClient) {
3399
+ const instancePlayback = this.ariClient.Playback(event.playback.id);
3400
+ instancePlayback.emitEvent(event);
3401
+ event.instancePlayback = instancePlayback;
3402
+ }
3403
+ if ("bridge" in event && event.bridge?.id && this.ariClient) {
3404
+ const instanceBridge = this.ariClient.Bridge(event.bridge.id);
3405
+ instanceBridge.emitEvent(event);
3406
+ event.instanceBridge = instanceBridge;
3407
+ }
3408
+ this.emit(event.type, event);
3409
+ }
3019
3410
  /**
3020
3411
  * Handles incoming WebSocket messages by parsing and processing events.
3021
3412
  *
@@ -3031,30 +3422,20 @@ var WebSocketClient = class extends import_events5.EventEmitter {
3031
3422
  handleMessage(rawMessage) {
3032
3423
  try {
3033
3424
  const event = JSON.parse(rawMessage);
3034
- if (this.subscribedEvents?.length && !this.subscribedEvents.includes(event.type)) {
3035
- return;
3036
- }
3037
- if ("channel" in event && event.channel?.id && this.ariClient) {
3038
- const instanceChannel = this.ariClient.Channel(event.channel.id);
3039
- instanceChannel.emitEvent(event);
3040
- event.instanceChannel = instanceChannel;
3425
+ const key = this.getEventKey(event);
3426
+ const existing = this.eventQueue.get(key);
3427
+ if (existing) {
3428
+ clearTimeout(existing);
3041
3429
  }
3042
- if ("playback" in event && event.playback?.id && this.ariClient) {
3043
- const instancePlayback = this.ariClient.Playback(event.playback.id);
3044
- instancePlayback.emitEvent(event);
3045
- event.instancePlayback = instancePlayback;
3046
- }
3047
- if ("bridge" in event && event.bridge?.id && this.ariClient) {
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"
3430
+ this.eventQueue.set(
3431
+ key,
3432
+ setTimeout(() => {
3433
+ this.processEvent(event);
3434
+ this.eventQueue.delete(key);
3435
+ }, 100)
3057
3436
  );
3437
+ } catch (error) {
3438
+ console.error("Error processing WebSocket message:", error);
3058
3439
  this.emit("error", new Error("Failed to decode WebSocket message"));
3059
3440
  }
3060
3441
  }
@@ -3070,21 +3451,26 @@ var WebSocketClient = class extends import_events5.EventEmitter {
3070
3451
  *
3071
3452
  * @emits reconnectFailed - Emitted if all reconnection attempts fail.
3072
3453
  */
3073
- reconnect(wsUrl) {
3454
+ async reconnect(wsUrl) {
3455
+ if (!this.shouldReconnect) {
3456
+ console.warn(
3457
+ "Reconnection skipped because WebSocket was intentionally closed."
3458
+ );
3459
+ return;
3460
+ }
3461
+ if (this.isReconnecting) {
3462
+ console.warn("J\xE1 h\xE1 uma tentativa de reconex\xE3o em andamento.");
3463
+ return;
3464
+ }
3074
3465
  this.isReconnecting = true;
3075
3466
  this.reconnectionAttempts++;
3076
- console.log(
3077
- `Initiating reconnection attempt #${this.reconnectionAttempts}...`
3078
- );
3079
- (0, import_exponential_backoff.backOff)(() => this.initializeWebSocket(wsUrl), this.backOffOptions).catch(
3080
- (error) => {
3081
- console.error(
3082
- `Failed to reconnect after ${this.reconnectionAttempts} attempts:`,
3083
- error instanceof Error ? error.message : "Unknown error"
3084
- );
3085
- this.emit("reconnectFailed", error);
3086
- }
3087
- );
3467
+ console.log(`Tentando reconex\xE3o #${this.reconnectionAttempts}...`);
3468
+ (0, import_exponential_backoff.backOff)(() => this.initializeWebSocket(wsUrl), this.backOffOptions).catch((error) => {
3469
+ console.error(`Falha ao reconectar: ${error.message}`);
3470
+ this.emit("reconnectFailed", error);
3471
+ }).finally(() => {
3472
+ this.isReconnecting = false;
3473
+ });
3088
3474
  }
3089
3475
  /**
3090
3476
  * Closes the WebSocket connection if it exists.
@@ -3095,18 +3481,34 @@ var WebSocketClient = class extends import_events5.EventEmitter {
3095
3481
  *
3096
3482
  * @throws {Error} Logs an error message if closing the WebSocket fails.
3097
3483
  */
3098
- close() {
3484
+ async close() {
3485
+ if (!this.ws) {
3486
+ console.warn("No WebSocket connection to close");
3487
+ return;
3488
+ }
3489
+ console.log("Closing WebSocket connection.");
3490
+ this.shouldReconnect = false;
3491
+ this.eventQueue.forEach((timeout) => clearTimeout(timeout));
3492
+ this.eventQueue.clear();
3493
+ const closeTimeout = setTimeout(() => {
3494
+ if (this.ws && this.ws.readyState !== import_ws.default.CLOSED) {
3495
+ this.ws.terminate();
3496
+ }
3497
+ }, 5e3);
3099
3498
  try {
3100
- if (this.ws) {
3499
+ this.ws.removeAllListeners();
3500
+ await new Promise((resolve) => {
3501
+ this.ws.once("close", () => {
3502
+ clearTimeout(closeTimeout);
3503
+ resolve();
3504
+ });
3101
3505
  this.ws.close();
3102
- this.ws = void 0;
3103
- console.log("WebSocket connection closed");
3104
- }
3506
+ });
3105
3507
  } catch (error) {
3106
- console.error(
3107
- "Error closing WebSocket:",
3108
- error instanceof Error ? error.message : "Unknown error"
3109
- );
3508
+ console.error("Error closing WebSocket:", error);
3509
+ } finally {
3510
+ this.ws = void 0;
3511
+ this.emit("disconnected");
3110
3512
  }
3111
3513
  }
3112
3514
  /**
@@ -3138,6 +3540,30 @@ var WebSocketClient = class extends import_events5.EventEmitter {
3138
3540
  getState() {
3139
3541
  return this.ws?.readyState ?? import_ws.default.CLOSED;
3140
3542
  }
3543
+ /**
3544
+ * Cleans up the WebSocketClient instance, resetting its state and clearing resources.
3545
+ *
3546
+ * This method performs the following cleanup operations:
3547
+ * - Clears the event queue and cancels any pending timeouts.
3548
+ * - Stops any ongoing reconnection attempts.
3549
+ * - Clears the stored WebSocket URL.
3550
+ * - Resets the reconnection attempt counter.
3551
+ * - Removes all event listeners attached to this instance.
3552
+ *
3553
+ * This method is typically called when the WebSocketClient is no longer needed or
3554
+ * before reinitializing the client to ensure a clean slate.
3555
+ *
3556
+ * @returns {void} This method doesn't return a value.
3557
+ */
3558
+ cleanup() {
3559
+ this.eventQueue.forEach((timeout) => clearTimeout(timeout));
3560
+ this.eventQueue.clear();
3561
+ this.shouldReconnect = false;
3562
+ this.isReconnecting = false;
3563
+ this.lastWsUrl = "";
3564
+ this.reconnectionAttempts = 0;
3565
+ this.removeAllListeners();
3566
+ }
3141
3567
  };
3142
3568
 
3143
3569
  // src/ari-client/ariClient.ts
@@ -3168,6 +3594,8 @@ var AriClient = class {
3168
3594
  }
3169
3595
  baseClient;
3170
3596
  webSocketClient;
3597
+ eventListeners = /* @__PURE__ */ new Map();
3598
+ // Armazena os listeners para limpeza
3171
3599
  channels;
3172
3600
  endpoints;
3173
3601
  applications;
@@ -3175,6 +3603,50 @@ var AriClient = class {
3175
3603
  sounds;
3176
3604
  asterisk;
3177
3605
  bridges;
3606
+ async cleanup() {
3607
+ try {
3608
+ console.log("Starting ARI Client cleanup...");
3609
+ if (this.webSocketClient) {
3610
+ await this.closeWebSocket();
3611
+ }
3612
+ await Promise.all([
3613
+ // Cleanup de channels
3614
+ (async () => {
3615
+ try {
3616
+ this.channels.cleanup();
3617
+ } catch (error) {
3618
+ console.error("Error cleaning up channels:", error);
3619
+ }
3620
+ })(),
3621
+ // Cleanup de playbacks
3622
+ (async () => {
3623
+ try {
3624
+ this.playbacks.cleanup();
3625
+ } catch (error) {
3626
+ console.error("Error cleaning up playbacks:", error);
3627
+ }
3628
+ })(),
3629
+ // Cleanup de bridges
3630
+ (async () => {
3631
+ try {
3632
+ this.bridges.cleanup();
3633
+ } catch (error) {
3634
+ console.error("Error cleaning up bridges:", error);
3635
+ }
3636
+ })()
3637
+ ]);
3638
+ this.eventListeners.forEach((listeners, event) => {
3639
+ listeners.forEach((listener) => {
3640
+ this.off(event, listener);
3641
+ });
3642
+ });
3643
+ this.eventListeners.clear();
3644
+ console.log("ARI Client cleanup completed successfully");
3645
+ } catch (error) {
3646
+ console.error("Error during ARI Client cleanup:", error);
3647
+ throw error;
3648
+ }
3649
+ }
3178
3650
  /**
3179
3651
  * Initializes a WebSocket connection for receiving events.
3180
3652
  *
@@ -3184,14 +3656,14 @@ var AriClient = class {
3184
3656
  * @throws {Error} If connection fails or if WebSocket is already connected
3185
3657
  */
3186
3658
  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
3659
  try {
3660
+ if (!apps.length) {
3661
+ throw new Error("At least one application name is required.");
3662
+ }
3663
+ if (this.webSocketClient) {
3664
+ await this.closeWebSocket();
3665
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
3666
+ }
3195
3667
  this.webSocketClient = new WebSocketClient(
3196
3668
  this.baseClient,
3197
3669
  apps,
@@ -3199,13 +3671,37 @@ var AriClient = class {
3199
3671
  this
3200
3672
  );
3201
3673
  await this.webSocketClient.connect();
3202
- console.log("WebSocket connection established successfully");
3203
3674
  } catch (error) {
3204
3675
  console.error("Failed to establish WebSocket connection:", error);
3205
3676
  this.webSocketClient = void 0;
3206
3677
  throw error;
3207
3678
  }
3208
3679
  }
3680
+ /**
3681
+ * Destroys the ARI Client instance, cleaning up all resources and removing circular references.
3682
+ * This method should be called when the ARI Client is no longer needed to ensure proper cleanup.
3683
+ *
3684
+ * @returns {Promise<void>} A promise that resolves when the destruction process is complete.
3685
+ * @throws {Error} If an error occurs during the destruction process.
3686
+ */
3687
+ async destroy() {
3688
+ try {
3689
+ console.log("Destroying ARI Client...");
3690
+ await this.cleanup();
3691
+ this.webSocketClient = void 0;
3692
+ this.channels = null;
3693
+ this.playbacks = null;
3694
+ this.bridges = null;
3695
+ this.endpoints = null;
3696
+ this.applications = null;
3697
+ this.sounds = null;
3698
+ this.asterisk = null;
3699
+ console.log("ARI Client destroyed successfully");
3700
+ } catch (error) {
3701
+ console.error("Error destroying ARI Client:", error);
3702
+ throw error;
3703
+ }
3704
+ }
3209
3705
  /**
3210
3706
  * Registers an event listener for WebSocket events.
3211
3707
  *
@@ -3217,8 +3713,14 @@ var AriClient = class {
3217
3713
  if (!this.webSocketClient) {
3218
3714
  throw new Error("WebSocket is not connected");
3219
3715
  }
3716
+ const existingListeners = this.eventListeners.get(event) || [];
3717
+ if (existingListeners.includes(listener)) {
3718
+ console.warn(`Listener already registered for event ${event}, reusing.`);
3719
+ return;
3720
+ }
3220
3721
  this.webSocketClient.on(event, listener);
3221
- console.log(`Event listener registered for ${event}`);
3722
+ existingListeners.push(listener);
3723
+ this.eventListeners.set(event, existingListeners);
3222
3724
  }
3223
3725
  /**
3224
3726
  * Registers a one-time event listener for WebSocket events.
@@ -3231,7 +3733,19 @@ var AriClient = class {
3231
3733
  if (!this.webSocketClient) {
3232
3734
  throw new Error("WebSocket is not connected");
3233
3735
  }
3234
- this.webSocketClient.once(event, listener);
3736
+ const existingListeners = this.eventListeners.get(event) || [];
3737
+ if (existingListeners.includes(listener)) {
3738
+ console.warn(
3739
+ `One-time listener already registered for event ${event}, reusing.`
3740
+ );
3741
+ return;
3742
+ }
3743
+ const wrappedListener = (data) => {
3744
+ listener(data);
3745
+ this.off(event, wrappedListener);
3746
+ };
3747
+ this.webSocketClient.once(event, wrappedListener);
3748
+ this.eventListeners.set(event, [...existingListeners, wrappedListener]);
3235
3749
  console.log(`One-time event listener registered for ${event}`);
3236
3750
  }
3237
3751
  /**
@@ -3246,19 +3760,57 @@ var AriClient = class {
3246
3760
  return;
3247
3761
  }
3248
3762
  this.webSocketClient.off(event, listener);
3763
+ const existingListeners = this.eventListeners.get(event) || [];
3764
+ this.eventListeners.set(
3765
+ event,
3766
+ existingListeners.filter((l) => l !== listener)
3767
+ );
3249
3768
  console.log(`Event listener removed for ${event}`);
3250
3769
  }
3251
3770
  /**
3252
3771
  * Closes the WebSocket connection if one exists.
3253
3772
  */
3254
3773
  closeWebSocket() {
3255
- if (!this.webSocketClient) {
3256
- console.warn("No WebSocket connection to close");
3257
- return;
3258
- }
3259
- this.webSocketClient.close();
3260
- this.webSocketClient = void 0;
3261
- console.log("WebSocket connection closed");
3774
+ return new Promise((resolve) => {
3775
+ if (!this.webSocketClient) {
3776
+ console.warn("No WebSocket connection to close");
3777
+ resolve();
3778
+ return;
3779
+ }
3780
+ console.log("Closing WebSocket connection and cleaning up listeners.");
3781
+ const closeTimeout = setTimeout(() => {
3782
+ if (this.webSocketClient) {
3783
+ this.webSocketClient.removeAllListeners();
3784
+ this.webSocketClient = void 0;
3785
+ }
3786
+ resolve();
3787
+ }, 5e3);
3788
+ this.eventListeners.forEach((listeners, event) => {
3789
+ listeners.forEach((listener) => {
3790
+ this.webSocketClient?.off(
3791
+ event,
3792
+ listener
3793
+ );
3794
+ });
3795
+ });
3796
+ this.eventListeners.clear();
3797
+ this.webSocketClient.once("close", () => {
3798
+ clearTimeout(closeTimeout);
3799
+ this.webSocketClient = void 0;
3800
+ console.log("WebSocket connection closed");
3801
+ resolve();
3802
+ });
3803
+ this.webSocketClient.close().then(() => {
3804
+ clearTimeout(closeTimeout);
3805
+ this.webSocketClient = void 0;
3806
+ resolve();
3807
+ }).catch((error) => {
3808
+ console.error("Error during WebSocket close:", error);
3809
+ clearTimeout(closeTimeout);
3810
+ this.webSocketClient = void 0;
3811
+ resolve();
3812
+ });
3813
+ });
3262
3814
  }
3263
3815
  /**
3264
3816
  * Creates or retrieves a Channel instance.
@@ -3299,7 +3851,7 @@ var AriClient = class {
3299
3851
  * @returns {boolean} True if WebSocket is connected, false otherwise
3300
3852
  */
3301
3853
  isWebSocketConnected() {
3302
- return !!this.webSocketClient;
3854
+ return !!this.webSocketClient && this.webSocketClient.isConnected();
3303
3855
  }
3304
3856
  };
3305
3857
  // Annotate the CommonJS export names for ESM import in node: