@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.
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(
@@ -954,9 +944,10 @@ var BridgeInstance = class {
954
944
  this.client = client;
955
945
  this.baseClient = baseClient;
956
946
  this.id = bridgeId || `bridge-${Date.now()}`;
957
- console.log(`BridgeInstance inicializada com ID: ${this.id}`);
958
947
  }
959
948
  eventEmitter = new EventEmitter();
949
+ listenersMap = /* @__PURE__ */ new Map();
950
+ // 🔹 Guarda listeners para remoção posterior
960
951
  bridgeData = null;
961
952
  id;
962
953
  /**
@@ -977,7 +968,7 @@ var BridgeInstance = class {
977
968
  *
978
969
  * @example
979
970
  * bridge.on('BridgeCreated', (event) => {
980
- * console.log('Bridge created:', event.bridge.id);
971
+ *
981
972
  * });
982
973
  * @param event
983
974
  * @param listener
@@ -986,13 +977,23 @@ var BridgeInstance = class {
986
977
  if (!event) {
987
978
  throw new Error("Event type is required");
988
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
+ }
989
987
  const wrappedListener = (data) => {
990
988
  if ("bridge" in data && data.bridge?.id === this.id) {
991
989
  listener(data);
992
990
  }
993
991
  };
994
992
  this.eventEmitter.on(event, wrappedListener);
995
- console.log(`Event listener registered for ${event} on bridge ${this.id}`);
993
+ if (!this.listenersMap.has(event)) {
994
+ this.listenersMap.set(event, []);
995
+ }
996
+ this.listenersMap.get(event).push(wrappedListener);
996
997
  }
997
998
  /**
998
999
  * Registers a one-time listener for specific bridge events.
@@ -1004,15 +1005,25 @@ var BridgeInstance = class {
1004
1005
  if (!event) {
1005
1006
  throw new Error("Event type is required");
1006
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
+ }
1007
1016
  const wrappedListener = (data) => {
1008
1017
  if ("bridge" in data && data.bridge?.id === this.id) {
1009
1018
  listener(data);
1019
+ this.off(event, wrappedListener);
1010
1020
  }
1011
1021
  };
1012
1022
  this.eventEmitter.once(event, wrappedListener);
1013
- console.log(
1014
- `One-time listener registered for ${event} on bridge ${this.id}`
1015
- );
1023
+ if (!this.listenersMap.has(eventKey)) {
1024
+ this.listenersMap.set(eventKey, []);
1025
+ }
1026
+ this.listenersMap.get(eventKey).push(wrappedListener);
1016
1027
  }
1017
1028
  /**
1018
1029
  * Removes event listener(s) from the bridge.
@@ -1026,14 +1037,25 @@ var BridgeInstance = class {
1026
1037
  }
1027
1038
  if (listener) {
1028
1039
  this.eventEmitter.off(event, listener);
1029
- console.log(
1030
- `Specific listener removed for ${event} on bridge ${this.id}`
1040
+ const storedListeners = this.listenersMap.get(event) || [];
1041
+ this.listenersMap.set(
1042
+ event,
1043
+ storedListeners.filter((l) => l !== listener)
1031
1044
  );
1032
1045
  } else {
1033
1046
  this.eventEmitter.removeAllListeners(event);
1034
- console.log(`All listeners removed for ${event} on bridge ${this.id}`);
1047
+ this.listenersMap.delete(event);
1035
1048
  }
1036
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
+ }
1037
1059
  /**
1038
1060
  * Emits an event if it corresponds to the current bridge.
1039
1061
  *
@@ -1046,15 +1068,23 @@ var BridgeInstance = class {
1046
1068
  }
1047
1069
  if ("bridge" in event && event.bridge?.id === this.id) {
1048
1070
  this.eventEmitter.emit(event.type, event);
1049
- console.log(`Event ${event.type} emitted for bridge ${this.id}`);
1050
1071
  }
1051
1072
  }
1052
1073
  /**
1053
1074
  * Removes all event listeners from this bridge instance.
1054
1075
  */
1055
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();
1056
1087
  this.eventEmitter.removeAllListeners();
1057
- console.log(`All listeners removed from bridge ${this.id}`);
1058
1088
  }
