@ipcom/asterisk-ari 0.0.136 → 0.0.138

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