@ipcom/asterisk-ari 0.0.155 → 0.0.157

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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(
@@ -975,9 +965,10 @@ var BridgeInstance = class {
975
965
  this.client = client;
976
966
  this.baseClient = baseClient;
977
967
  this.id = bridgeId || `bridge-${Date.now()}`;
978
- console.log(`BridgeInstance inicializada com ID: ${this.id}`);
979
968
  }
980
969
  eventEmitter = new import_events.EventEmitter();
970
+ listenersMap = /* @__PURE__ */ new Map();
971
+ // 🔹 Guarda listeners para remoção posterior
981
972
  bridgeData = null;
982
973
  id;
983
974
  /**
@@ -998,7 +989,7 @@ var BridgeInstance = class {
998
989
  *
999
990
  * @example
1000
991
  * bridge.on('BridgeCreated', (event) => {
1001
- * console.log('Bridge created:', event.bridge.id);
992
+ *
1002
993
  * });
1003
994
  * @param event
1004
995
  * @param listener
@@ -1007,13 +998,23 @@ var BridgeInstance = class {
1007
998
  if (!event) {
1008
999
  throw new Error("Event type is required");
1009
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
+ }
1010
1008
  const wrappedListener = (data) => {
1011
1009
  if ("bridge" in data && data.bridge?.id === this.id) {
1012
1010
  listener(data);
1013
1011
  }
1014
1012
  };
1015
1013
  this.eventEmitter.on(event, wrappedListener);
1016
- console.log(`Event listener registered for ${event} on bridge ${this.id}`);
1014
+ if (!this.listenersMap.has(event)) {
1015
+ this.listenersMap.set(event, []);
1016
+ }
1017
+ this.listenersMap.get(event).push(wrappedListener);
1017
1018
  }
1018
1019
  /**
1019
1020
  * Registers a one-time listener for specific bridge events.
@@ -1025,15 +1026,25 @@ var BridgeInstance = class {
1025
1026
  if (!event) {
1026
1027
  throw new Error("Event type is required");
1027
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
+ }
1028
1037
  const wrappedListener = (data) => {
1029
1038
  if ("bridge" in data && data.bridge?.id === this.id) {
1030
1039
  listener(data);
1040
+ this.off(event, wrappedListener);
1031
1041
  }
1032
1042
  };
1033
1043
  this.eventEmitter.once(event, wrappedListener);
1034
- console.log(
1035
- `One-time listener registered for ${event} on bridge ${this.id}`
1036
- );
1044
+ if (!this.listenersMap.has(eventKey)) {
1045
+ this.listenersMap.set(eventKey, []);
1046
+ }
1047
+ this.listenersMap.get(eventKey).push(wrappedListener);
1037
1048
  }
1038
1049
  /**
1039
1050
  * Removes event listener(s) from the bridge.
@@ -1047,14 +1058,25 @@ var BridgeInstance = class {
1047
1058
  }
1048
1059
  if (listener) {
1049
1060
  this.eventEmitter.off(event, listener);
1050
- console.log(
1051
- `Specific listener removed for ${event} on bridge ${this.id}`
1061
+ const storedListeners = this.listenersMap.get(event) || [];
1062
+ this.listenersMap.set(
1063
+ event,
1064
+ storedListeners.filter((l) => l !== listener)
1052
1065
  );
1053
1066
  } else {
1054
1067
  this.eventEmitter.removeAllListeners(event);
1055
- console.log(`All listeners removed for ${event} on bridge ${this.id}`);
1068
+ this.listenersMap.delete(event);
1056
1069
  }
1057
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
+ }
1058
1080
  /**
1059
1081
  * Emits an event if it corresponds to the current bridge.
1060
1082
  *
@@ -1067,15 +1089,23 @@ var BridgeInstance = class {
1067
1089
  }
1068
1090
  if ("bridge" in event && event.bridge?.id === this.id) {
1069
1091
  this.eventEmitter.emit(event.type, event);
1070
- console.log(`Event ${event.type} emitted for bridge ${this.id}`);
1071
1092
  }
1072
1093
  }
1073
1094
  /**
1074
1095
  * Removes all event listeners from this bridge instance.
1075
1096
  */
1076
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();
1077
1108
  this.eventEmitter.removeAllListeners();
1078
- console.log(`All listeners removed from bridge ${this.id}`);
1079
1109
  }
