@ipcom/asterisk-ari 0.0.135 → 0.0.137

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.
@@ -484,25 +484,25 @@ var require_backoff = __commonJS({
484
484
  Object.defineProperty(exports2, "__esModule", { value: true });
485
485
  var options_1 = require_options();
486
486
  var delay_factory_1 = require_delay_factory();
487
- function backOff3(request, options) {
487
+ function backOff2(request, options) {
488
488
  if (options === void 0) {
489
489
  options = {};
490
490
  }
491
491
  return __awaiter(this, void 0, void 0, function() {
492
- var sanitizedOptions, backOff4;
492
+ var sanitizedOptions, backOff3;
493
493
  return __generator(this, function(_a) {
494
494
  switch (_a.label) {
495
495
  case 0:
496
496
  sanitizedOptions = options_1.getSanitizedOptions(options);
497
- backOff4 = new BackOff(request, sanitizedOptions);
498
- return [4, backOff4.execute()];
497
+ backOff3 = new BackOff(request, sanitizedOptions);
498
+ return [4, backOff3.execute()];
499
499
  case 1:
500
500
  return [2, _a.sent()];
501
501
  }
502
502
  });
503
503
  });
504
504
  }
505
- exports2.backOff = backOff3;
505
+ exports2.backOff = backOff2;
506
506
  var BackOff = (
507
507
  /** @class */
508
508
  function() {
@@ -592,54 +592,158 @@ __export(src_exports, {
592
592
  });
593
593
  module.exports = __toCommonJS(src_exports);
594
594
 
595
- // src/ari-client/ariClient.ts
596
- var import_exponential_backoff2 = __toESM(require_backoff(), 1);
597
-
598
595
  // src/ari-client/baseClient.ts
599
596
  var import_axios = __toESM(require("axios"), 1);
600
597
  var BaseClient = class {
601
- client;
602
- constructor(baseUrl, username, password) {
598
+ constructor(baseUrl, username, password, timeout = 5e3) {
599
+ this.baseUrl = baseUrl;
600
+ this.username = username;
601
+ this.password = password;
602
+ if (!/^https?:\/\/.+/.test(baseUrl)) {
603
+ throw new Error(
604
+ "Invalid base URL. It must start with http:// or https://"
605
+ );
606
+ }
603
607
  this.client = import_axios.default.create({
604
608
  baseURL: baseUrl,
605
- auth: { username, password }
609
+ auth: { username, password },
610
+ timeout
606
611
  });
612
+ this.addInterceptors();
613
+ }
614
+ client;
615
+ getBaseUrl() {
616
+ return this.baseUrl;
617
+ }
618
+ /**
619
+ * Retorna as credenciais configuradas.
620
+ */
621
+ getCredentials() {
622
+ return {
623
+ baseUrl: this.baseUrl,
624
+ username: this.username,
625
+ password: this.password
626
+ };
627
+ }
628
+ /**
629
+ * Adds interceptors to the Axios instance.
630
+ */
631
+ addInterceptors() {
632
+ this.client.interceptors.request.use(
633
+ (config) => {
634
+ return config;
635
+ },
636
+ (error) => {
637
+ console.error("[Request Error]", error.message);
638
+ return Promise.reject(error);
639
+ }
640
+ );
641
+ this.client.interceptors.response.use(
642
+ (response) => response,
643
+ (error) => {
644
+ const status = error.response?.status;
645
+ const message = error.response?.data?.message || error.message || "Unknown error";
646
+ const errorDetails = {
647
+ status,
648
+ message,
649
+ url: error.config?.url || "Unknown URL",
650
+ method: error.config?.method?.toUpperCase() || "Unknown Method"
651
+ };
652
+ if (status === 404) {
653
+ console.warn(`[404] Not Found: ${errorDetails.url}`);
654
+ } else if (status >= 500) {
655
+ console.error(`[500] Server Error: ${errorDetails.url}`);
656
+ } else {
657
+ console.warn(
658
+ `[Response Error] ${errorDetails.method} ${errorDetails.url}: ${message}`
659
+ );
660
+ }
661
+ return Promise.reject(
662
+ new Error(
663
+ `[Error] ${errorDetails.method} ${errorDetails.url} - ${message} (Status: ${status})`
664
+ )
665
+ );
666
+ }
667
+ );
607
668
  }
608
669
  /**
609
670
  * Executes a GET request.
610
671
  * @param path - The API endpoint path.
672
+ * @param config - Optional Axios request configuration.
611
673
  */
612
- async get(path) {
613
- const response = await this.client.get(path);
614
- return response.data;
674
+ async get(path, config) {
675
+ try {
676
+ const response = await this.client.get(path, config);
677
+ return response.data;
678
+ } catch (error) {
679
+ this.handleRequestError(error);
680
+ }
615
681
  }
616
682
  /**
617
683
  * Executes a POST request.
618
684
  * @param path - The API endpoint path.
619
685
  * @param data - Optional payload to send with the request.
686
+ * @param config - Optional Axios request configuration.
620
687
  */
621
- async post(path, data) {
622
- const response = await this.client.post(path, data);
623
- return response.data;
688
+ async post(path, data, config) {
689
+ try {
690
+ const response = await this.client.post(path, data, config);
691
+ return response.data;
692
+ } catch (error) {
693
+ this.handleRequestError(error);
694
+ }
624
695
  }
625
696
  /**
626
697
  * Executes a PUT request.
627
698
  * @param path - The API endpoint path.
628
699
  * @param data - Payload to send with the request.
700
+ * @param config - Optional Axios request configuration.
629
701
  */
630
- async put(path, data) {
631
- const response = await this.client.put(path, data);
632
- return response.data;
702
+ async put(path, data, config) {
703
+ try {
704
+ const response = await this.client.put(path, data, config);
705
+ return response.data;
706
+ } catch (error) {
707
+ this.handleRequestError(error);
708
+ }
633
709
  }
634
710
  /**
635
711
  * Executes a DELETE request.
636
712
  * @param path - The API endpoint path.
713
+ * @param config - Optional Axios request configuration.
714
+ */
715
+ async delete(path, config) {
716
+ try {
717
+ const response = await this.client.delete(path, config);
718
+ return response.data;
719
+ } catch (error) {
720
+ this.handleRequestError(error);
721
+ }
722
+ }
723
+ /**
724
+ * Handles errors for HTTP requests.
725
+ * @param error - The error to handle.
637
726
  */
638
- async delete(path) {
639
- const response = await this.client.delete(path);
640
- return response.data;
727
+ handleRequestError(error) {
728
+ if (import_axios.default.isAxiosError(error)) {
729
+ console.error(`[HTTP Error] ${error.message}`);
730
+ throw new Error(error.message || "HTTP Error");
731
+ } else {
732
+ console.error(`[Unexpected Error] ${error}`);
733
+ throw error;
734
+ }
735
+ }
736
+ /**
737
+ * Sets custom headers for the client instance.
738
+ * Useful for adding dynamic tokens or specific API headers.
739
+ * @param headers - Headers to merge with existing configuration.
740
+ */
741
+ setHeaders(headers) {
742
+ this.client.defaults.headers.common = {
743
+ ...this.client.defaults.headers.common,
744
+ ...headers
745
+ };
641
746
  }
642
- // Gerenciamento de WebSocket clients
643
747
  };
644
748
 
645
749
  // src/ari-client/resources/applications.ts
@@ -702,13 +806,13 @@ var Asterisk = class {
702
806
  /**
703
807
  * Retrieves information about the Asterisk server.
704
808
  */
705
- async getInfo() {
809
+ async get() {
706
810
  return this.client.get("/asterisk/info");
707
811
  }
708
812
  /**
709
813
  * Lists all loaded modules in the Asterisk server.
710
814
  */
711
- async listModules() {
815
+ async list() {
712
816
  return this.client.get("/asterisk/modules");
713
817
  }
714
818
  /**
@@ -719,7 +823,7 @@ var Asterisk = class {
719
823
  * @returns A promise that resolves when the action is completed successfully.
720
824
  * @throws {Error} Throws an error if the HTTP method or action is invalid.
721
825
  */
722
- async manageModule(moduleName, action) {
826
+ async manage(moduleName, action) {
723
827
  const url = `/asterisk/modules/${moduleName}`;
724
828
  switch (action) {
725
829
  case "load":
@@ -911,120 +1015,75 @@ function toQueryParams2(options) {
911
1015
  Object.entries(options).filter(([, value]) => value !== void 0).map(([key, value]) => [key, value])
912
1016
  ).toString();
913
1017
  }
914
- function isPlaybackEvent(event, playbackId) {
915
- const hasPlayback = "playback" in event && event.playback?.id !== void 0;
916
- return hasPlayback && (!playbackId || event.playback?.id === playbackId);
917
- }
918
- function isChannelEvent(event, channelId) {
919
- const hasChannel = "channel" in event && event.channel?.id !== void 0;
920
- return hasChannel && (!channelId || event.channel?.id === channelId);
921
- }
922
1018
 
923
1019
  // src/ari-client/resources/channels.ts
924
- var ChannelInstance = class extends import_events.EventEmitter {
1020
+ var ChannelInstance = class {
925
1021
  // ID do canal
926
- constructor(client, baseClient, channelId = `channel-${Date.now()}`, app) {
927
- super();
1022
+ constructor(client, baseClient, channelId = `channel-${Date.now()}`) {
928
1023
  this.client = client;
929
1024
  this.baseClient = baseClient;
930
1025
  this.channelId = channelId;
931
- this.app = app;
932
1026
  this.id = channelId || `channel-${Date.now()}`;
933
- const wsClients = this.client.getWebSocketClients();
934
- const wsClient = wsClients.get(this.app);
935
- if (!wsClient) {
936
- throw new Error(
937
- `Nenhum WebSocket conectado dispon\xEDvel para o canal: ${this.id}`
938
- );
939
- }
940
- wsClient.addScopedMessageListener(
941
- this.id,
942
- this.app,
943
- (event) => {
944
- if (isChannelEvent(event, this.id)) {
945
- const channelEventType = event.type;
946
- this.emit(channelEventType, event);
947
- }
948
- }
949
- );
950
- this.once("ChannelDestroyed", () => {
951
- console.log(
952
- `Canal '${this.id}' destru\xEDdo. Removendo listeners associados.`
953
- );
954
- const wsClient2 = this.client.getWebSocketClients().get(this.app);
955
- if (wsClient2) {
956
- setTimeout(() => {
957
- wsClient2.removeScopedChannelListeners(this.id, this.app);
958
- }, 5e3);
959
- } else {
960
- console.warn(
961
- `WebSocketClient n\xE3o encontrado para o app '${this.app}'.`
962
- );
963
- }
964
- });
965
1027
  }
1028
+ eventEmitter = new import_events.EventEmitter();
966
1029
  channelData = null;
967
1030
  id;
968
1031
  /**
969
- * Adiciona um listener para eventos de canal.
1032
+ * Registra um listener para eventos específicos deste canal.
970
1033
  */
971
- on(event, callback) {
972
- const wsClient = this.client.getWebSocketClients().get(this.app);
973
- if (!wsClient) {
974
- throw new Error(
975
- `Nenhum WebSocket conectado dispon\xEDvel para '${this.app}'.`
976
- );
977
- }
978
- const scopedEvent = `${this.app}:Channel:${this.id}:${event}`;
979
- console.log({ metheod: "on", event, scopedEvent });
980
- const existingListeners = wsClient.listeners(scopedEvent);
981
- if (existingListeners.includes(callback)) {
982
- console.warn(`Listener j\xE1 registrado para o evento '${scopedEvent}'.`);
983
- return this;
984
- }
985
- wsClient.on(scopedEvent, callback);
986
- return this;
1034
+ on(event, listener) {
1035
+ const wrappedListener = (data) => {
1036
+ if ("channel" in data && data.channel?.id === this.id) {
1037
+ listener(data);
1038
+ }
1039
+ };
1040
+ this.eventEmitter.on(event, wrappedListener);
987
1041
  }
988
1042
  /**
989
- * Adiciona um listener para ser chamado apenas uma vez.
1043
+ * Registra um listener único para eventos específicos deste canal.
990
1044
  */
991
- once(event, callback) {
992
- const wsClient = this.client.getWebSocketClients().get(this.app);
993
- if (!wsClient) {
994
- throw new Error(
995
- `Nenhum WebSocket conectado dispon\xEDvel para '${this.app}'.`
996
- );
997
- }
998
- wsClient.removeWildcardListeners(this.id, event, "Channel");
999
- const scopedEvent = `${this.app}:Channel:${this.id}:${event}`;
1000
- console.log({ metheod: "once", event, scopedEvent });
1001
- const existingListeners = wsClient.listeners(scopedEvent);
1002
- if (existingListeners.some((listener2) => listener2 === callback)) {
1003
- console.warn(`Listener j\xE1 registrado para o evento '${scopedEvent}'.`);
1004
- return this;
1005
- }
1006
- const listener = (data) => {
1007
- callback(data);
1008
- wsClient.off(scopedEvent, listener);
1009
- wsClient.removeWildcardListeners(this.id, event, "Channel");
1045
+ once(event, listener) {
1046
+ const wrappedListener = (data) => {
1047
+ if ("channel" in data && data.channel?.id === this.id) {
1048
+ listener(data);
1049
+ }
1010
1050
  };
1011
- wsClient.on(scopedEvent, listener);
1012
- return this;
1051
+ this.eventEmitter.once(event, wrappedListener);
1013
1052
  }
1014
1053
  /**
1015
- * Remove um listener específico.
1054
+ * Remove um listener para eventos específicos deste canal.
1016
1055
  */
1017
- off(event, callback) {
1018
- const wsClient = this.client.getWebSocketClients().get(this.app);
1019
- if (!wsClient) {
1020
- throw new Error(
1021
- `Nenhum WebSocket conectado dispon\xEDvel para '${this.app}'.`
1022
- );
1056
+ off(event, listener) {
1057
+ if (listener) {
1058
+ this.eventEmitter.off(event, listener);
1059
+ } else {
1060
+ const listeners = this.eventEmitter.listeners(event);
1061
+ listeners.forEach((fn) => {
1062
+ this.eventEmitter.off(event, fn);
1063
+ });
1023
1064
  }
1024
- const scopedEvent = `${this.app}:Channel:${this.id}:${event}`;
1025
- wsClient.off(scopedEvent, callback);
1026
- wsClient.removeScopedChannelListeners(this.id, this.app);
1027
- return this;
1065
+ }
1066
+ /**
1067
+ * Obtém a quantidade de listeners registrados para o evento especificado.
1068
+ */
1069
+ getListenerCount(event) {
1070
+ return this.eventEmitter.listenerCount(event);
1071
+ }
1072
+ /**
1073
+ * Emite eventos internamente para o canal.
1074
+ * Verifica o ID do canal antes de emitir.
1075
+ */
1076
+ emitEvent(event) {
1077
+ if ("channel" in event && event.channel?.id === this.id) {
1078
+ this.eventEmitter.emit(event.type, event);
1079
+ }
1080
+ }
1081
+ /**
1082
+ * Remove todos os listeners para este canal.
1083
+ */
1084
+ removeAllListeners() {
1085
+ console.log(`Removendo todos os listeners para o canal ${this.id}`);
1086
+ this.eventEmitter.removeAllListeners();
1028
1087
  }
1029
1088
  async answer() {
1030
1089
  await this.baseClient.post(`/channels/${this.id}/answer`);
@@ -1083,7 +1142,7 @@ var ChannelInstance = class extends import_events.EventEmitter {
1083
1142
  console.log("Canal n\xE3o inicializado, buscando detalhes...");
1084
1143
  this.channelData = await this.getDetails();
1085
1144
  }
1086
- const playback = this.client.Playback(playbackId || v4_default(), this.app);
1145
+ const playback = this.client.Playback(playbackId || v4_default());
1087
1146
  if (!this.channelData?.id) {
1088
1147
  throw new Error("N\xE3o foi poss\xEDvel inicializar o canal. ID inv\xE1lido.");
1089
1148
  }
@@ -1091,28 +1150,6 @@ var ChannelInstance = class extends import_events.EventEmitter {
1091
1150
  `/channels/${this.channelData.id}/play/${playback.id}`,
1092
1151
  options
1093
1152
  );
1094
- const wsClient = this.client.getWebSocketClients().get(this.app);
1095
- if (!wsClient) {
1096
- throw new Error("WebSocketClient n\xE3o encontrado para o app.");
1097
- }
1098
- playback.once("PlaybackFinished", () => {
1099
- console.log(`PlaybackFinished '${playback.id}' no canal '${this.id}'.`);
1100
- wsClient.removeScopedPlaybackListeners(playback.id, this.app);
1101
- });
1102
- playback.once("PlaybackFinished", async () => {
1103
- console.log(`Playback conclu\xEDdo: ${playback.id}`);
1104
- this.client.getWebSocketClients().get(this.app)?.removeScopedPlaybackListeners(playback.id, this.app);
1105
- });
1106
- this.once("ChannelDestroyed", () => {
1107
- console.log(
1108
- `Canal '${this.id}' foi destru\xEDdo. Removendo listeners associados.`
1109
- );
1110
- setTimeout(() => {
1111
- console.log(`Removendo listeners associados ao canal '${this.id}'.`);
1112
- wsClient.removeScopedChannelListeners(this.id, this.app);
1113
- wsClient.removeScopedPlaybackListeners(playback.id, this.app);
1114
- }, 5e3);
1115
- });
1116
1153
  return playback;
1117
1154
  }
1118
1155
  /**
@@ -1226,19 +1263,54 @@ var ChannelInstance = class extends import_events.EventEmitter {
1226
1263
  await this.baseClient.delete(`/channels/${this.channelData.id}/hold`);
1227
1264
  }
1228
1265
  };
1229
- var Channels = class extends import_events.EventEmitter {
1266
+ var Channels = class {
1230
1267
  constructor(baseClient, client) {
1231
- super();
1232
1268
  this.baseClient = baseClient;
1233
1269
  this.client = client;
1234
1270
  }
1235
- Channel({ id, app }) {
1236
- if (!app) {
1237
- throw new Error(
1238
- "O nome do aplicativo (app) \xE9 obrigat\xF3rio para criar um Channel."
1271
+ channelInstances = /* @__PURE__ */ new Map();
1272
+ Channel({ id }) {
1273
+ if (!id) {
1274
+ const instance = new ChannelInstance(this.client, this.baseClient);
1275
+ this.channelInstances.set(instance.id, instance);
1276
+ return instance;
1277
+ }
1278
+ if (!this.channelInstances.has(id)) {
1279
+ const instance = new ChannelInstance(this.client, this.baseClient, id);
1280
+ this.channelInstances.set(id, instance);
1281
+ return instance;
1282
+ }
1283
+ return this.channelInstances.get(id);
1284
+ }
1285
+ /**
1286
+ * Remove uma instância de canal.
1287
+ */
1288
+ removeChannelInstance(channelId) {
1289
+ if (this.channelInstances.has(channelId)) {
1290
+ const instance = this.channelInstances.get(channelId);
1291
+ instance?.removeAllListeners();
1292
+ this.channelInstances.delete(channelId);
1293
+ console.log(`Inst\xE2ncia do canal ${channelId} removida.`);
1294
+ } else {
1295
+ console.warn(
1296
+ `Tentativa de remover uma inst\xE2ncia inexistente: ${channelId}`
1239
1297
  );
1240
1298
  }
1241
- return new ChannelInstance(this.client, this.baseClient, id, app);
1299
+ }
1300
+ /**
1301
+ * Propaga eventos do WebSocket para o canal correspondente.
1302
+ */
1303
+ propagateEventToChannel(event) {
1304
+ if ("channel" in event && event.channel?.id) {
1305
+ const instance = this.channelInstances.get(event.channel.id);
1306
+ if (instance) {
1307
+ instance.emitEvent(event);
1308
+ } else {
1309
+ console.warn(
1310
+ `Nenhuma inst\xE2ncia encontrada para o canal ${event.channel.id}`
1311
+ );
1312
+ }
1313
+ }
1242
1314
  }
1243
1315
  /**
1244
1316
  * Origina um canal físico diretamente, sem uma instância de `ChannelInstance`.
@@ -1466,143 +1538,67 @@ var Endpoints = class {
1466
1538
 
1467
1539
  // src/ari-client/resources/playbacks.ts
1468
1540
  var import_events2 = require("events");
1469
- var PlaybackInstance = class extends import_events2.EventEmitter {
1470
- // Garantimos que o ID esteja disponível
1471
- constructor(client, baseClient, playbackId = `playback-${Date.now()}`, app) {
1472
- super();
1541
+ var PlaybackInstance = class {
1542
+ constructor(client, baseClient, playbackId = `playback-${Date.now()}`) {
1473
1543
  this.client = client;
1474
1544
  this.baseClient = baseClient;
1475
1545
  this.playbackId = playbackId;
1476
- this.app = app;
1477
- this.id = playbackId || `playback-${Date.now()}`;
1478
- const wsClients = this.client.getWebSocketClients();
1479
- const wsClient = Array.from(wsClients.values()).find(
1480
- (client2) => client2.isConnected()
1481
- );
1482
- if (!wsClient) {
1483
- throw new Error(
1484
- `Nenhum WebSocket conectado dispon\xEDvel para o playback: ${this.id}`
1485
- );
1486
- }
1487
- try {
1488
- wsClient.addScopedMessageListener(
1489
- this.id,
1490
- this.app,
1491
- (event) => {
1492
- if (isPlaybackEvent(event, this.id)) {
1493
- const playbackEventType = event.type;
1494
- this.emit(playbackEventType, event);
1495
- }
1496
- }
1497
- );
1498
- } catch (error) {
1499
- console.error(
1500
- `Erro ao adicionar listener escopado para playback '${this.id}':`,
1501
- error
1502
- );
1503
- throw error;
1504
- }
1505
- this.on("removeListener", (eventName) => {
1506
- if (this.eventNames().length === 0 || this.listenerCount(eventName) === 0) {
1507
- wsClient.removeScopedMessageListeners(this.id, this.app);
1508
- console.log(
1509
- `Listeners escopados removidos para playback '${this.id}'.`
1510
- );
1511
- }
1512
- });
1546
+ this.id = playbackId;
1513
1547
  }
1548
+ eventEmitter = new import_events2.EventEmitter();
1514
1549
  playbackData = null;
1515
1550
  id;
1516
1551
  /**
1517
- * Adiciona um listener para eventos de playback.
1518
- */
1519
- /**
1520
- * Adiciona um listener para eventos de playback.
1552
+ * Registra um listener para eventos específicos deste playback.
1521
1553
  */
1554
+ on(event, listener) {
1555
+ const wrappedListener = (data) => {
1556
+ if ("playback" in data && data.playback?.id === this.id) {
1557
+ listener(data);
1558
+ }
1559
+ };
1560
+ this.eventEmitter.on(event, wrappedListener);
1561
+ }
1522
1562
  /**
1523
- * Adiciona um listener para eventos de playback.
1563
+ * Registra um listener único para eventos específicos deste playback.
1524
1564
  */
1525
- on(event, callback) {
1526
- const wsClient = this.client.getWebSocketClients().get(this.app);
1527
- if (!wsClient) {
1528
- throw new Error(
1529
- `Nenhum WebSocket conectado dispon\xEDvel para '${this.app}'.`
1530
- );
1531
- }
1532
- const scopedEvent = `${this.app}:Playback:${this.id}:${event}`;
1533
- const existingListeners = wsClient.listeners(scopedEvent);
1534
- if (existingListeners.some((listener) => listener === callback)) {
1535
- console.warn(`Listener j\xE1 registrado para o evento '${scopedEvent}'.`);
1536
- return this;
1537
- }
1538
- wsClient.on(scopedEvent, callback);
1539
- console.log(`Listener registrado para '${scopedEvent}'.`);
1540
- return this;
1565
+ once(event, listener) {
1566
+ const wrappedListener = (data) => {
1567
+ if ("playback" in data && data.playback?.id === this.id) {
1568
+ listener(data);
1569
+ }
1570
+ };
1571
+ this.eventEmitter.once(event, wrappedListener);
1541
1572
  }
1542
1573
  /**
1543
- * Adiciona um listener para ser chamado apenas uma vez.
1574
+ * Remove um listener para eventos específicos deste playback.
1544
1575
  */
1545
- once(event, callback) {
1546
- const wsClient = this.client.getWebSocketClients().get(this.app);
1547
- if (!wsClient) {
1548
- throw new Error(
1549
- `Nenhum WebSocket conectado dispon\xEDvel para '${this.app}'.`
1550
- );
1551
- }
1552
- const scopedEvent = `${this.app}:Playback:${this.id}:${event}`;
1553
- const existingListeners = wsClient.listeners(scopedEvent);
1554
- if (existingListeners.some((listener2) => listener2 === callback)) {
1555
- console.warn(`Listener j\xE1 registrado para o evento '${scopedEvent}'.`);
1556
- return this;
1576
+ off(event, listener) {
1577
+ if (listener) {
1578
+ this.eventEmitter.off(event, listener);
1579
+ } else {
1580
+ this.eventEmitter.removeAllListeners(event);
1557
1581
  }
1558
- const listener = (data) => {
1559
- callback(data);
1560
- wsClient.off(scopedEvent, listener);
1561
- console.log(`Listener removido ap\xF3s execu\xE7\xE3o para '${scopedEvent}'.`);
1562
- };
1563
- wsClient.on(scopedEvent, listener);
1564
- console.log(`Listener registrado para '${scopedEvent}' (apenas uma vez).`);
1565
- return this;
1566
1582
  }
1567
1583
  /**
1568
- * Remove um listener específico.
1584
+ * Emite eventos internamente para o playback.
1569
1585
  */
1570
- off(event, callback) {
1571
- const wsClient = this.client.getWebSocketClients().get(this.app);
1572
- if (!wsClient) {
1573
- throw new Error(
1574
- `Nenhum WebSocket conectado dispon\xEDvel para '${this.app}'.`
1575
- );
1586
+ emitEvent(event) {
1587
+ if ("playback" in event && event.playback?.id === this.id) {
1588
+ this.eventEmitter.emit(event.type, event);
1576
1589
  }
1577
- const scopedEvent = `${this.app}:Playback:${this.id}:${event}`;
1578
- const existingListeners = wsClient.listeners(scopedEvent);
1579
- if (!existingListeners.some((listener) => listener === callback)) {
1580
- console.warn(
1581
- `Nenhum listener registrado para '${scopedEvent}' que corresponda.`
1582
- );
1583
- return this;
1584
- }
1585
- wsClient.off(scopedEvent, callback);
1586
- console.log(`Listener removido para '${scopedEvent}'.`);
1587
- return this;
1588
1590
  }
1589
1591
  /**
1590
1592
  * Obtém os detalhes do playback.
1591
1593
  */
1592
- async getDetails() {
1593
- if (!this.id && !this.playbackData) {
1594
+ async get() {
1595
+ if (!this.id) {
1594
1596
  throw new Error("Nenhum playback associado a esta inst\xE2ncia.");
1595
1597
  }
1596
- console.log({
1597
- message: "NPM",
1598
- id: `/playbacks/${this.id}`,
1599
- data: this.playbackData
1600
- });
1601
- const details = await this.baseClient.get(
1598
+ this.playbackData = await this.baseClient.get(
1602
1599
  `/playbacks/${this.id}`
1603
1600
  );
1604
- this.playbackData = details;
1605
- return details;
1601
+ return this.playbackData;
1606
1602
  }
1607
1603
  /**
1608
1604
  * Controla o playback.
@@ -1615,133 +1611,84 @@ var PlaybackInstance = class extends import_events2.EventEmitter {
1615
1611
  `/playbacks/${this.id}/control?operation=${operation}`
1616
1612
  );
1617
1613
  }
1614
+ /**
1615
+ * Encerra o playback.
1616
+ */
1618
1617
  async stop() {
1619
1618
  if (!this.id) {
1620
1619
  throw new Error("Nenhum playback associado para encerrar.");
1621
1620
  }
1622
- try {
1623
- await this.baseClient.delete(`/playbacks/${this.id}`);
1624
- console.log(`Playback '${this.id}' parado com sucesso.`);
1625
- } catch (error) {
1626
- if (error.response?.status === 404) {
1627
- console.warn(
1628
- `Playback '${this.id}' n\xE3o encontrado para encerrar (404).`
1629
- );
1630
- return;
1631
- }
1632
- console.error(
1633
- `Erro ao encerrar o playback '${this.id}':`,
1634
- error.message || error
1635
- );
1636
- throw error;
1637
- }
1621
+ await this.baseClient.delete(`/playbacks/${this.id}`);
1622
+ }
1623
+ /**
1624
+ * Remove todos os listeners para este playback.
1625
+ */
1626
+ removeAllListeners() {
1627
+ this.eventEmitter.removeAllListeners();
1638
1628
  }
1639
1629
  };
1640
- var Playbacks = class extends import_events2.EventEmitter {
1630
+ var Playbacks = class {
1641
1631
  constructor(baseClient, client) {
1642
- super();
1643
1632
  this.baseClient = baseClient;
1644
1633
  this.client = client;
1645
1634
  }
1635
+ playbackInstances = /* @__PURE__ */ new Map();
1646
1636
  /**
1647
- * Inicializa uma nova instância de `PlaybackInstance`.
1637
+ * Gerencia instâncias de playback.
1648
1638
  */
1649
- Playback({ id, app }) {
1650
- if (!app) {
1651
- throw new Error(
1652
- "O nome do aplicativo (app) \xE9 obrigat\xF3rio para criar um Channel."
1653
- );
1639
+ Playback({ id }) {
1640
+ if (!id) {
1641
+ const instance = new PlaybackInstance(this.client, this.baseClient);
1642
+ this.playbackInstances.set(instance.id, instance);
1643
+ return instance;
1654
1644
  }
1655
- const playbackId = id || `playback-${Date.now()}`;
1656
- return new PlaybackInstance(this.client, this.baseClient, playbackId, app);
1657
- }
1658
- /**
1659
- * Obtém os clientes WebSocket disponíveis.
1660
- */
1661
- getWebSocketClients() {
1662
- return this.client.getWebSocketClients();
1663
- }
1664
- /**
1665
- * Retrieves details of a specific playback.
1666
- *
1667
- * @param playbackId - The unique identifier of the playback.
1668
- * @returns A promise that resolves to a Playback object containing the details of the specified playback.
1669
- */
1670
- async getDetails(playbackId) {
1671
- return this.baseClient.get(`/playbacks/${playbackId}`);
1645
+ if (!this.playbackInstances.has(id)) {
1646
+ const instance = new PlaybackInstance(this.client, this.baseClient, id);
1647
+ this.playbackInstances.set(id, instance);
1648
+ return instance;
1649
+ }
1650
+ return this.playbackInstances.get(id);
1672
1651
  }
1673
1652
  /**
1674
- * Controls a specific playback by performing various operations such as pause, resume, restart, reverse, forward, or stop.
1675
- *
1676
- * @param playbackId - The unique identifier of the playback to control.
1677
- * @param operation - The operation to perform on the playback. Possible values are:
1678
- * - "pause": Pauses the playback.
1679
- * - "unpause": Resumes a paused playback.
1680
- * - "restart": Restarts the playback from the beginning.
1681
- * - "reverse": Reverses the playback direction.
1682
- * - "forward": Moves the playback forward.
1683
- * - "stop": Stops the playback.
1684
- * @returns A promise that resolves when the control operation is successfully executed.
1653
+ * Remove uma instância de playback.
1685
1654
  */
1686
- async control(playbackId, operation) {
1687
- await this.baseClient.post(
1688
- `/playbacks/${playbackId}/control?operation=${operation}`
1689
- );
1655
+ removePlaybackInstance(playbackId) {
1656
+ if (this.playbackInstances.has(playbackId)) {
1657
+ const instance = this.playbackInstances.get(playbackId);
1658
+ instance?.removeAllListeners();
1659
+ this.playbackInstances.delete(playbackId);
1660
+ }
1690
1661
  }
1691
1662
  /**
1692
- * Stops a specific playback.
1693
- *
1694
- * @param playbackId - The unique identifier of the playback to stop.
1695
- * @returns A promise that resolves when the playback is successfully stopped.
1663
+ * Propaga eventos do WebSocket para o playback correspondente.
1696
1664
  */
1697
- async stop(playbackId) {
1698
- try {
1699
- await this.baseClient.delete(`/playbacks/${playbackId}`);
1700
- console.log(`Playback '${playbackId}' parado com sucesso.`);
1701
- } catch (error) {
1702
- if (error.response?.status === 404) {
1703
- console.warn(
1704
- `Playback '${playbackId}' n\xE3o encontrado para encerrar (404).`
1705
- );
1706
- return;
1665
+ propagateEventToPlayback(event) {
1666
+ if ("playback" in event && event.playback?.id) {
1667
+ const instance = this.playbackInstances.get(event.playback.id);
1668
+ if (instance) {
1669
+ instance.emitEvent(event);
1707
1670
  }
1708
- console.error(
1709
- `Erro ao encerrar o playback '${playbackId}':`,
1710
- error.message || error
1711
- );
1712
- throw error;
1713
1671
  }
1714
1672
  }
1715
1673
  /**
1716
- * Registers a listener for playback events.
1717
- * The listener is triggered for events such as "PlaybackFinished".
1718
- *
1719
- * @param eventType - The type of event to listen for.
1720
- * @param playbackId - The ID of the playback to associate with this listener.
1721
- * @param callback - The callback function to execute when the event occurs.
1674
+ * Obtém detalhes de um playback específico.
1722
1675
  */
1723
- registerListener(eventType, playbackId, callback) {
1724
- this.on(`${eventType}:${playbackId}`, callback);
1676
+ async getDetails(playbackId) {
1677
+ return this.baseClient.get(`/playbacks/${playbackId}`);
1725
1678
  }
1726
1679
  /**
1727
- * Unregisters a listener for playback events.
1728
- *
1729
- * @param eventType - The type of event to stop listening for.
1730
- * @param playbackId - The ID of the playback associated with the listener.
1731
- * @param callback - The callback function to remove.
1680
+ * Controla um playback específico.
1732
1681
  */
1733
- unregisterListener(eventType, playbackId, callback) {
1734
- this.off(`${eventType}:${playbackId}`, callback);
1682
+ async control(playbackId, operation) {
1683
+ const playback = this.Playback({ id: playbackId });
1684
+ await playback.control(operation);
1735
1685
  }
1736
1686
  /**
1737
- * Checks if a listener is already registered for a specific event and playback.
1738
- *
1739
- * @param eventType - The type of event to check.
1740
- * @param playbackId - The playback ID associated with the listener.
1741
- * @returns True if a listener is already registered, false otherwise.
1687
+ * Encerra um playback específico.
1742
1688
  */
1743
- isListenerRegistered(eventType, playbackId) {
1744
- return this.listenerCount(`${eventType}:${playbackId}`) > 0;
1689
+ async stop(playbackId) {
1690
+ const playback = this.Playback({ id: playbackId });
1691
+ await playback.stop();
1745
1692
  }
1746
1693
  };
1747
1694
 
@@ -1781,356 +1728,136 @@ var import_events3 = require("events");
1781
1728
  var import_exponential_backoff = __toESM(require_backoff(), 1);
1782
1729
  var import_ws = __toESM(require("ws"), 1);
1783
1730
  var WebSocketClient = class extends import_events3.EventEmitter {
1784
- /**
1785
- * Creates a new WebSocketClient instance.
1786
- * @param url - The WebSocket server URL to connect to.
1787
- */
1788
- constructor(url) {
1731
+ constructor(baseClient, apps, subscribedEvents, ariClient) {
1789
1732
  super();
1790
- this.url = url;
1733
+ this.baseClient = baseClient;
1734
+ this.apps = apps;
1735
+ this.subscribedEvents = subscribedEvents;
1736
+ this.ariClient = ariClient;
1791
1737
  }
1792
- ws = null;
1793
- isClosedManually = false;
1738
+ ws;
1794
1739
  isReconnecting = false;
1795
- messageListeners = [];
1796
- maxReconnectAttempts = 30;
1797
- reconnectAttempts = 0;
1798
- // Para gerenciar tentativas de reconexão
1799
- scopedListeners = /* @__PURE__ */ new Map();
1800
- async reconnect() {
1801
- console.log("Iniciando processo de reconex\xE3o...");
1802
- const backoffOptions = {
1803
- delayFirstAttempt: false,
1804
- startingDelay: 1e3,
1805
- // 1 segundo inicial
1806
- timeMultiple: 2,
1807
- // Multiplicador exponencial
1808
- maxDelay: 3e4,
1809
- // 30 segundos de atraso máximo
1810
- numOfAttempts: this.maxReconnectAttempts,
1811
- // Limite de tentativas
1812
- jitter: "full",
1813
- retry: (error, attemptNumber) => {
1814
- console.warn(
1815
- `Tentativa ${attemptNumber} de reconex\xE3o falhou: ${error.message}`
1816
- );
1817
- return !this.isClosedManually;
1818
- }
1819
- };
1820
- try {
1821
- await (0, import_exponential_backoff.backOff)(async () => {
1822
- console.log(`Tentando reconectar (#${this.reconnectAttempts + 1})...`);
1823
- await this.connect();
1824
- console.log("Reconex\xE3o bem-sucedida.");
1825
- }, backoffOptions);
1826
- this.reconnectAttempts = 0;
1827
- this.isReconnecting = false;
1828
- } catch (error) {
1829
- console.error(
1830
- `Reconex\xE3o falhou ap\xF3s ${this.maxReconnectAttempts} tentativas.`,
1831
- error
1740
+ maxReconnectAttempts = 10;
1741
+ backOffOptions = {
1742
+ numOfAttempts: 10,
1743
+ // Máximo de tentativas de reconexão
1744
+ startingDelay: 500,
1745
+ // Início com 500ms de atraso
1746
+ maxDelay: 1e4,
1747
+ // Limite máximo de atraso de 10s
1748
+ timeMultiple: 2,
1749
+ // Atraso aumenta exponencialmente
1750
+ jitter: "full",
1751
+ // Randomização para evitar colisões
1752
+ delayFirstAttempt: false,
1753
+ // Não atrase a primeira tentativa
1754
+ retry: (error, attemptNumber) => {
1755
+ console.warn(
1756
+ `Tentativa #${attemptNumber} falhou:`,
1757
+ error.message || error
1832
1758
  );
1833
- this.isReconnecting = false;
1759
+ return true;
1834
1760
  }
1835
- }
1761
+ };
1836
1762
  /**
1837
- * Establishes a connection to the WebSocket server.
1838
- * @returns A Promise that resolves when the connection is established, or rejects if an error occurs.
1839
- * @throws Will throw an error if the connection fails.
1763
+ * Conecta ao WebSocket.
1840
1764
  */
1841
1765
  async connect() {
1842
- if (this.isReconnecting) return;
1843
- return new Promise((resolve, reject) => {
1844
- this.removeAllListeners();
1845
- this.ws = new import_ws.default(this.url);
1846
- this.ws.on("open", () => {
1847
- console.log("WebSocket conectado.");
1848
- this.isClosedManually = false;
1849
- this.isReconnecting = false;
1850
- this.reconnectAttempts = 0;
1851
- console.log(
1852
- `Listeners ativos para 'message': ${this.listenerCount("message")}`
1853
- );
1854
- resolve();
1855
- });
1856
- this.ws.on("error", (err) => {
1857
- console.error("Erro na conex\xE3o WebSocket:", err);
1858
- reject(err);
1859
- });
1860
- this.ws.on("close", (code, reason) => {
1861
- console.warn(`WebSocket desconectado: ${code} - ${reason}`);
1862
- this.emit("close", { code, reason });
1863
- if (!this.isClosedManually) {
1864
- this.reconnect();
1865
- }
1866
- });
1867
- this.ws.on("message", (rawData) => {
1868
- this.handleMessage(rawData);
1869
- });
1870
- });
1871
- }
1872
- /**
1873
- * Checks if the WebSocket connection is currently open.
1874
- * @returns True if the connection is open, false otherwise.
1875
- */
1876
- isConnected() {
1877
- return this.ws?.readyState === import_ws.default.OPEN;
1878
- }
1879
- onMessage(callback) {
1880
- if (!this.messageListeners.includes(callback)) {
1881
- this.messageListeners.push(callback);
1882
- this.on("message", callback);
1883
- console.log("Listener registrado para 'message'.");
1766
+ const { baseUrl, username, password } = this.baseClient.getCredentials();
1767
+ const protocol = baseUrl.startsWith("https") ? "wss" : "ws";
1768
+ const normalizedHost = baseUrl.replace(/^https?:\/\//, "").replace(/\/ari$/, "");
1769
+ const queryParams = new URLSearchParams();
1770
+ queryParams.append("app", this.apps.join(","));
1771
+ if (this.subscribedEvents && this.subscribedEvents.length > 0) {
1772
+ this.subscribedEvents.forEach(
1773
+ (event) => queryParams.append("event", event)
1774
+ );
1884
1775
  } else {
1885
- console.warn("Tentativa de registrar listener duplicado para 'message'.");
1776
+ queryParams.append("subscribeAll", "true");
1886
1777
  }
1778
+ const wsUrl = `${protocol}://${encodeURIComponent(username)}:${encodeURIComponent(password)}@${normalizedHost}/ari/events?${queryParams.toString()}`;
1779
+ console.log("Conectando ao WebSocket em:", wsUrl);
1780
+ return this.initializeWebSocket(wsUrl);
1887
1781
  }
1888
1782
  /**
1889
- * Adds a listener for WebSocket events.
1890
- * @param event - The event type to listen for.
1891
- * @param callback - The function to call when the event occurs.
1892
- * @returns The WebSocketClient instance for chaining.
1783
+ * Inicializa a conexão WebSocket com lógica de reconexão.
1893
1784
  */
1894
- on(event, callback) {
1895
- if (event === "message") {
1896
- const existingListeners = this.listeners(event);
1897
- if (existingListeners.includes(callback)) {
1898
- console.warn(
1899
- `Listener j\xE1 registrado para o evento '${event}'. Ignorando duplicata.`
1900
- );
1901
- return this;
1902
- }
1903
- console.log(
1904
- `Registrando listener para o evento 'message'. Antes: ${this.listenerCount(
1905
- "message"
1906
- )} listener(s).`
1907
- );
1908
- }
1909
- super.on(event, callback);
1910
- if (event === "message") {
1911
- console.log(
1912
- `Listener registrado para 'message'. Depois: ${this.listenerCount(
1913
- "message"
1914
- )} listener(s).`
1915
- );
1916
- this.listListeners("message");
1917
- }
1918
- return this;
1919
- }
1920
- listListeners(event) {
1921
- const listeners = this.listeners(event);
1922
- console.log(`=== Listagem de Listeners para '${event}' ===`);
1923
- listeners.forEach((listener, index) => {
1924
- const scopedKey = Array.from(this.scopedListeners.entries()).find(
1925
- ([, value]) => value === listener
1926
- )?.[0];
1927
- console.log(`Listener #${index + 1}:`, listener.toString());
1928
- if (scopedKey) {
1929
- console.log(` Associado ao escopo: ${scopedKey}`);
1930
- }
1931
- });
1932
- console.log(`Total de listeners para '${event}': ${listeners.length}`);
1785
+ async initializeWebSocket(wsUrl) {
1786
+ return (0, import_exponential_backoff.backOff)(async () => {
1787
+ return new Promise((resolve, reject) => {
1788
+ this.ws = new import_ws.default(wsUrl);
1789
+ this.ws.on("open", () => {
1790
+ console.log("WebSocket conectado com sucesso.");
1791
+ this.isReconnecting = false;
1792
+ this.emit("connected");
1793
+ resolve();
1794
+ });
1795
+ this.ws.on("message", (data) => this.handleMessage(data.toString()));
1796
+ this.ws.on("close", (code) => {
1797
+ console.warn(
1798
+ `WebSocket desconectado com c\xF3digo ${code}. Tentando reconectar...`
1799
+ );
1800
+ if (!this.isReconnecting) {
1801
+ this.reconnect(wsUrl);
1802
+ }
1803
+ });
1804
+ this.ws.on("error", (err) => {
1805
+ console.error("Erro no WebSocket:", err.message);
1806
+ if (!this.isReconnecting) {
1807
+ this.reconnect(wsUrl);
1808
+ }
1809
+ reject(err);
1810
+ });
1811
+ });
1812
+ }, this.backOffOptions);
1933
1813
  }
1934
1814
  /**
1935
- * Removes a specific listener from a WebSocket event.
1936
- * @param event - The event type to remove the listener from.
1937
- * @param callback - The function to remove from the event listeners.
1938
- * @returns The WebSocketClient instance for chaining.
1815
+ * Processa as mensagens recebidas do WebSocket.
1939
1816
  */
1940
- /**
1941
- * Removes a specific listener from a WebSocket event.
1942
- * @param event - The event type to remove the listener from.
1943
- * @param callback - The function to remove from the event listeners.
1944
- * @returns The WebSocketClient instance for chaining.
1945
- */
1946
- off(event, callback) {
1947
- if (event === "message") {
1948
- console.log(
1949
- `Removendo listener para o evento 'message'. Total antes: ${this.listenerCount(
1950
- "message"
1951
- )}`
1952
- );
1953
- }
1954
- super.off(event, callback);
1955
- if (event === "message") {
1956
- console.log(
1957
- `Listener para o evento 'message' removido. Total agora: ${this.listenerCount(
1958
- "message"
1959
- )}`
1960
- );
1961
- }
1962
- return this;
1963
- }
1964
- removeWildcardListeners(id, event, type) {
1965
- console.log(
1966
- `Removendo listeners para '${type}:${id}:${event}' com wildcard no app`
1967
- );
1968
- for (const eventName of this.eventNames()) {
1969
- if (typeof eventName === "string") {
1970
- const eventString = String(eventName);
1971
- if (eventString.includes(`:${type}:${id}:${event}`)) {
1972
- console.log(`Removendo listener para o evento: ${eventString}`);
1973
- this.removeAllListeners(eventName);
1974
- }
1817
+ handleMessage(rawMessage) {
1818
+ try {
1819
+ const event = JSON.parse(rawMessage);
1820
+ if (this.subscribedEvents && !this.subscribedEvents.includes(event.type)) {
1821
+ return;
1975
1822
  }
1976
- }
1977
- }
1978
- /**
1979
- * Removes all listeners for a specific event, or all events if no event is specified.
1980
- * @param event - Optional. The event to remove all listeners from.
1981
- * @returns The WebSocketClient instance for chaining.
1982
- */
1983
- removeAllListeners(event) {
1984
- super.removeAllListeners(event);
1985
- return this;
1986
- }
1987
- /**
1988
- * Adiciona um listener escopado ao evento "message".
1989
- * @param instanceId - Identificador único da instância que está registrando o listener.
1990
- * @param app
1991
- * @param callback - Função de callback para o evento.
1992
- */
1993
- addScopedMessageListener(instanceId, app, callback) {
1994
- const key = `${app}:${instanceId}`;
1995
- if (this.scopedListeners.has(key)) {
1996
- console.warn(
1997
- `Listener escopado j\xE1 existe para '${key}'. Removendo antigo antes de adicionar um novo.`
1998
- );
1999
- this.removeScopedMessageListeners(instanceId, app);
2000
- }
2001
- const scopedListener = (data) => {
2002
- if (data.application === app && (data.type.startsWith("Channel") && "channel" in data && data.channel?.id === instanceId || data.type.startsWith("Playback") && "playbackId" in data && data.playbackId === instanceId)) {
2003
- callback(data);
1823
+ if ("channel" in event && event.channel?.id && this.ariClient) {
1824
+ const instanceChannel = this.ariClient.Channel(event.channel.id);
1825
+ instanceChannel.emitEvent(event);
1826
+ event.instanceChannel = instanceChannel;
2004
1827
  }
2005
- };
2006
- this.scopedListeners.set(key, scopedListener);
2007
- this.on("message", scopedListener);
2008
- console.log(
2009
- `Listener escopado adicionado para '${key}'. Total de listeners: ${this.listenerCount(
2010
- "message"
2011
- )}`
2012
- );
2013
- }
2014
- /**
2015
- * Remove todos os listeners associados a uma instância específica.
2016
- * @param instanceId - Identificador da instância cujos listeners serão removidos.
2017
- * @param app
2018
- */
2019
- removeScopedMessageListeners(instanceId, app) {
2020
- const key = `${app}:${instanceId}`;
2021
- const scopedListener = this.scopedListeners.get(key);
2022
- if (scopedListener) {
2023
- console.log(`Removendo listener escopado para '${key}'.`);
2024
- this.off("message", scopedListener);
2025
- this.scopedListeners.delete(key);
2026
- } else {
2027
- console.warn(`Nenhum listener encontrado para '${key}'.`);
2028
- }
2029
- }
2030
- removeAllScopedListeners() {
2031
- this.scopedListeners.forEach((_listener, key) => {
2032
- console.log(`Removendo listener escopado para '${key}' durante cleanup.`);
2033
- this.removeScopedMessageListeners(key.split(":")[1], key.split(":")[0]);
2034
- });
2035
- console.log("Todos os listeners escopados foram removidos.");
2036
- }
2037
- removeScopedChannelListeners(instanceId, app) {
2038
- const key = `${app}:${instanceId}`;
2039
- const scopedListener = this.scopedListeners.get(key);
2040
- if (scopedListener) {
2041
- console.log(`Removendo listener escopado para o canal '${key}'.`);
2042
- this.off("message", scopedListener);
2043
- this.scopedListeners.delete(key);
2044
- console.log(`Listener de canal removido para '${key}'.`);
2045
- } else {
2046
- console.warn(`Nenhum listener encontrado para o canal '${key}'.`);
2047
- }
2048
- }
2049
- removeScopedPlaybackListeners(playbackId, app) {
2050
- const key = `${app}:${playbackId}`;
2051
- const scopedListener = this.scopedListeners.get(key);
2052
- if (scopedListener) {
2053
- console.log(`Removendo listener escopado para o playback '${key}'.`);
2054
- this.off("message", scopedListener);
2055
- this.scopedListeners.delete(key);
2056
- console.log(`Listener de playback removido para '${key}'.`);
2057
- } else {
2058
- console.warn(`Nenhum listener encontrado para o playback '${key}'.`);
2059
- }
2060
- }
2061
- /**
2062
- * Handles incoming WebSocket messages.
2063
- * @param rawData - The raw data received from the WebSocket.
2064
- */
2065
- handleMessage(rawData) {
2066
- try {
2067
- const decodedData = JSON.parse(rawData.toString());
2068
- if (decodedData?.type && decodedData?.application) {
2069
- const scopedEvent = `${decodedData.application}:${decodedData.type}`;
2070
- if ("channel" in decodedData && decodedData.channel?.id) {
2071
- const channelScopedEvent = `${decodedData.application}:Channel:${decodedData.channel.id}:${decodedData.type}`;
2072
- this.emit(channelScopedEvent, decodedData);
2073
- if (decodedData.type === "ChannelDestroyed") {
2074
- console.log(`Canal destru\xEDdo detectado: ${decodedData.channel.id}`);
2075
- this.removeScopedChannelListeners(
2076
- decodedData.channel.id,
2077
- decodedData.application
2078
- );
2079
- }
2080
- } else if ("playback" in decodedData && decodedData.playback?.id) {
2081
- const playbackScopedEvent = `${decodedData.application}:Playback:${decodedData.playback.id}:${decodedData.type}`;
2082
- this.emit(playbackScopedEvent, decodedData);
2083
- if (decodedData.type === "PlaybackFinished") {
2084
- console.log(`Playback conclu\xEDdo: ${decodedData.playback.id}`);
2085
- this.removeScopedPlaybackListeners(
2086
- decodedData.playback.id,
2087
- decodedData.application
2088
- );
2089
- }
2090
- }
2091
- this.emit(scopedEvent, decodedData);
2092
- } else {
2093
- console.warn(
2094
- "Mensagem recebida sem tipo ou aplica\xE7\xE3o v\xE1lida:",
2095
- decodedData
2096
- );
1828
+ if ("playback" in event && event.playback?.id && this.ariClient) {
1829
+ const instancePlayback = this.ariClient.Playback(event.playback.id);
1830
+ instancePlayback.emitEvent(event);
1831
+ event.instancePlayback = instancePlayback;
2097
1832
  }
1833
+ this.emit(event.type, event);
2098
1834
  } catch (err) {
2099
- console.error("Erro ao decodificar mensagem do WebSocket:", err);
1835
+ console.error("Erro ao processar mensagem WebSocket:", err);
1836
+ this.emit("error", new Error("Falha ao decodificar mensagem WebSocket."));
2100
1837
  }
2101
1838
  }
2102
1839
  /**
2103
- * Sends data through the WebSocket connection.
2104
- * @param data - The data to send.
2105
- * @throws Will throw an error if the WebSocket is not connected.
1840
+ * Tenta reconectar ao WebSocket.
2106
1841
  */
2107
- send(data) {
2108
- if (!this.ws || this.ws.readyState !== import_ws.default.OPEN) {
2109
- throw new Error("WebSocket n\xE3o est\xE1 conectado.");
2110
- }
2111
- this.ws.send(data, (err) => {
2112
- if (err) {
2113
- console.error("Erro ao enviar dados pelo WebSocket:", err);
1842
+ reconnect(wsUrl) {
1843
+ this.isReconnecting = true;
1844
+ console.log("Iniciando tentativa de reconex\xE3o...");
1845
+ this.removeAllListeners();
1846
+ (0, import_exponential_backoff.backOff)(() => this.initializeWebSocket(wsUrl), this.backOffOptions).catch(
1847
+ (err) => {
1848
+ console.error(
1849
+ "Falha ao reconectar ap\xF3s v\xE1rias tentativas:",
1850
+ err.message || err
1851
+ );
2114
1852
  }
2115
- });
1853
+ );
2116
1854
  }
2117
1855
  /**
2118
- * Closes the WebSocket connection manually.
1856
+ * Fecha o WebSocket manualmente.
2119
1857
  */
2120
1858
  close() {
2121
- if (this.ws) {
2122
- this.isClosedManually = true;
2123
- this.ws.close();
2124
- this.ws = null;
2125
- console.log(this.scopedListeners);
2126
- this.scopedListeners.forEach((_listener, key) => {
2127
- console.log(`Removendo listener escopado para '${key}' ao fechar.`);
2128
- this.removeScopedMessageListeners(key.split(":")[1], key.split(":")[0]);
2129
- });
2130
- this.scopedListeners.clear();
2131
- this.removeAllListeners("message");
2132
- console.log("WebSocket fechado manualmente e todos os listeners limpos.");
2133
- }
1859
+ this.ws?.close();
1860
+ this.ws = void 0;
2134
1861
  }
2135
1862
  };
2136
1863
 
@@ -2150,35 +1877,8 @@ var AriClient = class {
2150
1877
  this.asterisk = new Asterisk(this.baseClient);
2151
1878
  this.bridges = new Bridges(this.baseClient);
2152
1879
  }
2153
- eventListeners = /* @__PURE__ */ new Map();
2154
- wsClients = /* @__PURE__ */ new Map();
2155
- // Map para armazenar conexões por app
2156
1880
  baseClient;
2157
- isReconnecting = /* @__PURE__ */ new Map();
2158
- // Estado de reconexão por app
2159
- webSocketReady = /* @__PURE__ */ new Map();
2160
- // Rastreamento do estado de cada conexão
2161
- channelInstances = /* @__PURE__ */ new Map();
2162
- pendingListeners = [];
2163
- processPendingListeners() {
2164
- for (const [app, wsClient] of this.wsClients.entries()) {
2165
- if (!wsClient.isConnected()) {
2166
- console.warn(`WebSocket para o app '${app}' ainda n\xE3o est\xE1 conectado.`);
2167
- continue;
2168
- }
2169
- while (this.pendingListeners.length > 0) {
2170
- const { event, callback } = this.pendingListeners.shift();
2171
- if (wsClient.listenerCount(event) > 0) {
2172
- console.log(
2173
- `Listener j\xE1 registrado para o evento '${event}' no app '${app}'. Ignorando duplicata.`
2174
- );
2175
- continue;
2176
- }
2177
- console.log(`Registrando listener para '${app}' no evento: ${event}`);
2178
- wsClient.on(event, callback);
2179
- }
2180
- }
2181
- }
1881
+ webSocketClient;
2182
1882
  channels;
2183
1883
  endpoints;
2184
1884
  applications;
@@ -2186,370 +1886,58 @@ var AriClient = class {
2186
1886
  sounds;
2187
1887
  asterisk;
2188
1888
  bridges;
2189
- // Getter para wsClients
2190
- getWebSocketClients() {
2191
- return this.wsClients;
2192
- }
2193
- /**
2194
- * Registra listeners globais para eventos de WebSocket.
2195
- */
2196
- on(eventType, callback, app) {
2197
- if (!app) {
2198
- throw new Error(
2199
- "O nome do app \xE9 obrigat\xF3rio para registrar um listener de evento."
2200
- );
2201
- }
2202
- console.log(
2203
- `Registrando listener para evento '${eventType}' no app '${app}'.`
2204
- );
2205
- const callbackKey = `${app}:${eventType}`;
2206
- if (this.eventListeners.has(callbackKey)) {
2207
- console.log(
2208
- `Listener para evento '${eventType}' j\xE1 est\xE1 registrado no app '${app}'. Ignorando duplicata.`
2209
- );
2210
- return;
2211
- }
2212
- const wrappedCallback = (event) => {
2213
- if (isChannelEvent(event)) {
2214
- const channelId = event.channel.id;
2215
- if (channelId) {
2216
- if (!this.channelInstances.has(channelId)) {
2217
- const channelInstance = this.createChannelInstance(channelId, app);
2218
- this.channelInstances.set(channelId, channelInstance);
2219
- }
2220
- event.instanceChannel = this.channelInstances.get(channelId);
2221
- event.instanceChannel?.emit(event.type, event);
2222
- }
2223
- }
2224
- callback(event);
2225
- };
2226
- const wsClient = this.wsClients.get(app);
2227
- if (wsClient) {
2228
- const scopedEvent = `${app}:${eventType}`;
2229
- wsClient.on(scopedEvent, wrappedCallback);
2230
- this.eventListeners.set(callbackKey, wrappedCallback);
2231
- console.log(
2232
- `Listener para evento '${eventType}' registrado com sucesso no app '${app}'.`
2233
- );
2234
- } else {
2235
- console.warn(`WebSocket para o app '${app}' n\xE3o est\xE1 conectado.`);
2236
- }
2237
- }
2238
- removeListener(eventType, app) {
2239
- if (app) {
2240
- const callbackKey = `${eventType}-${app}`;
2241
- const callback = this.eventListeners.get(callbackKey);
2242
- if (callback) {
2243
- const wsClient = this.wsClients.get(app);
2244
- if (wsClient) {
2245
- wsClient.off(eventType, callback);
2246
- console.log(
2247
- `Listener para evento '${eventType}' removido do app '${app}'`
2248
- );
2249
- }
2250
- this.eventListeners.delete(callbackKey);
2251
- }
2252
- } else {
2253
- for (const [_app, wsClient] of this.wsClients.entries()) {
2254
- const callbackKey = `${eventType}-${_app}`;
2255
- const callback = this.eventListeners.get(callbackKey);
2256
- if (callback) {
2257
- wsClient.off(eventType, callback);
2258
- console.log(
2259
- `Listener global para evento '${eventType}' removido do app '${_app}'`
2260
- );
2261
- this.eventListeners.delete(callbackKey);
2262
- }
2263
- }
2264
- }
2265
- }
2266
- /**
2267
- * Type guard to check if the given data object contains a valid channel property.
2268
- * This function is used to narrow down the type of the data object and ensure it has a channel with an id.
2269
- *
2270
- * @param data - The object to be checked for the presence of a channel property.
2271
- * @returns A type predicate indicating whether the data object contains a valid channel.
2272
- * Returns true if the data object has a channel property with an id, false otherwise.
2273
- */
2274
- hasChannel(data) {
2275
- return data?.channel?.id !== void 0;
2276
- }
2277
- // Método para criar uma instância de ChannelInstance
2278
- createChannelInstance(channelId, app) {
2279
- return this.channels.Channel({
2280
- id: channelId,
2281
- app
2282
- });
2283
- }
2284
- handleWebSocketEvent(event, app) {
2285
- console.log("Evento recebido no WebSocket:", event.type, event);
2286
- const { type, ...data } = event;
2287
- if (this.hasChannel(data)) {
2288
- const channelId = data.channel.id;
2289
- if (channelId) {
2290
- if (!this.channelInstances.has(channelId)) {
2291
- const channelInstance2 = this.createChannelInstance(channelId, app);
2292
- this.channelInstances.set(channelId, channelInstance2);
2293
- }
2294
- const channelInstance = this.channelInstances.get(channelId);
2295
- if (channelInstance) {
2296
- data.channel = { ...data.channel, ...channelInstance };
2297
- }
2298
- }
2299
- }
2300
- const listener = this.eventListeners.get(type);
2301
- if (listener) {
2302
- listener(data);
2303
- }
2304
- }
2305
- /**
2306
- * Connects to the ARI WebSocket for a specific application.
2307
- * This function establishes a WebSocket connection to the Asterisk ARI, sets up event listeners,
2308
- * and ensures the application is registered. It uses an exponential backoff strategy for connection attempts.
2309
- *
2310
- * @param app - The name of the application to connect to. This is required and used to identify the application in ARI.
2311
- * @param subscribedEvents - Optional array of WebSocketEventType to subscribe to specific events.
2312
- * If not provided or empty, it subscribes to all events.
2313
- * @returns A Promise that resolves when the WebSocket connection is successfully established and the application is registered.
2314
- * @throws Error if the 'app' parameter is not provided, or if connection attempts fail after multiple retries.
2315
- */
2316
1889
  /**
2317
- * Connects to the ARI WebSocket for one or more applications.
2318
- * Establishes a WebSocket connection for each app and subscribes to events.
2319
- *
2320
- * @param apps - Array of application names to connect to.
2321
- * @param subscribedEvents - Optional array of events to subscribe to.
2322
- * If not provided or empty, subscribes to all events.
2323
- * @returns A Promise that resolves when all connections are established.
1890
+ * Inicializa uma conexão WebSocket.
2324
1891
  */
2325
1892
  async connectWebSocket(apps, subscribedEvents) {
2326
- if (!Array.isArray(apps) || apps.length === 0) {
2327
- throw new Error("\xC9 necess\xE1rio fornecer pelo menos um aplicativo.");
1893
+ if (this.webSocketClient) {
1894
+ console.warn("WebSocket j\xE1 est\xE1 conectado.");
1895
+ return;
2328
1896
  }
2329
- const uniqueApps = [...new Set(apps)];
2330
- return Promise.all(
2331
- uniqueApps.map(async (app) => {
2332
- if (this.wsClients.has(app)) {
2333
- console.log(`Conex\xE3o WebSocket para '${app}' j\xE1 existe. Ignorando.`);
2334
- return;
2335
- }
2336
- await this.connectSingleWebSocket(app, subscribedEvents);
2337
- })
1897
+ this.webSocketClient = new WebSocketClient(
1898
+ this.baseClient,
1899
+ apps,
1900
+ subscribedEvents,
1901
+ this
2338
1902
  );
1903
+ await this.webSocketClient.connect();
2339
1904
  }
2340
1905
  /**
2341
- * Establishes a single WebSocket connection for a given app.
1906
+ * Adiciona um listener para eventos do WebSocket.
2342
1907
  */
2343
- async connectSingleWebSocket(app, subscribedEvents) {
2344
- if (!app) {
2345
- throw new Error("O nome do aplicativo \xE9 obrigat\xF3rio.");
2346
- }
2347
- if (this.webSocketReady.get(app)) {
2348
- console.log(`Conex\xE3o WebSocket para '${app}' j\xE1 est\xE1 ativa.`);
2349
- return this.webSocketReady.get(app);
2350
- }
2351
- const protocol = this.config.secure ? "wss" : "ws";
2352
- const eventsParam = subscribedEvents && subscribedEvents.length > 0 ? `&event=${subscribedEvents.join(",")}` : "&subscribeAll=true";
2353
- const wsUrl = `${protocol}://${encodeURIComponent(
2354
- this.config.username
2355
- )}:${encodeURIComponent(this.config.password)}@${this.config.host}:${this.config.port}/ari/events?app=${app}${eventsParam}`;
2356
- const backoffOptions = {
2357
- delayFirstAttempt: false,
2358
- startingDelay: 1e3,
2359
- timeMultiple: 2,
2360
- maxDelay: 3e4,
2361
- numOfAttempts: 10,
2362
- jitter: "full",
2363
- retry: (error, attemptNumber) => {
2364
- console.warn(
2365
- `Tentativa ${attemptNumber} falhou para '${app}': ${error.message}`
2366
- );
2367
- return !this.wsClients.has(app) || !this.wsClients.get(app)?.isConnected();
2368
- }
2369
- };
2370
- const webSocketPromise = new Promise(async (resolve, reject) => {
2371
- try {
2372
- if (this.isReconnecting.get(app)) {
2373
- console.warn(`J\xE1 est\xE1 tentando reconectar para o app '${app}'.`);
2374
- return;
2375
- }
2376
- this.isReconnecting.set(app, true);
2377
- const wsClient = new WebSocketClient(wsUrl);
2378
- wsClient.setMaxListeners(30);
2379
- await (0, import_exponential_backoff2.backOff)(async () => {
2380
- if (!wsClient) {
2381
- throw new Error("WebSocketClient instance is null.");
2382
- }
2383
- await wsClient.connect();
2384
- console.log(`WebSocket conectado para o app: ${app}`);
2385
- this.integrateWebSocketEvents(app, wsClient);
2386
- await this.ensureAppRegistered(app);
2387
- this.wsClients.set(app, wsClient);
2388
- this.processPendingListeners();
2389
- }, backoffOptions);
2390
- resolve();
2391
- } catch (error) {
2392
- console.error(`Erro ao conectar WebSocket para '${app}':`, error);
2393
- reject(error);
2394
- } finally {
2395
- this.isReconnecting.delete(app);
2396
- }
2397
- });
2398
- this.webSocketReady.set(app, webSocketPromise);
2399
- return webSocketPromise;
1908
+ on(event, listener) {
1909
+ this.webSocketClient?.on(event, listener);
2400
1910
  }
2401
1911
  /**
2402
- * Integrates WebSocket events with playback listeners.
1912
+ * Adiciona um listener único para eventos do WebSocket.
2403
1913
  */
2404
- integrateWebSocketEvents(app, wsClient) {
2405
- if (!wsClient) {
2406
- throw new Error(
2407
- `WebSocket client para o app '${app}' n\xE3o est\xE1 conectado.`
2408
- );
2409
- }
2410
- console.log(
2411
- "**************** INTEGRANDO EVENTOS DE PLAYBACK **************"
2412
- );
2413
- console.log(wsClient.eventNames());
2414
- console.log(
2415
- "**************** INTEGRANDO EVENTOS DE PLAYBACK **************"
2416
- );
2417
- if (wsClient.listenerCount("PlaybackStarted") > 0) {
2418
- return;
2419
- }
2420
- const eventHandlers = {
2421
- PlaybackStarted: (data) => {
2422
- if ("playbackId" in data) {
2423
- }
2424
- },
2425
- PlaybackFinished: (data) => {
2426
- if ("playbackId" in data) {
2427
- }
2428
- },
2429
- ChannelDtmfReceived: (data) => {
2430
- if (data.type === "ChannelDtmfReceived" && "digit" in data) {
2431
- } else {
2432
- console.warn(
2433
- `[${app}] Evento inesperado em ChannelDtmfReceived`,
2434
- data
2435
- );
2436
- }
2437
- },
2438
- ChannelStateChange: (data) => {
2439
- if ("channel" in data) {
2440
- }
2441
- }
2442
- };
2443
- for (const [eventType, handler] of Object.entries(eventHandlers)) {
2444
- if (handler) {
2445
- wsClient.on(eventType, handler);
2446
- }
2447
- }
2448
- console.log(`[${app}] Todos os eventos do WebSocket foram registrados.`);
1914
+ once(event, listener) {
1915
+ this.webSocketClient?.once(event, listener);
2449
1916
  }
2450
1917
  /**
2451
- * Ensures the ARI application is registered.
2452
- *
2453
- * @param app - The application name to ensure is registered.
2454
- * @returns {Promise<void>}
1918
+ * Remove um listener para eventos do WebSocket.
2455
1919
  */
2456
- async ensureAppRegistered(app) {
2457
- try {
2458
- const apps = await this.baseClient.get("/applications");
2459
- const appExists = apps.some((a) => a.name === app);
2460
- if (!appExists) {
2461
- console.log(`Registrando o aplicativo ARI: ${app}`);
2462
- await this.baseClient.post("/applications", { app });
2463
- console.log(`Aplicativo ${app} registrado com sucesso.`);
2464
- } else {
2465
- console.log(`Aplicativo ${app} j\xE1 est\xE1 registrado.`);
2466
- }
2467
- } catch (error) {
2468
- console.error(`Erro ao garantir o registro do aplicativo ${app}:`, error);
2469
- throw error;
2470
- }
2471
- }
2472
- logListeners() {
2473
- this.wsClients.forEach((wsClient, app) => {
2474
- console.log(`Listeners registrados para '${app}':`);
2475
- wsClient.eventNames().forEach((event) => {
2476
- const eventName = String(event);
2477
- const count = wsClient.listenerCount(event);
2478
- console.log(` Evento '${eventName}': ${count} listener(s)`);
2479
- });
2480
- });
1920
+ off(event, listener) {
1921
+ this.webSocketClient?.off(event, listener);
2481
1922
  }
2482
1923
  /**
2483
- * Checks if the WebSocket connection is active.
2484
- *
2485
- * @returns {boolean} True if connected, false otherwise.
1924
+ * Fecha a conexão WebSocket.
2486
1925
  */
2487
- anyWebSocketConnected() {
2488
- return Array.from(this.wsClients.values()).every(
2489
- (wsClient) => wsClient.isConnected()
2490
- );
2491
- }
2492
- isWebSocketConnected(app) {
2493
- const wsClient = this.wsClients.get(app);
2494
- return wsClient ? wsClient.isConnected() : false;
2495
- }
2496
- /**
2497
- * Closes the WebSocket connection and removes all associated listeners.
2498
- *
2499
- * This function terminates the active WebSocket connection if one exists,
2500
- * and cleans up by removing all event listeners attached to it. After calling
2501
- * this function, the WebSocket client will be set to null, effectively
2502
- * ending the connection and preparing for potential future connections.
2503
- *
2504
- * @returns {void} This function doesn't return a value.
2505
- */
2506
- closeWebSocket(app) {
2507
- const wsClient = this.wsClients.get(app);
2508
- if (wsClient) {
2509
- wsClient.close();
2510
- this.wsClients.delete(app);
2511
- console.log(`WebSocket para o app '${app}' foi fechado.`);
2512
- } else {
2513
- console.warn(`WebSocket para o app '${app}' n\xE3o encontrado.`);
2514
- }
2515
- }
2516
- closeAllWebSockets() {
2517
- this.wsClients.forEach((wsClient, app) => {
2518
- wsClient.close();
2519
- console.log(`WebSocket para o app '${app}' foi fechado.`);
2520
- });
2521
- this.wsClients.clear();
2522
- console.log("Todos os WebSockets foram fechados.");
1926
+ closeWebSocket() {
1927
+ this.webSocketClient?.close();
1928
+ this.webSocketClient = void 0;
2523
1929
  }
2524
1930
  /**
2525
1931
  * Inicializa uma nova instância de `ChannelInstance` para manipular canais localmente.
2526
- *
2527
- * @param channelId - O ID do canal, se disponível. Caso contrário, a instância será para um canal ainda não criado.
2528
- * @param app
2529
- * @returns Uma instância de `ChannelInstance` vinculada ao cliente atual.
2530
1932
  */
2531
- Channel(channelId, app) {
2532
- if (!app) {
2533
- throw new Error(
2534
- "O nome do aplicativo (app) \xE9 obrigat\xF3rio para criar um Channel."
2535
- );
2536
- }
2537
- return this.channels.Channel({ id: channelId, app });
1933
+ Channel(channelId) {
1934
+ return this.channels.Channel({ id: channelId });
2538
1935
  }
2539
1936
  /**
2540
1937
  * Inicializa uma nova instância de `PlaybackInstance` para manipular playbacks.
2541
- *
2542
- * @param playbackId - O ID do playback, se disponível. Caso contrário, a instância será para um playback ainda não inicializado.
2543
- * @param app
2544
- * @returns Uma instância de `PlaybackInstance` vinculada ao cliente atual.
2545
1938
  */
2546
- Playback(playbackId, app) {
2547
- if (!app) {
2548
- throw new Error(
2549
- "O nome do aplicativo (app) \xE9 obrigat\xF3rio para criar um Channel."
2550
- );
2551
- }
2552
- return this.playbacks.Playback({ id: playbackId, app });
1939
+ Playback(playbackId, _app) {
1940
+ return this.playbacks.Playback({ id: playbackId });
2553
1941
  }
2554
1942
  };
2555
1943
  // Annotate the CommonJS export names for ESM import in node: