callix-dialer-widget 2.6.0 → 2.8.0

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.
@@ -6,7 +6,7 @@ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read fr
6
6
  var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
7
7
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
8
8
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
9
- import React, { createContext, useMemo, useSyncExternalStore, useRef, useCallback, useContext, useEffect, useState, forwardRef, useLayoutEffect, useId, useImperativeHandle, StrictMode } from "react";
9
+ import React, { createContext, useMemo, useRef, useEffect, useSyncExternalStore, useCallback, useContext, useState, forwardRef, useLayoutEffect, useId, useImperativeHandle, StrictMode } from "react";
10
10
  import require$$0$2, { createPortal } from "react-dom";
11
11
  var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
12
12
  function getAugmentedNamespace(n) {
@@ -43725,6 +43725,13 @@ function requireClientSdkReact() {
43725
43725
  return clientSdkReact;
43726
43726
  }
43727
43727
  var clientSdkReactExports = requireClientSdkReact();
43728
+ class EngineAccessError extends Error {
43729
+ constructor(code, message) {
43730
+ super(message);
43731
+ this.code = code;
43732
+ this.name = "EngineAccessError";
43733
+ }
43734
+ }
43728
43735
  class EngineSignal {
43729
43736
  constructor(value) {
43730
43737
  this.value = value;
@@ -43791,7 +43798,8 @@ class EngineClient {
43791
43798
  outputMuted: false,
43792
43799
  outputVolume: 100,
43793
43800
  transcript: INITIAL_TRANSCRIPT,
43794
- sessionLost: null
43801
+ sessionLost: null,
43802
+ rejected: null
43795
43803
  };
43796
43804
  }
43797
43805
  // ------------------------------------------------------------------ store
@@ -43824,7 +43832,16 @@ class EngineClient {
43824
43832
  if (this.ws !== ws) return;
43825
43833
  this.reconnectDelay = RECONNECT_MIN_MS;
43826
43834
  this.setState({ wsState: "open" });
43827
- const hello = { type: "hello", role: "tab", agentId: this.agentId, token: this.options.token, label: this.options.label ?? "Discador", ...this.session ? { session: this.session } : {} };
43835
+ const sessionToken = !this.session && this.options.sessionToken ? this.options.sessionToken() : null;
43836
+ const hello = {
43837
+ type: "hello",
43838
+ role: "tab",
43839
+ agentId: this.agentId,
43840
+ ...this.options.token ? { token: this.options.token } : {},
43841
+ ...sessionToken ? { sessionToken } : {},
43842
+ label: this.options.label ?? "Discador",
43843
+ ...this.session ? { session: this.session } : {}
43844
+ };
43828
43845
  this.send(hello);
43829
43846
  this.log("conectado ao host do motor");
43830
43847
  };
@@ -43871,18 +43888,30 @@ class EngineClient {
43871
43888
  for (const waiter of this.viewWaiters.splice(0)) clearTimeout(waiter.timer);
43872
43889
  this.setState({ wsState: "closed", tabId: null, engaged: false });
43873
43890
  }
43874
- /** a view atual, ou a próxima que o host mandar (welcome/view) */
43875
- waitForView(timeoutMs = 15e3) {
43876
- if (this.state.view && this.state.tabId) return Promise.resolve(this.state.view);
43891
+ /**
43892
+ * A view atual, ou a próxima que o host mandar (welcome/view). Com `accept`, espera uma
43893
+ * view que passe no filtro (ex.: motor subindo depois de entregar a sessão). Rejeita com
43894
+ * `EngineAccessError` se o host recusou esta aba (`rejected`).
43895
+ */
43896
+ waitForView(timeoutMs = 15e3, accept, options) {
43897
+ const rejected = this.state.rejected;
43898
+ if (rejected) return Promise.reject(new EngineAccessError(rejected.code, rejected.message));
43899
+ if (!(options == null ? void 0 : options.fresh) && this.state.view && this.state.tabId && (!accept || accept(this.state.view))) return Promise.resolve(this.state.view);
43877
43900
  return new Promise((resolve, reject) => {
43878
43901
  const timer = setTimeout(() => {
43879
43902
  const index = this.viewWaiters.findIndex((w) => w.timer === timer);
43880
43903
  if (index >= 0) this.viewWaiters.splice(index, 1);
43881
43904
  reject(new Error("o motor não respondeu"));
43882
43905
  }, timeoutMs);
43883
- this.viewWaiters.push({ resolve, timer });
43906
+ this.viewWaiters.push({ resolve, reject, accept, timer });
43884
43907
  });
43885
43908
  }
43909
+ rejectWaiters(error2) {
43910
+ for (const waiter of this.viewWaiters.splice(0)) {
43911
+ clearTimeout(waiter.timer);
43912
+ waiter.reject(error2);
43913
+ }
43914
+ }
43886
43915
  send(message) {
43887
43916
  if (this.ws && this.ws.readyState === WebSocket.OPEN) this.ws.send(JSON.stringify(message));
43888
43917
  }
@@ -43890,7 +43919,7 @@ class EngineClient {
43890
43919
  switch (message.type) {
43891
43920
  case "welcome": {
43892
43921
  if (Array.isArray(message.iceServers) && message.iceServers.length > 0) this.iceServers = message.iceServers;
43893
- this.setState({ tabId: message.tabId });
43922
+ this.setState({ tabId: message.tabId, rejected: null });
43894
43923
  this.applyView(message.view);
43895
43924
  this.log(`sou a aba ${message.tabId}`);
43896
43925
  if (this.options.autoEngage !== false && message.view.engagedTabId !== message.tabId) this.engage();
@@ -43908,6 +43937,19 @@ class EngineClient {
43908
43937
  void this.onSignal(message.payload);
43909
43938
  break;
43910
43939
  case "error":
43940
+ if (message.code === "unauthorized") {
43941
+ this.setState({ lastError: message.message, rejected: { code: message.code, message: message.message } });
43942
+ this.session = null;
43943
+ this.reconnectDelay = RECONNECT_MAX_MS;
43944
+ this.log(`acesso recusado pelo host: ${message.message}`);
43945
+ this.rejectWaiters(new EngineAccessError(message.code, message.message));
43946
+ break;
43947
+ }
43948
+ if (message.code === "session-changed") {
43949
+ this.session = null;
43950
+ this.log("a sessão deste agente foi trocada por um login novo; reconectando com a credencial atual");
43951
+ break;
43952
+ }
43911
43953
  this.setState({ lastError: message.message });
43912
43954
  this.log(`erro do host: ${message.message}`);
43913
43955
  break;
@@ -43945,6 +43987,10 @@ class EngineClient {
43945
43987
  if (call && typeof call.muted === "boolean" && call.muted !== this.state.inputMuted && call.state !== "ended") patch.inputMuted = call.muted;
43946
43988
  this.setState(patch);
43947
43989
  for (const waiter of this.viewWaiters.splice(0)) {
43990
+ if (waiter.accept && !waiter.accept(view)) {
43991
+ this.viewWaiters.push(waiter);
43992
+ continue;
43993
+ }
43948
43994
  clearTimeout(waiter.timer);
43949
43995
  waiter.resolve(view);
43950
43996
  }
@@ -43957,10 +44003,24 @@ class EngineClient {
43957
44003
  };
43958
44004
  }
43959
44005
  // ------------------------------------------------------------------ sessão e comandos
43960
- /** modo sdk: o widget fez login no back-end e entrega a sessão ao host */
44006
+ /**
44007
+ * Modo sdk: o widget fez login no back-end e entrega a sessão ao host. Com a conexão
44008
+ * aberta manda `session`; recusada ou caída, reconecta agora apresentando a sessão no hello
44009
+ * (é assim que um login novo substitui a sessão de outro navegador).
44010
+ */
43961
44011
  setSession(session) {
43962
44012
  this.session = session;
43963
- this.send({ type: "session", session });
44013
+ this.setState({ rejected: null, sessionLost: null });
44014
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
44015
+ this.send({ type: "session", session });
44016
+ return;
44017
+ }
44018
+ if (this.reconnectTimer) {
44019
+ clearTimeout(this.reconnectTimer);
44020
+ this.reconnectTimer = null;
44021
+ }
44022
+ this.reconnectDelay = RECONNECT_MIN_MS;
44023
+ this.connect();
43964
44024
  }
43965
44025
  engage() {
43966
44026
  this.send({ type: "engage" });
@@ -52376,6 +52436,8 @@ function FloatingDialer({
52376
52436
  sessionStatus = null,
52377
52437
  loginConfirmation = null,
52378
52438
  onCancelLoginConfirmation,
52439
+ onTakeOverSession,
52440
+ onLogoutAllTabs,
52379
52441
  variant = "default",
52380
52442
  portalTarget = null
52381
52443
  }) {
@@ -52728,7 +52790,9 @@ function FloatingDialer({
52728
52790
  error: sessionError,
52729
52791
  statusMessage: sessionStatus,
52730
52792
  confirmation: loginConfirmation,
52731
- onCancelConfirmation: onCancelLoginConfirmation
52793
+ onCancelConfirmation: onCancelLoginConfirmation,
52794
+ onTakeOver: onTakeOverSession,
52795
+ onLogoutAll: onLogoutAllTabs
52732
52796
  }
52733
52797
  );
52734
52798
  if (isMiniMode) {
@@ -52864,7 +52928,7 @@ function FloatingDialer({
52864
52928
  }
52865
52929
  );
52866
52930
  }
52867
- function LoginForm({ defaultValue, onSubmit, isLoading, error: error2, statusMessage, confirmation, onCancelConfirmation }) {
52931
+ function LoginForm({ defaultValue, onSubmit, isLoading, error: error2, statusMessage, confirmation, onCancelConfirmation, onTakeOver, onLogoutAll }) {
52868
52932
  const [value, setValue] = useState(defaultValue);
52869
52933
  const [touched, setTouched] = useState(false);
52870
52934
  useEffect(() => {
@@ -52895,8 +52959,41 @@ function LoginForm({ defaultValue, onSubmit, isLoading, error: error2, statusMes
52895
52959
  }
52896
52960
  );
52897
52961
  }
52962
+ if (confirmation == null ? void 0 : confirmation.elsewhere) {
52963
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`space-y-4`, "data-login-state": "elsewhere", children: [
52964
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`space-y-2 text-center`, children: [
52965
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-[11px] font-semibold uppercase tracking-wide text-slate-500`, children: "Discador ativo em outra aba" }),
52966
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: tw`inline-flex items-center gap-2 rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-sm font-semibold text-slate-800`, children: [
52967
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FiUser, { className: tw`text-primary` }),
52968
+ confirmation.username
52969
+ ] })
52970
+ ] }),
52971
+ error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-center text-xs text-red-500`, children: error2 }),
52972
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
52973
+ "button",
52974
+ {
52975
+ type: "button",
52976
+ id: "takeOverBtn",
52977
+ onClick: () => onTakeOver == null ? void 0 : onTakeOver(),
52978
+ className: tw`flex w-full items-center justify-center gap-2 rounded-xl bg-primary px-4 py-2 text-white shadow-lg transition hover:bg-primaryDark`,
52979
+ children: "Usar o discador nesta aba"
52980
+ }
52981
+ ),
52982
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
52983
+ "button",
52984
+ {
52985
+ type: "button",
52986
+ id: "logoutAllBtn",
52987
+ onClick: () => onLogoutAll == null ? void 0 : onLogoutAll(),
52988
+ className: tw`w-full text-center text-xs font-semibold text-red-500 transition hover:text-red-600`,
52989
+ children: "Sair em todas as abas"
52990
+ }
52991
+ ),
52992
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-center text-[11px] text-slate-400`, children: "O Callix aceita uma conexão por usuário: você já está conectado em outra aba deste navegador e pode continuar por lá." })
52993
+ ] });
52994
+ }
52898
52995
  if (confirmation) {
52899
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`space-y-4`, children: [
52996
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`space-y-4`, "data-login-state": "choose", children: [
52900
52997
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`space-y-2 text-center`, children: [
52901
52998
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-[11px] font-semibold uppercase tracking-wide text-slate-500`, children: "Entrar como" }),
52902
52999
  /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: tw`inline-flex items-center gap-2 rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-sm font-semibold text-slate-800`, children: [
@@ -52905,10 +53002,12 @@ function LoginForm({ defaultValue, onSubmit, isLoading, error: error2, statusMes
52905
53002
  ] })
52906
53003
  ] }),
52907
53004
  error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-center text-xs text-red-500`, children: error2 }),
53005
+ !error2 && confirmation.note && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-center text-xs text-slate-500`, children: confirmation.note }),
52908
53006
  /* @__PURE__ */ jsxRuntimeExports.jsx(
52909
53007
  "button",
52910
53008
  {
52911
53009
  type: "button",
53010
+ id: "loginConfirmBtn",
52912
53011
  onClick: () => void onSubmit(confirmation.username),
52913
53012
  className: tw`flex w-full items-center justify-center gap-2 rounded-xl bg-primary px-4 py-2 text-white shadow-lg transition hover:bg-primaryDark`,
52914
53013
  children: "Entrar"
@@ -52923,7 +53022,7 @@ function LoginForm({ defaultValue, onSubmit, isLoading, error: error2, statusMes
52923
53022
  children: "Usar outro usuário"
52924
53023
  }
52925
53024
  ),
52926
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-center text-[11px] text-slate-400`, children: "Se você já estiver conectado em outra aba, continue usando o discador por lá." })
53025
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-center text-[11px] text-slate-400`, children: "Ao entrar, todas as abas abertas passam a usar o discador. Vai usar o Callix web? Então não entre aqui: nada é conectado sem a sua confirmação." })
52927
53026
  ] });