1080
1110
  /**
1081
1111
  * Retrieves the current details of the bridge.
@@ -1091,7 +1121,6 @@ var BridgeInstance = class {
1091
1121
  this.bridgeData = await this.baseClient.get(
1092
1122
  `/bridges/${this.id}`
1093
1123
  );
1094
- console.log(`Details retrieved for bridge ${this.id}`);
1095
1124
  return this.bridgeData;
1096
1125
  } catch (error) {
1097
1126
  const message = getErrorMessage(error);
@@ -1114,7 +1143,6 @@ var BridgeInstance = class {
1114
1143
  await this.baseClient.post(
1115
1144
  `/bridges/${this.id}/addChannel?${queryParams}`
1116
1145
  );
1117
- console.log(`Channels added to bridge ${this.id}`);
1118
1146
  } catch (error) {
1119
1147
  const message = getErrorMessage(error);
1120
1148
  console.error(`Error adding channels to bridge ${this.id}:`, message);
@@ -1135,7 +1163,6 @@ var BridgeInstance = class {
1135
1163
  await this.baseClient.post(
1136
1164
  `/bridges/${this.id}/removeChannel?${queryParams}`
1137
1165
  );
1138
- console.log(`Channels removed from bridge ${this.id}`);
1139
1166
  } catch (error) {
1140
1167
  const message = getErrorMessage(error);
1141
1168
  console.error(`Error removing channels from bridge ${this.id}:`, message);
@@ -1161,7 +1188,6 @@ var BridgeInstance = class {
1161
1188
  `/bridges/${this.id}/play?${queryParams}`,
1162
1189
  { media: request.media }
1163
1190
  );
1164
- console.log(`Media playback started on bridge ${this.id}`);
1165
1191
  return result;
1166
1192
  } catch (error) {
1167
1193
  const message = getErrorMessage(error);
@@ -1180,7 +1206,6 @@ var BridgeInstance = class {
1180
1206
  await this.baseClient.delete(
1181
1207
  `/bridges/${this.id}/play/${playbackId}`
1182
1208
  );
1183
- console.log(`Playback ${playbackId} stopped on bridge ${this.id}`);
1184
1209
  } catch (error) {
1185
1210
  const message = getErrorMessage(error);
1186
1211
  console.error(`Error stopping playback on bridge ${this.id}:`, message);
@@ -1198,7 +1223,6 @@ var BridgeInstance = class {
1198
1223
  await this.baseClient.post(
1199
1224
  `/bridges/${this.id}/videoSource/${channelId}`
1200
1225
  );
1201
- console.log(`Video source set for bridge ${this.id}`);
1202
1226
  } catch (error) {
1203
1227
  const message = getErrorMessage(error);
1204
1228
  console.error(
@@ -1216,7 +1240,6 @@ var BridgeInstance = class {
1216
1240
  async clearVideoSource() {
1217
1241
  try {
1218
1242
  await this.baseClient.delete(`/bridges/${this.id}/videoSource`);
1219
- console.log(`Video source removed from bridge ${this.id}`);
1220
1243
  } catch (error) {
1221
1244
  const message = getErrorMessage(error);
1222
1245
  console.error(
@@ -1250,6 +1273,7 @@ var Bridges = class {
1250
1273
  this.client = client;
1251
1274
  }
1252
1275
  bridgeInstances = /* @__PURE__ */ new Map();
1276
+ eventQueue = /* @__PURE__ */ new Map();
1253
1277
  /**
1254
1278
  * Creates or retrieves a Bridge instance.
1255
1279
  *
@@ -1270,23 +1294,41 @@ var Bridges = class {
1270
1294
  if (!id) {
1271
1295
  const instance = new BridgeInstance(this.client, this.baseClient);
1272
1296
  this.bridgeInstances.set(instance.id, instance);
1273
- console.log(`New bridge instance created with ID: ${instance.id}`);
1274
1297
  return instance;
1275
1298
  }
1276
1299
  if (!this.bridgeInstances.has(id)) {
1277
1300
  const instance = new BridgeInstance(this.client, this.baseClient, id);
1278
1301
  this.bridgeInstances.set(id, instance);
1279
- console.log(`New bridge instance created with provided ID: ${id}`);
1280
1302
  return instance;
1281
1303
  }
1282
- console.log(`Returning existing bridge instance: ${id}`);
1283
1304
  return this.bridgeInstances.get(id);
1284
1305
  } catch (error) {
1285
1306
  const message = getErrorMessage(error);
1286
- console.error(`Error creating/retrieving bridge instance:`, message);
1307
+ console.warn(`Error creating/retrieving bridge instance:`, message);
1287
1308
  throw new Error(`Failed to manage bridge instance: ${message}`);
1288
1309
  }
1289
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
+ }
1290
1332
  /**
1291
1333
  * Removes a bridge instance from the collection of managed bridges.
1292
1334
  *
@@ -1299,15 +1341,20 @@ var Bridges = class {
1299
1341
  */
