@ipcom/asterisk-ari 0.0.156 → 0.0.158

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