1059
1089
  /**
1060
1090
  * Retrieves the current details of the bridge.
@@ -1070,7 +1100,6 @@ var BridgeInstance = class {
1070
1100
  this.bridgeData = await this.baseClient.get(
1071
1101
  `/bridges/${this.id}`
1072
1102
  );
1073
- console.log(`Details retrieved for bridge ${this.id}`);
1074
1103
  return this.bridgeData;
1075
1104
  } catch (error) {
1076
1105
  const message = getErrorMessage(error);
@@ -1093,7 +1122,6 @@ var BridgeInstance = class {
1093
1122
  await this.baseClient.post(
1094
1123
  `/bridges/${this.id}/addChannel?${queryParams}`
1095
1124
  );
1096
- console.log(`Channels added to bridge ${this.id}`);
1097
1125
  } catch (error) {
1098
1126
  const message = getErrorMessage(error);
1099
1127
  console.error(`Error adding channels to bridge ${this.id}:`, message);
@@ -1114,7 +1142,6 @@ var BridgeInstance = class {
1114
1142
  await this.baseClient.post(
1115
1143
  `/bridges/${this.id}/removeChannel?${queryParams}`
1116
1144
  );
1117
- console.log(`Channels removed from bridge ${this.id}`);
1118
1145
  } catch (error) {
1119
1146
  const message = getErrorMessage(error);
1120
1147
  console.error(`Error removing channels from bridge ${this.id}:`, message);
@@ -1140,7 +1167,6 @@ var BridgeInstance = class {
1140
1167
  `/bridges/${this.id}/play?${queryParams}`,
1141
1168
  { media: request.media }
1142
1169
  );
1143
- console.log(`Media playback started on bridge ${this.id}`);
1144
1170
  return result;
1145
1171
  } catch (error) {
1146
1172
  const message = getErrorMessage(error);
@@ -1159,7 +1185,6 @@ var BridgeInstance = class {
1159
1185
  await this.baseClient.delete(
1160
1186
  `/bridges/${this.id}/play/${playbackId}`
1161
1187
  );
1162
- console.log(`Playback ${playbackId} stopped on bridge ${this.id}`);
1163
1188
  } catch (error) {
1164
1189
  const message = getErrorMessage(error);
1165
1190
  console.error(`Error stopping playback on bridge ${this.id}:`, message);
@@ -1177,7 +1202,6 @@ var BridgeInstance = class {
1177
1202
  await this.baseClient.post(
1178
1203
  `/bridges/${this.id}/videoSource/${channelId}`
1179
1204
  );
1180
- console.log(`Video source set for bridge ${this.id}`);
1181
1205
  } catch (error) {
1182
1206
  const message = getErrorMessage(error);
1183
1207
  console.error(
@@ -1195,7 +1219,6 @@ var BridgeInstance = class {
1195
1219
  async clearVideoSource() {
1196
1220
  try {
1197
1221
  await this.baseClient.delete(`/bridges/${this.id}/videoSource`);
1198
- console.log(`Video source removed from bridge ${this.id}`);
1199
1222
  } catch (error) {
1200
1223
  const message = getErrorMessage(error);
1201
1224
  console.error(
@@ -1229,6 +1252,7 @@ var Bridges = class {
1229
1252
  this.client = client;
1230
1253
  }
1231
1254
  bridgeInstances = /* @__PURE__ */ new Map();
1255
+ eventQueue = /* @__PURE__ */ new Map();
1232
1256
  /**
1233
1257
  * Creates or retrieves a Bridge instance.
1234
1258
  *
@@ -1249,23 +1273,41 @@ var Bridges = class {
1249
1273
  if (!id) {
1250
1274
  const instance = new BridgeInstance(this.client, this.baseClient);
1251
1275
  this.bridgeInstances.set(instance.id, instance);
1252
- console.log(`New bridge instance created with ID: ${instance.id}`);
1253
1276
  return instance;
1254
1277
  }
1255
1278
  if (!this.bridgeInstances.has(id)) {
1256
1279
  const instance = new BridgeInstance(this.client, this.baseClient, id);
1257
1280
  this.bridgeInstances.set(id, instance);
1258
- console.log(`New bridge instance created with provided ID: ${id}`);
1259
1281
  return instance;
1260
1282
  }
1261
- console.log(`Returning existing bridge instance: ${id}`);
1262
1283
  return this.bridgeInstances.get(id);
1263
1284
  } catch (error) {
1264
1285
  const message = getErrorMessage(error);
1265
- console.error(`Error creating/retrieving bridge instance:`, message);
1286
+ console.warn(`Error creating/retrieving bridge instance:`, message);
1266
1287
  throw new Error(`Failed to manage bridge instance: ${message}`);
1267
1288
  }
1268
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
+ }
1269
1311
  /**
1270
1312
  * Removes a bridge instance from the collection of managed bridges.
1271
1313
  *
@@ -1278,15 +1320,20 @@ var Bridges = class {
1278
1320
  */