52928
53027
  }
52929
53028
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("form", { className: tw`space-y-4`, onSubmit: handleSubmit, children: [
@@ -58427,6 +58526,8 @@ function MobileDialerLogin({
58427
58526
  sessionStatus,
58428
58527
  loginConfirmation,
58429
58528
  onCancelLoginConfirmation,
58529
+ onTakeOverSession,
58530
+ onLogoutAllTabs,
58430
58531
  density
58431
58532
  }) {
58432
58533
  const [loginInput, setLoginInput] = useState(username ?? "");
@@ -58463,19 +58564,33 @@ function MobileDialerLogin({
58463
58564
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "login-spinner", "aria-hidden": "true" }),
58464
58565
  loadingUser && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "login-loading-user", children: loadingUser }),
58465
58566
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "login-status", children: sessionStatus || "Conectando..." })
58466
- ] }) : showConfirmation && loginConfirmation ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "login-confirm", children: [
58567
+ ] }) : showConfirmation && loginConfirmation ? loginConfirmation.elsewhere ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "login-confirm", "data-login-state": "elsewhere", children: [
58568
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "login-confirm-label", children: "Discador ativo em outra aba" }),
58569
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "login-user-chip", children: [
58570
+ /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-user-check", "aria-hidden": "true" }),
58571
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: loginConfirmation.username })
58572
+ ] }),
58573
+ sessionError && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "input-error", children: sessionError }),
58574
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", className: "login-confirm-btn login-confirm-btn-blue", id: "takeOverBtn", onClick: () => onTakeOverSession == null ? void 0 : onTakeOverSession(), children: [
58575
+ /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-arrow-down", "aria-hidden": "true" }),
58576
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Usar o discador nesta aba" })
58577
+ ] }),
58578
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "login-secondary-btn login-secondary-danger", id: "logoutAllBtn", onClick: () => onLogoutAllTabs == null ? void 0 : onLogoutAllTabs(), children: "Sair em todas as abas" }),
58579
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "login-hint", children: "O Callix aceita uma conexão por usuário: você já está conectado em outra aba deste navegador e pode continuar por lá." })
58580
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "login-confirm", "data-login-state": "choose", children: [
58467
58581
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "login-confirm-label", children: "Entrar como" }),
58468
58582
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "login-user-chip", children: [
58469
58583
  /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-user", "aria-hidden": "true" }),
58470
58584
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: loginConfirmation.username })
58471
58585
  ] }),
58472
58586
  sessionError && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "input-error", children: sessionError }),
58473
- /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", className: "login-confirm-btn", onClick: () => void submitLogin(loginConfirmation.username), children: [
58587
+ !sessionError && loginConfirmation.note && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "login-note", children: loginConfirmation.note }),
58588
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", className: "login-confirm-btn", id: "loginConfirmBtn", onClick: () => void submitLogin(loginConfirmation.username), children: [
58474
58589
  /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-sign-in-alt", "aria-hidden": "true" }),