1300
1342
  removeBridgeInstance(bridgeId) {
1301
1343
  if (!bridgeId) {
1302
- throw new Error("ID da bridge \xE9 obrigat\xF3rio");
1303
- }
1304
- if (this.bridgeInstances.has(bridgeId)) {
1305
- const instance = this.bridgeInstances.get(bridgeId);
1306
- instance?.removeAllListeners();
1307
- this.bridgeInstances.delete(bridgeId);
1308
- console.log(`Inst\xE2ncia de bridge removida: ${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
+ }
1309
1356
  } else {
1310
- console.warn(`Tentativa de remover inst\xE2ncia inexistente: ${bridgeId}`);
1357
+ console.warn(`Attempt to remove non-existent instance: ${bridgeId}`);
1311
1358
  }
1312
1359
  }
1313
1360
  /**
@@ -1324,28 +1371,51 @@ var Bridges = class {
1324
1371
  *
1325
1372
  * @remarks
1326
1373
  * - If the event is invalid (null or undefined), a warning is logged and the function returns early.
1327
- * - 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.
1328
1375
  * - If a matching bridge instance is found, the event is emitted to that instance.
1329
1376
  * - If no matching bridge instance is found, a warning is logged.
1330
1377
  */
1331
1378
  propagateEventToBridge(event) {
1332
- if (!event) {
1333
- console.warn("Evento WebSocket inv\xE1lido recebido");
1379
+ if (!event || !("bridge" in event) || !event.bridge?.id) {
1380
+ console.warn("Invalid WebSocket event received");
1334
1381
  return;
1335
1382
  }
1336
- if ("bridge" in event && event.bridge?.id && bridgeEvents.includes(event.type)) {
1337
- const instance = this.bridgeInstances.get(event.bridge.id);
1338
- if (instance) {
1339
- instance.emitEvent(event);
1340
- console.log(
1341
- `Evento propagado para bridge ${event.bridge.id}: ${event.type}`
1342
- );
1343
- } else {
1344
- console.warn(
1345
- `Nenhuma inst\xE2ncia encontrada para bridge ${event.bridge.id}`
1346
- );
1347
- }
1348
- }
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();
1349
1419
  }
1350
1420
  /**
1351
1421
  * Lists all active bridges in the system.
@@ -1361,7 +1431,7 @@ var Bridges = class {
1361
1431
  * @example
1362
1432
  * try {
1363
1433
  * const bridges = await bridgesInstance.list();
1364
- * console.log('Active bridges:', bridges);
1434
+ *
1365
1435
  * } catch (error) {
1366
1436
  * console.error('Failed to fetch bridges:', error);
1367
1437
  * }
@@ -1606,7 +1676,7 @@ var Bridges = class {
1606
1676
  };
1607
1677
 
1608
1678
  // src/ari-client/resources/channels.ts
1609
- var import_events3 = require("events");
1679
+ var import_events2 = require("events");
1610
1680
  var import_axios3 = require("axios");
1611
1681
 
1612
1682
  // node_modules/uuid/dist/esm/stringify.js
@@ -1670,8 +1740,10 @@ var ChannelInstance = class {
1670
1740
  this.baseClient = baseClient;
1671
1741
  this.id = channelId || `channel-${Date.now()}`;
1672
1742
  }
1673
- eventEmitter = new import_events3.EventEmitter();
1743
+ eventEmitter = new import_events2.EventEmitter();
1674
1744
  channelData = null;
1745
+ listenersMap = /* @__PURE__ */ new Map();
1746
+ // 🔹 Guarda listeners para remoção posterior
1675
1747
  id;
1676
1748
  /**
1677
1749
  * Registers an event listener for specific channel events
@@ -1680,12 +1752,23 @@ var ChannelInstance = class {
1680
1752
  if (!event) {
1681
1753
  throw new Error("Event type is required");
1682
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
+ }
1683
1762
  const wrappedListener = (data) => {
1684
1763
  if ("channel" in data && data.channel?.id === this.id) {
1685
1764
  listener(data);
1686
1765
  }
1687
1766
  };
1688
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);
1689
1772
  }
1690
1773
  /**
1691
1774
  * Registers a one-time event listener
@@ -1694,12 +1777,25 @@ var ChannelInstance = class {
1694
1777
  if (!event) {
1695
1778
  throw new Error("Event type is required");
1696
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
+ }
1697
1788
  const wrappedListener = (data) => {
1698
1789
  if ("channel" in data && data.channel?.id === this.id) {
1699
1790
  listener(data);
1791
+ this.off(event, wrappedListener);
1700
1792
  }
1701
1793
  };
1702
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);
1703
1799
  }
1704
1800
  /**
1705
1801
  * Removes event listener(s) for a specific WebSocket event type.
@@ -1716,10 +1812,25 @@ var ChannelInstance = class {
1716
1812
  }
1717
1813
  if (listener) {
1718
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
+ );
1719
1820
  } else {
1720
1821
  this.eventEmitter.removeAllListeners(event);
1822
+ this.listenersMap.delete(event);
1721
1823
  }
1722
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
+ }
1723
1834
  /**
1724
1835
  * Emits an event if it matches the current channel
1725
1836
  */
@@ -1739,6 +1850,16 @@ var ChannelInstance = class {
1739
1850
  * @return {void} This method does not return a value.
1740
1851
  */
1741
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();
1742
1863
  this.eventEmitter.removeAllListeners();
1743
1864
  }
1744
1865
  /**
@@ -2027,6 +2148,7 @@ var Channels = class {
2027
2148
  this.client = client;
2028
2149
  }
2029
2150
  channelInstances = /* @__PURE__ */ new Map();
2151
+ eventQueue = /* @__PURE__ */ new Map();
2030
2152
  /**
2031
2153
  * Creates or retrieves a ChannelInstance.
2032
2154
  *
@@ -2062,6 +2184,32 @@ var Channels = class {
2062
2184
  throw new Error(`Failed to manage channel instance: ${message}`);
2063
2185
  }
2064
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
+ }
2065
2213
  /**
2066
2214
  * Retrieves the details of a specific channel.
2067
2215
  *
@@ -2088,10 +2236,16 @@ var Channels = class {
2088
2236
  if (!channelId) {
2089
2237
  throw new Error("Channel ID is required");
2090
2238
  }
2091
- if (this.channelInstances.has(channelId)) {
2092
- const instance = this.channelInstances.get(channelId);
2093
- instance?.removeAllListeners();
2094
- 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
+ }
2095
2249
  } else {
2096
2250
  console.warn(`Attempt to remove non-existent instance: ${channelId}`);
2097
2251
  }
@@ -2100,18 +2254,29 @@ var Channels = class {
2100
2254
  * Propagates a WebSocket event to a specific channel.
2101
2255
  */
2102
2256
  propagateEventToChannel(event) {
2103
- if (!event) {
2257
+ if (!event || !("channel" in event) || !event.channel?.id) {
2104
2258
  console.warn("Invalid WebSocket event received");
2105
2259
  return;
2106
2260
  }
2107
- if ("channel" in event && event.channel?.id) {
2108
- const instance = this.channelInstances.get(event.channel.id);
2109
- if (instance) {
2110
- instance.emitEvent(event);
2111
- } else {
2112
- console.warn(`No instance found for channel ${event.channel.id}`);
2113
- }
2114
- }
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
+ );
2115
2280
  }
2116
2281
  /**
2117
2282
  * Initiates a new channel.
@@ -2566,7 +2731,7 @@ var Endpoints = class {
2566
2731
  };
2567
2732
 
2568
2733
  // src/ari-client/resources/playbacks.ts
2569
- var import_events4 = require("events");
2734
+ var import_events3 = require("events");
2570
2735
  var import_axios4 = require("axios");
2571
2736
  var getErrorMessage3 = (error) => {
2572
2737
  if ((0, import_axios4.isAxiosError)(error)) {
@@ -2591,7 +2756,9 @@ var PlaybackInstance = class {
2591
2756
  this.playbackId = playbackId;
2592
2757
  this.id = playbackId;
2593
2758
  }
2594
- eventEmitter = new import_events4.EventEmitter();
2759
+ eventEmitter = new import_events3.EventEmitter();
2760
+ listenersMap = /* @__PURE__ */ new Map();
2761
+ // 🔹 Guarda listeners para remoção posterior
2595
2762
  playbackData = null;
2596
2763
  id;
2597
2764
  /**
@@ -2604,12 +2771,23 @@ var PlaybackInstance = class {
2604
2771
  if (!event) {
2605
2772
  throw new Error("Event type is required");
2606
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
+ }
2607
2781
  const wrappedListener = (data) => {
2608
2782
  if ("playback" in data && data.playback?.id === this.id) {
2609
2783
  listener(data);
2610
2784
  }
2611
2785
  };
2612
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);
2613
2791
  }
2614
2792
  /**
2615
2793
  * Registers a one-time event listener for a specific WebSocket event type.
@@ -2621,12 +2799,25 @@ var PlaybackInstance = class {
2621
2799
  if (!event) {
2622
2800
  throw new Error("Event type is required");
2623
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
+ }
2624
2810
  const wrappedListener = (data) => {
2625
2811
  if ("playback" in data && data.playback?.id === this.id) {
2626
2812
  listener(data);
2813
+ this.off(event, wrappedListener);
2627
2814
  }
2628
2815
  };
2629
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);
2630
2821
  }
2631
2822
  /**
2632
2823
  * Removes event listener(s) for a specific WebSocket event type.
@@ -2640,10 +2831,25 @@ var PlaybackInstance = class {
2640
2831
  }
2641
2832
  if (listener) {
2642
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
+ );
2643
2839
  } else {
2644
2840
  this.eventEmitter.removeAllListeners(event);
2841
+ this.listenersMap.delete(event);
2645
2842
  }
2646
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
+ }
2647
2853
  /**
2648
2854
  * Emits a WebSocket event if it matches the current playback instance.
2649
2855
  *
@@ -2717,9 +2923,29 @@ var PlaybackInstance = class {
2717
2923
  }
2718
2924
  }
2719
2925
  /**
2720
- * 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.
2721
2937
  */
2722
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();
2723
2949
  this.eventEmitter.removeAllListeners();
2724
2950
  }
2725
2951
  /**
@@ -2746,6 +2972,7 @@ var Playbacks = class {
2746
2972
  this.client = client;
2747
2973
  }
2748
2974
  playbackInstances = /* @__PURE__ */ new Map();
2975
+ eventQueue = /* @__PURE__ */ new Map();
2749
2976
  /**
2750
2977
  * Gets or creates a playback instance
2751
2978
  * @param {Object} [params] - Optional parameters for getting/creating a playback instance
@@ -2772,6 +2999,43 @@ var Playbacks = class {
2772
2999
  throw new Error(`Failed to manage playback instance: ${message}`);
2773
3000
  }
2774
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
+ }
2775
3039
  /**
2776
3040
  * Removes a playback instance and cleans up its resources
2777
3041
  * @param {string} playbackId - ID of the playback instance to remove
@@ -2781,10 +3045,16 @@ var Playbacks = class {
2781
3045
  if (!playbackId) {
2782
3046
  throw new Error("Playback ID is required");
2783
3047
  }
2784
- if (this.playbackInstances.has(playbackId)) {
2785
- const instance = this.playbackInstances.get(playbackId);
2786
- instance?.removeAllListeners();
2787
- 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
+ }
2788
3058
  } else {
2789
3059
  console.warn(`Attempt to remove non-existent instance: ${playbackId}`);
2790
3060
  }
@@ -2794,17 +3064,29 @@ var Playbacks = class {
2794
3064
  * @param {WebSocketEvent} event - The WebSocket event to propagate
2795
3065
  */
2796
3066
  propagateEventToPlayback(event) {
2797
- if (!event) {
3067
+ if (!event || !("playback" in event) || !event.playback?.id) {
3068
+ console.warn("Invalid WebSocket event received");
2798
3069
  return;
2799
3070
  }
2800
- if ("playback" in event && event.playback?.id) {
2801
- const instance = this.playbackInstances.get(event.playback.id);
2802
- if (instance) {
2803
- instance.emitEvent(event);
2804
- } else {
2805
- console.warn(`No instance found for playback ${event.playback.id}`);
2806
- }
2807
- }
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
+ );
2808
3090
  }
2809
3091
  /**
2810
3092
  * Retrieves details of a specific playback
@@ -2910,13 +3192,13 @@ var Sounds = class {
2910
3192
  };
2911
3193
 
2912
3194
  // src/ari-client/websocketClient.ts
2913
- var import_events5 = require("events");
3195
+ var import_events4 = require("events");
2914
3196
  var import_exponential_backoff = __toESM(require_backoff(), 1);
2915
3197
  var import_ws = __toESM(require("ws"), 1);
2916
3198
  var DEFAULT_MAX_RECONNECT_ATTEMPTS = 30;
2917
3199
  var DEFAULT_STARTING_DELAY = 500;
2918
3200
  var DEFAULT_MAX_DELAY = 1e4;
2919
- var WebSocketClient = class extends import_events5.EventEmitter {
3201
+ var WebSocketClient = class extends import_events4.EventEmitter {
2920
3202
  /**
2921
3203
  * Creates a new WebSocketClient instance.
2922
3204
  *
@@ -2942,9 +3224,57 @@ var WebSocketClient = class extends import_events5.EventEmitter {
2942
3224
  }
2943
3225
  ws;
2944
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
2945
3231
  maxReconnectAttempts = DEFAULT_MAX_RECONNECT_ATTEMPTS;
2946
3232
  reconnectionAttempts = 0;
2947
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
+ }
2948
3278
  backOffOptions = {
2949
3279
  numOfAttempts: DEFAULT_MAX_RECONNECT_ATTEMPTS,
2950
3280
  startingDelay: DEFAULT_STARTING_DELAY,
@@ -2971,21 +3301,29 @@ var WebSocketClient = class extends import_events5.EventEmitter {
2971
3301
  * @throws Will throw an error if the connection cannot be established.
2972
3302
  */
2973
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;
2974
3312
  const { baseUrl, username, password } = this.baseClient.getCredentials();
2975
3313
  const protocol = baseUrl.startsWith("https") ? "wss" : "ws";
2976
3314
  const normalizedHost = baseUrl.replace(/^https?:\/\//, "").replace(/\/ari$/, "");
2977
3315
  const queryParams = new URLSearchParams();
2978
3316
  queryParams.append("app", this.apps.join(","));
2979
- if (this.subscribedEvents?.length) {
2980
- this.subscribedEvents.forEach(
2981
- (event) => queryParams.append("event", event)
2982
- );
2983
- } else {
2984
- queryParams.append("subscribeAll", "true");
2985
- }
3317
+ this.subscribedEvents?.forEach(
3318
+ (event) => queryParams.append("event", event)
3319
+ );
2986
3320
  this.lastWsUrl = `${protocol}://${encodeURIComponent(username)}:${encodeURIComponent(password)}@${normalizedHost}/ari/events?${queryParams.toString()}`;
2987
3321
  console.log("Connecting to WebSocket...");
2988
- return this.initializeWebSocket(this.lastWsUrl);
3322
+ try {
3323
+ await this.initializeWebSocket(this.lastWsUrl);
3324
+ } finally {
3325
+ this.isConnecting = false;
3326
+ }
2989
3327
  }
2990
3328
  /**
2991
3329
  * Initializes a WebSocket connection with exponential backoff retry mechanism.
@@ -3007,8 +3345,9 @@ var WebSocketClient = class extends import_events5.EventEmitter {
3007
3345
  return new Promise((resolve, reject) => {
3008
3346
  try {
3009
3347
  this.ws = new import_ws.default(wsUrl);
3010
- this.ws.on("open", () => {
3348
+ this.ws.once("open", () => {
3011
3349
  console.log("WebSocket connection established successfully");
3350
+ this.setupHeartbeat();
3012
3351
  if (this.isReconnecting) {
3013
3352
  this.emit("reconnected", {
3014
3353
  apps: this.apps,
@@ -3021,7 +3360,7 @@ var WebSocketClient = class extends import_events5.EventEmitter {
3021
3360
  resolve();
3022
3361
  });
3023
3362
  this.ws.on("message", (data) => this.handleMessage(data.toString()));
3024
- this.ws.on("close", (code) => {
3363
+ this.ws.once("close", (code) => {
3025
3364
  console.warn(
3026
3365
  `WebSocket disconnected with code ${code}. Attempting to reconnect...`
3027
3366
  );
@@ -3029,7 +3368,7 @@ var WebSocketClient = class extends import_events5.EventEmitter {
3029
3368
  this.reconnect(this.lastWsUrl);
3030
3369
  }
3031
3370
  });
3032
- this.ws.on("error", (err) => {
3371
+ this.ws.once("error", (err) => {
3033
3372
  console.error("WebSocket error:", err.message);
3034
3373
  if (!this.isReconnecting) {
3035
3374
  this.reconnect(this.lastWsUrl);
@@ -3042,6 +3381,34 @@ var WebSocketClient = class extends import_events5.EventEmitter {
3042
3381
  });
3043
3382
  }, this.backOffOptions);
3044
3383
  }
3384
+ getEventKey(event) {
3385
+ const ids = [];
3386
+ if ("channel" in event && event.channel?.id) ids.push(event.channel.id);
3387
+ if ("playback" in event && event.playback?.id) ids.push(event.playback.id);
3388
+ if ("bridge" in event && event.bridge?.id) ids.push(event.bridge.id);
3389
+ return `${event.type}-${ids.join("-")}`;
3390
+ }
3391
+ processEvent(event) {
3392
+ if (this.subscribedEvents?.length && !this.subscribedEvents.includes(event.type)) {
3393
+ return;
3394
+ }
3395
+ if ("channel" in event && event.channel?.id && this.ariClient) {
3396
+ const instanceChannel = this.ariClient.Channel(event.channel.id);
3397
+ instanceChannel.emitEvent(event);
3398
+ event.instanceChannel = instanceChannel;
3399
+ }
3400
+ if ("playback" in event && event.playback?.id && this.ariClient) {
3401
+ const instancePlayback = this.ariClient.Playback(event.playback.id);
3402
+ instancePlayback.emitEvent(event);
3403
+ event.instancePlayback = instancePlayback;
3404
+ }
3405
+ if ("bridge" in event && event.bridge?.id && this.ariClient) {
3406
+ const instanceBridge = this.ariClient.Bridge(event.bridge.id);
3407
+ instanceBridge.emitEvent(event);
3408
+ event.instanceBridge = instanceBridge;
3409
+ }
3410
+ this.emit(event.type, event);
3411
+ }
3045
3412
  /**
3046
3413
  * Handles incoming WebSocket messages by parsing and processing events.
3047
3414
  *
@@ -3057,30 +3424,20 @@ var WebSocketClient = class extends import_events5.EventEmitter {
3057
3424
  handleMessage(rawMessage) {
3058
3425
  try {
3059
3426
  const event = JSON.parse(rawMessage);
3060
- if (this.subscribedEvents?.length && !this.subscribedEvents.includes(event.type)) {
3061
- return;
3062
- }
3063
- if ("channel" in event && event.channel?.id && this.ariClient) {
3064
- const instanceChannel = this.ariClient.Channel(event.channel.id);
3065
- instanceChannel.emitEvent(event);
3066
- event.instanceChannel = instanceChannel;
3067
- }
3068
- if ("playback" in event && event.playback?.id && this.ariClient) {
3069
- const instancePlayback = this.ariClient.Playback(event.playback.id);
3070
- instancePlayback.emitEvent(event);
3071
- event.instancePlayback = instancePlayback;
3072
- }
3073
- if ("bridge" in event && event.bridge?.id && this.ariClient) {
3074
- const instanceBridge = this.ariClient.Bridge(event.bridge.id);
3075
- instanceBridge.emitEvent(event);
3076
- event.instanceBridge = instanceBridge;
3427
+ const key = this.getEventKey(event);
3428
+ const existing = this.eventQueue.get(key);
3429
+ if (existing) {
3430
+ clearTimeout(existing);
3077
3431
  }
3078
- this.emit(event.type, event);
3079
- } catch (error) {
3080
- console.error(
3081
- "Error processing WebSocket message:",
3082
- error instanceof Error ? error.message : "Unknown error"
3432
+ this.eventQueue.set(
3433
+ key,
3434
+ setTimeout(() => {
3435
+ this.processEvent(event);
3436
+ this.eventQueue.delete(key);
3437
+ }, 100)
3083
3438
  );
3439
+ } catch (error) {
3440
+ console.error("Error processing WebSocket message:", error);
3084
3441
  this.emit("error", new Error("Failed to decode WebSocket message"));
3085
3442
  }
3086
3443
  }
@@ -3096,21 +3453,26 @@ var WebSocketClient = class extends import_events5.EventEmitter {
3096
3453
  *
3097
3454
  * @emits reconnectFailed - Emitted if all reconnection attempts fail.
3098
3455
  */
3099
- reconnect(wsUrl) {
3456
+ async reconnect(wsUrl) {
3457
+ if (!this.shouldReconnect) {
3458
+ console.warn(
3459
+ "Reconnection skipped because WebSocket was intentionally closed."
3460
+ );
3461
+ return;
3462
+ }
3463
+ if (this.isReconnecting) {
3464
+ console.warn("J\xE1 h\xE1 uma tentativa de reconex\xE3o em andamento.");
3465
+ return;
3466
+ }
3100
3467
  this.isReconnecting = true;
3101
3468
  this.reconnectionAttempts++;
3102
- console.log(
3103
- `Initiating reconnection attempt #${this.reconnectionAttempts}...`
3104
- );
3105
- (0, import_exponential_backoff.backOff)(() => this.initializeWebSocket(wsUrl), this.backOffOptions).catch(
3106
- (error) => {
3107
- console.error(
3108
- `Failed to reconnect after ${this.reconnectionAttempts} attempts:`,
3109
- error instanceof Error ? error.message : "Unknown error"
3110
- );
3111
- this.emit("reconnectFailed", error);
3112
- }
3113
- );
3469
+ console.log(`Tentando reconex\xE3o #${this.reconnectionAttempts}...`);
3470
+ (0, import_exponential_backoff.backOff)(() => this.initializeWebSocket(wsUrl), this.backOffOptions).catch((error) => {
3471
+ console.error(`Falha ao reconectar: ${error.message}`);
3472
+ this.emit("reconnectFailed", error);
3473
+ }).finally(() => {
3474
+ this.isReconnecting = false;
3475
+ });
3114
3476
  }
3115
3477
  /**
3116
3478
  * Closes the WebSocket connection if it exists.
@@ -3121,18 +3483,34 @@ var WebSocketClient = class extends import_events5.EventEmitter {
3121
3483
  *
3122
3484
  * @throws {Error} Logs an error message if closing the WebSocket fails.
3123
3485
  */
3124
- close() {
3486
+ async close() {
3487
+ if (!this.ws) {
3488
+ console.warn("No WebSocket connection to close");
3489
+ return;
3490
+ }
3491
+ console.log("Closing WebSocket connection.");
3492
+ this.shouldReconnect = false;
3493
+ this.eventQueue.forEach((timeout) => clearTimeout(timeout));
3494
+ this.eventQueue.clear();
3495
+ const closeTimeout = setTimeout(() => {
3496
+ if (this.ws && this.ws.readyState !== import_ws.default.CLOSED) {
3497
+ this.ws.terminate();
3498
+ }
3499
+ }, 5e3);
3125
3500
  try {
3126
- if (this.ws) {
3501
+ this.ws.removeAllListeners();
3502
+ await new Promise((resolve) => {
3503
+ this.ws.once("close", () => {
3504
+ clearTimeout(closeTimeout);
3505
+ resolve();
3506
+ });
3127
3507
  this.ws.close();
3128
- this.ws = void 0;
3129
- console.log("WebSocket connection closed");
3130
- }
3508
+ });
3131
3509
  } catch (error) {
3132
- console.error(
3133
- "Error closing WebSocket:",
3134
- error instanceof Error ? error.message : "Unknown error"
3135
- );
3510
+ console.error("Error closing WebSocket:", error);
3511
+ } finally {
3512
+ this.ws = void 0;
3513
+ this.emit("disconnected");
3136
3514
  }
3137
3515
  }
3138
3516
  /**
@@ -3164,6 +3542,30 @@ var WebSocketClient = class extends import_events5.EventEmitter {
3164
3542
  getState() {
3165
3543
  return this.ws?.readyState ?? import_ws.default.CLOSED;
3166
3544
  }
3545
+ /**
3546
+ * Cleans up the WebSocketClient instance, resetting its state and clearing resources.
3547
+ *
3548
+ * This method performs the following cleanup operations:
3549
+ * - Clears the event queue and cancels any pending timeouts.
3550
+ * - Stops any ongoing reconnection attempts.
3551
+ * - Clears the stored WebSocket URL.
3552
+ * - Resets the reconnection attempt counter.
3553
+ * - Removes all event listeners attached to this instance.
3554
+ *
3555
+ * This method is typically called when the WebSocketClient is no longer needed or
3556
+ * before reinitializing the client to ensure a clean slate.
3557
+ *
3558
+ * @returns {void} This method doesn't return a value.
3559
+ */
3560
+ cleanup() {
3561
+ this.eventQueue.forEach((timeout) => clearTimeout(timeout));
3562
+ this.eventQueue.clear();
3563
+ this.shouldReconnect = false;
3564
+ this.isReconnecting = false;
3565
+ this.lastWsUrl = "";
3566
+ this.reconnectionAttempts = 0;
3567
+ this.removeAllListeners();
3568
+ }
3167
3569
  };
3168
3570
 
3169
3571
  // src/ari-client/ariClient.ts
@@ -3194,6 +3596,8 @@ var AriClient = class {
3194
3596
  }
3195
3597
  baseClient;
3196
3598
  webSocketClient;
3599
+ eventListeners = /* @__PURE__ */ new Map();
3600
+ // Armazena os listeners para limpeza
3197
3601
  channels;
3198
3602
  endpoints;
3199
3603
  applications;
@@ -3201,6 +3605,50 @@ var AriClient = class {
3201
3605
  sounds;
3202
3606
  asterisk;
3203
3607
  bridges;
3608
+ async cleanup() {
3609
+ try {
3610
+ console.log("Starting ARI Client cleanup...");
3611
+ if (this.webSocketClient) {
3612
+ await this.closeWebSocket();
3613
+ }
3614
+ await Promise.all([
3615
+ // Cleanup de channels
3616
+ (async () => {
3617
+ try {
3618
+ this.channels.cleanup();
3619
+ } catch (error) {
3620
+ console.error("Error cleaning up channels:", error);
3621
+ }
3622
+ })(),
3623
+ // Cleanup de playbacks
3624
+ (async () => {
3625
+ try {
3626
+ this.playbacks.cleanup();
3627
+ } catch (error) {
3628
+ console.error("Error cleaning up playbacks:", error);
3629
+ }
3630
+ })(),
3631
+ // Cleanup de bridges
3632
+ (async () => {
3633
+ try {
3634
+ this.bridges.cleanup();
3635
+ } catch (error) {
3636
+ console.error("Error cleaning up bridges:", error);
3637
+ }
3638
+ })()
3639
+ ]);
3640
+ this.eventListeners.forEach((listeners, event) => {
3641
+ listeners.forEach((listener) => {
3642
+ this.off(event, listener);
3643
+ });
3644
+ });
3645
+ this.eventListeners.clear();
3646
+ console.log("ARI Client cleanup completed successfully");
3647
+ } catch (error) {
3648
+ console.error("Error during ARI Client cleanup:", error);
3649
+ throw error;
3650
+ }
3651
+ }
3204
3652
  /**
3205
3653
  * Initializes a WebSocket connection for receiving events.
3206
3654
  *
@@ -3210,14 +3658,14 @@ var AriClient = class {
3210
3658
  * @throws {Error} If connection fails or if WebSocket is already connected
3211
3659
  */
3212
3660
  async connectWebSocket(apps, subscribedEvents) {
3213
- if (!apps.length) {
3214
- throw new Error("At least one application name is required");
3215
- }
3216
- if (this.webSocketClient) {
3217
- console.warn("WebSocket is already connected");
3218
- return;
3219
- }
3220
3661
  try {
3662
+ if (!apps.length) {
3663
+ throw new Error("At least one application name is required.");
3664
+ }
3665
+ if (this.webSocketClient) {
3666
+ await this.closeWebSocket();
3667
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
3668
+ }
3221
3669
  this.webSocketClient = new WebSocketClient(
3222
3670
  this.baseClient,
3223
3671
  apps,
@@ -3225,13 +3673,38 @@ var AriClient = class {
3225
3673
  this
3226
3674
  );
3227
3675
  await this.webSocketClient.connect();
3228
- console.log("WebSocket connection established successfully");
3676
+ console.log("WebSocket connection established successfully.");
3229
3677
  } catch (error) {
3230
3678
  console.error("Failed to establish WebSocket connection:", error);
3231
3679
  this.webSocketClient = void 0;
3232
3680
  throw error;
3233
3681
  }
3234
3682
  }
3683
+ /**
3684
+ * Destroys the ARI Client instance, cleaning up all resources and removing circular references.
3685
+ * This method should be called when the ARI Client is no longer needed to ensure proper cleanup.
3686
+ *
3687
+ * @returns {Promise<void>} A promise that resolves when the destruction process is complete.
3688
+ * @throws {Error} If an error occurs during the destruction process.
3689
+ */
3690
+ async destroy() {
3691
+ try {
3692
+ console.log("Destroying ARI Client...");
3693
+ await this.cleanup();
3694
+ this.webSocketClient = void 0;
3695
+ this.channels = null;
3696
+ this.playbacks = null;
3697
+ this.bridges = null;
3698
+ this.endpoints = null;
3699
+ this.applications = null;
3700
+ this.sounds = null;
3701
+ this.asterisk = null;
3702
+ console.log("ARI Client destroyed successfully");
3703
+ } catch (error) {
3704
+ console.error("Error destroying ARI Client:", error);
3705
+ throw error;
3706
+ }
3707
+ }
3235
3708
  /**
3236
3709
  * Registers an event listener for WebSocket events.
3237
3710
  *
@@ -3243,8 +3716,15 @@ var AriClient = class {
3243
3716
  if (!this.webSocketClient) {
3244
3717
  throw new Error("WebSocket is not connected");
3245
3718
  }
3719
+ const existingListeners = this.eventListeners.get(event) || [];
3720
+ if (existingListeners.includes(listener)) {
3721
+ console.warn(`Listener already registered for event ${event}, reusing.`);
3722
+ return;
3723
+ }
3246
3724
  this.webSocketClient.on(event, listener);
3247
- console.log(`Event listener registered for ${event}`);
3725
+ existingListeners.push(listener);
3726
+ this.eventListeners.set(event, existingListeners);
3727
+ console.log(`Event listener successfully registered for ${event}`);
3248
3728
  }
3249
3729
  /**
3250
3730
  * Registers a one-time event listener for WebSocket events.
@@ -3257,7 +3737,19 @@ var AriClient = class {
3257
3737
  if (!this.webSocketClient) {
3258
3738
  throw new Error("WebSocket is not connected");
3259
3739
  }
3260
- this.webSocketClient.once(event, listener);
3740
+ const existingListeners = this.eventListeners.get(event) || [];
3741
+ if (existingListeners.includes(listener)) {
3742
+ console.warn(
3743
+ `One-time listener already registered for event ${event}, reusing.`
3744
+ );
3745
+ return;
3746
+ }
3747
+ const wrappedListener = (data) => {
3748
+ listener(data);
3749
+ this.off(event, wrappedListener);
3750
+ };
3751
+ this.webSocketClient.once(event, wrappedListener);
3752
+ this.eventListeners.set(event, [...existingListeners, wrappedListener]);
3261
3753
  console.log(`One-time event listener registered for ${event}`);
3262
3754
  }
3263
3755
  /**
@@ -3272,19 +3764,57 @@ var AriClient = class {
3272
3764
  return;
3273
3765
  }
3274
3766
  this.webSocketClient.off(event, listener);
3767
+ const existingListeners = this.eventListeners.get(event) || [];
3768
+ this.eventListeners.set(
3769
+ event,
3770
+ existingListeners.filter((l) => l !== listener)
3771
+ );
3275
3772
  console.log(`Event listener removed for ${event}`);
3276
3773
  }
3277
3774
  /**
3278
3775
  * Closes the WebSocket connection if one exists.
3279
3776
  */
3280
3777
  closeWebSocket() {
3281
- if (!this.webSocketClient) {
3282
- console.warn("No WebSocket connection to close");
3283
- return;
3284
- }
3285
- this.webSocketClient.close();
3286
- this.webSocketClient = void 0;
3287
- console.log("WebSocket connection closed");
3778
+ return new Promise((resolve) => {
3779
+ if (!this.webSocketClient) {
3780
+ console.warn("No WebSocket connection to close");
3781
+ resolve();
3782
+ return;
3783
+ }
3784
+ console.log("Closing WebSocket connection and cleaning up listeners.");
3785
+ const closeTimeout = setTimeout(() => {
3786
+ if (this.webSocketClient) {
3787
+ this.webSocketClient.removeAllListeners();
3788
+ this.webSocketClient = void 0;
3789
+ }
3790
+ resolve();
3791
+ }, 5e3);
3792
+ this.eventListeners.forEach((listeners, event) => {
3793
+ listeners.forEach((listener) => {
3794
+ this.webSocketClient?.off(
3795
+ event,
3796
+ listener
3797
+ );
3798
+ });
3799
+ });
3800
+ this.eventListeners.clear();
3801
+ this.webSocketClient.once("close", () => {
3802
+ clearTimeout(closeTimeout);
3803
+ this.webSocketClient = void 0;
3804
+ console.log("WebSocket connection closed");
3805
+ resolve();
3806
+ });
3807
+ this.webSocketClient.close().then(() => {
3808
+ clearTimeout(closeTimeout);
3809
+ this.webSocketClient = void 0;
3810
+ resolve();
3811
+ }).catch((error) => {
3812
+ console.error("Error during WebSocket close:", error);
3813
+ clearTimeout(closeTimeout);
3814
+ this.webSocketClient = void 0;
3815
+ resolve();
3816
+ });
3817
+ });
3288
3818
  }
3289
3819
  /**
3290
3820
  * Creates or retrieves a Channel instance.
@@ -3325,7 +3855,7 @@ var AriClient = class {
3325
3855
  * @returns {boolean} True if WebSocket is connected, false otherwise
3326
3856
  */
3327
3857
  isWebSocketConnected() {
3328
- return !!this.webSocketClient;
3858
+ return !!this.webSocketClient && this.webSocketClient.isConnected();
3329
3859
  }
3330
3860
  };
3331
3861
  // Annotate the CommonJS export names for ESM import in node: