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