58475
58590
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Entrar" })
58476
58591
  ] }),
58477
58592
  !loginConfirmation.locked && onCancelLoginConfirmation && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", className: "login-secondary-btn", onClick: onCancelLoginConfirmation, children: "Usar outro usuário" }),
58478
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "login-hint", children: "Se você já estiver conectado em outra aba, continue usando o discador por lá." })
58593
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "login-hint", children: "Ao entrar, todas as abas abertas passam a usar o discador. Vai usar o Callix web? Então não entre aqui: nada é conectado sem a sua confirmação." })
58479
58594
  ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
58480
58595
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "phone-number-display", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
58481
58596
  "input",
@@ -58568,6 +58683,7 @@ Gostaríamos de fazer um *diagnóstico gratuito* para te apresentar os valores r
58568
58683
  }
58569
58684
  function AuthenticatedMobileDialer({
58570
58685
  username,
58686
+ onLogout,
58571
58687
  showClientInfoModal = false,
58572
58688
  density
58573
58689
  }) {
@@ -59334,13 +59450,30 @@ function AuthenticatedMobileDialer({
59334
59450
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "pause-timer", children: pauseDurationLabel })
59335
59451
  ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "status-text", children: statusBadgeText })
59336
59452
  ] }) }),
59337
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "status-actions", children: isOnBreak ? /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "resume-btn", id: "resumeBtn", onClick: handleGoOffBreak, children: [
59338
- /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-play" }),
59339
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Voltar" })
59340
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "pause-btn", id: "pauseBtn", onClick: () => setIsPauseModalOpen(true), children: [
59341
- /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-pause" }),
59342
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Pausa" })
59343
- ] }) })
59453
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "status-actions", children: [
59454
+ isOnBreak ? /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "resume-btn", id: "resumeBtn", onClick: handleGoOffBreak, children: [
59455
+ /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-play" }),
59456
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Voltar" })
59457
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "pause-btn", id: "pauseBtn", onClick: () => setIsPauseModalOpen(true), children: [
59458
+ /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-pause" }),
59459
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Pausa" })
59460
+ ] }),
59461
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
59462
+ "button",
59463
+ {
59464
+ type: "button",
59465
+ className: "logout-btn",
59466
+ id: "logoutBtn",
59467
+ title: "Sair do discador (todas as abas)",
59468
+ "aria-label": "Sair do discador",
59469
+ onClick: () => {
59470
+ if (typeof window !== "undefined" && !window.confirm("Sair do discador em todas as abas?")) return;
59471
+ onLogout();
59472
+ },
59473
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-right-from-bracket" })
59474
+ }
59475
+ )
59476
+ ] })
59344
59477
  ] }),
59345
59478
  /* @__PURE__ */ jsxRuntimeExports.jsx(DialerNoticeBanner, { notice: dialerNotice, onDismiss: dismissDialerNotice, onRecheck: () => void recheckDevices(), onHeightChange: setNoticeHeight }),
59346
59479
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "phone-number-display", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -60176,6 +60309,206 @@ function AuthenticatedMiniDialer({
60176
60309
  )
60177
60310
  ] });
60178
60311
  }
60312
+ const PROBE_TIMEOUT_MS = 600;
60313
+ const TAKEOVER_GRACE_MS = 3e3;
60314
+ const TAKEOVER_JITTER_MS = 600;
60315
+ const LEADER_HEARTBEAT_MS = 3e3;
60316
+ const LEADER_STALE_MS = 9e3;
60317
+ function channelName(username) {
60318
+ return `callix-dialer:${username.trim().toLowerCase()}`;
60319
+ }
60320
+ function leaderKey(username) {
60321
+ return `callix_dialer_${username}_leader`;
60322
+ }
60323
+ const TAB_ROLE_KEY = "callix_dialer_tab_role";
60324
+ const BUS_FALLBACK_SUFFIX = "_bus";
60325
+ function newTabId() {
60326
+ const cryptoApi = typeof globalThis.crypto !== "undefined" ? globalThis.crypto : void 0;
60327
+ if (cryptoApi && "randomUUID" in cryptoApi) return cryptoApi.randomUUID().slice(0, 8);
60328
+ return Math.random().toString(36).slice(2, 10);
60329
+ }
60330
+ function defaultChannelFactory(name) {
60331
+ if (typeof BroadcastChannel === "undefined") return null;
60332
+ try {
60333
+ return new BroadcastChannel(name);
60334
+ } catch {
60335
+ return null;
60336
+ }
60337
+ }
60338
+ function defaultStorage() {
60339
+ try {
60340
+ return typeof localStorage !== "undefined" ? localStorage : null;
60341
+ } catch {
60342
+ return null;
60343
+ }
60344
+ }
60345
+ class DialerTabBus {
60346
+ constructor(options) {
60347
+ this.listeners = /* @__PURE__ */ new Set();
60348
+ this.storageListener = null;
60349
+ this.closed = false;
60350
+ this.username = options.username;
60351
+ this.tabId = options.tabId ?? newTabId();
60352
+ this.status = options.status;
60353
+ this.storage = options.storage === void 0 ? defaultStorage() : options.storage;
60354
+ const factory = options.channelFactory ?? defaultChannelFactory;
60355
+ this.channel = factory(channelName(this.username));
60356
+ if (this.channel) {
60357
+ this.channel.onmessage = (event) => this.receive(event.data);
60358
+ } else if (typeof window !== "undefined" && this.storage) {
60359
+ const key = this.fallbackKey();
60360
+ this.storageListener = (event) => {
60361
+ if (event.key !== key || !event.newValue) return;
60362
+ try {
60363
+ const parsed = JSON.parse(event.newValue);
60364
+ this.receive(parsed.message);
60365
+ } catch {
60366
+ }
60367
+ };
60368
+ window.addEventListener("storage", this.storageListener);
60369
+ }
60370
+ }
60371
+ fallbackKey() {
60372
+ return `callix_dialer_${this.username}${BUS_FALLBACK_SUFFIX}`;
60373
+ }
60374
+ on(listener) {
60375
+ this.listeners.add(listener);
60376
+ return () => {
60377
+ this.listeners.delete(listener);
60378
+ };
60379
+ }
60380
+ post(message) {
60381
+ if (this.closed) return;
60382
+ const full = { ...message, from: this.tabId };
60383
+ if (this.channel) {
60384
+ try {
60385
+ this.channel.postMessage(full);
60386
+ } catch {
60387
+ }
60388
+ return;
60389
+ }
60390
+ if (this.storage) {
60391
+ try {
60392
+ this.storage.setItem(this.fallbackKey(), JSON.stringify({ nonce: Math.random(), at: Date.now(), message: full }));
60393
+ } catch {
60394
+ }
60395
+ }
60396
+ }
60397
+ receive(data) {
60398
+ if (this.closed || !data || typeof data !== "object") return;
60399
+ const message = data;
60400
+ if (typeof message.type !== "string" || typeof message.from !== "string" || message.from === this.tabId) return;
60401
+ if (message.type === "ping") {
60402
+ const status = this.status();
60403
+ this.post({ type: "pong", role: status.role, authenticated: status.authenticated });
60404
+ }
60405
+ for (const listener of this.listeners) listener(message);
60406
+ }
60407
+ /** pergunta quem está aí; devolve as abas que responderam dentro do prazo */
60408
+ probe(timeoutMs = PROBE_TIMEOUT_MS) {
60409
+ return new Promise((resolve) => {
60410
+ const found = /* @__PURE__ */ new Map();
60411
+ const off = this.on((message) => {
60412
+ if (message.type === "pong") found.set(message.from, { tabId: message.from, role: message.role, authenticated: message.authenticated });
60413
+ });
60414
+ this.post({ type: "ping" });
60415
+ setTimeout(() => {
60416
+ off();
60417
+ resolve([...found.values()]);
60418
+ }, timeoutMs);
60419
+ });
60420
+ }
60421
+ close() {
60422
+ this.closed = true;
60423
+ this.listeners.clear();
60424
+ if (this.channel) {
60425
+ this.channel.onmessage = null;
60426
+ try {
60427
+ this.channel.close();
60428
+ } catch {
60429
+ }
60430
+ }
60431
+ if (this.storageListener && typeof window !== "undefined") {
60432
+ window.removeEventListener("storage", this.storageListener);
60433
+ this.storageListener = null;
60434
+ }
60435
+ }
60436
+ }
60437
+ function findLeader(pongs) {
60438
+ return pongs.find((pong) => pong.role === "leader" && pong.authenticated) ?? null;
60439
+ }
60440
+ function anyDialerTab(pongs) {
60441
+ return pongs.some((pong) => pong.role !== null);
60442
+ }
60443
+ function readLeader(username, storage = defaultStorage(), now = Date.now()) {
60444
+ if (!storage) return null;
60445
+ try {
60446
+ const raw = storage.getItem(leaderKey(username));
60447
+ if (!raw) return null;
60448
+ const parsed = JSON.parse(raw);
60449
+ if (typeof parsed.tabId !== "string" || typeof parsed.at !== "number") return null;
60450
+ if (now - parsed.at > LEADER_STALE_MS) return null;
60451
+ return { tabId: parsed.tabId, at: parsed.at };
60452
+ } catch {
60453
+ return null;
60454
+ }
60455
+ }
60456
+ function claimLeader(username, tabId, storage = defaultStorage(), now = Date.now()) {
60457
+ if (!storage) return true;
60458
+ const current = readLeader(username, storage, now);
60459
+ if (current && current.tabId !== tabId) return false;
60460
+ try {
60461
+ storage.setItem(leaderKey(username), JSON.stringify({ tabId, at: now }));
60462
+ } catch {
60463
+ }
60464
+ return true;
60465
+ }
60466
+ function releaseLeader(username, tabId, storage = defaultStorage()) {
60467
+ if (!storage) return;
60468
+ try {
60469
+ const raw = storage.getItem(leaderKey(username));
60470
+ if (!raw) return;
60471
+ const parsed = JSON.parse(raw);
60472
+ if (parsed.tabId === tabId) storage.removeItem(leaderKey(username));
60473
+ } catch {
60474
+ }
60475
+ }
60476
+ function defaultSessionStorage() {
60477
+ try {
60478
+ return typeof sessionStorage !== "undefined" ? sessionStorage : null;
60479
+ } catch {
60480
+ return null;
60481
+ }
60482
+ }
60483
+ function saveTabRole(username, role, storage = defaultSessionStorage()) {
60484
+ if (!storage) return;
60485
+ try {
60486
+ storage.setItem(TAB_ROLE_KEY, JSON.stringify({ username, role, at: Date.now() }));
60487
+ } catch {
60488
+ }
60489
+ }
60490
+ function readTabRole(username, storage = defaultSessionStorage()) {
60491
+ if (!storage) return null;
60492
+ try {
60493
+ const raw = storage.getItem(TAB_ROLE_KEY);
60494
+ if (!raw) return null;
60495
+ const parsed = JSON.parse(raw);
60496
+ if (parsed.username !== username) return null;
60497
+ return parsed.role === "leader" || parsed.role === "member" ? parsed.role : null;
60498
+ } catch {
60499
+ return null;
60500
+ }
60501
+ }
60502
+ function clearTabRole(storage = defaultSessionStorage()) {
60503
+ if (!storage) return;
60504
+ try {
60505
+ storage.removeItem(TAB_ROLE_KEY);
60506
+ } catch {
60507
+ }
60508
+ }
60509
+ function takeoverDelayMs(random = Math.random()) {
60510
+ return TAKEOVER_GRACE_MS + Math.floor(random * TAKEOVER_JITTER_MS);
60511
+ }
60179
60512
  const DEFAULT_CLICK_TO_CALL_INTERVAL_MS = 4e3;
