callix-dialer-widget 2.8.3 → 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
|
@@ -160,7 +160,13 @@ aparecia "(--) ----- ----" na aba espelho). Click-to-call com o agente em pausa
|
|
|
160
160
|
agente sai da pausa, espera o Callix liberá-lo (até 8 s) e disca; se não liberar, a tarefa
|
|
161
161
|
volta à fila. No modo motor, um `dial` recebido em pausa faz o mesmo dentro do motor.
|
|
162
162
|
|
|
163
|
-
|
|
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.
|
|
164
170
|
|
|
165
171
|
## 🔐 Login e usuário conhecido
|
|
166
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 });
|
|
@@ -60362,6 +60405,27 @@ function AuthenticatedMiniDialer({
|
|
|
60362
60405
|
)
|
|
60363
60406
|
] });
|
|
60364
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
|
+
}
|
|
60365
60429
|
const PROBE_TIMEOUT_MS = 600;
|
|
60366
60430
|
const TAKEOVER_GRACE_MS = 3e3;
|
|
60367
60431
|
const TAKEOVER_JITTER_MS = 600;
|
|
@@ -60814,7 +60878,7 @@ function ClickToCallAgent({ enabled = true, pollIntervalMs, sessionToken, onTask
|
|
|
60814
60878
|
}, [enabled, pollIntervalMs, sessionToken]);
|
|
60815
60879
|
return null;
|
|
60816
60880
|
}
|
|
60817
|
-
const WIDGET_VERSION = "2.8.
|
|
60881
|
+
const WIDGET_VERSION = "2.8.4";
|
|
60818
60882
|
let currentSipWebSocket = null;
|
|
60819
60883
|
function sendSipUnregisterIfPossible(ws) {
|
|
60820
60884
|
if (ws.readyState !== WebSocket.OPEN) return;
|
|
@@ -61179,6 +61243,8 @@ function DialerWrapper({ options, containerEl }) {
|
|
|
61179
61243
|
const isLoadingRef = useRef(false);
|
|
61180
61244
|
isLoadingRef.current = isLoading;
|
|
61181
61245
|
const logoffPendingRef = useRef(false);
|
|
61246
|
+
const logoutInFlightRef = useRef(null);
|
|
61247
|
+
const logoutRetryRef = useRef(false);
|
|
61182
61248
|
const handleLoginRef = useRef(async () => void 0);
|
|
61183
61249
|
const handleLogoutRef = useRef(async () => void 0);
|
|
61184
61250
|
const cancelTakeover = useCallback(() => {
|
|
@@ -61223,6 +61289,7 @@ function DialerWrapper({ options, containerEl }) {
|
|
|
61223
61289
|
log: (message) => console.log("[Callix Dialer/motor]", message)
|
|
61224
61290
|
});
|
|
61225
61291
|
client2.onSessionLost((lost) => {
|
|
61292
|
+
if (logoffPendingRef.current) return;
|
|
61226
61293
|
const notice = describeSessionDrop(lost.originator);
|
|
61227
61294
|
setError(`${notice.title}: ${notice.detail}${notice.action ? ` ${notice.action}` : ""}`);
|
|
61228
61295
|
setIsAuthenticated(false);
|
|
@@ -61396,9 +61463,8 @@ function DialerWrapper({ options, containerEl }) {
|
|
|
61396
61463
|
}
|
|
61397
61464
|
if (!view) return;
|
|
61398
61465
|
setEngineWaiting(false);
|
|
61399
|
-
if (view.engine === "stopped" || view.engine === "crashed") logoffPendingRef.current = false;
|
|
61400
61466
|
const hasSession = (view.engine === "running" || view.engine === "starting") && !sessionLost;
|
|
61401
|
-
if (hasSession && !logoffPendingRef.current && !isAuthenticatedRef.current && !isLoadingRef.current && !joining) {
|
|
61467
|
+
if (hasSession && !view.loggingOff && !view.logoffError && !logoffPendingRef.current && !isAuthenticatedRef.current && !isLoadingRef.current && !joining) {
|
|
61402
61468
|
joining = true;
|
|
61403
61469
|
console.log("[Callix Dialer] o motor já segura a sessão deste agente; entrando por aqui também");
|
|
61404
61470
|
void joinEngine(user).finally(() => {
|
|
@@ -61567,6 +61633,11 @@ function DialerWrapper({ options, containerEl }) {
|
|
|
61567
61633
|
}, [engineMode, tabRole, username]);
|
|
61568
61634
|
const handleLogin = useCallback(async (user) => {
|
|
61569
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
|
+
}
|
|
61570
61641
|
setIsLoading(true);
|
|
61571
61642
|
setError(null);
|
|
61572
61643
|
setLoginStatus("Preparando login...");
|
|
@@ -61585,6 +61656,9 @@ function DialerWrapper({ options, containerEl }) {
|
|
|
61585
61656
|
console.log("[Callix Dialer] o motor recusou a credencial desta aba; fazendo login novo que substitui a sessão de lá");
|
|
61586
61657
|
}
|
|
61587
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
|
+
}
|
|
61588
61662
|
const engineHasSession = view !== null && (view.engine === "running" || view.engine === "starting") && !view.sessionLost;
|
|
61589
61663
|
let token = null;
|
|
61590
61664
|
if (engineHasSession) {
|
|
@@ -61800,54 +61874,116 @@ function DialerWrapper({ options, containerEl }) {
|
|
|
61800
61874
|
useEffect(() => {
|
|
61801
61875
|
handleLoginRef.current = handleLogin;
|
|
61802
61876
|
}, [handleLogin]);
|
|
61803
|
-
const handleLogout = useCallback(
|
|
61804
|
-
|
|
61877
|
+
const handleLogout = useCallback((opts) => {
|
|
61878
|
+
if (logoutInFlightRef.current) return logoutInFlightRef.current;
|
|
61805
61879
|
const broadcast = (opts == null ? void 0 : opts.broadcast) !== false;
|
|
61806
61880
|
const keepDecision = (opts == null ? void 0 : opts.keepDecision) === true;
|
|
61807
61881
|
const currentUsername = username;
|
|
61882
|
+
const currentToken = sessionToken ?? (currentUsername ? readStoredToken(currentUsername) : null);
|
|
61883
|
+
const ownsSdkSession = tabRoleRef.current === "leader" || isAuthenticatedRef.current;
|
|
61808
61884
|
cancelTakeover();
|
|
61809
|
-
|
|
61810
|
-
|
|
61811
|
-
|
|
61812
|
-
(_a2 = engineRef.current) == null ? void 0 : _a2.command({ name: "logoff" });
|
|
61813
|
-
}
|
|
61814
|
-
} else {
|
|
61815
|
-
if (broadcast) (_b = busRef.current) == null ? void 0 : _b.post({ type: "logout" });
|
|
61816
|
-
if (cleanupRef.current) {
|
|
61817
|
-
console.log("[Callix Dialer] 📴 Logout: Chamando goOffline()...");
|
|
61818
|
-
cleanupRef.current.goOffline();
|
|
61819
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
61820
|
-
console.log("[Callix Dialer] 🛑 Logout: Chamando stop()...");
|
|
61821
|
-
cleanupRef.current.stop();
|
|
61822
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
61823
|
-
}
|
|
61824
|
-
if (currentUsername) releaseLeader(currentUsername, tabIdRef.current);
|
|
61825
|
-
}
|
|
61826
|
-
setIsAuthenticated(false);
|
|
61827
|
-
setSessionToken(null);
|
|
61828
|
-
setCallixDomain(null);
|
|
61885
|
+
setIsLoading(true);
|
|
61886
|
+
isLoadingRef.current = true;
|
|
61887
|
+
setLoginStatus("Encerrando sessão...");
|
|
61829
61888
|
setError(null);
|
|
61830
|
-
|
|
61831
|
-
|
|
61832
|
-
if (currentUsername) saveTabRole(currentUsername, "member");
|
|
61833
|
-
return;
|
|
61834
|
-
}
|
|
61835
|
-
setTabRole(null);
|
|
61836
|
-
clearTabRole();
|
|
61837
|
-
setUsername(presetUsername ?? null);
|
|
61838
|
-
if (typeof window !== "undefined" && currentUsername) {
|
|
61889
|
+
const run = async () => {
|
|
61890
|
+
var _a2, _b;
|
|
61839
61891
|
try {
|
|
61840
|
-
|
|
61841
|
-
|
|
61842
|
-
|
|
61843
|
-
|
|
61844
|
-
|
|
61845
|
-
|
|
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
|
+
}
|
|
61846
61962
|
} catch (err) {
|
|
61847
|
-
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);
|
|
61848
61979
|
}
|
|
61849
|
-
}
|
|
61850
|
-
|
|
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]);
|
|
61851
61987
|
useEffect(() => {
|
|
61852
61988
|
handleLogoutRef.current = handleLogout;
|
|
61853
61989
|
}, [handleLogout]);
|
|
@@ -61875,6 +62011,7 @@ function DialerWrapper({ options, containerEl }) {
|
|
|
61875
62011
|
const handleSdkSessionDropped = useCallback(
|
|
61876
62012
|
(originator, notice) => {
|
|
61877
62013
|
var _a2;
|
|
62014
|
+
if (logoutInFlightRef.current) return;
|
|
61878
62015
|
const user = username;
|
|
61879
62016
|
if (user) releaseLeader(user, tabIdRef.current);
|
|
61880
62017
|
(_a2 = busRef.current) == null ? void 0 : _a2.post({ type: "dropped", originator });
|