@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/cjs/index.cjs +435 -1042
- package/dist/cjs/index.cjs.map +4 -4
- package/dist/esm/index.js +435 -1042
- package/dist/esm/index.js.map +4 -4
- package/dist/types/ari-client/ariClient.d.ts +19 -83
- package/dist/types/ari-client/ariClient.d.ts.map +1 -1
- package/dist/types/ari-client/baseClient.d.ts +37 -5
- package/dist/types/ari-client/baseClient.d.ts.map +1 -1
- package/dist/types/ari-client/interfaces/events.types.d.ts +42 -36
- package/dist/types/ari-client/interfaces/events.types.d.ts.map +1 -1
- package/dist/types/ari-client/resources/asterisk.d.ts +3 -3
- package/dist/types/ari-client/resources/asterisk.d.ts.map +1 -1
- package/dist/types/ari-client/resources/channels.d.ts +36 -17
- package/dist/types/ari-client/resources/channels.d.ts.map +1 -1
- package/dist/types/ari-client/resources/playbacks.d.ts +36 -74
- package/dist/types/ari-client/resources/playbacks.d.ts.map +1 -1
- package/dist/types/ari-client/websocketClient.d.ts +21 -76
- package/dist/types/ari-client/websocketClient.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -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
|
|
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,
|
|
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
|
-
|
|
498
|
-
return [4,
|
|
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 =
|
|
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
|
-
|
|
602
|
-
|
|
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
|
-
|
|
614
|
-
|
|
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
|
-
|
|
623
|
-
|
|
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
|
-
|
|
632
|
-
|
|
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
|
-
|
|
639
|
-
|
|
640
|
-
|
|
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
|
|
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
|
|
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
|
|
826
|
+
async manage(moduleName, action) {
|
|
723
827
|
const url = `/asterisk/modules/${moduleName}`;
|
|
724
828
|
switch (action) {
|
|
725
829
|
case "load":
|
|
@@ -911,118 +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
|
|
1020
|
+
var ChannelInstance = class {
|
|
925
1021
|
// ID do canal
|
|
926
|
-
constructor(client, baseClient, channelId = `channel-${Date.now()}
|
|
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
|
-
wsClient2.removeScopedChannelListeners(this.id, this.app);
|
|
957
|
-
} else {
|
|
958
|
-
console.warn(
|
|
959
|
-
`WebSocketClient n\xE3o encontrado para o app '${this.app}'.`
|
|
960
|
-
);
|
|
961
|
-
}
|
|
962
|
-
});
|
|
963
1027
|
}
|
|
1028
|
+
eventEmitter = new import_events.EventEmitter();
|
|
964
1029
|
channelData = null;
|
|
965
1030
|
id;
|
|
966
1031
|
/**
|
|
967
|
-
*
|
|
1032
|
+
* Registra um listener para eventos específicos deste canal.
|
|
968
1033
|
*/
|
|
969
|
-
on(event,
|
|
970
|
-
const
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
const scopedEvent = `${this.app}:Channel:${this.id}:${event}`;
|
|
977
|
-
console.log({ metheod: "on", event, scopedEvent });
|
|
978
|
-
const existingListeners = wsClient.listeners(scopedEvent);
|
|
979
|
-
if (existingListeners.includes(callback)) {
|
|
980
|
-
console.warn(`Listener j\xE1 registrado para o evento '${scopedEvent}'.`);
|
|
981
|
-
return this;
|
|
982
|
-
}
|
|
983
|
-
wsClient.on(scopedEvent, callback);
|
|
984
|
-
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);
|
|
985
1041
|
}
|
|
986
1042
|
/**
|
|
987
|
-
*
|
|
1043
|
+
* Registra um listener único para eventos específicos deste canal.
|
|
988
1044
|
*/
|
|
989
|
-
once(event,
|
|
990
|
-
const
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
);
|
|
995
|
-
}
|
|
996
|
-
wsClient.removeWildcardListeners(this.id, event, "Channel");
|
|
997
|
-
const scopedEvent = `${this.app}:Channel:${this.id}:${event}`;
|
|
998
|
-
console.log({ metheod: "once", event, scopedEvent });
|
|
999
|
-
const existingListeners = wsClient.listeners(scopedEvent);
|
|
1000
|
-
if (existingListeners.some((listener2) => listener2 === callback)) {
|
|
1001
|
-
console.warn(`Listener j\xE1 registrado para o evento '${scopedEvent}'.`);
|
|
1002
|
-
return this;
|
|
1003
|
-
}
|
|
1004
|
-
const listener = (data) => {
|
|
1005
|
-
callback(data);
|
|
1006
|
-
wsClient.off(scopedEvent, listener);
|
|
1007
|
-
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
|
+
}
|
|
1008
1050
|
};
|
|
1009
|
-
|
|
1010
|
-
return this;
|
|
1051
|
+
this.eventEmitter.once(event, wrappedListener);
|
|
1011
1052
|
}
|
|
1012
1053
|
/**
|
|
1013
|
-
* Remove um listener
|
|
1054
|
+
* Remove um listener para eventos específicos deste canal.
|
|
1014
1055
|
*/
|
|
1015
|
-
off(event,
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
)
|
|
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
|
+
});
|
|
1021
1064
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
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();
|
|
1026
1087
|
}
|
|
1027
1088
|
async answer() {
|
|
1028
1089
|
await this.baseClient.post(`/channels/${this.id}/answer`);
|
|
@@ -1081,7 +1142,7 @@ var ChannelInstance = class extends import_events.EventEmitter {
|
|
|
1081
1142
|
console.log("Canal n\xE3o inicializado, buscando detalhes...");
|
|
1082
1143
|
this.channelData = await this.getDetails();
|
|
1083
1144
|
}
|
|
1084
|
-
const playback = this.client.Playback(playbackId || v4_default()
|
|
1145
|
+
const playback = this.client.Playback(playbackId || v4_default());
|
|
1085
1146
|
if (!this.channelData?.id) {
|
|
1086
1147
|
throw new Error("N\xE3o foi poss\xEDvel inicializar o canal. ID inv\xE1lido.");
|
|
1087
1148
|
}
|
|
@@ -1089,25 +1150,6 @@ var ChannelInstance = class extends import_events.EventEmitter {
|
|
|
1089
1150
|
`/channels/${this.channelData.id}/play/${playback.id}`,
|
|
1090
1151
|
options
|
|
1091
1152
|
);
|
|
1092
|
-
const wsClient = this.client.getWebSocketClients().get(this.app);
|
|
1093
|
-
if (!wsClient) {
|
|
1094
|
-
throw new Error("WebSocketClient n\xE3o encontrado para o app.");
|
|
1095
|
-
}
|
|
1096
|
-
playback.once("PlaybackFinished", () => {
|
|
1097
|
-
console.log(`PlaybackFinished '${playback.id}' no canal '${this.id}'.`);
|
|
1098
|
-
wsClient.removeScopedPlaybackListeners(playback.id, this.app);
|
|
1099
|
-
});
|
|
1100
|
-
playback.once("PlaybackFinished", async () => {
|
|
1101
|
-
console.log(`Playback conclu\xEDdo: ${playback.id}`);
|
|
1102
|
-
this.client.getWebSocketClients().get(this.app)?.removeScopedPlaybackListeners(playback.id, this.app);
|
|
1103
|
-
});
|
|
1104
|
-
this.once("ChannelDestroyed", () => {
|
|
1105
|
-
console.log(
|
|
1106
|
-
`Canal '${this.id}' foi destru\xEDdo. Removendo listeners associados.`
|
|
1107
|
-
);
|
|
1108
|
-
wsClient.removeScopedChannelListeners(this.id, this.app);
|
|
1109
|
-
wsClient.removeScopedPlaybackListeners(playback.id, this.app);
|
|
1110
|
-
});
|
|
1111
1153
|
return playback;
|
|
1112
1154
|
}
|
|
1113
1155
|
/**
|
|
@@ -1221,19 +1263,54 @@ var ChannelInstance = class extends import_events.EventEmitter {
|
|
|
1221
1263
|
await this.baseClient.delete(`/channels/${this.channelData.id}/hold`);
|
|
1222
1264
|
}
|
|
1223
1265
|
};
|
|
1224
|
-
var Channels = class
|
|
1266
|
+
var Channels = class {
|
|
1225
1267
|
constructor(baseClient, client) {
|
|
1226
|
-
super();
|
|
1227
1268
|
this.baseClient = baseClient;
|
|
1228
1269
|
this.client = client;
|
|
1229
1270
|
}
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
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}`
|
|
1234
1297
|
);
|
|
1235
1298
|
}
|
|
1236
|
-
|
|
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
|
+
}
|
|
1237
1314
|
}
|
|
1238
1315
|
/**
|
|
1239
1316
|
* Origina um canal físico diretamente, sem uma instância de `ChannelInstance`.
|
|
@@ -1461,143 +1538,67 @@ var Endpoints = class {
|
|
|
1461
1538
|
|
|
1462
1539
|
// src/ari-client/resources/playbacks.ts
|
|
1463
1540
|
var import_events2 = require("events");
|
|
1464
|
-
var PlaybackInstance = class
|
|
1465
|
-
|
|
1466
|
-
constructor(client, baseClient, playbackId = `playback-${Date.now()}`, app) {
|
|
1467
|
-
super();
|
|
1541
|
+
var PlaybackInstance = class {
|
|
1542
|
+
constructor(client, baseClient, playbackId = `playback-${Date.now()}`) {
|
|
1468
1543
|
this.client = client;
|
|
1469
1544
|
this.baseClient = baseClient;
|
|
1470
1545
|
this.playbackId = playbackId;
|
|
1471
|
-
this.
|
|
1472
|
-
this.id = playbackId || `playback-${Date.now()}`;
|
|
1473
|
-
const wsClients = this.client.getWebSocketClients();
|
|
1474
|
-
const wsClient = Array.from(wsClients.values()).find(
|
|
1475
|
-
(client2) => client2.isConnected()
|
|
1476
|
-
);
|
|
1477
|
-
if (!wsClient) {
|
|
1478
|
-
throw new Error(
|
|
1479
|
-
`Nenhum WebSocket conectado dispon\xEDvel para o playback: ${this.id}`
|
|
1480
|
-
);
|
|
1481
|
-
}
|
|
1482
|
-
try {
|
|
1483
|
-
wsClient.addScopedMessageListener(
|
|
1484
|
-
this.id,
|
|
1485
|
-
this.app,
|
|
1486
|
-
(event) => {
|
|
1487
|
-
if (isPlaybackEvent(event, this.id)) {
|
|
1488
|
-
const playbackEventType = event.type;
|
|
1489
|
-
this.emit(playbackEventType, event);
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
);
|
|
1493
|
-
} catch (error) {
|
|
1494
|
-
console.error(
|
|
1495
|
-
`Erro ao adicionar listener escopado para playback '${this.id}':`,
|
|
1496
|
-
error
|
|
1497
|
-
);
|
|
1498
|
-
throw error;
|
|
1499
|
-
}
|
|
1500
|
-
this.on("removeListener", (eventName) => {
|
|
1501
|
-
if (this.eventNames().length === 0 || this.listenerCount(eventName) === 0) {
|
|
1502
|
-
wsClient.removeScopedMessageListeners(this.id, this.app);
|
|
1503
|
-
console.log(
|
|
1504
|
-
`Listeners escopados removidos para playback '${this.id}'.`
|
|
1505
|
-
);
|
|
1506
|
-
}
|
|
1507
|
-
});
|
|
1546
|
+
this.id = playbackId;
|
|
1508
1547
|
}
|
|
1548
|
+
eventEmitter = new import_events2.EventEmitter();
|
|
1509
1549
|
playbackData = null;
|
|
1510
1550
|
id;
|
|
1511
1551
|
/**
|
|
1512
|
-
*
|
|
1513
|
-
*/
|
|
1514
|
-
/**
|
|
1515
|
-
* Adiciona um listener para eventos de playback.
|
|
1552
|
+
* Registra um listener para eventos específicos deste playback.
|
|
1516
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
|
+
}
|
|
1517
1562
|
/**
|
|
1518
|
-
*
|
|
1563
|
+
* Registra um listener único para eventos específicos deste playback.
|
|
1519
1564
|
*/
|
|
1520
|
-
|
|
1521
|
-
const
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
const scopedEvent = `${this.app}:Playback:${this.id}:${event}`;
|
|
1528
|
-
const existingListeners = wsClient.listeners(scopedEvent);
|
|
1529
|
-
if (existingListeners.some((listener) => listener === callback)) {
|
|
1530
|
-
console.warn(`Listener j\xE1 registrado para o evento '${scopedEvent}'.`);
|
|
1531
|
-
return this;
|
|
1532
|
-
}
|
|
1533
|
-
wsClient.on(scopedEvent, callback);
|
|
1534
|
-
console.log(`Listener registrado para '${scopedEvent}'.`);
|
|
1535
|
-
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);
|
|
1536
1572
|
}
|
|
1537
1573
|
/**
|
|
1538
|
-
*
|
|
1574
|
+
* Remove um listener para eventos específicos deste playback.
|
|
1539
1575
|
*/
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
);
|
|
1546
|
-
}
|
|
1547
|
-
const scopedEvent = `${this.app}:Playback:${this.id}:${event}`;
|
|
1548
|
-
const existingListeners = wsClient.listeners(scopedEvent);
|
|
1549
|
-
if (existingListeners.some((listener2) => listener2 === callback)) {
|
|
1550
|
-
console.warn(`Listener j\xE1 registrado para o evento '${scopedEvent}'.`);
|
|
1551
|
-
return this;
|
|
1576
|
+
off(event, listener) {
|
|
1577
|
+
if (listener) {
|
|
1578
|
+
this.eventEmitter.off(event, listener);
|
|
1579
|
+
} else {
|
|
1580
|
+
this.eventEmitter.removeAllListeners(event);
|
|
1552
1581
|
}
|
|
1553
|
-
const listener = (data) => {
|
|
1554
|
-
callback(data);
|
|
1555
|
-
wsClient.off(scopedEvent, listener);
|
|
1556
|
-
console.log(`Listener removido ap\xF3s execu\xE7\xE3o para '${scopedEvent}'.`);
|
|
1557
|
-
};
|
|
1558
|
-
wsClient.on(scopedEvent, listener);
|
|
1559
|
-
console.log(`Listener registrado para '${scopedEvent}' (apenas uma vez).`);
|
|
1560
|
-
return this;
|
|
1561
1582
|
}
|
|
1562
1583
|
/**
|
|
1563
|
-
*
|
|
1584
|
+
* Emite eventos internamente para o playback.
|
|
1564
1585
|
*/
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
throw new Error(
|
|
1569
|
-
`Nenhum WebSocket conectado dispon\xEDvel para '${this.app}'.`
|
|
1570
|
-
);
|
|
1586
|
+
emitEvent(event) {
|
|
1587
|
+
if ("playback" in event && event.playback?.id === this.id) {
|
|
1588
|
+
this.eventEmitter.emit(event.type, event);
|
|
1571
1589
|
}
|
|
1572
|
-
const scopedEvent = `${this.app}:Playback:${this.id}:${event}`;
|
|
1573
|
-
const existingListeners = wsClient.listeners(scopedEvent);
|
|
1574
|
-
if (!existingListeners.some((listener) => listener === callback)) {
|
|
1575
|
-
console.warn(
|
|
1576
|
-
`Nenhum listener registrado para '${scopedEvent}' que corresponda.`
|
|
1577
|
-
);
|
|
1578
|
-
return this;
|
|
1579
|
-
}
|
|
1580
|
-
wsClient.off(scopedEvent, callback);
|
|
1581
|
-
console.log(`Listener removido para '${scopedEvent}'.`);
|
|
1582
|
-
return this;
|
|
1583
1590
|
}
|
|
1584
1591
|
/**
|
|
1585
1592
|
* Obtém os detalhes do playback.
|
|
1586
1593
|
*/
|
|
1587
|
-
async
|
|
1588
|
-
if (!this.id
|
|
1594
|
+
async get() {
|
|
1595
|
+
if (!this.id) {
|
|
1589
1596
|
throw new Error("Nenhum playback associado a esta inst\xE2ncia.");
|
|
1590
1597
|
}
|
|
1591
|
-
|
|
1592
|
-
message: "NPM",
|
|
1593
|
-
id: `/playbacks/${this.id}`,
|
|
1594
|
-
data: this.playbackData
|
|
1595
|
-
});
|
|
1596
|
-
const details = await this.baseClient.get(
|
|
1598
|
+
this.playbackData = await this.baseClient.get(
|
|
1597
1599
|
`/playbacks/${this.id}`
|
|
1598
1600
|
);
|
|
1599
|
-
this.playbackData
|
|
1600
|
-
return details;
|
|
1601
|
+
return this.playbackData;
|
|
1601
1602
|
}
|
|
1602
1603
|
/**
|
|
1603
1604
|
* Controla o playback.
|
|
@@ -1610,133 +1611,84 @@ var PlaybackInstance = class extends import_events2.EventEmitter {
|
|
|
1610
1611
|
`/playbacks/${this.id}/control?operation=${operation}`
|
|
1611
1612
|
);
|
|
1612
1613
|
}
|
|
1614
|
+
/**
|
|
1615
|
+
* Encerra o playback.
|
|
1616
|
+
*/
|
|
1613
1617
|
async stop() {
|
|
1614
1618
|
if (!this.id) {
|
|
1615
1619
|
throw new Error("Nenhum playback associado para encerrar.");
|
|
1616
1620
|
}
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
);
|
|
1625
|
-
return;
|
|
1626
|
-
}
|
|
1627
|
-
console.error(
|
|
1628
|
-
`Erro ao encerrar o playback '${this.id}':`,
|
|
1629
|
-
error.message || error
|
|
1630
|
-
);
|
|
1631
|
-
throw error;
|
|
1632
|
-
}
|
|
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();
|
|
1633
1628
|
}
|
|
1634
1629
|
};
|
|
1635
|
-
var Playbacks = class
|
|
1630
|
+
var Playbacks = class {
|
|
1636
1631
|
constructor(baseClient, client) {
|
|
1637
|
-
super();
|
|
1638
1632
|
this.baseClient = baseClient;
|
|
1639
1633
|
this.client = client;
|
|
1640
1634
|
}
|
|
1635
|
+
playbackInstances = /* @__PURE__ */ new Map();
|
|
1641
1636
|
/**
|
|
1642
|
-
*
|
|
1637
|
+
* Gerencia instâncias de playback.
|
|
1643
1638
|
*/
|
|
1644
|
-
Playback({ id
|
|
1645
|
-
if (!
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
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;
|
|
1649
1644
|
}
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
getWebSocketClients() {
|
|
1657
|
-
return this.client.getWebSocketClients();
|
|
1658
|
-
}
|
|
1659
|
-
/**
|
|
1660
|
-
* Retrieves details of a specific playback.
|
|
1661
|
-
*
|
|
1662
|
-
* @param playbackId - The unique identifier of the playback.
|
|
1663
|
-
* @returns A promise that resolves to a Playback object containing the details of the specified playback.
|
|
1664
|
-
*/
|
|
1665
|
-
async getDetails(playbackId) {
|
|
1666
|
-
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);
|
|
1667
1651
|
}
|
|
1668
1652
|
/**
|
|
1669
|
-
*
|
|
1670
|
-
*
|
|
1671
|
-
* @param playbackId - The unique identifier of the playback to control.
|
|
1672
|
-
* @param operation - The operation to perform on the playback. Possible values are:
|
|
1673
|
-
* - "pause": Pauses the playback.
|
|
1674
|
-
* - "unpause": Resumes a paused playback.
|
|
1675
|
-
* - "restart": Restarts the playback from the beginning.
|
|
1676
|
-
* - "reverse": Reverses the playback direction.
|
|
1677
|
-
* - "forward": Moves the playback forward.
|
|
1678
|
-
* - "stop": Stops the playback.
|
|
1679
|
-
* @returns A promise that resolves when the control operation is successfully executed.
|
|
1653
|
+
* Remove uma instância de playback.
|
|
1680
1654
|
*/
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
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
|
+
}
|
|
1685
1661
|
}
|
|
1686
1662
|
/**
|
|
1687
|
-
*
|
|
1688
|
-
*
|
|
1689
|
-
* @param playbackId - The unique identifier of the playback to stop.
|
|
1690
|
-
* @returns A promise that resolves when the playback is successfully stopped.
|
|
1663
|
+
* Propaga eventos do WebSocket para o playback correspondente.
|
|
1691
1664
|
*/
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
if (error.response?.status === 404) {
|
|
1698
|
-
console.warn(
|
|
1699
|
-
`Playback '${playbackId}' n\xE3o encontrado para encerrar (404).`
|
|
1700
|
-
);
|
|
1701
|
-
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);
|
|
1702
1670
|
}
|
|
1703
|
-
console.error(
|
|
1704
|
-
`Erro ao encerrar o playback '${playbackId}':`,
|
|
1705
|
-
error.message || error
|
|
1706
|
-
);
|
|
1707
|
-
throw error;
|
|
1708
1671
|
}
|
|
1709
1672
|
}
|
|
1710
1673
|
/**
|
|
1711
|
-
*
|
|
1712
|
-
* The listener is triggered for events such as "PlaybackFinished".
|
|
1713
|
-
*
|
|
1714
|
-
* @param eventType - The type of event to listen for.
|
|
1715
|
-
* @param playbackId - The ID of the playback to associate with this listener.
|
|
1716
|
-
* @param callback - The callback function to execute when the event occurs.
|
|
1674
|
+
* Obtém detalhes de um playback específico.
|
|
1717
1675
|
*/
|
|
1718
|
-
|
|
1719
|
-
this.
|
|
1676
|
+
async getDetails(playbackId) {
|
|
1677
|
+
return this.baseClient.get(`/playbacks/${playbackId}`);
|
|
1720
1678
|
}
|
|
1721
1679
|
/**
|
|
1722
|
-
*
|
|
1723
|
-
*
|
|
1724
|
-
* @param eventType - The type of event to stop listening for.
|
|
1725
|
-
* @param playbackId - The ID of the playback associated with the listener.
|
|
1726
|
-
* @param callback - The callback function to remove.
|
|
1680
|
+
* Controla um playback específico.
|
|
1727
1681
|
*/
|
|
1728
|
-
|
|
1729
|
-
this.
|
|
1682
|
+
async control(playbackId, operation) {
|
|
1683
|
+
const playback = this.Playback({ id: playbackId });
|
|
1684
|
+
await playback.control(operation);
|
|
1730
1685
|
}
|
|
1731
1686
|
/**
|
|
1732
|
-
*
|
|
1733
|
-
*
|
|
1734
|
-
* @param eventType - The type of event to check.
|
|
1735
|
-
* @param playbackId - The playback ID associated with the listener.
|
|
1736
|
-
* @returns True if a listener is already registered, false otherwise.
|
|
1687
|
+
* Encerra um playback específico.
|
|
1737
1688
|
*/
|
|
1738
|
-
|
|
1739
|
-
|
|
1689
|
+
async stop(playbackId) {
|
|
1690
|
+
const playback = this.Playback({ id: playbackId });
|
|
1691
|
+
await playback.stop();
|
|
1740
1692
|
}
|
|
1741
1693
|
};
|
|
1742
1694
|
|
|
@@ -1776,356 +1728,136 @@ var import_events3 = require("events");
|
|
|
1776
1728
|
var import_exponential_backoff = __toESM(require_backoff(), 1);
|
|
1777
1729
|
var import_ws = __toESM(require("ws"), 1);
|
|
1778
1730
|
var WebSocketClient = class extends import_events3.EventEmitter {
|
|
1779
|
-
|
|
1780
|
-
* Creates a new WebSocketClient instance.
|
|
1781
|
-
* @param url - The WebSocket server URL to connect to.
|
|
1782
|
-
*/
|
|
1783
|
-
constructor(url) {
|
|
1731
|
+
constructor(baseClient, apps, subscribedEvents, ariClient) {
|
|
1784
1732
|
super();
|
|
1785
|
-
this.
|
|
1733
|
+
this.baseClient = baseClient;
|
|
1734
|
+
this.apps = apps;
|
|
1735
|
+
this.subscribedEvents = subscribedEvents;
|
|
1736
|
+
this.ariClient = ariClient;
|
|
1786
1737
|
}
|
|
1787
|
-
ws
|
|
1788
|
-
isClosedManually = false;
|
|
1738
|
+
ws;
|
|
1789
1739
|
isReconnecting = false;
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
retry: (error, attemptNumber) => {
|
|
1809
|
-
console.warn(
|
|
1810
|
-
`Tentativa ${attemptNumber} de reconex\xE3o falhou: ${error.message}`
|
|
1811
|
-
);
|
|
1812
|
-
return !this.isClosedManually;
|
|
1813
|
-
}
|
|
1814
|
-
};
|
|
1815
|
-
try {
|
|
1816
|
-
await (0, import_exponential_backoff.backOff)(async () => {
|
|
1817
|
-
console.log(`Tentando reconectar (#${this.reconnectAttempts + 1})...`);
|
|
1818
|
-
await this.connect();
|
|
1819
|
-
console.log("Reconex\xE3o bem-sucedida.");
|
|
1820
|
-
}, backoffOptions);
|
|
1821
|
-
this.reconnectAttempts = 0;
|
|
1822
|
-
this.isReconnecting = false;
|
|
1823
|
-
} catch (error) {
|
|
1824
|
-
console.error(
|
|
1825
|
-
`Reconex\xE3o falhou ap\xF3s ${this.maxReconnectAttempts} tentativas.`,
|
|
1826
|
-
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
|
|
1827
1758
|
);
|
|
1828
|
-
|
|
1759
|
+
return true;
|
|
1829
1760
|
}
|
|
1830
|
-
}
|
|
1761
|
+
};
|
|
1831
1762
|
/**
|
|
1832
|
-
*
|
|
1833
|
-
* @returns A Promise that resolves when the connection is established, or rejects if an error occurs.
|
|
1834
|
-
* @throws Will throw an error if the connection fails.
|
|
1763
|
+
* Conecta ao WebSocket.
|
|
1835
1764
|
*/
|
|
1836
1765
|
async connect() {
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
console.log(
|
|
1847
|
-
`Listeners ativos para 'message': ${this.listenerCount("message")}`
|
|
1848
|
-
);
|
|
1849
|
-
resolve();
|
|
1850
|
-
});
|
|
1851
|
-
this.ws.on("error", (err) => {
|
|
1852
|
-
console.error("Erro na conex\xE3o WebSocket:", err);
|
|
1853
|
-
reject(err);
|
|
1854
|
-
});
|
|
1855
|
-
this.ws.on("close", (code, reason) => {
|
|
1856
|
-
console.warn(`WebSocket desconectado: ${code} - ${reason}`);
|
|
1857
|
-
this.emit("close", { code, reason });
|
|
1858
|
-
if (!this.isClosedManually) {
|
|
1859
|
-
this.reconnect();
|
|
1860
|
-
}
|
|
1861
|
-
});
|
|
1862
|
-
this.ws.on("message", (rawData) => {
|
|
1863
|
-
this.handleMessage(rawData);
|
|
1864
|
-
});
|
|
1865
|
-
});
|
|
1866
|
-
}
|
|
1867
|
-
/**
|
|
1868
|
-
* Checks if the WebSocket connection is currently open.
|
|
1869
|
-
* @returns True if the connection is open, false otherwise.
|
|
1870
|
-
*/
|
|
1871
|
-
isConnected() {
|
|
1872
|
-
return this.ws?.readyState === import_ws.default.OPEN;
|
|
1873
|
-
}
|
|
1874
|
-
onMessage(callback) {
|
|
1875
|
-
if (!this.messageListeners.includes(callback)) {
|
|
1876
|
-
this.messageListeners.push(callback);
|
|
1877
|
-
this.on("message", callback);
|
|
1878
|
-
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
|
+
);
|
|
1879
1775
|
} else {
|
|
1880
|
-
|
|
1776
|
+
queryParams.append("subscribeAll", "true");
|
|
1881
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);
|
|
1882
1781
|
}
|
|
1883
1782
|
/**
|
|
1884
|
-
*
|
|
1885
|
-
* @param event - The event type to listen for.
|
|
1886
|
-
* @param callback - The function to call when the event occurs.
|
|
1887
|
-
* @returns The WebSocketClient instance for chaining.
|
|
1783
|
+
* Inicializa a conexão WebSocket com lógica de reconexão.
|
|
1888
1784
|
*/
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
console.log(`=== Listagem de Listeners para '${event}' ===`);
|
|
1918
|
-
listeners.forEach((listener, index) => {
|
|
1919
|
-
const scopedKey = Array.from(this.scopedListeners.entries()).find(
|
|
1920
|
-
([, value]) => value === listener
|
|
1921
|
-
)?.[0];
|
|
1922
|
-
console.log(`Listener #${index + 1}:`, listener.toString());
|
|
1923
|
-
if (scopedKey) {
|
|
1924
|
-
console.log(` Associado ao escopo: ${scopedKey}`);
|
|
1925
|
-
}
|
|
1926
|
-
});
|
|
1927
|
-
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);
|
|
1928
1813
|
}
|
|
1929
1814
|
/**
|
|
1930
|
-
*
|
|
1931
|
-
* @param event - The event type to remove the listener from.
|
|
1932
|
-
* @param callback - The function to remove from the event listeners.
|
|
1933
|
-
* @returns The WebSocketClient instance for chaining.
|
|
1815
|
+
* Processa as mensagens recebidas do WebSocket.
|
|
1934
1816
|
*/
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
*/
|
|
1941
|
-
off(event, callback) {
|
|
1942
|
-
if (event === "message") {
|
|
1943
|
-
console.log(
|
|
1944
|
-
`Removendo listener para o evento 'message'. Total antes: ${this.listenerCount(
|
|
1945
|
-
"message"
|
|
1946
|
-
)}`
|
|
1947
|
-
);
|
|
1948
|
-
}
|
|
1949
|
-
super.off(event, callback);
|
|
1950
|
-
if (event === "message") {
|
|
1951
|
-
console.log(
|
|
1952
|
-
`Listener para o evento 'message' removido. Total agora: ${this.listenerCount(
|
|
1953
|
-
"message"
|
|
1954
|
-
)}`
|
|
1955
|
-
);
|
|
1956
|
-
}
|
|
1957
|
-
return this;
|
|
1958
|
-
}
|
|
1959
|
-
removeWildcardListeners(id, event, type) {
|
|
1960
|
-
console.log(
|
|
1961
|
-
`Removendo listeners para '${type}:${id}:${event}' com wildcard no app`
|
|
1962
|
-
);
|
|
1963
|
-
for (const eventName of this.eventNames()) {
|
|
1964
|
-
if (typeof eventName === "string") {
|
|
1965
|
-
const eventString = String(eventName);
|
|
1966
|
-
if (eventString.includes(`:${type}:${id}:${event}`)) {
|
|
1967
|
-
console.log(`Removendo listener para o evento: ${eventString}`);
|
|
1968
|
-
this.removeAllListeners(eventName);
|
|
1969
|
-
}
|
|
1817
|
+
handleMessage(rawMessage) {
|
|
1818
|
+
try {
|
|
1819
|
+
const event = JSON.parse(rawMessage);
|
|
1820
|
+
if (this.subscribedEvents && !this.subscribedEvents.includes(event.type)) {
|
|
1821
|
+
return;
|
|
1970
1822
|
}
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
* @param event - Optional. The event to remove all listeners from.
|
|
1976
|
-
* @returns The WebSocketClient instance for chaining.
|
|
1977
|
-
*/
|
|
1978
|
-
removeAllListeners(event) {
|
|
1979
|
-
super.removeAllListeners(event);
|
|
1980
|
-
return this;
|
|
1981
|
-
}
|
|
1982
|
-
/**
|
|
1983
|
-
* Adiciona um listener escopado ao evento "message".
|
|
1984
|
-
* @param instanceId - Identificador único da instância que está registrando o listener.
|
|
1985
|
-
* @param app
|
|
1986
|
-
* @param callback - Função de callback para o evento.
|
|
1987
|
-
*/
|
|
1988
|
-
addScopedMessageListener(instanceId, app, callback) {
|
|
1989
|
-
const key = `${app}:${instanceId}`;
|
|
1990
|
-
if (this.scopedListeners.has(key)) {
|
|
1991
|
-
console.warn(
|
|
1992
|
-
`Listener escopado j\xE1 existe para '${key}'. Removendo antigo antes de adicionar um novo.`
|
|
1993
|
-
);
|
|
1994
|
-
this.removeScopedMessageListeners(instanceId, app);
|
|
1995
|
-
}
|
|
1996
|
-
const scopedListener = (data) => {
|
|
1997
|
-
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)) {
|
|
1998
|
-
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;
|
|
1999
1827
|
}
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
`Listener escopado adicionado para '${key}'. Total de listeners: ${this.listenerCount(
|
|
2005
|
-
"message"
|
|
2006
|
-
)}`
|
|
2007
|
-
);
|
|
2008
|
-
}
|
|
2009
|
-
/**
|
|
2010
|
-
* Remove todos os listeners associados a uma instância específica.
|
|
2011
|
-
* @param instanceId - Identificador da instância cujos listeners serão removidos.
|
|
2012
|
-
* @param app
|
|
2013
|
-
*/
|
|
2014
|
-
removeScopedMessageListeners(instanceId, app) {
|
|
2015
|
-
const key = `${app}:${instanceId}`;
|
|
2016
|
-
const scopedListener = this.scopedListeners.get(key);
|
|
2017
|
-
if (scopedListener) {
|
|
2018
|
-
console.log(`Removendo listener escopado para '${key}'.`);
|
|
2019
|
-
this.off("message", scopedListener);
|
|
2020
|
-
this.scopedListeners.delete(key);
|
|
2021
|
-
} else {
|
|
2022
|
-
console.warn(`Nenhum listener encontrado para '${key}'.`);
|
|
2023
|
-
}
|
|
2024
|
-
}
|
|
2025
|
-
removeAllScopedListeners() {
|
|
2026
|
-
this.scopedListeners.forEach((_listener, key) => {
|
|
2027
|
-
console.log(`Removendo listener escopado para '${key}' durante cleanup.`);
|
|
2028
|
-
this.removeScopedMessageListeners(key.split(":")[1], key.split(":")[0]);
|
|
2029
|
-
});
|
|
2030
|
-
console.log("Todos os listeners escopados foram removidos.");
|
|
2031
|
-
}
|
|
2032
|
-
removeScopedChannelListeners(instanceId, app) {
|
|
2033
|
-
const key = `${app}:${instanceId}`;
|
|
2034
|
-
const scopedListener = this.scopedListeners.get(key);
|
|
2035
|
-
if (scopedListener) {
|
|
2036
|
-
console.log(`Removendo listener escopado para o canal '${key}'.`);
|
|
2037
|
-
this.off("message", scopedListener);
|
|
2038
|
-
this.scopedListeners.delete(key);
|
|
2039
|
-
console.log(`Listener de canal removido para '${key}'.`);
|
|
2040
|
-
} else {
|
|
2041
|
-
console.warn(`Nenhum listener encontrado para o canal '${key}'.`);
|
|
2042
|
-
}
|
|
2043
|
-
}
|
|
2044
|
-
removeScopedPlaybackListeners(playbackId, app) {
|
|
2045
|
-
const key = `${app}:${playbackId}`;
|
|
2046
|
-
const scopedListener = this.scopedListeners.get(key);
|
|
2047
|
-
if (scopedListener) {
|
|
2048
|
-
console.log(`Removendo listener escopado para o playback '${key}'.`);
|
|
2049
|
-
this.off("message", scopedListener);
|
|
2050
|
-
this.scopedListeners.delete(key);
|
|
2051
|
-
console.log(`Listener de playback removido para '${key}'.`);
|
|
2052
|
-
} else {
|
|
2053
|
-
console.warn(`Nenhum listener encontrado para o playback '${key}'.`);
|
|
2054
|
-
}
|
|
2055
|
-
}
|
|
2056
|
-
/**
|
|
2057
|
-
* Handles incoming WebSocket messages.
|
|
2058
|
-
* @param rawData - The raw data received from the WebSocket.
|
|
2059
|
-
*/
|
|
2060
|
-
handleMessage(rawData) {
|
|
2061
|
-
try {
|
|
2062
|
-
const decodedData = JSON.parse(rawData.toString());
|
|
2063
|
-
if (decodedData?.type && decodedData?.application) {
|
|
2064
|
-
const scopedEvent = `${decodedData.application}:${decodedData.type}`;
|
|
2065
|
-
if ("channel" in decodedData && decodedData.channel?.id) {
|
|
2066
|
-
const channelScopedEvent = `${decodedData.application}:Channel:${decodedData.channel.id}:${decodedData.type}`;
|
|
2067
|
-
this.emit(channelScopedEvent, decodedData);
|
|
2068
|
-
if (decodedData.type === "ChannelDestroyed") {
|
|
2069
|
-
console.log(`Canal destru\xEDdo detectado: ${decodedData.channel.id}`);
|
|
2070
|
-
this.removeScopedChannelListeners(
|
|
2071
|
-
decodedData.channel.id,
|
|
2072
|
-
decodedData.application
|
|
2073
|
-
);
|
|
2074
|
-
}
|
|
2075
|
-
} else if ("playback" in decodedData && decodedData.playback?.id) {
|
|
2076
|
-
const playbackScopedEvent = `${decodedData.application}:Playback:${decodedData.playback.id}:${decodedData.type}`;
|
|
2077
|
-
this.emit(playbackScopedEvent, decodedData);
|
|
2078
|
-
if (decodedData.type === "PlaybackFinished") {
|
|
2079
|
-
console.log(`Playback conclu\xEDdo: ${decodedData.playback.id}`);
|
|
2080
|
-
this.removeScopedPlaybackListeners(
|
|
2081
|
-
decodedData.playback.id,
|
|
2082
|
-
decodedData.application
|
|
2083
|
-
);
|
|
2084
|
-
}
|
|
2085
|
-
}
|
|
2086
|
-
this.emit(scopedEvent, decodedData);
|
|
2087
|
-
} else {
|
|
2088
|
-
console.warn(
|
|
2089
|
-
"Mensagem recebida sem tipo ou aplica\xE7\xE3o v\xE1lida:",
|
|
2090
|
-
decodedData
|
|
2091
|
-
);
|
|
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;
|
|
2092
1832
|
}
|
|
1833
|
+
this.emit(event.type, event);
|
|
2093
1834
|
} catch (err) {
|
|
2094
|
-
console.error("Erro ao
|
|
1835
|
+
console.error("Erro ao processar mensagem WebSocket:", err);
|
|
1836
|
+
this.emit("error", new Error("Falha ao decodificar mensagem WebSocket."));
|
|
2095
1837
|
}
|
|
2096
1838
|
}
|
|
2097
1839
|
/**
|
|
2098
|
-
*
|
|
2099
|
-
* @param data - The data to send.
|
|
2100
|
-
* @throws Will throw an error if the WebSocket is not connected.
|
|
1840
|
+
* Tenta reconectar ao WebSocket.
|
|
2101
1841
|
*/
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
console.error(
|
|
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
|
+
);
|
|
2109
1852
|
}
|
|
2110
|
-
|
|
1853
|
+
);
|
|
2111
1854
|
}
|
|
2112
1855
|
/**
|
|
2113
|
-
*
|
|
1856
|
+
* Fecha o WebSocket manualmente.
|
|
2114
1857
|
*/
|
|
2115
1858
|
close() {
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
this.ws.close();
|
|
2119
|
-
this.ws = null;
|
|
2120
|
-
console.log(this.scopedListeners);
|
|
2121
|
-
this.scopedListeners.forEach((_listener, key) => {
|
|
2122
|
-
console.log(`Removendo listener escopado para '${key}' ao fechar.`);
|
|
2123
|
-
this.removeScopedMessageListeners(key.split(":")[1], key.split(":")[0]);
|
|
2124
|
-
});
|
|
2125
|
-
this.scopedListeners.clear();
|
|
2126
|
-
this.removeAllListeners("message");
|
|
2127
|
-
console.log("WebSocket fechado manualmente e todos os listeners limpos.");
|
|
2128
|
-
}
|
|
1859
|
+
this.ws?.close();
|
|
1860
|
+
this.ws = void 0;
|
|
2129
1861
|
}
|
|
2130
1862
|
};
|
|
2131
1863
|
|
|
@@ -2145,35 +1877,8 @@ var AriClient = class {
|
|
|
2145
1877
|
this.asterisk = new Asterisk(this.baseClient);
|
|
2146
1878
|
this.bridges = new Bridges(this.baseClient);
|
|
2147
1879
|
}
|
|
2148
|
-
eventListeners = /* @__PURE__ */ new Map();
|
|
2149
|
-
wsClients = /* @__PURE__ */ new Map();
|
|
2150
|
-
// Map para armazenar conexões por app
|
|
2151
1880
|
baseClient;
|
|
2152
|
-
|
|
2153
|
-
// Estado de reconexão por app
|
|
2154
|
-
webSocketReady = /* @__PURE__ */ new Map();
|
|
2155
|
-
// Rastreamento do estado de cada conexão
|
|
2156
|
-
channelInstances = /* @__PURE__ */ new Map();
|
|
2157
|
-
pendingListeners = [];
|
|
2158
|
-
processPendingListeners() {
|
|
2159
|
-
for (const [app, wsClient] of this.wsClients.entries()) {
|
|
2160
|
-
if (!wsClient.isConnected()) {
|
|
2161
|
-
console.warn(`WebSocket para o app '${app}' ainda n\xE3o est\xE1 conectado.`);
|
|
2162
|
-
continue;
|
|
2163
|
-
}
|
|
2164
|
-
while (this.pendingListeners.length > 0) {
|
|
2165
|
-
const { event, callback } = this.pendingListeners.shift();
|
|
2166
|
-
if (wsClient.listenerCount(event) > 0) {
|
|
2167
|
-
console.log(
|
|
2168
|
-
`Listener j\xE1 registrado para o evento '${event}' no app '${app}'. Ignorando duplicata.`
|
|
2169
|
-
);
|
|
2170
|
-
continue;
|
|
2171
|
-
}
|
|
2172
|
-
console.log(`Registrando listener para '${app}' no evento: ${event}`);
|
|
2173
|
-
wsClient.on(event, callback);
|
|
2174
|
-
}
|
|
2175
|
-
}
|
|
2176
|
-
}
|
|
1881
|
+
webSocketClient;
|
|
2177
1882
|
channels;
|
|
2178
1883
|
endpoints;
|
|
2179
1884
|
applications;
|
|
@@ -2181,370 +1886,58 @@ var AriClient = class {
|
|
|
2181
1886
|
sounds;
|
|
2182
1887
|
asterisk;
|
|
2183
1888
|
bridges;
|
|
2184
|
-
// Getter para wsClients
|
|
2185
|
-
getWebSocketClients() {
|
|
2186
|
-
return this.wsClients;
|
|
2187
|
-
}
|
|
2188
|
-
/**
|
|
2189
|
-
* Registra listeners globais para eventos de WebSocket.
|
|
2190
|
-
*/
|
|
2191
|
-
on(eventType, callback, app) {
|
|
2192
|
-
if (!app) {
|
|
2193
|
-
throw new Error(
|
|
2194
|
-
"O nome do app \xE9 obrigat\xF3rio para registrar um listener de evento."
|
|
2195
|
-
);
|
|
2196
|
-
}
|
|
2197
|
-
console.log(
|
|
2198
|
-
`Registrando listener para evento '${eventType}' no app '${app}'.`
|
|
2199
|
-
);
|
|
2200
|
-
const callbackKey = `${app}:${eventType}`;
|
|
2201
|
-
if (this.eventListeners.has(callbackKey)) {
|
|
2202
|
-
console.log(
|
|
2203
|
-
`Listener para evento '${eventType}' j\xE1 est\xE1 registrado no app '${app}'. Ignorando duplicata.`
|
|
2204
|
-
);
|
|
2205
|
-
return;
|
|
2206
|
-
}
|
|
2207
|
-
const wrappedCallback = (event) => {
|
|
2208
|
-
if (isChannelEvent(event)) {
|
|
2209
|
-
const channelId = event.channel.id;
|
|
2210
|
-
if (channelId) {
|
|
2211
|
-
if (!this.channelInstances.has(channelId)) {
|
|
2212
|
-
const channelInstance = this.createChannelInstance(channelId, app);
|
|
2213
|
-
this.channelInstances.set(channelId, channelInstance);
|
|
2214
|
-
}
|
|
2215
|
-
event.instanceChannel = this.channelInstances.get(channelId);
|
|
2216
|
-
event.instanceChannel?.emit(event.type, event);
|
|
2217
|
-
}
|
|
2218
|
-
}
|
|
2219
|
-
callback(event);
|
|
2220
|
-
};
|
|
2221
|
-
const wsClient = this.wsClients.get(app);
|
|
2222
|
-
if (wsClient) {
|
|
2223
|
-
const scopedEvent = `${app}:${eventType}`;
|
|
2224
|
-
wsClient.on(scopedEvent, wrappedCallback);
|
|
2225
|
-
this.eventListeners.set(callbackKey, wrappedCallback);
|
|
2226
|
-
console.log(
|
|
2227
|
-
`Listener para evento '${eventType}' registrado com sucesso no app '${app}'.`
|
|
2228
|
-
);
|
|
2229
|
-
} else {
|
|
2230
|
-
console.warn(`WebSocket para o app '${app}' n\xE3o est\xE1 conectado.`);
|
|
2231
|
-
}
|
|
2232
|
-
}
|
|
2233
|
-
removeListener(eventType, app) {
|
|
2234
|
-
if (app) {
|
|
2235
|
-
const callbackKey = `${eventType}-${app}`;
|
|
2236
|
-
const callback = this.eventListeners.get(callbackKey);
|
|
2237
|
-
if (callback) {
|
|
2238
|
-
const wsClient = this.wsClients.get(app);
|
|
2239
|
-
if (wsClient) {
|
|
2240
|
-
wsClient.off(eventType, callback);
|
|
2241
|
-
console.log(
|
|
2242
|
-
`Listener para evento '${eventType}' removido do app '${app}'`
|
|
2243
|
-
);
|
|
2244
|
-
}
|
|
2245
|
-
this.eventListeners.delete(callbackKey);
|
|
2246
|
-
}
|
|
2247
|
-
} else {
|
|
2248
|
-
for (const [_app, wsClient] of this.wsClients.entries()) {
|
|
2249
|
-
const callbackKey = `${eventType}-${_app}`;
|
|
2250
|
-
const callback = this.eventListeners.get(callbackKey);
|
|
2251
|
-
if (callback) {
|
|
2252
|
-
wsClient.off(eventType, callback);
|
|
2253
|
-
console.log(
|
|
2254
|
-
`Listener global para evento '${eventType}' removido do app '${_app}'`
|
|
2255
|
-
);
|
|
2256
|
-
this.eventListeners.delete(callbackKey);
|
|
2257
|
-
}
|
|
2258
|
-
}
|
|
2259
|
-
}
|
|
2260
|
-
}
|
|
2261
|
-
/**
|
|
2262
|
-
* Type guard to check if the given data object contains a valid channel property.
|
|
2263
|
-
* This function is used to narrow down the type of the data object and ensure it has a channel with an id.
|
|
2264
|
-
*
|
|
2265
|
-
* @param data - The object to be checked for the presence of a channel property.
|
|
2266
|
-
* @returns A type predicate indicating whether the data object contains a valid channel.
|
|
2267
|
-
* Returns true if the data object has a channel property with an id, false otherwise.
|
|
2268
|
-
*/
|
|
2269
|
-
hasChannel(data) {
|
|
2270
|
-
return data?.channel?.id !== void 0;
|
|
2271
|
-
}
|
|
2272
|
-
// Método para criar uma instância de ChannelInstance
|
|
2273
|
-
createChannelInstance(channelId, app) {
|
|
2274
|
-
return this.channels.Channel({
|
|
2275
|
-
id: channelId,
|
|
2276
|
-
app
|
|
2277
|
-
});
|
|
2278
|
-
}
|
|
2279
|
-
handleWebSocketEvent(event, app) {
|
|
2280
|
-
console.log("Evento recebido no WebSocket:", event.type, event);
|
|
2281
|
-
const { type, ...data } = event;
|
|
2282
|
-
if (this.hasChannel(data)) {
|
|
2283
|
-
const channelId = data.channel.id;
|
|
2284
|
-
if (channelId) {
|
|
2285
|
-
if (!this.channelInstances.has(channelId)) {
|
|
2286
|
-
const channelInstance2 = this.createChannelInstance(channelId, app);
|
|
2287
|
-
this.channelInstances.set(channelId, channelInstance2);
|
|
2288
|
-
}
|
|
2289
|
-
const channelInstance = this.channelInstances.get(channelId);
|
|
2290
|
-
if (channelInstance) {
|
|
2291
|
-
data.channel = { ...data.channel, ...channelInstance };
|
|
2292
|
-
}
|
|
2293
|
-
}
|
|
2294
|
-
}
|
|
2295
|
-
const listener = this.eventListeners.get(type);
|
|
2296
|
-
if (listener) {
|
|
2297
|
-
listener(data);
|
|
2298
|
-
}
|
|
2299
|
-
}
|
|
2300
|
-
/**
|
|
2301
|
-
* Connects to the ARI WebSocket for a specific application.
|
|
2302
|
-
* This function establishes a WebSocket connection to the Asterisk ARI, sets up event listeners,
|
|
2303
|
-
* and ensures the application is registered. It uses an exponential backoff strategy for connection attempts.
|
|
2304
|
-
*
|
|
2305
|
-
* @param app - The name of the application to connect to. This is required and used to identify the application in ARI.
|
|
2306
|
-
* @param subscribedEvents - Optional array of WebSocketEventType to subscribe to specific events.
|
|
2307
|
-
* If not provided or empty, it subscribes to all events.
|
|
2308
|
-
* @returns A Promise that resolves when the WebSocket connection is successfully established and the application is registered.
|
|
2309
|
-
* @throws Error if the 'app' parameter is not provided, or if connection attempts fail after multiple retries.
|
|
2310
|
-
*/
|
|
2311
1889
|
/**
|
|
2312
|
-
*
|
|
2313
|
-
* Establishes a WebSocket connection for each app and subscribes to events.
|
|
2314
|
-
*
|
|
2315
|
-
* @param apps - Array of application names to connect to.
|
|
2316
|
-
* @param subscribedEvents - Optional array of events to subscribe to.
|
|
2317
|
-
* If not provided or empty, subscribes to all events.
|
|
2318
|
-
* @returns A Promise that resolves when all connections are established.
|
|
1890
|
+
* Inicializa uma conexão WebSocket.
|
|
2319
1891
|
*/
|
|
2320
1892
|
async connectWebSocket(apps, subscribedEvents) {
|
|
2321
|
-
if (
|
|
2322
|
-
|
|
1893
|
+
if (this.webSocketClient) {
|
|
1894
|
+
console.warn("WebSocket j\xE1 est\xE1 conectado.");
|
|
1895
|
+
return;
|
|
2323
1896
|
}
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
return;
|
|
2330
|
-
}
|
|
2331
|
-
await this.connectSingleWebSocket(app, subscribedEvents);
|
|
2332
|
-
})
|
|
1897
|
+
this.webSocketClient = new WebSocketClient(
|
|
1898
|
+
this.baseClient,
|
|
1899
|
+
apps,
|
|
1900
|
+
subscribedEvents,
|
|
1901
|
+
this
|
|
2333
1902
|
);
|
|
1903
|
+
await this.webSocketClient.connect();
|
|
2334
1904
|
}
|
|
2335
1905
|
/**
|
|
2336
|
-
*
|
|
1906
|
+
* Adiciona um listener para eventos do WebSocket.
|
|
2337
1907
|
*/
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
throw new Error("O nome do aplicativo \xE9 obrigat\xF3rio.");
|
|
2341
|
-
}
|
|
2342
|
-
if (this.webSocketReady.get(app)) {
|
|
2343
|
-
console.log(`Conex\xE3o WebSocket para '${app}' j\xE1 est\xE1 ativa.`);
|
|
2344
|
-
return this.webSocketReady.get(app);
|
|
2345
|
-
}
|
|
2346
|
-
const protocol = this.config.secure ? "wss" : "ws";
|
|
2347
|
-
const eventsParam = subscribedEvents && subscribedEvents.length > 0 ? `&event=${subscribedEvents.join(",")}` : "&subscribeAll=true";
|
|
2348
|
-
const wsUrl = `${protocol}://${encodeURIComponent(
|
|
2349
|
-
this.config.username
|
|
2350
|
-
)}:${encodeURIComponent(this.config.password)}@${this.config.host}:${this.config.port}/ari/events?app=${app}${eventsParam}`;
|
|
2351
|
-
const backoffOptions = {
|
|
2352
|
-
delayFirstAttempt: false,
|
|
2353
|
-
startingDelay: 1e3,
|
|
2354
|
-
timeMultiple: 2,
|
|
2355
|
-
maxDelay: 3e4,
|
|
2356
|
-
numOfAttempts: 10,
|
|
2357
|
-
jitter: "full",
|
|
2358
|
-
retry: (error, attemptNumber) => {
|
|
2359
|
-
console.warn(
|
|
2360
|
-
`Tentativa ${attemptNumber} falhou para '${app}': ${error.message}`
|
|
2361
|
-
);
|
|
2362
|
-
return !this.wsClients.has(app) || !this.wsClients.get(app)?.isConnected();
|
|
2363
|
-
}
|
|
2364
|
-
};
|
|
2365
|
-
const webSocketPromise = new Promise(async (resolve, reject) => {
|
|
2366
|
-
try {
|
|
2367
|
-
if (this.isReconnecting.get(app)) {
|
|
2368
|
-
console.warn(`J\xE1 est\xE1 tentando reconectar para o app '${app}'.`);
|
|
2369
|
-
return;
|
|
2370
|
-
}
|
|
2371
|
-
this.isReconnecting.set(app, true);
|
|
2372
|
-
const wsClient = new WebSocketClient(wsUrl);
|
|
2373
|
-
wsClient.setMaxListeners(30);
|
|
2374
|
-
await (0, import_exponential_backoff2.backOff)(async () => {
|
|
2375
|
-
if (!wsClient) {
|
|
2376
|
-
throw new Error("WebSocketClient instance is null.");
|
|
2377
|
-
}
|
|
2378
|
-
await wsClient.connect();
|
|
2379
|
-
console.log(`WebSocket conectado para o app: ${app}`);
|
|
2380
|
-
this.integrateWebSocketEvents(app, wsClient);
|
|
2381
|
-
await this.ensureAppRegistered(app);
|
|
2382
|
-
this.wsClients.set(app, wsClient);
|
|
2383
|
-
this.processPendingListeners();
|
|
2384
|
-
}, backoffOptions);
|
|
2385
|
-
resolve();
|
|
2386
|
-
} catch (error) {
|
|
2387
|
-
console.error(`Erro ao conectar WebSocket para '${app}':`, error);
|
|
2388
|
-
reject(error);
|
|
2389
|
-
} finally {
|
|
2390
|
-
this.isReconnecting.delete(app);
|
|
2391
|
-
}
|
|
2392
|
-
});
|
|
2393
|
-
this.webSocketReady.set(app, webSocketPromise);
|
|
2394
|
-
return webSocketPromise;
|
|
1908
|
+
on(event, listener) {
|
|
1909
|
+
this.webSocketClient?.on(event, listener);
|
|
2395
1910
|
}
|
|
2396
1911
|
/**
|
|
2397
|
-
*
|
|
1912
|
+
* Adiciona um listener único para eventos do WebSocket.
|
|
2398
1913
|
*/
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
throw new Error(
|
|
2402
|
-
`WebSocket client para o app '${app}' n\xE3o est\xE1 conectado.`
|
|
2403
|
-
);
|
|
2404
|
-
}
|
|
2405
|
-
console.log(
|
|
2406
|
-
"**************** INTEGRANDO EVENTOS DE PLAYBACK **************"
|
|
2407
|
-
);
|
|
2408
|
-
console.log(wsClient.eventNames());
|
|
2409
|
-
console.log(
|
|
2410
|
-
"**************** INTEGRANDO EVENTOS DE PLAYBACK **************"
|
|
2411
|
-
);
|
|
2412
|
-
if (wsClient.listenerCount("PlaybackStarted") > 0) {
|
|
2413
|
-
return;
|
|
2414
|
-
}
|
|
2415
|
-
const eventHandlers = {
|
|
2416
|
-
PlaybackStarted: (data) => {
|
|
2417
|
-
if ("playbackId" in data) {
|
|
2418
|
-
}
|
|
2419
|
-
},
|
|
2420
|
-
PlaybackFinished: (data) => {
|
|
2421
|
-
if ("playbackId" in data) {
|
|
2422
|
-
}
|
|
2423
|
-
},
|
|
2424
|
-
ChannelDtmfReceived: (data) => {
|
|
2425
|
-
if (data.type === "ChannelDtmfReceived" && "digit" in data) {
|
|
2426
|
-
} else {
|
|
2427
|
-
console.warn(
|
|
2428
|
-
`[${app}] Evento inesperado em ChannelDtmfReceived`,
|
|
2429
|
-
data
|
|
2430
|
-
);
|
|
2431
|
-
}
|
|
2432
|
-
},
|
|
2433
|
-
ChannelStateChange: (data) => {
|
|
2434
|
-
if ("channel" in data) {
|
|
2435
|
-
}
|
|
2436
|
-
}
|
|
2437
|
-
};
|
|
2438
|
-
for (const [eventType, handler] of Object.entries(eventHandlers)) {
|
|
2439
|
-
if (handler) {
|
|
2440
|
-
wsClient.on(eventType, handler);
|
|
2441
|
-
}
|
|
2442
|
-
}
|
|
2443
|
-
console.log(`[${app}] Todos os eventos do WebSocket foram registrados.`);
|
|
1914
|
+
once(event, listener) {
|
|
1915
|
+
this.webSocketClient?.once(event, listener);
|
|
2444
1916
|
}
|
|
2445
1917
|
/**
|
|
2446
|
-
*
|
|
2447
|
-
*
|
|
2448
|
-
* @param app - The application name to ensure is registered.
|
|
2449
|
-
* @returns {Promise<void>}
|
|
1918
|
+
* Remove um listener para eventos do WebSocket.
|
|
2450
1919
|
*/
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
const apps = await this.baseClient.get("/applications");
|
|
2454
|
-
const appExists = apps.some((a) => a.name === app);
|
|
2455
|
-
if (!appExists) {
|
|
2456
|
-
console.log(`Registrando o aplicativo ARI: ${app}`);
|
|
2457
|
-
await this.baseClient.post("/applications", { app });
|
|
2458
|
-
console.log(`Aplicativo ${app} registrado com sucesso.`);
|
|
2459
|
-
} else {
|
|
2460
|
-
console.log(`Aplicativo ${app} j\xE1 est\xE1 registrado.`);
|
|
2461
|
-
}
|
|
2462
|
-
} catch (error) {
|
|
2463
|
-
console.error(`Erro ao garantir o registro do aplicativo ${app}:`, error);
|
|
2464
|
-
throw error;
|
|
2465
|
-
}
|
|
2466
|
-
}
|
|
2467
|
-
logListeners() {
|
|
2468
|
-
this.wsClients.forEach((wsClient, app) => {
|
|
2469
|
-
console.log(`Listeners registrados para '${app}':`);
|
|
2470
|
-
wsClient.eventNames().forEach((event) => {
|
|
2471
|
-
const eventName = String(event);
|
|
2472
|
-
const count = wsClient.listenerCount(event);
|
|
2473
|
-
console.log(` Evento '${eventName}': ${count} listener(s)`);
|
|
2474
|
-
});
|
|
2475
|
-
});
|
|
1920
|
+
off(event, listener) {
|
|
1921
|
+
this.webSocketClient?.off(event, listener);
|
|
2476
1922
|
}
|
|
2477
1923
|
/**
|
|
2478
|
-
*
|
|
2479
|
-
*
|
|
2480
|
-
* @returns {boolean} True if connected, false otherwise.
|
|
1924
|
+
* Fecha a conexão WebSocket.
|
|
2481
1925
|
*/
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
);
|
|
2486
|
-
}
|
|
2487
|
-
isWebSocketConnected(app) {
|
|
2488
|
-
const wsClient = this.wsClients.get(app);
|
|
2489
|
-
return wsClient ? wsClient.isConnected() : false;
|
|
2490
|
-
}
|
|
2491
|
-
/**
|
|
2492
|
-
* Closes the WebSocket connection and removes all associated listeners.
|
|
2493
|
-
*
|
|
2494
|
-
* This function terminates the active WebSocket connection if one exists,
|
|
2495
|
-
* and cleans up by removing all event listeners attached to it. After calling
|
|
2496
|
-
* this function, the WebSocket client will be set to null, effectively
|
|
2497
|
-
* ending the connection and preparing for potential future connections.
|
|
2498
|
-
*
|
|
2499
|
-
* @returns {void} This function doesn't return a value.
|
|
2500
|
-
*/
|
|
2501
|
-
closeWebSocket(app) {
|
|
2502
|
-
const wsClient = this.wsClients.get(app);
|
|
2503
|
-
if (wsClient) {
|
|
2504
|
-
wsClient.close();
|
|
2505
|
-
this.wsClients.delete(app);
|
|
2506
|
-
console.log(`WebSocket para o app '${app}' foi fechado.`);
|
|
2507
|
-
} else {
|
|
2508
|
-
console.warn(`WebSocket para o app '${app}' n\xE3o encontrado.`);
|
|
2509
|
-
}
|
|
2510
|
-
}
|
|
2511
|
-
closeAllWebSockets() {
|
|
2512
|
-
this.wsClients.forEach((wsClient, app) => {
|
|
2513
|
-
wsClient.close();
|
|
2514
|
-
console.log(`WebSocket para o app '${app}' foi fechado.`);
|
|
2515
|
-
});
|
|
2516
|
-
this.wsClients.clear();
|
|
2517
|
-
console.log("Todos os WebSockets foram fechados.");
|
|
1926
|
+
closeWebSocket() {
|
|
1927
|
+
this.webSocketClient?.close();
|
|
1928
|
+
this.webSocketClient = void 0;
|
|
2518
1929
|
}
|
|
2519
1930
|
/**
|
|
2520
1931
|
* Inicializa uma nova instância de `ChannelInstance` para manipular canais localmente.
|
|
2521
|
-
*
|
|
2522
|
-
* @param channelId - O ID do canal, se disponível. Caso contrário, a instância será para um canal ainda não criado.
|
|
2523
|
-
* @param app
|
|
2524
|
-
* @returns Uma instância de `ChannelInstance` vinculada ao cliente atual.
|
|
2525
1932
|
*/
|
|
2526
|
-
Channel(channelId
|
|
2527
|
-
|
|
2528
|
-
throw new Error(
|
|
2529
|
-
"O nome do aplicativo (app) \xE9 obrigat\xF3rio para criar um Channel."
|
|
2530
|
-
);
|
|
2531
|
-
}
|
|
2532
|
-
return this.channels.Channel({ id: channelId, app });
|
|
1933
|
+
Channel(channelId) {
|
|
1934
|
+
return this.channels.Channel({ id: channelId });
|
|
2533
1935
|
}
|
|
2534
1936
|
/**
|
|
2535
1937
|
* Inicializa uma nova instância de `PlaybackInstance` para manipular playbacks.
|
|
2536
|
-
*
|
|
2537
|
-
* @param playbackId - O ID do playback, se disponível. Caso contrário, a instância será para um playback ainda não inicializado.
|
|
2538
|
-
* @param app
|
|
2539
|
-
* @returns Uma instância de `PlaybackInstance` vinculada ao cliente atual.
|
|
2540
1938
|
*/
|
|
2541
|
-
Playback(playbackId,
|
|
2542
|
-
|
|
2543
|
-
throw new Error(
|
|
2544
|
-
"O nome do aplicativo (app) \xE9 obrigat\xF3rio para criar um Channel."
|
|
2545
|
-
);
|
|
2546
|
-
}
|
|
2547
|
-
return this.playbacks.Playback({ id: playbackId, app });
|
|
1939
|
+
Playback(playbackId, _app) {
|
|
1940
|
+
return this.playbacks.Playback({ id: playbackId });
|
|
2548
1941
|
}
|
|
2549
1942
|
};
|
|
2550
1943
|
// Annotate the CommonJS export names for ESM import in node:
|