60180
60513
  const BUSY_INTERVAL_FACTOR = 3;
60181
60514
  const DISPATCH_COOLDOWN_MS = 6e3;
@@ -60398,7 +60731,7 @@ function ClickToCallAgent({ enabled = true, pollIntervalMs, sessionToken, onTask
60398
60731
  }, [enabled, pollIntervalMs, sessionToken]);
60399
60732
  return null;
60400
60733
  }
60401
- const WIDGET_VERSION = "2.6.0";
60734
+ const WIDGET_VERSION = "2.8.0";
60402
60735
  let currentSipWebSocket = null;
60403
60736
  function sendSipUnregisterIfPossible(ws) {
60404
60737
  if (ws.readyState !== WebSocket.OPEN) return;
@@ -60524,9 +60857,23 @@ function useDialerConfig() {
60524
60857
  return context;
60525
60858
  }
60526
60859
  const SdkCleanupManager = forwardRef(
60527
- function SdkCleanupManager2({ children: children2 }, ref) {
60860
+ function SdkCleanupManager2({ children: children2, onSessionDropped }, ref) {
60528
60861
  const operator = useCallOperator();
60529
60862
  const controls = useCallOperatorControls();
60863
+ const operatorState = useCallOperatorState();
60864
+ const droppedRef = useRef(onSessionDropped);
60865
+ droppedRef.current = onSessionDropped;
60866
+ useCallOperatorEventListener("userSessionDropped", (data) => {
60867
+ var _a2;
60868
+ const originator = (data == null ? void 0 : data.originator) ?? null;
60869
+ (_a2 = droppedRef.current) == null ? void 0 : _a2.call(droppedRef, originator, describeSessionDrop(originator));
60870
+ });
60871
+ const startupError = (operatorState == null ? void 0 : operatorState.state) === "startupError" ? operatorState.error ?? null : null;
60872
+ useEffect(() => {
60873
+ var _a2;
60874
+ if (startupError !== "userIsAlreadyConnectedOnAnotherDevice") return;
60875
+ (_a2 = droppedRef.current) == null ? void 0 : _a2.call(droppedRef, "anotherDevice", describeStartupError(startupError));
60876
+ }, [startupError]);
60530
60877
  useImperativeHandle(ref, () => ({
60531
60878
  goOffline: () => {
60532
60879
  console.log("[Callix Dialer] 📴 Chamando goOffline() para enviar SIP UNREGISTER...");
@@ -60547,16 +60894,18 @@ const SdkCleanupManager = forwardRef(
60547
60894
  }
60548
60895
  }
60549
60896
  }), [operator, controls]);
60897
+ const controlsRef = useRef(controls);
60898
+ controlsRef.current = controls;
60550
60899
  useEffect(() => {
60551
60900
  return () => {
60552
60901
  console.log("[Callix Dialer] 🔌 SdkCleanupManager desmontando, chamando goOffline()...");
60553
60902
  try {
60554
- controls.goOffline();
60903
+ controlsRef.current.goOffline();
60555
60904
  } catch (err) {
60556
60905
  console.warn("[Callix Dialer] ⚠️ Erro no cleanup automático:", err);
60557
60906
  }
60558
60907
  };
60559
- }, [controls]);
60908
+ }, []);
60560
60909
  return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: children2 });
60561
60910
  }
60562
60911
  );
@@ -60695,6 +61044,13 @@ const getStorageKeys = (username) => ({
60695
61044
  SKIP_MANUAL_QUALIFICATION: `callix_dialer_${username}_skip_manual_qualification`
60696
61045
  });
60697
61046
  const LAST_USERNAME_KEY = "callix_dialer_last_username";
