callix-dialer-widget 2.8.2 → 2.8.4
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/WIDGET-README.md
CHANGED
|
@@ -154,7 +154,19 @@ guardado, a tela avisa que a conexão está voltando, e ele é enviado no recone
|
|
|
154
154
|
tiver menos de 15 s; mais velho que isso é descartado, para não atingir outra chamada. Antes,
|
|
155
155
|
um "encerrar" dado nesse momento deixava a chamada ativa no motor.
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
Espelho e F5 no meio da chamada (2.8.3): a tela de chamada mostra o número da chamada em
|
|
158
|
+
curso informado pelo SDK ou pelo motor quando ninguém digitou nada naquele teclado (antes
|
|
159
|
+
aparecia "(--) ----- ----" na aba espelho). Click-to-call com o agente em pausa (2.8.3): o
|
|
160
|
+
agente sai da pausa, espera o Callix liberá-lo (até 8 s) e disca; se não liberar, a tarefa
|
|
161
|
+
volta à fila. No modo motor, um `dial` recebido em pausa faz o mesmo dentro do motor.
|
|
162
|
+
|
|
163
|
+
Saída confirmada (2.8.4): o widget aguarda o motor e o back-end confirmarem o
|
|
164
|
+
encerramento antes de apagar a credencial local. Se a saída falhar, mostra o erro
|
|
165
|
+
e permite tentar novamente; se o Chromium cair, tenta concluir a limpeza pela API.
|
|
166
|
+
O modo SDK direto também encerra a sessão no back-end. Atualize o widget, o
|
|
167
|
+
back-end e o motor juntos para usar este fluxo.
|
|
168
|
+
|
|
169
|
+
Sem `engineUrl` o SDK continua rodando no navegador.
|
|
158
170
|
|
|
159
171
|
## 🔐 Login e usuário conhecido
|
|
160
172
|
|
|
@@ -43832,7 +43832,12 @@ class EngineClient {
|
|
|
43832
43832
|
const ws = new Impl(engineWsUrl(this.options.url));
|
|
43833
43833
|
this.ws = ws;
|
|
43834
43834
|
ws.onopen = () => {
|
|
43835
|
+
var _a2, _b;
|
|
43835
43836
|
if (this.ws !== ws) return;
|
|
43837
|
+
if (this.pendingLogoffToken !== void 0 && ((_b = (_a2 = this.options).sessionToken) == null ? void 0 : _b.call(_a2)) !== this.pendingLogoffToken) {
|
|
43838
|
+
this.pendingCommands = this.pendingCommands.filter((entry) => entry.command.name !== "logoff");
|
|
43839
|
+
this.rejectWaiters(new EngineAccessError("session-changed", "A sessão mudou antes de confirmar a saída."));
|
|
43840
|
+
}
|
|
43836
43841
|
this.reconnectDelay = RECONNECT_MIN_MS;
|
|
43837
43842
|
this.setState({ wsState: "open" });
|
|
43838
43843
|
const sessionToken = !this.session && this.options.sessionToken ? this.options.sessionToken() : null;
|
|
@@ -43889,7 +43894,8 @@ class EngineClient {
|
|
|
43889
43894
|
ws.onclose = null;
|
|
43890
43895
|
ws.close();
|
|
43891
43896
|
}
|
|
43892
|
-
|
|
43897
|
+
this.pendingCommands = [];
|
|
43898
|
+
this.rejectWaiters(new Error("a conexão com o motor foi encerrada"));
|
|
43893
43899
|
this.setState({ wsState: "closed", tabId: null, engaged: false });
|
|
43894
43900
|
}
|
|
43895
43901
|
/**
|
|
@@ -43951,6 +43957,10 @@ class EngineClient {
|
|
|
43951
43957
|
break;
|
|
43952
43958
|
}
|
|
43953
43959
|
if (message.code === "session-changed") {
|
|
43960
|
+
if (this.pendingLogoffToken !== void 0) {
|
|
43961
|
+
this.pendingCommands = this.pendingCommands.filter((entry) => entry.command.name !== "logoff");
|
|
43962
|
+
this.rejectWaiters(new EngineAccessError(message.code, message.message));
|
|
43963
|
+
}
|
|
43954
43964
|
this.session = null;
|
|
43955
43965
|
this.log("a sessão deste agente foi trocada por um login novo; reconectando com a credencial atual");
|
|
43956
43966
|
break;
|
|
@@ -44014,6 +44024,10 @@ class EngineClient {
|
|
|
44014
44024
|
* (é assim que um login novo substitui a sessão de outro navegador).
|
|
44015
44025
|
*/
|
|
44016
44026
|
setSession(session) {
|
|
44027
|
+
if (this.pendingLogoffToken !== void 0) {
|
|
44028
|
+
this.pendingCommands = this.pendingCommands.filter((entry) => entry.command.name !== "logoff");
|
|
44029
|
+
this.rejectWaiters(new EngineAccessError("session-changed", "Uma sessão nova substituiu o pedido de saída anterior."));
|
|
44030
|
+
}
|
|
44017
44031
|
this.session = session;
|
|
44018
44032
|
this.setState({ rejected: null, sessionLost: null });
|
|
44019
44033
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
@@ -44042,6 +44056,35 @@ class EngineClient {
|
|
|
44042
44056
|
release() {
|
|
44043
44057
|
this.send({ type: "release" });
|
|
44044
44058
|
}
|
|
44059
|
+
/** Só confirma a saída quando o host terminou o encerramento do SDK e da sessão. */
|
|
44060
|
+
async logoff(timeoutMs = 4e4) {
|
|
44061
|
+
var _a2, _b;
|
|
44062
|
+
if (this.state.rejected) throw new EngineAccessError(this.state.rejected.code, this.state.rejected.message);
|
|
44063
|
+
this.session = null;
|
|
44064
|
+
this.pendingCommands = [];
|
|
44065
|
+
this.pendingLogoffToken = (_b = (_a2 = this.options).sessionToken) == null ? void 0 : _b.call(_a2);
|
|
44066
|
+
const command = { name: "logoff" };
|
|
44067
|
+
let acknowledged = false;
|
|
44068
|
+
const confirmation = this.waitForView(timeoutMs, (view) => {
|
|
44069
|
+
if (view.loggingOff) {
|
|
44070
|
+
acknowledged = true;
|
|
44071
|
+
return false;
|
|
44072
|
+
}
|
|
44073
|
+
return view.engine === "stopped" || view.engine === "crashed" || acknowledged && Boolean(view.logoffError);
|
|
44074
|
+
}, { fresh: true });
|
|
44075
|
+
this.command(command);
|
|
44076
|
+
try {
|
|
44077
|
+
const view = await confirmation;
|
|
44078
|
+
if (view.logoffError) throw new Error(view.logoffError);
|
|
44079
|
+
if (!acknowledged || view.engine !== "stopped") throw new Error("O motor parou sem confirmar o encerramento da sessão no Callix.");
|
|
44080
|
+
} catch (error2) {
|
|
44081
|
+
if (error2 instanceof EngineAccessError) throw error2;
|
|
44082
|
+
throw new Error(error2 instanceof Error && error2.message !== "o motor não respondeu" ? error2.message : "O motor não confirmou o encerramento da sessão.");
|
|
44083
|
+
} finally {
|
|
44084
|
+
this.pendingCommands = this.pendingCommands.filter((entry) => entry.command !== command);
|
|
44085
|
+
this.pendingLogoffToken = void 0;
|
|
44086
|
+
}
|
|
44087
|
+
}
|
|
44045
44088
|
command(command) {
|
|
44046
44089
|
if (this.ws && this.ws.readyState === WebSocket.OPEN && this.state.tabId) {
|
|
44047
44090
|
this.send({ type: "command", command });
|
|
@@ -58735,7 +58778,7 @@ function AuthenticatedMobileDialer({
|
|
|
58735
58778
|
showClientInfoModal = false,
|
|
58736
58779
|
density
|
|
58737
58780
|
}) {
|
|
58738
|
-
var _a2, _b, _c, _d, _e;
|
|
58781
|
+
var _a2, _b, _c, _d, _e, _f;
|
|
58739
58782
|
const { skipManualQualification } = useDialerConfig();
|
|
58740
58783
|
const [dialValue, setDialValue] = useState("");
|
|
58741
58784
|
const [activeScreen, setActiveScreen] = useState("dialer");
|
|
@@ -58775,6 +58818,7 @@ function AuthenticatedMobileDialer({
|
|
|
58775
58818
|
const currentCall = useCallOperatorCurrentCall();
|
|
58776
58819
|
const { hangup, answer, reject } = useCallOperatorCurrentCallControls();
|
|
58777
58820
|
const callInfo = useCallOperatorCurrentCallInfo();
|
|
58821
|
+
const activeCallNumber = ((_a2 = currentCall == null ? void 0 : currentCall.data) == null ? void 0 : _a2.destinationNumber) || (currentCall == null ? void 0 : currentCall.callerNumber) || "";
|
|
58778
58822
|
const rawSuccessQualifications = useCallOperatorSuccessQualifications();
|
|
58779
58823
|
const rawDiscardQualifications = useCallOperatorDiscardQualifications();
|
|
58780
58824
|
const successQualifications = useMemo(() => rawSuccessQualifications ?? [], [rawSuccessQualifications]);
|
|
@@ -59349,6 +59393,8 @@ function AuthenticatedMobileDialer({
|
|
|
59349
59393
|
phoneNumber = callInfo.info.campaignContact.phoneNumber;
|
|
59350
59394
|
} else if (dialValue) {
|
|
59351
59395
|
phoneNumber = dialValue;
|
|
59396
|
+
} else if (activeCallNumber) {
|
|
59397
|
+
phoneNumber = activeCallNumber;
|
|
59352
59398
|
}
|
|
59353
59399
|
const cleanNumber = phoneNumber.replace(/[^\d+]/g, "");
|
|
59354
59400
|
let whatsappNumber = cleanNumber;
|
|
@@ -59364,7 +59410,7 @@ function AuthenticatedMobileDialer({
|
|
|
59364
59410
|
const whatsappUrl = `https://wa.me/${whatsappNumber.replace("+", "")}?text=${encodedMessage}`;
|
|
59365
59411
|
console.log("[MobileDialer] Abrindo WhatsApp com template:", whatsappUrl);
|
|
59366
59412
|
window.open(whatsappUrl, "_blank");
|
|
59367
|
-
}, [callInfo, dialValue, userData, username, contactData.finalData]);
|
|
59413
|
+
}, [callInfo, dialValue, userData, username, contactData.finalData, activeCallNumber]);
|
|
59368
59414
|
const handleGoOnBreak = useCallback(
|
|
59369
59415
|
async (breakId) => {
|
|
59370
59416
|
if (!goOnBreak) return;
|
|
@@ -59432,8 +59478,9 @@ function AuthenticatedMobileDialer({
|
|
|
59432
59478
|
if ((callInfo == null ? void 0 : callInfo.type) === "campaign" && ((_a3 = callInfo.info.campaignContact) == null ? void 0 : _a3.phoneNumber)) {
|
|
59433
59479
|
return formatPhoneForDisplay(callInfo.info.campaignContact.phoneNumber);
|
|
59434
59480
|
}
|
|
59435
|
-
|
|
59436
|
-
|
|
59481
|
+
if (formattedDial) return formattedDial;
|
|
59482
|
+
return activeCallNumber ? formatPhoneForDisplay(activeCallNumber) : "(--) ----- ----";
|
|
59483
|
+
}, [callInfo, formattedDial, activeCallNumber]);
|
|
59437
59484
|
const callStatusLabel = useMemo(() => {
|
|
59438
59485
|
if (isConnecting) return "Ligando...";
|
|
59439
59486
|
if (isInCall) return "Em chamada";
|
|
@@ -59448,7 +59495,7 @@ function AuthenticatedMobileDialer({
|
|
|
59448
59495
|
if (isOnBreak) return "paused";
|
|
59449
59496
|
return "available";
|
|
59450
59497
|
}, [isError, isOnBreak]);
|
|
59451
|
-
const activeBreakName = (operatorState == null ? void 0 : operatorState.state) === "onBreak" ? (
|
|
59498
|
+
const activeBreakName = (operatorState == null ? void 0 : operatorState.state) === "onBreak" ? (_b = operatorState.break) == null ? void 0 : _b.name : void 0;
|
|
59452
59499
|
const statusBadgeText = useMemo(() => {
|
|
59453
59500
|
if (isError) return (dialerNotice == null ? void 0 : dialerNotice.source) === "device" ? dialerNotice.title : "Erro";
|
|
59454
59501
|
if (isOnBreak) return currentBreakName || activeBreakName || "Em pausa";
|
|
@@ -59591,15 +59638,15 @@ function AuthenticatedMobileDialer({
|
|
|
59591
59638
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "client-card-header", children: [
|
|
59592
59639
|
/* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-user-circle client-avatar" }),
|
|
59593
59640
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "client-main-info", children: [
|
|
59594
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "client-name", id: "clientName", children: findValue(contactData.finalData, ["DECISOR", "NOME", "CLIENTE", "PROSPECT"]) ?? ((
|
|
59595
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "client-company", id: "clientCompany", children: findValue(contactData.finalData, ["EMPRESA", "RAZAO SOCIAL", "RAZÃO SOCIAL", "RAZAO_SOCIAL"]) ?? ((
|
|
59641
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "client-name", id: "clientName", children: findValue(contactData.finalData, ["DECISOR", "NOME", "CLIENTE", "PROSPECT"]) ?? ((_c = callInfo.info.campaignContact) == null ? void 0 : _c.label) ?? "--" }),
|
|
59642
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "client-company", id: "clientCompany", children: findValue(contactData.finalData, ["EMPRESA", "RAZAO SOCIAL", "RAZÃO SOCIAL", "RAZAO_SOCIAL"]) ?? ((_d = callInfo.info.campaign) == null ? void 0 : _d.name) ?? "--" })
|
|
59596
59643
|
] })
|
|
59597
59644
|
] }),
|
|
59598
59645
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "client-card-divider" }),
|
|
59599
59646
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "client-info-list", children: [
|
|
59600
59647
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "client-info-row", children: [
|
|
59601
59648
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "client-info-label", children: "Socio" }),
|
|
59602
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "client-info-value", children: findValue(contactData.finalData, ["SOCIO", "PROSPECT", "DECISOR", "NOME", "CLIENTE"]) ?? ((
|
|
59649
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "client-info-value", children: findValue(contactData.finalData, ["SOCIO", "PROSPECT", "DECISOR", "NOME", "CLIENTE"]) ?? ((_e = callInfo.info.campaignContact) == null ? void 0 : _e.label) ?? "--" })
|
|
59603
59650
|
] }),
|
|
59604
59651
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "client-info-row", children: [
|
|
59605
59652
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "client-info-label", children: "Empresa" }),
|
|
@@ -59796,7 +59843,7 @@ function AuthenticatedMobileDialer({
|
|
|
59796
59843
|
{
|
|
59797
59844
|
isOpen: isClientInfoModalOpen,
|
|
59798
59845
|
campaignCallInfo,
|
|
59799
|
-
campaignContactPhoneId: ((
|
|
59846
|
+
campaignContactPhoneId: ((_f = currentCall == null ? void 0 : currentCall.data) == null ? void 0 : _f.campaignContactPhoneId) ?? null,
|
|
59800
59847
|
callDuration: callDurationSeconds,
|
|
59801
59848
|
isCallActive: isInCall || isConnecting,
|
|
59802
59849
|
onClose: () => setIsClientInfoModalOpen(false)
|
|
@@ -60358,6 +60405,27 @@ function AuthenticatedMiniDialer({
|
|
|
60358
60405
|
)
|
|
60359
60406
|
] });
|
|
60360
60407
|
}
|
|
60408
|
+
async function endUserSdkSession(apiUrl, token, timeoutMs = 2e4) {
|
|
60409
|
+
const controller = new AbortController();
|
|
60410
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
60411
|
+
try {
|
|
60412
|
+
const response = await fetch(apiUrl, {
|
|
60413
|
+
method: "DELETE",
|
|
60414
|
+
headers: { Authorization: `Bearer ${token}`, "x-session-token": token },
|
|
60415
|
+
credentials: "same-origin",
|
|
60416
|
+
signal: controller.signal
|
|
60417
|
+
});
|
|
60418
|
+
const data = await response.json().catch(() => null);
|
|
60419
|
+
if (!response.ok || (data == null ? void 0 : data.success) === false) {
|
|
60420
|
+
throw new Error((data == null ? void 0 : data.error) || `O Callix não confirmou o encerramento da sessão (${response.status}).`);
|
|
60421
|
+
}
|
|
60422
|
+
} catch (error2) {
|
|
60423
|
+
if (controller.signal.aborted) throw new Error("O Callix não respondeu ao pedido de encerramento da sessão.");
|
|
60424
|
+
throw error2;
|
|
60425
|
+
} finally {
|
|
60426
|
+
clearTimeout(timer);
|
|
60427
|
+
}
|
|
60428
|
+
}
|
|
60361
60429
|
const PROBE_TIMEOUT_MS = 600;
|
|
60362
60430
|
const TAKEOVER_GRACE_MS = 3e3;
|
|
60363
60431
|
const TAKEOVER_JITTER_MS = 600;
|
|
@@ -60563,6 +60631,7 @@ const BUSY_INTERVAL_FACTOR = 3;
|
|
|
60563
60631
|
const DISPATCH_COOLDOWN_MS = 6e3;
|
|
60564
60632
|
const CALL_START_TIMEOUT_MS = 4e3;
|
|
60565
60633
|
const CALL_START_CHECK_MS = 100;
|
|
60634
|
+
const LEAVE_BREAK_TIMEOUT_MS = 8e3;
|
|
60566
60635
|
const MAX_BACKOFF_MS = 6e4;
|
|
60567
60636
|
const NOT_CONFIGURED_INTERVAL_MS = 5 * 6e4;
|
|
60568
60637
|
const NOT_CONFIGURED_CODES = /* @__PURE__ */ new Set(["STORE_NOT_CONFIGURED", "SERVER_NOT_CONFIGURED"]);
|
|
@@ -60578,11 +60647,13 @@ function buildHeaders(sessionToken) {
|
|
|
60578
60647
|
const delay = (ms2) => new Promise((resolve) => setTimeout(resolve, ms2));
|
|
60579
60648
|
function ClickToCallAgent({ enabled = true, pollIntervalMs, sessionToken, onTask }) {
|
|
60580
60649
|
const operatorState = useCallOperatorState();
|
|
60581
|
-
const { makeManualCall } = useCallOperatorControls();
|
|
60650
|
+
const { makeManualCall, becomeAvailable } = useCallOperatorControls();
|
|
60582
60651
|
const stateRef = useRef(operatorState == null ? void 0 : operatorState.state);
|
|
60583
60652
|
stateRef.current = operatorState == null ? void 0 : operatorState.state;
|
|
60584
60653
|
const makeManualCallRef = useRef(makeManualCall);
|
|
60585
60654
|
makeManualCallRef.current = makeManualCall;
|
|
60655
|
+
const becomeAvailableRef = useRef(becomeAvailable);
|
|
60656
|
+
becomeAvailableRef.current = becomeAvailable;
|
|
60586
60657
|
const onTaskRef = useRef(onTask);
|
|
60587
60658
|
onTaskRef.current = onTask;
|
|
60588
60659
|
const tickerRef = useRef(null);
|
|
@@ -60644,6 +60715,16 @@ function ClickToCallAgent({ enabled = true, pollIntervalMs, sessionToken, onTask
|
|
|
60644
60715
|
console.error("[ClickToCall] não foi possível confirmar a tarefa", options.taskId, "; o servidor a marcará como não confirmada");
|
|
60645
60716
|
return false;
|
|
60646
60717
|
};
|
|
60718
|
+
const waitForOperatorState = (predicate, timeoutMs) => new Promise((resolve) => {
|
|
60719
|
+
const deadline = Date.now() + timeoutMs;
|
|
60720
|
+
const check = () => {
|
|
60721
|
+
if (disposed) return resolve(false);
|
|
60722
|
+
if (predicate(stateRef.current)) return resolve(true);
|
|
60723
|
+
if (Date.now() >= deadline) return resolve(false);
|
|
60724
|
+
setTimeout(check, CALL_START_CHECK_MS);
|
|
60725
|
+
};
|
|
60726
|
+
check();
|
|
60727
|
+
});
|
|
60647
60728
|
const waitForCallStart = () => new Promise((resolve) => {
|
|
60648
60729
|
const deadline = Date.now() + CALL_START_TIMEOUT_MS;
|
|
60649
60730
|
const check = () => {
|
|
@@ -60677,6 +60758,23 @@ function ClickToCallAgent({ enabled = true, pollIntervalMs, sessionToken, onTask
|
|
|
60677
60758
|
} catch (error2) {
|
|
60678
60759
|
console.warn("[ClickToCall] onTask lançou erro:", error2);
|
|
60679
60760
|
}
|
|
60761
|
+
if (presenceFromOperatorState(stateRef.current) === "on_break") {
|
|
60762
|
+
console.log("[ClickToCall] operador em pausa: saindo da pausa para ligar");
|
|
60763
|
+
try {
|
|
60764
|
+
becomeAvailableRef.current();
|
|
60765
|
+
} catch (error2) {
|
|
60766
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
60767
|
+
console.error("[ClickToCall] becomeAvailable falhou:", message);
|
|
60768
|
+
await ack({ taskId: task.id, status: "failed", error: `Não deu para sair da pausa: ${message}`, retry: true });
|
|
60769
|
+
return;
|
|
60770
|
+
}
|
|
60771
|
+
const available = await waitForOperatorState((state2) => state2 === "idle", LEAVE_BREAK_TIMEOUT_MS);
|
|
60772
|
+
if (!available) {
|
|
60773
|
+
console.warn("[ClickToCall] o operador não saiu da pausa a tempo (estado", stateRef.current, "): devolvendo à fila");
|
|
60774
|
+
await ack({ taskId: task.id, status: "failed", error: `Operador não saiu da pausa (${stateRef.current ?? "desconhecido"})`, retry: true });
|
|
60775
|
+
return;
|
|
60776
|
+
}
|
|
60777
|
+
}
|
|
60680
60778
|
try {
|
|
60681
60779
|
makeManualCallRef.current(task.phone);
|
|
60682
60780
|
} catch (error2) {
|
|
@@ -60780,7 +60878,7 @@ function ClickToCallAgent({ enabled = true, pollIntervalMs, sessionToken, onTask
|
|
|
60780
60878
|
}, [enabled, pollIntervalMs, sessionToken]);
|
|
60781
60879
|
return null;
|
|
60782
60880
|
}
|
|
60783
|
-
const WIDGET_VERSION = "2.8.
|
|
60881
|
+
const WIDGET_VERSION = "2.8.4";
|
|
60784
60882
|
let currentSipWebSocket = null;
|
|
60785
60883
|
function sendSipUnregisterIfPossible(ws) {
|
|
60786
60884
|
if (ws.readyState !== WebSocket.OPEN) return;
|
|
@@ -61145,6 +61243,8 @@ function DialerWrapper({ options, containerEl }) {
|
|
|
61145
61243
|
const isLoadingRef = useRef(false);
|
|
61146
61244
|
isLoadingRef.current = isLoading;
|
|
61147
61245
|
const logoffPendingRef = useRef(false);
|
|
61246
|
+
const logoutInFlightRef = useRef(null);
|
|
61247
|
+
const logoutRetryRef = useRef(false);
|
|
61148
61248
|
const handleLoginRef = useRef(async () => void 0);
|
|
61149
61249
|
const handleLogoutRef = useRef(async () => void 0);
|
|
61150
61250
|
const cancelTakeover = useCallback(() => {
|
|
@@ -61189,6 +61289,7 @@ function DialerWrapper({ options, containerEl }) {
|
|
|
61189
61289
|
log: (message) => console.log("[Callix Dialer/motor]", message)
|
|
61190
61290
|
});
|
|
61191
61291
|
client2.onSessionLost((lost) => {
|
|
61292
|
+
if (logoffPendingRef.current) return;
|
|
61192
61293
|
const notice = describeSessionDrop(lost.originator);
|
|
61193
61294
|
setError(`${notice.title}: ${notice.detail}${notice.action ? ` ${notice.action}` : ""}`);
|
|
61194
61295
|
setIsAuthenticated(false);
|
|
@@ -61362,9 +61463,8 @@ function DialerWrapper({ options, containerEl }) {
|
|
|
61362
61463
|
}
|
|
61363
61464
|
if (!view) return;
|
|
61364
61465
|
setEngineWaiting(false);
|
|
61365
|
-
if (view.engine === "stopped" || view.engine === "crashed") logoffPendingRef.current = false;
|
|
61366
61466
|
const hasSession = (view.engine === "running" || view.engine === "starting") && !sessionLost;
|
|
61367
|
-
if (hasSession && !logoffPendingRef.current && !isAuthenticatedRef.current && !isLoadingRef.current && !joining) {
|
|
61467
|
+
if (hasSession && !view.loggingOff && !view.logoffError && !logoffPendingRef.current && !isAuthenticatedRef.current && !isLoadingRef.current && !joining) {
|
|
61368
61468
|
joining = true;
|
|
61369
61469
|
console.log("[Callix Dialer] o motor já segura a sessão deste agente; entrando por aqui também");
|
|
61370
61470
|
void joinEngine(user).finally(() => {
|
|
@@ -61533,6 +61633,11 @@ function DialerWrapper({ options, containerEl }) {
|
|
|
61533
61633
|
}, [engineMode, tabRole, username]);
|
|
61534
61634
|
const handleLogin = useCallback(async (user) => {
|
|
61535
61635
|
var _a2, _b, _c, _d;
|
|
61636
|
+
if (logoutInFlightRef.current) return;
|
|
61637
|
+
if (logoutRetryRef.current) {
|
|
61638
|
+
await handleLogoutRef.current({ broadcast: false });
|
|
61639
|
+
if (logoutRetryRef.current) return;
|
|
61640
|
+
}
|
|
61536
61641
|
setIsLoading(true);
|
|
61537
61642
|
setError(null);
|
|
61538
61643
|
setLoginStatus("Preparando login...");
|
|
@@ -61551,6 +61656,9 @@ function DialerWrapper({ options, containerEl }) {
|
|
|
61551
61656
|
console.log("[Callix Dialer] o motor recusou a credencial desta aba; fazendo login novo que substitui a sessão de lá");
|
|
61552
61657
|
}
|
|
61553
61658
|
const domain = engineDomain();
|
|
61659
|
+
if ((view == null ? void 0 : view.loggingOff) || (view == null ? void 0 : view.logoffError) && (view.engine === "running" || view.engine === "starting")) {
|
|
61660
|
+
throw new Error(view.logoffError || "A sessão anterior ainda está sendo encerrada. Aguarde antes de entrar novamente.");
|
|
61661
|
+
}
|
|
61554
61662
|
const engineHasSession = view !== null && (view.engine === "running" || view.engine === "starting") && !view.sessionLost;
|
|
61555
61663
|
let token = null;
|
|
61556
61664
|
if (engineHasSession) {
|
|
@@ -61766,54 +61874,116 @@ function DialerWrapper({ options, containerEl }) {
|
|
|
61766
61874
|
useEffect(() => {
|
|
61767
61875
|
handleLoginRef.current = handleLogin;
|
|
61768
61876
|
}, [handleLogin]);
|
|
61769
|
-
const handleLogout = useCallback(
|
|
61770
|
-
|
|
61877
|
+
const handleLogout = useCallback((opts) => {
|
|
61878
|
+
if (logoutInFlightRef.current) return logoutInFlightRef.current;
|
|
61771
61879
|
const broadcast = (opts == null ? void 0 : opts.broadcast) !== false;
|
|
61772
61880
|
const keepDecision = (opts == null ? void 0 : opts.keepDecision) === true;
|
|
61773
61881
|
const currentUsername = username;
|
|
61882
|
+
const currentToken = sessionToken ?? (currentUsername ? readStoredToken(currentUsername) : null);
|
|
61883
|
+
const ownsSdkSession = tabRoleRef.current === "leader" || isAuthenticatedRef.current;
|
|
61774
61884
|
cancelTakeover();
|
|
61775
|
-
|
|
61776
|
-
|
|
61777
|
-
|
|
61778
|
-
(_a2 = engineRef.current) == null ? void 0 : _a2.command({ name: "logoff" });
|
|
61779
|
-
}
|
|
61780
|
-
} else {
|
|
61781
|
-
if (broadcast) (_b = busRef.current) == null ? void 0 : _b.post({ type: "logout" });
|
|
61782
|
-
if (cleanupRef.current) {
|
|
61783
|
-
console.log("[Callix Dialer] 📴 Logout: Chamando goOffline()...");
|
|
61784
|
-
cleanupRef.current.goOffline();
|
|
61785
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
61786
|
-
console.log("[Callix Dialer] 🛑 Logout: Chamando stop()...");
|
|
61787
|
-
cleanupRef.current.stop();
|
|
61788
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
61789
|
-
}
|
|
61790
|
-
if (currentUsername) releaseLeader(currentUsername, tabIdRef.current);
|
|
61791
|
-
}
|
|
61792
|
-
setIsAuthenticated(false);
|
|
61793
|
-
setSessionToken(null);
|
|
61794
|
-
setCallixDomain(null);
|
|
61885
|
+
setIsLoading(true);
|
|
61886
|
+
isLoadingRef.current = true;
|
|
61887
|
+
setLoginStatus("Encerrando sessão...");
|
|
61795
61888
|
setError(null);
|
|
61796
|
-
|
|
61797
|
-
|
|
61798
|
-
if (currentUsername) saveTabRole(currentUsername, "member");
|
|
61799
|
-
return;
|
|
61800
|
-
}
|
|
61801
|
-
setTabRole(null);
|
|
61802
|
-
clearTabRole();
|
|
61803
|
-
setUsername(presetUsername ?? null);
|
|
61804
|
-
if (typeof window !== "undefined" && currentUsername) {
|
|
61889
|
+
const run = async () => {
|
|
61890
|
+
var _a2, _b;
|
|
61805
61891
|
try {
|
|
61806
|
-
|
|
61807
|
-
|
|
61808
|
-
|
|
61809
|
-
|
|
61810
|
-
|
|
61811
|
-
|
|
61892
|
+
if (options.engineUrl) {
|
|
61893
|
+
const client2 = engineRef.current;
|
|
61894
|
+
const state = client2 == null ? void 0 : client2.getState();
|
|
61895
|
+
if (!client2) throw new Error("Não há conexão com o motor para confirmar a saída.");
|
|
61896
|
+
if (!(state == null ? void 0 : state.rejected) && !(state == null ? void 0 : state.sessionLost)) {
|
|
61897
|
+
logoffPendingRef.current = true;
|
|
61898
|
+
const finishStoppedSession = async () => {
|
|
61899
|
+
if (!currentToken) throw new Error("O motor parou sem confirmar a saída e esta aba não tem o token da sessão.");
|
|
61900
|
+
const stored = currentUsername ? readStoredToken(currentUsername) : null;
|
|
61901
|
+
if (stored && stored !== currentToken) throw new EngineAccessError("session-changed", "Outra aba substituiu a sessão durante o encerramento.");
|
|
61902
|
+
await endUserSdkSession(options.apiUrl || "/api/user-sdk-session", currentToken);
|
|
61903
|
+
};
|
|
61904
|
+
const isStopped = () => {
|
|
61905
|
+
const view = client2.getState().view;
|
|
61906
|
+
return (view == null ? void 0 : view.engine) === "stopped" || (view == null ? void 0 : view.engine) === "crashed";
|
|
61907
|
+
};
|
|
61908
|
+
if (isStopped()) {
|
|
61909
|
+
await finishStoppedSession();
|
|
61910
|
+
} else {
|
|
61911
|
+
try {
|
|
61912
|
+
await client2.logoff();
|
|
61913
|
+
} catch (err) {
|
|
61914
|
+
if (!isStopped()) throw err;
|
|
61915
|
+
await finishStoppedSession();
|
|
61916
|
+
}
|
|
61917
|
+
}
|
|
61918
|
+
}
|
|
61919
|
+
} else {
|
|
61920
|
+
if (broadcast) (_a2 = busRef.current) == null ? void 0 : _a2.post({ type: "logout" });
|
|
61921
|
+
if (cleanupRef.current) {
|
|
61922
|
+
cleanupRef.current.goOffline();
|
|
61923
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
61924
|
+
(_b = cleanupRef.current) == null ? void 0 : _b.stop();
|
|
61925
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
61926
|
+
}
|
|
61927
|
+
if (ownsSdkSession && !keepDecision) {
|
|
61928
|
+
if (!currentToken) throw new Error("Não há token para encerrar a sessão no Callix.");
|
|
61929
|
+
const stored = currentUsername ? readStoredToken(currentUsername) : null;
|
|
61930
|
+
if (stored && stored !== currentToken) throw new EngineAccessError("session-changed", "Outra aba substituiu a sessão durante o encerramento.");
|
|
61931
|
+
await endUserSdkSession(options.apiUrl || "/api/user-sdk-session", currentToken);
|
|
61932
|
+
}
|
|
61933
|
+
if (currentUsername) releaseLeader(currentUsername, tabIdRef.current);
|
|
61934
|
+
}
|
|
61935
|
+
logoutRetryRef.current = false;
|
|
61936
|
+
logoffPendingRef.current = false;
|
|
61937
|
+
setIsAuthenticated(false);
|
|
61938
|
+
setSessionToken(null);
|
|
61939
|
+
currentSessionRef.current.token = null;
|
|
61940
|
+
setCallixDomain(null);
|
|
61941
|
+
if (keepDecision) {
|
|
61942
|
+
setTabRole("member");
|
|
61943
|
+
if (currentUsername) saveTabRole(currentUsername, "member");
|
|
61944
|
+
return;
|
|
61945
|
+
}
|
|
61946
|
+
setTabRole(null);
|
|
61947
|
+
clearTabRole();
|
|
61948
|
+
setUsername(presetUsername ?? null);
|
|
61949
|
+
if (typeof window !== "undefined" && currentUsername && (options.engineUrl || ownsSdkSession)) {
|
|
61950
|
+
try {
|
|
61951
|
+
const STORAGE_KEYS = getStorageKeys(currentUsername);
|
|
61952
|
+
if (localStorage.getItem(STORAGE_KEYS.SESSION_TOKEN) === currentToken) {
|
|
61953
|
+
if (localStorage.getItem(LAST_USERNAME_KEY) === currentUsername) localStorage.removeItem(LAST_USERNAME_KEY);
|
|
61954
|
+
localStorage.removeItem(STORAGE_KEYS.SESSION_TOKEN);
|
|
61955
|
+
localStorage.removeItem(STORAGE_KEYS.DOMAIN);
|
|
61956
|
+
localStorage.removeItem(STORAGE_KEYS.TIMESTAMP);
|
|
61957
|
+
}
|
|
61958
|
+
} catch (err) {
|
|
61959
|
+
console.warn("[Callix Dialer] Não foi possível limpar os dados locais da sessão:", err);
|
|
61960
|
+
}
|
|
61961
|
+
}
|
|
61812
61962
|
} catch (err) {
|
|
61813
|
-
console.
|
|
61963
|
+
console.error("[Callix Dialer] Não foi possível confirmar a saída:", err);
|
|
61964
|
+
if (err instanceof EngineAccessError && err.code === "session-changed") {
|
|
61965
|
+
logoutRetryRef.current = false;
|
|
61966
|
+
logoffPendingRef.current = false;
|
|
61967
|
+
setSessionToken(null);
|
|
61968
|
+
setIsAuthenticated(false);
|
|
61969
|
+
setError("A sessão foi substituída por outro login durante a saída. Entre novamente para usar a sessão atual.");
|
|
61970
|
+
return;
|
|
61971
|
+
}
|
|
61972
|
+
logoutRetryRef.current = true;
|
|
61973
|
+
setIsAuthenticated(false);
|
|
61974
|
+
setError(`Não foi possível confirmar a saída: ${err instanceof Error ? err.message : "falha ao encerrar a sessão"}. Ao entrar novamente, o discador tentará concluir o encerramento primeiro.`);
|
|
61975
|
+
} finally {
|
|
61976
|
+
setIsLoading(false);
|
|
61977
|
+
isLoadingRef.current = false;
|
|
61978
|
+
setLoginStatus(null);
|
|
61814
61979
|
}
|
|
61815
|
-
}
|
|
61816
|
-
|
|
61980
|
+
};
|
|
61981
|
+
const completion = run().finally(() => {
|
|
61982
|
+
logoutInFlightRef.current = null;
|
|
61983
|
+
});
|
|
61984
|
+
logoutInFlightRef.current = completion;
|
|
61985
|
+
return completion;
|
|
61986
|
+
}, [username, sessionToken, presetUsername, options.apiUrl, options.engineUrl, cancelTakeover]);
|
|
61817
61987
|
useEffect(() => {
|
|
61818
61988
|
handleLogoutRef.current = handleLogout;
|
|
61819
61989
|
}, [handleLogout]);
|
|
@@ -61841,6 +62011,7 @@ function DialerWrapper({ options, containerEl }) {
|
|
|
61841
62011
|
const handleSdkSessionDropped = useCallback(
|
|
61842
62012
|
(originator, notice) => {
|
|
61843
62013
|
var _a2;
|
|
62014
|
+
if (logoutInFlightRef.current) return;
|
|
61844
62015
|
const user = username;
|
|
61845
62016
|
if (user) releaseLeader(user, tabIdRef.current);
|
|
61846
62017
|
(_a2 = busRef.current) == null ? void 0 : _a2.post({ type: "dropped", originator });
|