1279
1321
  removeBridgeInstance(bridgeId) {
1280
1322
  if (!bridgeId) {
1281
- throw new Error("ID da bridge \xE9 obrigat\xF3rio");
1282
- }
1283
- if (this.bridgeInstances.has(bridgeId)) {
1284
- const instance = this.bridgeInstances.get(bridgeId);
1285
- instance?.removeAllListeners();
1286
- this.bridgeInstances.delete(bridgeId);
1287
- console.log(`Inst\xE2ncia de bridge removida: ${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
+ }
1288
1335
  } else {
1289
- console.warn(`Tentativa de remover inst\xE2ncia inexistente: ${bridgeId}`);
1336
+ console.warn(`Attempt to remove non-existent instance: ${bridgeId}`);
1290
1337
  }
1291
1338
  }
1292
1339
  /**
@@ -1303,28 +1350,51 @@ var Bridges = class {
1303
1350
  *
1304
1351
  * @remarks
1305
1352
  * - If the event is invalid (null or undefined), a warning is logged and the function returns early.
1306
- * - 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.
1307
1354
  * - If a matching bridge instance is found, the event is emitted to that instance.
1308
1355
  * - If no matching bridge instance is found, a warning is logged.
1309
1356
  */
1310
1357
  propagateEventToBridge(event) {
1311
- if (!event) {
1312
- console.warn("Evento WebSocket inv\xE1lido recebido");
1358
+ if (!event || !("bridge" in event) || !event.bridge?.id) {
1359
+ console.warn("Invalid WebSocket event received");
1313
1360
  return;
1314
1361
  }
1315
- if ("bridge" in event && event.bridge?.id && bridgeEvents.includes(event.type)) {
1316
- const instance = this.bridgeInstances.get(event.bridge.id);
1317
- if (instance) {
1318
- instance.emitEvent(event);
1319
- console.log(
1320
- `Evento propagado para bridge ${event.bridge.id}: ${event.type}`
1321
- );
1322
- } else {
1323
- console.warn(
1324
- `Nenhuma inst\xE2ncia encontrada para bridge ${event.bridge.id}`
1325
- );
1326
- }
1327
- }
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();
1328
1398
  }
1329
1399
  /**
1330
1400
  * Lists all active bridges in the system.
@@ -1340,7 +1410,7 @@ var Bridges = class {
1340
1410
  * @example
1341
1411
  * try {
1342
1412
  * const bridges = await bridgesInstance.list();
1343
- * console.log('Active bridges:', bridges);
1413
+ *
1344
1414
  * } catch (error) {
1345
1415
  * console.error('Failed to fetch bridges:', error);
1346
1416
  * }
@@ -1651,6 +1721,8 @@ var ChannelInstance = class {
1651
1721
  }
1652
1722
  eventEmitter = new EventEmitter2();
1653
1723
  channelData = null;
1724
+ listenersMap = /* @__PURE__ */ new Map();
1725
+ // 🔹 Guarda listeners para remoção posterior
1654
1726
  id;
1655
1727
  /**
1656
1728
  * Registers an event listener for specific channel events
@@ -1659,12 +1731,23 @@ var ChannelInstance = class {
1659
1731
  if (!event) {
1660
1732
  throw new Error("Event type is required");
1661
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
+ }
1662
1741
  const wrappedListener = (data) => {
1663
1742
  if ("channel" in data && data.channel?.id === this.id) {
1664
1743
  listener(data);
1665
1744
  }
1666
1745
  };
1667
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);
1668
1751
  }
1669
1752
  /**
1670
1753
  * Registers a one-time event listener
@@ -1673,12 +1756,25 @@ var ChannelInstance = class {
1673
1756
  if (!event) {
1674
1757
  throw new Error("Event type is required");
1675
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
+ }
1676
1767
  const wrappedListener = (data) => {
1677
1768
  if ("channel" in data && data.channel?.id === this.id) {
1678
1769
  listener(data);
1770
+ this.off(event, wrappedListener);
1679
1771
  }
1680
1772
  };
1681
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);
1682
1778
  }
1683
1779
  /**
1684
1780
  * Removes event listener(s) for a specific WebSocket event type.
@@ -1695,10 +1791,25 @@ var ChannelInstance = class {
1695
1791
  }
1696
1792
  if (listener) {
1697
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
+ );
1698
1799
  } else {
1699
1800
  this.eventEmitter.removeAllListeners(event);
1801
+ this.listenersMap.delete(event);
1700
1802
  }
1701
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
+ }
1702
1813
  /**
1703
1814
  * Emits an event if it matches the current channel
1704
1815
  */
@@ -1718,6 +1829,16 @@ var ChannelInstance = class {
1718
1829
  * @return {void} This method does not return a value.
1719
1830
  */
1720
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();
1721
1842
  this.eventEmitter.removeAllListeners();
1722
1843
  }
1723
1844
  /**
@@ -2006,6 +2127,7 @@ var Channels = class {
2006
2127
  this.client = client;
2007
2128
  }
2008
2129
  channelInstances = /* @__PURE__ */ new Map();
2130
+ eventQueue = /* @__PURE__ */ new Map();
2009
2131
  /**
2010
2132
  * Creates or retrieves a ChannelInstance.
2011
2133
  *
@@ -2041,6 +2163,32 @@ var Channels = class {
2041
2163
  throw new Error(`Failed to manage channel instance: ${message}`);
2042
2164
  }
2043
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
+ }
2044
2192
  /**
2045
2193
  * Retrieves the details of a specific channel.
2046
2194
  *
@@ -2067,10 +2215,16 @@ var Channels = class {
2067
2215
  if (!channelId) {
2068
2216
  throw new Error("Channel ID is required");
2069
2217
  }
2070
- if (this.channelInstances.has(channelId)) {
2071
- const instance = this.channelInstances.get(channelId);
2072
- instance?.removeAllListeners();
2073
- 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
+ }
2074
2228
  } else {
2075
2229
  console.warn(`Attempt to remove non-existent instance: ${channelId}`);
2076
2230
  }
@@ -2079,18 +2233,29 @@ var Channels = class {
2079
2233
  * Propagates a WebSocket event to a specific channel.
2080
2234
  */
2081
2235
  propagateEventToChannel(event) {
2082
- if (!event) {
2236
+ if (!event || !("channel" in event) || !event.channel?.id) {
2083
2237
  console.warn("Invalid WebSocket event received");
2084
2238
  return;
2085
2239
  }
2086
- if ("channel" in event && event.channel?.id) {
2087
- const instance = this.channelInstances.get(event.channel.id);
2088
- if (instance) {
2089
- instance.emitEvent(event);
2090
- } else {
2091
- console.warn(`No instance found for channel ${event.channel.id}`);
2092
- }
2093
- }
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
+ );
2094
2259
  }
2095
2260
  /**
2096
2261
  * Initiates a new channel.
@@ -2571,6 +2736,8 @@ var PlaybackInstance = class {
2571
2736
  this.id = playbackId;
2572
2737
  }
2573
2738
  eventEmitter = new EventEmitter3();
2739
+ listenersMap = /* @__PURE__ */ new Map();
2740
+ // 🔹 Guarda listeners para remoção posterior
2574
2741
  playbackData = null;
2575
2742
  id;
2576
2743
  /**
@@ -2583,12 +2750,23 @@ var PlaybackInstance = class {
2583
2750
  if (!event) {
2584
2751
  throw new Error("Event type is required");
2585
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
+ }
2586
2760
  const wrappedListener = (data) => {
2587
2761
  if ("playback" in data && data.playback?.id === this.id) {
2588
2762
  listener(data);
2589
2763
  }
2590
2764
  };
2591
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);
2592
2770
  }
2593
2771
  /**
2594
2772
  * Registers a one-time event listener for a specific WebSocket event type.
@@ -2600,12 +2778,25 @@ var PlaybackInstance = class {
2600
2778
  if (!event) {
2601
2779
  throw new Error("Event type is required");
2602
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
+ }
2603
2789
  const wrappedListener = (data) => {
2604
2790
  if ("playback" in data && data.playback?.id === this.id) {
2605
2791
  listener(data);
2792
+ this.off(event, wrappedListener);
2606
2793
  }
2607
2794
  };
2608
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);
2609
2800
  }
2610
2801
  /**
2611
2802
  * Removes event listener(s) for a specific WebSocket event type.
@@ -2619,10 +2810,25 @@ var PlaybackInstance = class {
2619
2810
  }
2620
2811
  if (listener) {
2621
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
+ );
2622
2818
  } else {
2623
2819
  this.eventEmitter.removeAllListeners(event);
2820
+ this.listenersMap.delete(event);
2624
2821
  }
2625
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
+ }
2626
2832
  /**
2627
2833
  * Emits a WebSocket event if it matches the current playback instance.
2628
2834
  *
@@ -2696,9 +2902,29 @@ var PlaybackInstance = class {
2696
2902
  }
2697
2903
  }
2698
2904
  /**
2699
- * 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.
2700
2916
  */
2701
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();
2702
2928
  this.eventEmitter.removeAllListeners();
2703
2929
  }
2704
2930
  /**
@@ -2725,6 +2951,7 @@ var Playbacks = class {
2725
2951
  this.client = client;
2726
2952
  }
2727
2953
  playbackInstances = /* @__PURE__ */ new Map();
2954
+ eventQueue = /* @__PURE__ */ new Map();
2728
2955
  /**
2729
2956
  * Gets or creates a playback instance
2730
2957
  * @param {Object} [params] - Optional parameters for getting/creating a playback instance
@@ -2751,6 +2978,43 @@ var Playbacks = class {
2751
2978
  throw new Error(`Failed to manage playback instance: ${message}`);
2752
2979
  }
2753
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
+ }
2754
3018
  /**
2755
3019
  * Removes a playback instance and cleans up its resources
2756
3020
  * @param {string} playbackId - ID of the playback instance to remove
@@ -2760,10 +3024,16 @@ var Playbacks = class {
2760
3024
  if (!playbackId) {
2761
3025
  throw new Error("Playback ID is required");
2762
3026
  }
2763
- if (this.playbackInstances.has(playbackId)) {
2764
- const instance = this.playbackInstances.get(playbackId);
2765
- instance?.removeAllListeners();
2766
- 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
+ }
2767
3037
  } else {
2768
3038
  console.warn(`Attempt to remove non-existent instance: ${playbackId}`);
2769
3039
  }
@@ -2773,17 +3043,29 @@ var Playbacks = class {
2773
3043
  * @param {WebSocketEvent} event - The WebSocket event to propagate
2774
3044
  */
2775
3045
  propagateEventToPlayback(event) {
2776
- if (!event) {
3046
+ if (!event || !("playback" in event) || !event.playback?.id) {
3047
+ console.warn("Invalid WebSocket event received");
2777
3048
  return;
2778
3049
  }
2779
- if ("playback" in event && event.playback?.id) {
2780
- const instance = this.playbackInstances.get(event.playback.id);
2781
- if (instance) {
2782
- instance.emitEvent(event);
2783
- } else {
2784
- console.warn(`No instance found for playback ${event.playback.id}`);
2785
- }
2786
- }
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
+ );
2787
3069
  }
2788
3070
  /**
2789
3071
  * Retrieves details of a specific playback
@@ -2921,9 +3203,57 @@ var WebSocketClient = class extends EventEmitter4 {
2921
3203
  }
2922
3204
  ws;
2923
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
2924
3210
  maxReconnectAttempts = DEFAULT_MAX_RECONNECT_ATTEMPTS;
2925
3211
  reconnectionAttempts = 0;
2926
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
+ }
2927
3257
  backOffOptions = {
2928
3258
  numOfAttempts: DEFAULT_MAX_RECONNECT_ATTEMPTS,
2929
3259
  startingDelay: DEFAULT_STARTING_DELAY,
@@ -2950,21 +3280,29 @@ var WebSocketClient = class extends EventEmitter4 {
2950
3280
  * @throws Will throw an error if the connection cannot be established.
2951
3281
  */
2952
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;
2953
3291
  const { baseUrl, username, password } = this.baseClient.getCredentials();
2954
3292
  const protocol = baseUrl.startsWith("https") ? "wss" : "ws";
2955
3293
  const normalizedHost = baseUrl.replace(/^https?:\/\//, "").replace(/\/ari$/, "");
2956
3294
  const queryParams = new URLSearchParams();
2957
3295
  queryParams.append("app", this.apps.join(","));
2958
- if (this.subscribedEvents?.length) {
2959
- this.subscribedEvents.forEach(
2960
- (event) => queryParams.append("event", event)
2961
- );
2962
- } else {
2963
- queryParams.append("subscribeAll", "true");
2964
- }
3296
+ this.subscribedEvents?.forEach(
3297
+ (event) => queryParams.append("event", event)
3298
+ );
2965
3299
  this.lastWsUrl = `${protocol}://${encodeURIComponent(username)}:${encodeURIComponent(password)}@${normalizedHost}/ari/events?${queryParams.toString()}`;
2966
3300
  console.log("Connecting to WebSocket...");
2967
- return this.initializeWebSocket(this.lastWsUrl);
3301
+ try {
3302
+ await this.initializeWebSocket(this.lastWsUrl);
3303
+ } finally {
3304
+ this.isConnecting = false;
3305
+ }
2968
3306
  }
2969
3307
  /**
2970
3308
  * Initializes a WebSocket connection with exponential backoff retry mechanism.
@@ -2986,8 +3324,9 @@ var WebSocketClient = class extends EventEmitter4 {
2986
3324
  return new Promise((resolve, reject) => {
2987
3325
  try {
2988
3326
  this.ws = new WebSocket(wsUrl);
2989
- this.ws.on("open", () => {
3327
+ this.ws.once("open", () => {
2990
3328
  console.log("WebSocket connection established successfully");
3329
+ this.setupHeartbeat();
2991
3330
  if (this.isReconnecting) {
2992
3331
  this.emit("reconnected", {
2993
3332
  apps: this.apps,
@@ -3000,7 +3339,7 @@ var WebSocketClient = class extends EventEmitter4 {
3000
3339
  resolve();
3001
3340
  });
3002
3341
  this.ws.on("message", (data) => this.handleMessage(data.toString()));
3003
- this.ws.on("close", (code) => {
3342
+ this.ws.once("close", (code) => {
3004
3343
  console.warn(
3005
3344
  `WebSocket disconnected with code ${code}. Attempting to reconnect...`
3006
3345
  );
@@ -3008,7 +3347,7 @@ var WebSocketClient = class extends EventEmitter4 {
3008
3347
  this.reconnect(this.lastWsUrl);
3009
3348
  }
3010
3349
  });
3011
- this.ws.on("error", (err) => {
3350
+ this.ws.once("error", (err) => {
3012
3351
  console.error("WebSocket error:", err.message);
3013
3352
  if (!this.isReconnecting) {
3014
3353
  this.reconnect(this.lastWsUrl);
@@ -3021,6 +3360,34 @@ var WebSocketClient = class extends EventEmitter4 {
3021
3360
  });
3022
3361
  }, this.backOffOptions);
3023
3362
  }
3363
+ getEventKey(event) {
3364
+ const ids = [];
3365
+ if ("channel" in event && event.channel?.id) ids.push(event.channel.id);
3366
+ if ("playback" in event && event.playback?.id) ids.push(event.playback.id);
3367
+ if ("bridge" in event && event.bridge?.id) ids.push(event.bridge.id);
3368
+ return `${event.type}-${ids.join("-")}`;
3369
+ }
3370
+ processEvent(event) {
3371
+ if (this.subscribedEvents?.length && !this.subscribedEvents.includes(event.type)) {
3372
+ return;
3373
+ }
3374
+ if ("channel" in event && event.channel?.id && this.ariClient) {
3375
+ const instanceChannel = this.ariClient.Channel(event.channel.id);
3376
+ instanceChannel.emitEvent(event);
3377
+ event.instanceChannel = instanceChannel;
3378
+ }
3379
+ if ("playback" in event && event.playback?.id && this.ariClient) {
3380
+ const instancePlayback = this.ariClient.Playback(event.playback.id);
3381
+ instancePlayback.emitEvent(event);
3382
+ event.instancePlayback = instancePlayback;
3383
+ }
3384
+ if ("bridge" in event && event.bridge?.id && this.ariClient) {
3385
+ const instanceBridge = this.ariClient.Bridge(event.bridge.id);
3386
+ instanceBridge.emitEvent(event);
3387
+ event.instanceBridge = instanceBridge;
3388
+ }
3389
+ this.emit(event.type, event);
3390
+ }
3024
3391
  /**
3025
3392
  * Handles incoming WebSocket messages by parsing and processing events.
3026
3393
  *
@@ -3036,30 +3403,20 @@ var WebSocketClient = class extends EventEmitter4 {
3036
3403
  handleMessage(rawMessage) {
3037
3404
  try {
3038
3405
  const event = JSON.parse(rawMessage);
3039
- if (this.subscribedEvents?.length && !this.subscribedEvents.includes(event.type)) {
3040
- return;
3041
- }
3042
- if ("channel" in event && event.channel?.id && this.ariClient) {
3043
- const instanceChannel = this.ariClient.Channel(event.channel.id);
3044
- instanceChannel.emitEvent(event);
3045
- event.instanceChannel = instanceChannel;
3406
+ const key = this.getEventKey(event);
3407
+ const existing = this.eventQueue.get(key);
3408
+ if (existing) {
3409
+ clearTimeout(existing);
3046
3410
  }
3047
- if ("playback" in event && event.playback?.id && this.ariClient) {
3048
- const instancePlayback = this.ariClient.Playback(event.playback.id);
3049
- instancePlayback.emitEvent(event);
3050
- event.instancePlayback = instancePlayback;
3051
- }
3052
- if ("bridge" in event && event.bridge?.id && this.ariClient) {
3053
- const instanceBridge = this.ariClient.Bridge(event.bridge.id);
3054
- instanceBridge.emitEvent(event);
3055
- event.instanceBridge = instanceBridge;
3056
- }
3057
- this.emit(event.type, event);
3058
- } catch (error) {
3059
- console.error(
3060
- "Error processing WebSocket message:",
3061
- error instanceof Error ? error.message : "Unknown error"
3411
+ this.eventQueue.set(
3412
+ key,
3413
+ setTimeout(() => {
3414
+ this.processEvent(event);
3415
+ this.eventQueue.delete(key);
3416
+ }, 100)
3062
3417
  );
3418
+ } catch (error) {
3419
+ console.error("Error processing WebSocket message:", error);
3063
3420
  this.emit("error", new Error("Failed to decode WebSocket message"));
3064
3421
  }
3065
3422
  }
@@ -3075,21 +3432,26 @@ var WebSocketClient = class extends EventEmitter4 {
3075
3432
  *
3076
3433
  * @emits reconnectFailed - Emitted if all reconnection attempts fail.
3077
3434
  */
3078
- reconnect(wsUrl) {
3435
+ async reconnect(wsUrl) {
3436
+ if (!this.shouldReconnect) {
3437
+ console.warn(
3438
+ "Reconnection skipped because WebSocket was intentionally closed."
3439
+ );
3440
+ return;
3441
+ }
3442
+ if (this.isReconnecting) {
3443
+ console.warn("J\xE1 h\xE1 uma tentativa de reconex\xE3o em andamento.");
3444
+ return;
3445
+ }
3079
3446
  this.isReconnecting = true;
3080
3447
  this.reconnectionAttempts++;
3081
- console.log(
3082
- `Initiating reconnection attempt #${this.reconnectionAttempts}...`
3083
- );
3084
- (0, import_exponential_backoff.backOff)(() => this.initializeWebSocket(wsUrl), this.backOffOptions).catch(
3085
- (error) => {
3086
- console.error(
3087
- `Failed to reconnect after ${this.reconnectionAttempts} attempts:`,
3088
- error instanceof Error ? error.message : "Unknown error"
3089
- );
3090
- this.emit("reconnectFailed", error);
3091
- }
3092
- );
3448
+ console.log(`Tentando reconex\xE3o #${this.reconnectionAttempts}...`);
3449
+ (0, import_exponential_backoff.backOff)(() => this.initializeWebSocket(wsUrl), this.backOffOptions).catch((error) => {
3450
+ console.error(`Falha ao reconectar: ${error.message}`);
3451
+ this.emit("reconnectFailed", error);
3452
+ }).finally(() => {
3453
+ this.isReconnecting = false;
3454
+ });
3093
3455
  }
3094
3456
  /**
3095
3457
  * Closes the WebSocket connection if it exists.
@@ -3100,18 +3462,34 @@ var WebSocketClient = class extends EventEmitter4 {
3100
3462
  *
3101
3463
  * @throws {Error} Logs an error message if closing the WebSocket fails.
3102
3464
  */
3103
- close() {
3465
+ async close() {
3466
+ if (!this.ws) {
3467
+ console.warn("No WebSocket connection to close");
3468
+ return;
3469
+ }
3470
+ console.log("Closing WebSocket connection.");
3471
+ this.shouldReconnect = false;
3472
+ this.eventQueue.forEach((timeout) => clearTimeout(timeout));
3473
+ this.eventQueue.clear();
3474
+ const closeTimeout = setTimeout(() => {
3475
+ if (this.ws && this.ws.readyState !== WebSocket.CLOSED) {
3476
+ this.ws.terminate();
3477
+ }
3478
+ }, 5e3);
3104
3479
  try {
3105
- if (this.ws) {
3480
+ this.ws.removeAllListeners();
3481
+ await new Promise((resolve) => {
3482
+ this.ws.once("close", () => {
3483
+ clearTimeout(closeTimeout);
3484
+ resolve();
3485
+ });
3106
3486
  this.ws.close();
3107
- this.ws = void 0;
3108
- console.log("WebSocket connection closed");
3109
- }
3487
+ });
3110
3488
  } catch (error) {
3111
- console.error(
3112
- "Error closing WebSocket:",
3113
- error instanceof Error ? error.message : "Unknown error"
3114
- );
3489
+ console.error("Error closing WebSocket:", error);
3490
+ } finally {
3491
+ this.ws = void 0;
3492
+ this.emit("disconnected");
3115
3493
  }
3116
3494
  }
3117
3495
  /**
@@ -3143,6 +3521,30 @@ var WebSocketClient = class extends EventEmitter4 {
3143
3521
  getState() {
3144
3522
  return this.ws?.readyState ?? WebSocket.CLOSED;
3145
3523
  }
3524
+ /**
3525
+ * Cleans up the WebSocketClient instance, resetting its state and clearing resources.
3526
+ *
3527
+ * This method performs the following cleanup operations:
3528
+ * - Clears the event queue and cancels any pending timeouts.
3529
+ * - Stops any ongoing reconnection attempts.
3530
+ * - Clears the stored WebSocket URL.
3531
+ * - Resets the reconnection attempt counter.
3532
+ * - Removes all event listeners attached to this instance.
3533
+ *
3534
+ * This method is typically called when the WebSocketClient is no longer needed or
3535
+ * before reinitializing the client to ensure a clean slate.
3536
+ *
3537
+ * @returns {void} This method doesn't return a value.
3538
+ */
3539
+ cleanup() {
3540
+ this.eventQueue.forEach((timeout) => clearTimeout(timeout));
3541
+ this.eventQueue.clear();
3542
+ this.shouldReconnect = false;
3543
+ this.isReconnecting = false;
3544
+ this.lastWsUrl = "";
3545
+ this.reconnectionAttempts = 0;
3546
+ this.removeAllListeners();
3547
+ }
3146
3548
  };
3147
3549
 
3148
3550
  // src/ari-client/ariClient.ts
@@ -3173,6 +3575,8 @@ var AriClient = class {
3173
3575
  }
3174
3576
  baseClient;
3175
3577
  webSocketClient;
3578
+ eventListeners = /* @__PURE__ */ new Map();
3579
+ // Armazena os listeners para limpeza
3176
3580
  channels;
3177
3581
  endpoints;
3178
3582
  applications;
@@ -3180,6 +3584,50 @@ var AriClient = class {
3180
3584
  sounds;
3181
3585
  asterisk;
3182
3586
  bridges;
3587
+ async cleanup() {
3588
+ try {
3589
+ console.log("Starting ARI Client cleanup...");
3590
+ if (this.webSocketClient) {
3591
+ await this.closeWebSocket();
3592
+ }
3593
+ await Promise.all([
3594
+ // Cleanup de channels
3595
+ (async () => {
3596
+ try {
3597
+ this.channels.cleanup();
3598
+ } catch (error) {
3599
+ console.error("Error cleaning up channels:", error);
3600
+ }
3601
+ })(),
3602
+ // Cleanup de playbacks
3603
+ (async () => {
3604
+ try {
3605
+ this.playbacks.cleanup();
3606
+ } catch (error) {
3607
+ console.error("Error cleaning up playbacks:", error);
3608
+ }
3609
+ })(),
3610
+ // Cleanup de bridges
3611
+ (async () => {
3612
+ try {
3613
+ this.bridges.cleanup();
3614
+ } catch (error) {
3615
+ console.error("Error cleaning up bridges:", error);
3616
+ }
3617
+ })()
3618
+ ]);
3619
+ this.eventListeners.forEach((listeners, event) => {
3620
+ listeners.forEach((listener) => {
3621
+ this.off(event, listener);
3622
+ });
3623
+ });
3624
+ this.eventListeners.clear();
3625
+ console.log("ARI Client cleanup completed successfully");
3626
+ } catch (error) {
3627
+ console.error("Error during ARI Client cleanup:", error);
3628
+ throw error;
3629
+ }
3630
+ }
3183
3631
  /**
3184
3632
  * Initializes a WebSocket connection for receiving events.
3185
3633
  *
@@ -3189,14 +3637,14 @@ var AriClient = class {
3189
3637
  * @throws {Error} If connection fails or if WebSocket is already connected
3190
3638
  */
3191
3639
  async connectWebSocket(apps, subscribedEvents) {
3192
- if (!apps.length) {
3193
- throw new Error("At least one application name is required");
3194
- }
3195
- if (this.webSocketClient) {
3196
- console.warn("WebSocket is already connected");
3197
- return;
3198
- }
3199
3640
  try {
3641
+ if (!apps.length) {
3642
+ throw new Error("At least one application name is required.");
3643
+ }
3644
+ if (this.webSocketClient) {
3645
+ await this.closeWebSocket();
3646
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
3647
+ }
3200
3648
  this.webSocketClient = new WebSocketClient(
3201
3649
  this.baseClient,
3202
3650
  apps,
@@ -3204,13 +3652,38 @@ var AriClient = class {
3204
3652
  this
3205
3653
  );
3206
3654
  await this.webSocketClient.connect();
3207
- console.log("WebSocket connection established successfully");
3655
+ console.log("WebSocket connection established successfully.");
3208
3656
  } catch (error) {
3209
3657
  console.error("Failed to establish WebSocket connection:", error);
3210
3658
  this.webSocketClient = void 0;
3211
3659
  throw error;
3212
3660
  }
3213
3661
  }
3662
+ /**
3663
+ * Destroys the ARI Client instance, cleaning up all resources and removing circular references.
3664
+ * This method should be called when the ARI Client is no longer needed to ensure proper cleanup.
3665
+ *
3666
+ * @returns {Promise<void>} A promise that resolves when the destruction process is complete.
3667
+ * @throws {Error} If an error occurs during the destruction process.
3668
+ */
3669
+ async destroy() {
3670
+ try {
3671
+ console.log("Destroying ARI Client...");
3672
+ await this.cleanup();
3673
+ this.webSocketClient = void 0;
3674
+ this.channels = null;
3675
+ this.playbacks = null;
3676
+ this.bridges = null;
3677
+ this.endpoints = null;
3678
+ this.applications = null;
3679
+ this.sounds = null;
3680
+ this.asterisk = null;
3681
+ console.log("ARI Client destroyed successfully");
3682
+ } catch (error) {
3683
+ console.error("Error destroying ARI Client:", error);
3684
+ throw error;
3685
+ }
3686
+ }
3214
3687
  /**
3215
3688
  * Registers an event listener for WebSocket events.
3216
3689
  *
@@ -3222,8 +3695,15 @@ var AriClient = class {
3222
3695
  if (!this.webSocketClient) {
3223
3696
  throw new Error("WebSocket is not connected");
3224
3697
  }
3698
+ const existingListeners = this.eventListeners.get(event) || [];
3699
+ if (existingListeners.includes(listener)) {
3700
+ console.warn(`Listener already registered for event ${event}, reusing.`);
3701
+ return;
3702
+ }
3225
3703
  this.webSocketClient.on(event, listener);
3226
- console.log(`Event listener registered for ${event}`);
3704
+ existingListeners.push(listener);
3705
+ this.eventListeners.set(event, existingListeners);
3706
+ console.log(`Event listener successfully registered for ${event}`);
3227
3707
  }
3228
3708
  /**
3229
3709
  * Registers a one-time event listener for WebSocket events.
@@ -3236,7 +3716,19 @@ var AriClient = class {
3236
3716
  if (!this.webSocketClient) {
3237
3717
  throw new Error("WebSocket is not connected");
3238
3718
  }
3239
- this.webSocketClient.once(event, listener);
3719
+ const existingListeners = this.eventListeners.get(event) || [];
3720
+ if (existingListeners.includes(listener)) {
3721
+ console.warn(
3722
+ `One-time listener already registered for event ${event}, reusing.`
3723
+ );
3724
+ return;
3725
+ }
3726
+ const wrappedListener = (data) => {
3727
+ listener(data);
3728
+ this.off(event, wrappedListener);
3729
+ };
3730
+ this.webSocketClient.once(event, wrappedListener);
3731
+ this.eventListeners.set(event, [...existingListeners, wrappedListener]);
3240
3732
  console.log(`One-time event listener registered for ${event}`);
3241
3733
  }
3242
3734
  /**
@@ -3251,19 +3743,57 @@ var AriClient = class {
3251
3743
  return;
3252
3744
  }
3253
3745
  this.webSocketClient.off(event, listener);
3746
+ const existingListeners = this.eventListeners.get(event) || [];
3747
+ this.eventListeners.set(
3748
+ event,
3749
+ existingListeners.filter((l) => l !== listener)
3750
+ );
3254
3751
  console.log(`Event listener removed for ${event}`);
3255
3752
  }
3256
3753
  /**
3257
3754
  * Closes the WebSocket connection if one exists.
3258
3755
  */
3259
3756
  closeWebSocket() {
3260
- if (!this.webSocketClient) {
3261
- console.warn("No WebSocket connection to close");
3262
- return;
3263
- }
3264
- this.webSocketClient.close();
3265
- this.webSocketClient = void 0;
3266
- console.log("WebSocket connection closed");
3757
+ return new Promise((resolve) => {
3758
+ if (!this.webSocketClient) {
3759
+ console.warn("No WebSocket connection to close");
3760
+ resolve();
3761
+ return;
3762
+ }
3763
+ console.log("Closing WebSocket connection and cleaning up listeners.");
3764
+ const closeTimeout = setTimeout(() => {
3765
+ if (this.webSocketClient) {
3766
+ this.webSocketClient.removeAllListeners();
3767
+ this.webSocketClient = void 0;
3768
+ }
3769
+ resolve();
3770
+ }, 5e3);
3771
+ this.eventListeners.forEach((listeners, event) => {
3772
+ listeners.forEach((listener) => {
3773
+ this.webSocketClient?.off(
3774
+ event,
3775
+ listener
3776
+ );
3777
+ });
3778
+ });
3779
+ this.eventListeners.clear();
3780
+ this.webSocketClient.once("close", () => {
3781
+ clearTimeout(closeTimeout);
3782
+ this.webSocketClient = void 0;
3783
+ console.log("WebSocket connection closed");
3784
+ resolve();
3785
+ });
3786
+ this.webSocketClient.close().then(() => {
3787
+ clearTimeout(closeTimeout);
3788
+ this.webSocketClient = void 0;
3789
+ resolve();
3790
+ }).catch((error) => {
3791
+ console.error("Error during WebSocket close:", error);
3792
+ clearTimeout(closeTimeout);
3793
+ this.webSocketClient = void 0;
3794
+ resolve();
3795
+ });
3796
+ });
3267
3797
  }
3268
3798
  /**
3269
3799
  * Creates or retrieves a Channel instance.
@@ -3304,7 +3834,7 @@ var AriClient = class {
3304
3834
  * @returns {boolean} True if WebSocket is connected, false otherwise
3305
3835
  */
3306
3836
  isWebSocketConnected() {
3307
- return !!this.webSocketClient;
3837
+ return !!this.webSocketClient && this.webSocketClient.isConnected();
3308
3838
  }
3309
3839
  };
3310
3840
  export {