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