61047
+ function readStoredToken(user) {
61048
+ try {
61049
+ return localStorage.getItem(getStorageKeys(user).SESSION_TOKEN);
61050
+ } catch {
61051
+ return null;
61052
+ }
61053
+ }
60698
61054
  async function fetchSkipQualification(domain, token) {
60699
61055
  var _a2, _b;
60700
61056
  try {
@@ -60724,10 +61080,35 @@ function DialerWrapper({ options, containerEl }) {
60724
61080
  const [isDisconnecting, setIsDisconnecting] = useState(false);
60725
61081
  const presetUsername = options.presetUsername ? options.presetUsername.trim() : null;
60726
61082
  const uiVariant = options.uiVariant ?? "default";
60727
- const autoLogin = options.autoLogin === true;
60728
61083
  const portalTarget = useMemo(() => options.portalTarget ?? containerEl ?? null, [options.portalTarget, containerEl]);
60729
- const autoLoginAttemptedRef = useRef(false);
60730
- const lastPresetRef = useRef(presetUsername);
61084
+ const tabIdRef = useRef("");
61085
+ if (!tabIdRef.current) tabIdRef.current = newTabId();
61086
+ const [tabRole, setTabRole] = useState(null);
61087
+ const tabRoleRef = useRef(null);
61088
+ tabRoleRef.current = tabRole;
61089
+ const [tabDecided, setTabDecided] = useState(false);
61090
+ const [tabNote, setTabNote] = useState(null);
61091
+ const [engineWaiting, setEngineWaiting] = useState(false);
61092
+ const busRef = useRef(null);
61093
+ const takeoverTimerRef = useRef(null);
61094
+ const isAuthenticatedRef = useRef(false);
61095
+ isAuthenticatedRef.current = isAuthenticated;
61096
+ const isLoadingRef = useRef(false);
61097
+ isLoadingRef.current = isLoading;
61098
+ const logoffPendingRef = useRef(false);
61099
+ const handleLoginRef = useRef(async () => void 0);
61100
+ const handleLogoutRef = useRef(async () => void 0);
61101
+ const cancelTakeover = useCallback(() => {
61102
+ if (takeoverTimerRef.current) {
61103
+ clearTimeout(takeoverTimerRef.current);
61104
+ takeoverTimerRef.current = null;
61105
+ }
61106
+ }, []);
61107
+ useEffect(() => {
61108
+ if (options.autoLogin) {
61109
+ console.info("[Callix Dialer] autoLogin: desde a 2.7.0 o widget não cria sessão sem confirmação; ele entra sozinho só quando já existe uma conexão ativa deste usuário (outra aba ou o motor).");
61110
+ }
61111
+ }, [options.autoLogin]);
60731
61112
  const currentSessionRef = useRef({
60732
61113
  apiUrl: options.apiUrl || "/api/user-sdk-session",
60733
61114
  token: null
@@ -60749,7 +61130,10 @@ function DialerWrapper({ options, containerEl }) {
60749
61130
  current == null ? void 0 : current.close();
60750
61131
  const client2 = new EngineClient({
60751
61132
  url: options.engineUrl ?? "",
60752
- token: options.engineToken ?? "",
61133
+ token: options.engineToken || void 0,
61134
+ // credencial da aba: o token da sessão do Callix guardado neste navegador (lido a cada
61135
+ // hello, para a reconexão depois de uma troca de sessão pegar o token novo)
61136
+ sessionToken: () => readStoredToken(user),
60753
61137
  agentId: user,
60754
61138
  label: "Discador",
60755
61139
  log: (message) => console.log("[Callix Dialer/motor]", message)
@@ -60759,7 +61143,7 @@ function DialerWrapper({ options, containerEl }) {
60759
61143
  setError(`${notice.title}: ${notice.detail}${notice.action ? ` ${notice.action}` : ""}`);
60760
61144
  setIsAuthenticated(false);
60761
61145
  setSessionToken(null);
60762
- autoLoginAttemptedRef.current = true;
61146
+ setTabNote(null);
60763
61147
  });
60764
61148
  client2.connect();
60765
61149
  engineRef.current = client2;
@@ -60776,9 +61160,19 @@ function DialerWrapper({ options, containerEl }) {
60776
61160
  []
60777
61161
  );
60778
61162
  useEffect(() => {
61163
+ let unloadHandled = false;
60779
61164
  const handleBeforeUnload = () => {
60780
- if (engineModeRef.current) return;
61165
+ var _a2;
61166
+ if (engineModeRef.current || unloadHandled) return;
61167
+ unloadHandled = true;
61168
+ setTimeout(() => {
61169
+ unloadHandled = false;
61170
+ }, 2e3);
60781
61171
  const { apiUrl, token } = currentSessionRef.current;
61172
+ if (tabRoleRef.current === "leader") {
61173
+ (_a2 = busRef.current) == null ? void 0 : _a2.post({ type: "leaving" });
61174
+ if (busRef.current) releaseLeader(busRef.current.username, tabIdRef.current);
61175
+ }
60782
61176
  if (token) {
60783
61177
  const data = JSON.stringify({ logout: true });
60784
61178
  const logoutUrl = `${apiUrl}?logout=true`;
@@ -60797,8 +61191,10 @@ function DialerWrapper({ options, containerEl }) {
60797
61191
  }
60798
61192
  };
60799
61193
  window.addEventListener("beforeunload", handleBeforeUnload);
61194
+ window.addEventListener("pagehide", handleBeforeUnload);
60800
61195
  return () => {
60801
61196
  window.removeEventListener("beforeunload", handleBeforeUnload);
61197
+ window.removeEventListener("pagehide", handleBeforeUnload);
60802
61198
  };
60803
61199
  }, []);
60804
61200
  useEffect(() => {
@@ -60836,8 +61232,257 @@ function DialerWrapper({ options, containerEl }) {
60836
61232
  setUsername(presetUsername);
60837
61233
  }
60838
61234
  }, [presetUsername, username]);
61235
+ const engineDomain = useCallback(
61236
+ () => options.domain || new URL(options.apiUrl || "/api/user-sdk-session", window.location.href).host,
61237
+ [options.domain, options.apiUrl]
61238
+ );
61239
+ const enterEngine = useCallback(async (user, domain, token) => {
61240
+ if (token) {
61241
+ const skip = await fetchSkipQualification(domain, token);
61242
+ if (skip !== null) {
61243
+ setSkipManualQualification(skip);
61244
+ try {
61245
+ localStorage.setItem(getStorageKeys(user).SKIP_MANUAL_QUALIFICATION, String(skip));
61246
+ } catch {
61247
+ }
61248
+ }
61249
+ }
61250
+ try {
61251
+ localStorage.setItem(LAST_USERNAME_KEY, user);
61252
+ if (token) {
61253
+ const STORAGE_KEYS = getStorageKeys(user);
61254
+ localStorage.setItem(STORAGE_KEYS.SESSION_TOKEN, token);
61255
+ localStorage.setItem(STORAGE_KEYS.DOMAIN, domain);
61256
+ localStorage.setItem(STORAGE_KEYS.TIMESTAMP, Date.now().toString());
61257
+ }
61258
+ } catch {
61259
+ }
61260
+ setSessionToken(token);
61261
+ setCallixDomain(domain);
61262
+ setUsername(user);
61263
+ setIsAuthenticated(true);
61264
+ setError(null);
61265
+ setTabNote(null);
61266
+ setProviderKey(Date.now());
61267
+ }, []);
61268
+ const joinEngine = useCallback(
61269
+ async (user) => {
61270
+ setIsLoading(true);
61271
+ setError(null);
61272
+ setLoginStatus("Entrando na sessão do motor...");
61273
+ try {
61274
+ await enterEngine(user, engineDomain(), readStoredToken(user));
61275
+ console.log("[Callix Dialer] Conectado ao motor como", user);
61276
+ } catch (err) {
61277
+ console.error("[Callix Dialer] Erro ao entrar na sessão do motor:", err);
61278
+ setError(err instanceof Error ? err.message : "Erro ao conectar ao motor");
61279
+ } finally {
61280
+ setIsLoading(false);
61281
+ setLoginStatus(null);
61282
+ }
61283
+ },
61284
+ [enterEngine, engineDomain]
61285
+ );
61286
+ useEffect(() => {
61287
+ if (!engineMode || !username || isRestoringSession) return;
61288
+ const user = username;
61289
+ const client2 = ensureEngine(user);
61290
+ setEngineWaiting(!client2.getState().view);
61291
+ const fallback = setTimeout(() => setEngineWaiting(false), 15e3);
61292
+ let joining = false;
61293
+ let rejectedNote = false;
61294
+ const sync = () => {
61295
+ const { view, sessionLost, rejected } = client2.getState();
61296
+ if (!rejected && rejectedNote) {
61297
+ rejectedNote = false;
61298
+ setTabNote(null);
61299
+ }
61300
+ if (rejected) {
61301
+ rejectedNote = true;
61302
+ setEngineWaiting(false);
61303
+ if (isLoadingRef.current) return;
61304
+ if (isAuthenticatedRef.current) {
61305
+ console.log("[Callix Dialer] o motor recusou esta aba: a sessão do agente foi trocada por um login em outro lugar");
61306
+ setIsAuthenticated(false);
61307
+ setSessionToken(null);
61308
+ setCallixDomain(null);
61309
+ }
61310
+ setTabNote(rejected.message);
61311
+ return;
61312
+ }
61313
+ if (!view) return;
61314
+ setEngineWaiting(false);
61315
+ if (view.engine === "stopped" || view.engine === "crashed") logoffPendingRef.current = false;
61316
+ const hasSession = (view.engine === "running" || view.engine === "starting") && !sessionLost;
61317
+ if (hasSession && !logoffPendingRef.current && !isAuthenticatedRef.current && !isLoadingRef.current && !joining) {
61318
+ joining = true;
61319
+ console.log("[Callix Dialer] o motor já segura a sessão deste agente; entrando por aqui também");
61320
+ void joinEngine(user).finally(() => {
61321
+ joining = false;
61322
+ });
61323
+ } else if (view.engine === "stopped" && isAuthenticatedRef.current && !isLoadingRef.current) {
61324
+ console.log("[Callix Dialer] o motor desligou (Sair em outra aba ou servidor reiniciado); voltando à tela de login");
61325
+ setIsAuthenticated(false);
61326
+ setSessionToken(null);
61327
+ setCallixDomain(null);
61328
+ setTabNote("O discador foi encerrado em outra aba ou o motor foi reiniciado.");
61329
+ }
61330
+ };
61331
+ sync();
61332
+ const off = client2.subscribe(sync);
61333
+ return () => {
61334
+ off();
61335
+ clearTimeout(fallback);
61336
+ };
61337
+ }, [engineMode, username, isRestoringSession, ensureEngine, joinEngine]);
61338
+ const scheduleTakeoverRef = useRef(null);
61339
+ useEffect(() => {
61340
+ if (engineMode || !username) {
61341
+ setTabDecided(true);
61342
+ return;
61343
+ }
61344
+ setTabDecided(false);
61345
+ const user = username;
61346
+ const bus = new DialerTabBus({
61347
+ username: user,
61348
+ tabId: tabIdRef.current,
61349
+ status: () => ({ role: tabRoleRef.current, authenticated: isAuthenticatedRef.current })
61350
+ });
61351
+ busRef.current = bus;
61352
+ const becomeMember = () => {
61353
+ if (isAuthenticatedRef.current || tabRoleRef.current === "leader") return;
61354
+ setTabRole("member");
61355
+ saveTabRole(user, "member");
61356
+ setTabNote(null);
61357
+ setError(null);
61358
+ setTabDecided(true);
61359
+ };
61360
+ const scheduleTakeover = () => {
61361
+ cancelTakeover();
61362
+ takeoverTimerRef.current = setTimeout(() => {
61363
+ takeoverTimerRef.current = null;
61364
+ if (tabRoleRef.current !== "member" || isAuthenticatedRef.current || isLoadingRef.current) return;
61365
+ const current = readLeader(user);
61366
+ if (current && current.tabId !== tabIdRef.current) return;
61367
+ if (!claimLeader(user, tabIdRef.current)) return;
61368
+ bus.post({ type: "claim" });
61369
+ console.log("[Callix Dialer] a aba com o discador fechou; esta aba assume a conexão");
61370
+ void handleLoginRef.current(user);
61371
+ }, takeoverDelayMs());
61372
+ };
61373
+ scheduleTakeoverRef.current = scheduleTakeover;
61374
+ const off = bus.on((message) => {
61375
+ switch (message.type) {
61376
+ case "login":
61377
+ case "claim":
61378
+ cancelTakeover();
61379
+ becomeMember();
61380
+ break;
61381
+ case "leaving":
61382
+ if (tabRoleRef.current === "member" && !isAuthenticatedRef.current) scheduleTakeover();
61383
+ break;
61384
+ case "takeover":
61385
+ if (tabRoleRef.current === "leader") {
61386
+ console.log("[Callix Dialer] outra aba pediu o discador; soltando a conexão");
61387
+ void handleLogoutRef.current({ broadcast: false, keepDecision: true });
61388
+ }
61389
+ break;
61390
+ case "logout":
61391
+ cancelTakeover();
61392
+ void handleLogoutRef.current({ broadcast: false });
61393
+ setTabNote("Você saiu do discador em outra aba.");
61394
+ break;
61395
+ case "dropped": {
61396
+ cancelTakeover();
61397
+ releaseLeader(user, tabIdRef.current);
61398
+ setTabRole(null);
61399
+ clearTabRole();
61400
+ const notice = describeSessionDrop(message.originator);
61401
+ setError(`${notice.title}: ${notice.detail}${notice.action ? ` ${notice.action}` : ""}`);
61402
+ break;
61403
+ }
61404
+ }
61405
+ });
61406
+ return () => {
61407
+ off();
61408
+ bus.close();
61409
+ cancelTakeover();
61410
+ scheduleTakeoverRef.current = null;
61411
+ if (busRef.current === bus) busRef.current = null;
61412
+ setTabDecided(false);
61413
+ };
61414
+ }, [engineMode, username, cancelTakeover]);
61415
+ useEffect(() => {
61416
+ if (engineMode || !username || isAuthenticated || tabDecided || isRestoringSession || isLoading) return;
61417
+ const bus = busRef.current;
61418
+ if (!bus) return;
61419
+ const user = username;
61420
+ const stored = readTabRole(user);
61421
+ if (stored === "leader") {
61422
+ cancelTakeover();
61423
+ setTabDecided(true);
61424
+ if (claimLeader(user, tabIdRef.current)) {
61425
+ bus.post({ type: "claim" });
61426
+ console.log("[Callix Dialer] esta aba segurava o discador antes de recarregar; reconectando");
61427
+ void handleLoginRef.current(user);
61428
+ } else {
61429
+ setTabRole("member");
61430
+ saveTabRole(user, "member");
61431
+ }
61432
+ return;
61433
+ }
61434
+ let cancelled = false;
61435
+ void bus.probe().then((pongs) => {
61436
+ var _a2;
61437
+ if (cancelled) return;
61438
+ setTabDecided(true);
61439
+ const leaderLock = readLeader(user);
61440
+ if (findLeader(pongs) || leaderLock && leaderLock.tabId !== tabIdRef.current || stored === "member" && anyDialerTab(pongs)) {
61441
+ setTabRole("member");
61442
+ saveTabRole(user, "member");
61443
+ } else if (stored === "member") {
61444
+ setTabRole("member");
61445
+ (_a2 = scheduleTakeoverRef.current) == null ? void 0 : _a2.call(scheduleTakeoverRef);
61446
+ } else {
61447
+ setTabRole(null);
61448
+ clearTabRole();
61449
+ }
61450
+ });
61451
+ return () => {
61452
+ cancelled = true;
61453
+ };
61454
+ }, [engineMode, username, isAuthenticated, tabDecided, isRestoringSession, isLoading, cancelTakeover]);
61455
+ useEffect(() => {
61456
+ if (engineMode || tabRole !== "leader" || !username) return;
61457
+ const user = username;
61458
+ claimLeader(user, tabIdRef.current);
61459
+ const timer = setInterval(() => claimLeader(user, tabIdRef.current), LEADER_HEARTBEAT_MS);
61460
+ return () => clearInterval(timer);
61461
+ }, [engineMode, tabRole, username]);
61462
+ useEffect(() => {
61463
+ if (engineMode || tabRole !== "member" || !username) return;
61464
+ const user = username;
61465
+ const check = () => {
61466
+ if (document.visibilityState !== "visible" || takeoverTimerRef.current) return;
61467
+ const bus = busRef.current;
61468
+ if (!bus) return;
61469
+ void bus.probe().then((pongs) => {
61470
+ var _a2;
61471
+ if (tabRoleRef.current !== "member" || isAuthenticatedRef.current || takeoverTimerRef.current) return;
61472
+ if (findLeader(pongs) || readLeader(user)) return;
61473
+ console.log("[Callix Dialer] a aba com o discador não responde; esta aba vai assumir");
61474
+ (_a2 = scheduleTakeoverRef.current) == null ? void 0 : _a2.call(scheduleTakeoverRef);
61475
+ });
61476
+ };
61477
+ document.addEventListener("visibilitychange", check);
61478
+ window.addEventListener("focus", check);
61479
+ return () => {
61480
+ document.removeEventListener("visibilitychange", check);
61481
+ window.removeEventListener("focus", check);
61482
+ };
61483
+ }, [engineMode, tabRole, username]);
60839
61484
  const handleLogin = useCallback(async (user) => {
60840
- var _a2, _b;
61485
+ var _a2, _b, _c, _d;
60841
61486
  setIsLoading(true);
60842
61487
  setError(null);
60843
61488
  setLoginStatus("Preparando login...");
@@ -60846,17 +61491,20 @@ function DialerWrapper({ options, containerEl }) {
60846
61491
  const engineApiUrl = options.apiUrl || "/api/user-sdk-session";
60847
61492
  try {
60848
61493
  setLoginStatus("Conectando ao motor...");
61494
+ logoffPendingRef.current = false;
60849
61495
  const client2 = ensureEngine(user);
60850
- const view = await client2.waitForView(15e3);
60851
- const domain = options.domain || new URL(engineApiUrl, window.location.href).host;
60852
- const engineHasSession = (view.engine === "running" || view.engine === "starting") && !view.sessionLost;
61496
+ let view = null;
61497
+ try {
61498
+ view = await client2.waitForView(15e3);
61499
+ } catch (err) {
61500
+ if (!(err instanceof EngineAccessError)) throw err;
61501
+ console.log("[Callix Dialer] o motor recusou a credencial desta aba; fazendo login novo que substitui a sessão de lá");
61502
+ }
61503
+ const domain = engineDomain();
61504
+ const engineHasSession = view !== null && (view.engine === "running" || view.engine === "starting") && !view.sessionLost;
60853
61505
  let token = null;
60854
61506
  if (engineHasSession) {
60855
- try {
60856
- token = localStorage.getItem(getStorageKeys(user).SESSION_TOKEN);
60857
- } catch {
60858
- token = null;
60859
- }
61507
+ token = readStoredToken(user);
60860
61508
  console.log("[Callix Dialer] motor já tem a sessão do agente; entrando sem novo login");
60861
61509
  } else {
60862
61510
  setLoginStatus("Autenticando no Callix...");
@@ -60872,35 +61520,20 @@ function DialerWrapper({ options, containerEl }) {
60872
61520
  }
60873
61521
  const data = await response.json();
60874
61522
  token = data.userSessionToken;
60875
- client2.setSession({ domain, userSessionToken: token });
60876
- setLoginStatus("Subindo o motor...");
60877
- }
60878
- if (token) {
60879
- const skip = await fetchSkipQualification(domain, token);
60880
- if (skip !== null) {
60881
- setSkipManualQualification(skip);
60882
- try {
60883
- localStorage.setItem(getStorageKeys(user).SKIP_MANUAL_QUALIFICATION, String(skip));
60884
- } catch {
60885
- }
60886
- }
60887
- }
60888
- try {
60889
- localStorage.setItem(LAST_USERNAME_KEY, user);
60890
- if (token) {
61523
+ try {
60891
61524
  const STORAGE_KEYS = getStorageKeys(user);
61525
+ localStorage.setItem(LAST_USERNAME_KEY, user);
60892
61526
  localStorage.setItem(STORAGE_KEYS.SESSION_TOKEN, token);
60893
61527
  localStorage.setItem(STORAGE_KEYS.DOMAIN, domain);
60894
61528
  localStorage.setItem(STORAGE_KEYS.TIMESTAMP, Date.now().toString());
61529
+ } catch {
60895
61530
  }
60896
- } catch {
61531
+ client2.setSession({ domain, userSessionToken: token });
61532
+ setLoginStatus("Subindo o motor...");
61533
+ const up = await client2.waitForView(2e4, (v) => v.engine === "running" || v.engine === "starting" || Boolean(v.sessionLost), { fresh: true });
61534
+ if (up.sessionLost) throw new Error(up.sessionLost.message);
60897
61535
  }
60898
- setSessionToken(token);
60899
- setCallixDomain(domain);
60900
- setUsername(user);
60901
- setIsAuthenticated(true);
60902
- setError(null);
60903
- setProviderKey(Date.now());
61536
+ await enterEngine(user, domain, token);
60904
61537
  console.log("[Callix Dialer] Conectado ao motor como", user);
60905
61538
  } catch (err) {
60906
61539
  console.error("[Callix Dialer] Erro ao entrar pelo motor:", err);
@@ -60913,6 +61546,20 @@ function DialerWrapper({ options, containerEl }) {
60913
61546
  }
60914
61547
  return;
60915
61548
  }
61549
+ if (!claimLeader(user, tabIdRef.current)) {
61550
+ console.log("[Callix Dialer] outra aba já segura a conexão deste usuário; esta aba vira membro");
61551
+ setTabRole("member");
61552
+ saveTabRole(user, "member");
61553
+ setTabDecided(true);
61554
+ setUsername(user);
61555
+ setIsLoading(false);
61556
+ setLoginStatus(null);
61557
+ return;
61558
+ }
61559
+ (_a2 = busRef.current) == null ? void 0 : _a2.post({ type: "claim" });
61560
+ cancelTakeover();
61561
+ setTabRole("leader");
61562
+ saveTabRole(user, "leader");
60916
61563
  const apiUrl = options.apiUrl || "/api/user-sdk-session";
60917
61564
  let previousToken;
60918
61565
  if (typeof window !== "undefined") {
@@ -61007,6 +61654,9 @@ function DialerWrapper({ options, containerEl }) {
61007
61654
  setError(null);
61008
61655
  setProviderKey(Date.now());
61009
61656
  console.log("[Callix Dialer] Nova providerKey gerada para forçar recriação do SDK");
61657
+ setTabNote(null);
61658
+ setTabDecided(true);
61659
+ (_b = busRef.current) == null ? void 0 : _b.post({ type: "login" });
61010
61660
  try {
61011
61661
  const configUrl = `https://${resolvedDomain}/sdk/v1/client/user-service/config`;
61012
61662
  setLoginStatus("Carregando configurações...");
@@ -61021,7 +61671,7 @@ function DialerWrapper({ options, containerEl }) {
61021
61671
  if (configResponse.ok) {
61022
61672
  const configData = await configResponse.json();
61023
61673
  console.log("[Callix Dialer] 📦 Dados da config recebidos:", JSON.stringify(configData, null, 2));
61024
- const shouldSkipQualification = ((_b = (_a2 = configData == null ? void 0 : configData.service) == null ? void 0 : _a2.outboundOptions) == null ? void 0 : _b.skipQualification) === true;
61674
+ const shouldSkipQualification = ((_d = (_c = configData == null ? void 0 : configData.service) == null ? void 0 : _c.outboundOptions) == null ? void 0 : _d.skipQualification) === true;
61025
61675
  console.log("[Callix Dialer] ✅ skipQualification configurado:", shouldSkipQualification);
61026
61676
  setSkipManualQualification(shouldSkipQualification);
61027
61677
  if (typeof window !== "undefined") {
@@ -61055,53 +61705,52 @@ function DialerWrapper({ options, containerEl }) {
61055
61705
  setError(err instanceof Error ? err.message : "Erro ao autenticar");
61056
61706
  setIsAuthenticated(false);
61057
61707
  setSessionToken(null);
61708
+ releaseLeader(user, tabIdRef.current);
61709
+ setTabRole(null);
61710
+ clearTabRole();
61058
61711
  } finally {
61059
61712
  setIsLoading(false);
61060
61713
  setLoginStatus(null);
61061
61714
  }
61062
- }, [options.apiUrl, options.domain, options.engineUrl, isAuthenticated, sessionToken, ensureEngine]);
61063
- useEffect(() => {
61064
- if (lastPresetRef.current !== presetUsername) {
61065
- lastPresetRef.current = presetUsername;
61066
- autoLoginAttemptedRef.current = false;
61067
- }
61068
- if (!presetUsername || !autoLogin) {
61069
- return;
61070
- }
61071
- if (isAuthenticated || isLoading || isRestoringSession || error2 || autoLoginAttemptedRef.current) {
61072
- return;
61073
- }
61074
- autoLoginAttemptedRef.current = true;
61075
- void handleLogin(presetUsername);
61076
- }, [presetUsername, autoLogin, isAuthenticated, isLoading, isRestoringSession, error2, handleLogin]);
61715
+ }, [options.apiUrl, options.domain, options.engineUrl, isAuthenticated, sessionToken, ensureEngine, enterEngine, engineDomain, cancelTakeover]);
61077
61716
  useEffect(() => {
61078
- if (!autoLogin || presetUsername || !username || isAuthenticated || isLoading || isRestoringSession || error2 || autoLoginAttemptedRef.current) {
61079
- return;
61080
- }
61081
- console.log("[Callix Dialer] Auto-login com username restaurado:", username);
61082
- autoLoginAttemptedRef.current = true;
61083
- void handleLogin(username);
61084
- }, [autoLogin, presetUsername, username, isAuthenticated, isLoading, isRestoringSession, error2, handleLogin]);
61085
- const handleLogout = useCallback(async () => {
61086
- var _a2;
61717
+ handleLoginRef.current = handleLogin;
61718
+ }, [handleLogin]);
61719
+ const handleLogout = useCallback(async (opts) => {
61720
+ var _a2, _b;
61721
+ const broadcast = (opts == null ? void 0 : opts.broadcast) !== false;
61722
+ const keepDecision = (opts == null ? void 0 : opts.keepDecision) === true;
61087
61723
  const currentUsername = username;
61724
+ cancelTakeover();
61088
61725
  if (options.engineUrl) {
61089
- (_a2 = engineRef.current) == null ? void 0 : _a2.command({ name: "logoff" });
61090
- } else if (cleanupRef.current) {
61091
- console.log("[Callix Dialer] 📴 Logout: Chamando goOffline()...");
61092
- cleanupRef.current.goOffline();
61093
- await new Promise((resolve) => setTimeout(resolve, 500));
61094
- console.log("[Callix Dialer] 🛑 Logout: Chamando stop()...");
61095
- cleanupRef.current.stop();
61096
- await new Promise((resolve) => setTimeout(resolve, 500));
61726
+ if (isAuthenticatedRef.current) {
61727
+ logoffPendingRef.current = true;
61728
+ (_a2 = engineRef.current) == null ? void 0 : _a2.command({ name: "logoff" });
61729
+ }
61730
+ } else {
61731
+ if (broadcast) (_b = busRef.current) == null ? void 0 : _b.post({ type: "logout" });
61732
+ if (cleanupRef.current) {
61733
+ console.log("[Callix Dialer] 📴 Logout: Chamando goOffline()...");
61734
+ cleanupRef.current.goOffline();
61735
+ await new Promise((resolve) => setTimeout(resolve, 500));
61736
+ console.log("[Callix Dialer] 🛑 Logout: Chamando stop()...");
61737
+ cleanupRef.current.stop();
61738
+ await new Promise((resolve) => setTimeout(resolve, 500));
61739
+ }
61740
+ if (currentUsername) releaseLeader(currentUsername, tabIdRef.current);
61097
61741
  }
61098
- setUsername(presetUsername ?? null);
61099
61742
  setIsAuthenticated(false);
61100
61743
  setSessionToken(null);
61101
61744
  setCallixDomain(null);
61102
61745
  setError(null);
61103
- autoLoginAttemptedRef.current = true;
61104
- lastPresetRef.current = presetUsername ?? null;
61746
+ if (keepDecision) {
61747
+ setTabRole("member");
61748
+ if (currentUsername) saveTabRole(currentUsername, "member");
61749
+ return;
61750
+ }
61751
+ setTabRole(null);
61752
+ clearTabRole();
61753
+ setUsername(presetUsername ?? null);
61105
61754
  if (typeof window !== "undefined" && currentUsername) {
61106
61755
  try {
61107
61756
  const STORAGE_KEYS = getStorageKeys(currentUsername);
@@ -61114,14 +61763,53 @@ function DialerWrapper({ options, containerEl }) {
61114
61763
  console.warn("[Callix Dialer] Não foi possível limpar sessão:", err);
61115
61764
  }
61116
61765
  }
61117
- }, [username, presetUsername, options.engineUrl]);
61118
- const pendingAutoLogin = autoLogin && Boolean(username) && !isAuthenticated && !error2 && !autoLoginAttemptedRef.current;
61766
+ }, [username, presetUsername, options.engineUrl, cancelTakeover]);
61767
+ useEffect(() => {
61768
+ handleLogoutRef.current = handleLogout;
61769
+ }, [handleLogout]);
61770
+ const handleTakeOver = useCallback(async () => {
61771
+ const user = username;
61772
+ const bus = busRef.current;
61773
+ if (!user || !bus) return;
61774
+ setIsLoading(true);
61775
+ setError(null);
61776
+ setLoginStatus("Trazendo o discador para esta aba...");
61777
+ bus.post({ type: "takeover" });
61778
+ await new Promise((resolve) => setTimeout(resolve, 1500));
61779
+ const current = readLeader(user);
61780
+ if (current && current.tabId !== tabIdRef.current) {
61781
+ console.log("[Callix Dialer] a outra aba não soltou a conexão; continuando como membro");
61782
+ setIsLoading(false);
61783
+ setLoginStatus(null);
61784
+ return;
61785
+ }
61786
+ await handleLoginRef.current(user);
61787
+ }, [username]);
61788
+ const handleLogoutAll = useCallback(() => {
61789
+ void handleLogoutRef.current();
61790
+ }, []);
61791
+ const handleSdkSessionDropped = useCallback(
61792
+ (originator, notice) => {
61793
+ var _a2;
61794
+ const user = username;
61795
+ if (user) releaseLeader(user, tabIdRef.current);
61796
+ (_a2 = busRef.current) == null ? void 0 : _a2.post({ type: "dropped", originator });
61797
+ setTabRole(null);
61798
+ clearTabRole();
61799
+ setError(`${notice.title}: ${notice.detail}${notice.action ? ` ${notice.action}` : ""}`);
61800
+ setIsAuthenticated(false);
61801
+ setSessionToken(null);
61802
+ setCallixDomain(null);
61803
+ },
61804
+ [username]
61805
+ );
61806
+ const deciding = Boolean(username) && !isAuthenticated && (engineMode ? engineWaiting : !tabDecided);
61119
61807
  const loginConfirmation = useMemo(() => {
61120
- if (isAuthenticated || isLoading || isRestoringSession || isDisconnecting || !username || pendingAutoLogin) {
61808
+ if (isAuthenticated || isLoading || isRestoringSession || isDisconnecting || !username || deciding) {
61121
61809
  return null;
61122
61810
  }
61123
- return { username, locked: Boolean(presetUsername) };
61124
- }, [isAuthenticated, isLoading, isRestoringSession, isDisconnecting, username, presetUsername, pendingAutoLogin]);
61811
+ return { username, locked: Boolean(presetUsername), elsewhere: !engineMode && tabRole === "member", note: tabNote };
61812
+ }, [isAuthenticated, isLoading, isRestoringSession, isDisconnecting, username, presetUsername, deciding, engineMode, tabRole, tabNote]);
61125
61813
  const handleCancelLoginConfirmation = useCallback(() => {
61126
61814
  setUsername(null);
61127
61815
  setError(null);
@@ -61161,11 +61849,13 @@ function DialerWrapper({ options, containerEl }) {
61161
61849
  username,
61162
61850
  onLogin: handleLogin,
61163
61851
  onLogout: handleLogout,
61164
- isLoadingSession: isLoading || isRestoringSession || pendingAutoLogin,
61852
+ isLoadingSession: isLoading || isRestoringSession || deciding,
61165
61853
  sessionError: error2,
61166
61854
  sessionStatus: loginStatus,
61167
61855
  loginConfirmation,
61168
61856
  onCancelLoginConfirmation: handleCancelLoginConfirmation,
61857
+ onTakeOverSession: handleTakeOver,
61858
+ onLogoutAllTabs: handleLogoutAll,
61169
61859
  variant: uiVariant,
61170
61860
  portalTarget,
61171
61861
  density: options.density,
@@ -61210,7 +61900,7 @@ function DialerWrapper({ options, containerEl }) {
61210
61900
  {
61211
61901
  domain: callixDomain,
61212
61902
  userSessionToken: sessionToken,
61213
- children: /* @__PURE__ */ jsxRuntimeExports.jsxs(SdkCleanupManager, { ref: cleanupRef, children: [
61903
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs(SdkCleanupManager, { ref: cleanupRef, onSessionDropped: handleSdkSessionDropped, children: [
61214
61904
  /* @__PURE__ */ jsxRuntimeExports.jsx(
61215
61905
  DialerComponent,
61216
61906
  {