callix-dialer-widget 2.6.0 → 2.7.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) {
@@ -52376,6 +52376,8 @@ function FloatingDialer({
52376
52376
  sessionStatus = null,
52377
52377
  loginConfirmation = null,
52378
52378
  onCancelLoginConfirmation,
52379
+ onTakeOverSession,
52380
+ onLogoutAllTabs,
52379
52381
  variant = "default",
52380
52382
  portalTarget = null
52381
52383
  }) {
@@ -52728,7 +52730,9 @@ function FloatingDialer({
52728
52730
  error: sessionError,
52729
52731
  statusMessage: sessionStatus,
52730
52732
  confirmation: loginConfirmation,
52731
- onCancelConfirmation: onCancelLoginConfirmation
52733
+ onCancelConfirmation: onCancelLoginConfirmation,
52734
+ onTakeOver: onTakeOverSession,
52735
+ onLogoutAll: onLogoutAllTabs
52732
52736
  }
52733
52737
  );
52734
52738
  if (isMiniMode) {
@@ -52864,7 +52868,7 @@ function FloatingDialer({
52864
52868
  }
52865
52869
  );
52866
52870
  }
52867
- function LoginForm({ defaultValue, onSubmit, isLoading, error: error2, statusMessage, confirmation, onCancelConfirmation }) {
52871
+ function LoginForm({ defaultValue, onSubmit, isLoading, error: error2, statusMessage, confirmation, onCancelConfirmation, onTakeOver, onLogoutAll }) {
52868
52872
  const [value, setValue] = useState(defaultValue);
52869
52873
  const [touched, setTouched] = useState(false);
52870
52874
  useEffect(() => {
@@ -52895,8 +52899,41 @@ function LoginForm({ defaultValue, onSubmit, isLoading, error: error2, statusMes
52895
52899
  }
52896
52900
  );
52897
52901
  }
52902
+ if (confirmation == null ? void 0 : confirmation.elsewhere) {
52903
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`space-y-4`, "data-login-state": "elsewhere", children: [
52904
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`space-y-2 text-center`, children: [
52905
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-[11px] font-semibold uppercase tracking-wide text-slate-500`, children: "Discador ativo em outra aba" }),
52906
+ /* @__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: [
52907
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FiUser, { className: tw`text-primary` }),
52908
+ confirmation.username
52909
+ ] })
52910
+ ] }),
52911
+ error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-center text-xs text-red-500`, children: error2 }),
52912
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
52913
+ "button",
52914
+ {
52915
+ type: "button",
52916
+ id: "takeOverBtn",
52917
+ onClick: () => onTakeOver == null ? void 0 : onTakeOver(),
52918
+ 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`,
52919
+ children: "Usar o discador nesta aba"
52920
+ }
52921
+ ),
52922
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
52923
+ "button",
52924
+ {
52925
+ type: "button",
52926
+ id: "logoutAllBtn",
52927
+ onClick: () => onLogoutAll == null ? void 0 : onLogoutAll(),
52928
+ className: tw`w-full text-center text-xs font-semibold text-red-500 transition hover:text-red-600`,
52929
+ children: "Sair em todas as abas"
52930
+ }
52931
+ ),
52932
+ /* @__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á." })
52933
+ ] });
52934
+ }
52898
52935
  if (confirmation) {
52899
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`space-y-4`, children: [
52936
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`space-y-4`, "data-login-state": "choose", children: [
52900
52937
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: tw`space-y-2 text-center`, children: [
52901
52938
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-[11px] font-semibold uppercase tracking-wide text-slate-500`, children: "Entrar como" }),
52902
52939
  /* @__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 +52942,12 @@ function LoginForm({ defaultValue, onSubmit, isLoading, error: error2, statusMes
52905
52942
  ] })
52906
52943
  ] }),
52907
52944
  error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-center text-xs text-red-500`, children: error2 }),
52945
+ !error2 && confirmation.note && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: tw`text-center text-xs text-slate-500`, children: confirmation.note }),
52908
52946
  /* @__PURE__ */ jsxRuntimeExports.jsx(
52909
52947
  "button",
52910
52948
  {
52911
52949
  type: "button",
52950
+ id: "loginConfirmBtn",
52912
52951
  onClick: () => void onSubmit(confirmation.username),
52913
52952
  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
52953
  children: "Entrar"
@@ -52923,7 +52962,7 @@ function LoginForm({ defaultValue, onSubmit, isLoading, error: error2, statusMes
52923
52962
  children: "Usar outro usuário"
52924
52963
  }
52925
52964
  ),
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á." })
52965
+ /* @__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
52966
  ] });
52928
52967
  }
52929
52968
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("form", { className: tw`space-y-4`, onSubmit: handleSubmit, children: [
@@ -58427,6 +58466,8 @@ function MobileDialerLogin({
58427
58466
  sessionStatus,
58428
58467
  loginConfirmation,
58429
58468
  onCancelLoginConfirmation,
58469
+ onTakeOverSession,
58470
+ onLogoutAllTabs,
58430
58471
  density
58431
58472
  }) {
58432
58473
  const [loginInput, setLoginInput] = useState(username ?? "");
@@ -58463,19 +58504,33 @@ function MobileDialerLogin({
58463
58504
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "login-spinner", "aria-hidden": "true" }),
58464
58505
  loadingUser && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "login-loading-user", children: loadingUser }),
58465
58506
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "login-status", children: sessionStatus || "Conectando..." })
58466
- ] }) : showConfirmation && loginConfirmation ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "login-confirm", children: [
58507
+ ] }) : showConfirmation && loginConfirmation ? loginConfirmation.elsewhere ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "login-confirm", "data-login-state": "elsewhere", children: [
58508
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "login-confirm-label", children: "Discador ativo em outra aba" }),
58509
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "login-user-chip", children: [
58510
+ /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-user-check", "aria-hidden": "true" }),
58511
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: loginConfirmation.username })
58512
+ ] }),
58513
+ sessionError && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "input-error", children: sessionError }),
58514
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", className: "login-confirm-btn login-confirm-btn-blue", id: "takeOverBtn", onClick: () => onTakeOverSession == null ? void 0 : onTakeOverSession(), children: [
58515
+ /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-arrow-down", "aria-hidden": "true" }),
58516
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Usar o discador nesta aba" })
58517
+ ] }),
58518
+ /* @__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" }),
58519
+ /* @__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á." })
58520
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "login-confirm", "data-login-state": "choose", children: [
58467
58521
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "login-confirm-label", children: "Entrar como" }),
58468
58522
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "login-user-chip", children: [
58469
58523
  /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-user", "aria-hidden": "true" }),
58470
58524
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: loginConfirmation.username })
58471
58525
  ] }),
58472
58526
  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: [
58527
+ !sessionError && loginConfirmation.note && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "login-note", children: loginConfirmation.note }),
58528
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", className: "login-confirm-btn", id: "loginConfirmBtn", onClick: () => void submitLogin(loginConfirmation.username), children: [
58474
58529
  /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-sign-in-alt", "aria-hidden": "true" }),
58475
58530
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Entrar" })
58476
58531
  ] }),
58477
58532
  !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á." })
58533
+ /* @__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
58534
  ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
58480
58535
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "phone-number-display", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
58481
58536
  "input",
@@ -58568,6 +58623,7 @@ Gostaríamos de fazer um *diagnóstico gratuito* para te apresentar os valores r
58568
58623
  }
58569
58624
  function AuthenticatedMobileDialer({
58570
58625
  username,
58626
+ onLogout,
58571
58627
  showClientInfoModal = false,
58572
58628
  density
58573
58629
  }) {
@@ -59334,13 +59390,30 @@ function AuthenticatedMobileDialer({
59334
59390
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "pause-timer", children: pauseDurationLabel })
59335
59391
  ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "status-text", children: statusBadgeText })
59336
59392
  ] }) }),
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
- ] }) })
59393
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "status-actions", children: [
59394
+ isOnBreak ? /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "resume-btn", id: "resumeBtn", onClick: handleGoOffBreak, children: [
59395
+ /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-play" }),
59396
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Voltar" })
59397
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "pause-btn", id: "pauseBtn", onClick: () => setIsPauseModalOpen(true), children: [
59398
+ /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-pause" }),
59399
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Pausa" })
59400
+ ] }),
59401
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
59402
+ "button",
59403
+ {
59404
+ type: "button",
59405
+ className: "logout-btn",
59406
+ id: "logoutBtn",
59407
+ title: "Sair do discador (todas as abas)",
59408
+ "aria-label": "Sair do discador",
59409
+ onClick: () => {
59410
+ if (typeof window !== "undefined" && !window.confirm("Sair do discador em todas as abas?")) return;
59411
+ onLogout();
59412
+ },
59413
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-right-from-bracket" })
59414
+ }
59415
+ )
59416
+ ] })
59344
59417
  ] }),
59345
59418
  /* @__PURE__ */ jsxRuntimeExports.jsx(DialerNoticeBanner, { notice: dialerNotice, onDismiss: dismissDialerNotice, onRecheck: () => void recheckDevices(), onHeightChange: setNoticeHeight }),
59346
59419
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "phone-number-display", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -60176,6 +60249,206 @@ function AuthenticatedMiniDialer({
60176
60249
  )
60177
60250
  ] });
60178
60251
  }
60252
+ const PROBE_TIMEOUT_MS = 600;
60253
+ const TAKEOVER_GRACE_MS = 3e3;
60254
+ const TAKEOVER_JITTER_MS = 600;
60255
+ const LEADER_HEARTBEAT_MS = 3e3;
60256
+ const LEADER_STALE_MS = 9e3;
60257
+ function channelName(username) {
60258
+ return `callix-dialer:${username.trim().toLowerCase()}`;
60259
+ }
60260
+ function leaderKey(username) {
60261
+ return `callix_dialer_${username}_leader`;
60262
+ }
60263
+ const TAB_ROLE_KEY = "callix_dialer_tab_role";
60264
+ const BUS_FALLBACK_SUFFIX = "_bus";
60265
+ function newTabId() {
60266
+ const cryptoApi = typeof globalThis.crypto !== "undefined" ? globalThis.crypto : void 0;
60267
+ if (cryptoApi && "randomUUID" in cryptoApi) return cryptoApi.randomUUID().slice(0, 8);
60268
+ return Math.random().toString(36).slice(2, 10);
60269
+ }
60270
+ function defaultChannelFactory(name) {
60271
+ if (typeof BroadcastChannel === "undefined") return null;
60272
+ try {
60273
+ return new BroadcastChannel(name);
60274
+ } catch {
60275
+ return null;
60276
+ }
60277
+ }
60278
+ function defaultStorage() {
60279
+ try {
60280
+ return typeof localStorage !== "undefined" ? localStorage : null;
60281
+ } catch {
60282
+ return null;
60283
+ }
60284
+ }
60285
+ class DialerTabBus {
60286
+ constructor(options) {
60287
+ this.listeners = /* @__PURE__ */ new Set();
60288
+ this.storageListener = null;
60289
+ this.closed = false;
60290
+ this.username = options.username;
60291
+ this.tabId = options.tabId ?? newTabId();
60292
+ this.status = options.status;
60293
+ this.storage = options.storage === void 0 ? defaultStorage() : options.storage;
60294
+ const factory = options.channelFactory ?? defaultChannelFactory;
60295
+ this.channel = factory(channelName(this.username));
60296
+ if (this.channel) {
60297
+ this.channel.onmessage = (event) => this.receive(event.data);
60298
+ } else if (typeof window !== "undefined" && this.storage) {
60299
+ const key = this.fallbackKey();
60300
+ this.storageListener = (event) => {
60301
+ if (event.key !== key || !event.newValue) return;
60302
+ try {
60303
+ const parsed = JSON.parse(event.newValue);
60304
+ this.receive(parsed.message);
60305
+ } catch {
60306
+ }
60307
+ };
60308
+ window.addEventListener("storage", this.storageListener);
60309
+ }
60310
+ }
60311
+ fallbackKey() {
60312
+ return `callix_dialer_${this.username}${BUS_FALLBACK_SUFFIX}`;
60313
+ }
60314
+ on(listener) {
60315
+ this.listeners.add(listener);
60316
+ return () => {
60317
+ this.listeners.delete(listener);
60318
+ };
60319
+ }
60320
+ post(message) {
60321
+ if (this.closed) return;
60322
+ const full = { ...message, from: this.tabId };
60323
+ if (this.channel) {
60324
+ try {
60325
+ this.channel.postMessage(full);
60326
+ } catch {
60327
+ }
60328
+ return;
60329
+ }
60330
+ if (this.storage) {
60331
+ try {
60332
+ this.storage.setItem(this.fallbackKey(), JSON.stringify({ nonce: Math.random(), at: Date.now(), message: full }));
60333
+ } catch {
60334
+ }
60335
+ }
60336
+ }
60337
+ receive(data) {
60338
+ if (this.closed || !data || typeof data !== "object") return;
60339
+ const message = data;
60340
+ if (typeof message.type !== "string" || typeof message.from !== "string" || message.from === this.tabId) return;
60341
+ if (message.type === "ping") {
60342
+ const status = this.status();
60343
+ this.post({ type: "pong", role: status.role, authenticated: status.authenticated });
60344
+ }
60345
+ for (const listener of this.listeners) listener(message);
60346
+ }
60347
+ /** pergunta quem está aí; devolve as abas que responderam dentro do prazo */
60348
+ probe(timeoutMs = PROBE_TIMEOUT_MS) {
60349
+ return new Promise((resolve) => {
60350
+ const found = /* @__PURE__ */ new Map();
60351
+ const off = this.on((message) => {
60352
+ if (message.type === "pong") found.set(message.from, { tabId: message.from, role: message.role, authenticated: message.authenticated });
60353
+ });
60354
+ this.post({ type: "ping" });
60355
+ setTimeout(() => {
60356
+ off();
60357
+ resolve([...found.values()]);
60358
+ }, timeoutMs);
60359
+ });
60360
+ }
60361
+ close() {
60362
+ this.closed = true;
60363
+ this.listeners.clear();
60364
+ if (this.channel) {
60365
+ this.channel.onmessage = null;
60366
+ try {
60367
+ this.channel.close();
60368
+ } catch {
60369
+ }
60370
+ }
60371
+ if (this.storageListener && typeof window !== "undefined") {
60372
+ window.removeEventListener("storage", this.storageListener);
60373
+ this.storageListener = null;
60374
+ }
60375
+ }
60376
+ }
60377
+ function findLeader(pongs) {
60378
+ return pongs.find((pong) => pong.role === "leader" && pong.authenticated) ?? null;
60379
+ }
60380
+ function anyDialerTab(pongs) {
60381
+ return pongs.some((pong) => pong.role !== null);
60382
+ }
60383
+ function readLeader(username, storage = defaultStorage(), now = Date.now()) {
60384
+ if (!storage) return null;
60385
+ try {
60386
+ const raw = storage.getItem(leaderKey(username));
60387
+ if (!raw) return null;
60388
+ const parsed = JSON.parse(raw);
60389
+ if (typeof parsed.tabId !== "string" || typeof parsed.at !== "number") return null;
60390
+ if (now - parsed.at > LEADER_STALE_MS) return null;
60391
+ return { tabId: parsed.tabId, at: parsed.at };
60392
+ } catch {
60393
+ return null;
60394
+ }
60395
+ }
60396
+ function claimLeader(username, tabId, storage = defaultStorage(), now = Date.now()) {
60397
+ if (!storage) return true;
60398
+ const current = readLeader(username, storage, now);
60399
+ if (current && current.tabId !== tabId) return false;
60400
+ try {
60401
+ storage.setItem(leaderKey(username), JSON.stringify({ tabId, at: now }));
60402
+ } catch {
60403
+ }
60404
+ return true;
60405
+ }
60406
+ function releaseLeader(username, tabId, storage = defaultStorage()) {
60407
+ if (!storage) return;
60408
+ try {
60409
+ const raw = storage.getItem(leaderKey(username));
60410
+ if (!raw) return;
60411
+ const parsed = JSON.parse(raw);
60412
+ if (parsed.tabId === tabId) storage.removeItem(leaderKey(username));
60413
+ } catch {
60414
+ }
60415
+ }
60416
+ function defaultSessionStorage() {
60417
+ try {
60418
+ return typeof sessionStorage !== "undefined" ? sessionStorage : null;
60419
+ } catch {
60420
+ return null;
60421
+ }
60422
+ }
60423
+ function saveTabRole(username, role, storage = defaultSessionStorage()) {
60424
+ if (!storage) return;
60425
+ try {
60426
+ storage.setItem(TAB_ROLE_KEY, JSON.stringify({ username, role, at: Date.now() }));
60427
+ } catch {
60428
+ }
60429
+ }
60430
+ function readTabRole(username, storage = defaultSessionStorage()) {
60431
+ if (!storage) return null;
60432
+ try {
60433
+ const raw = storage.getItem(TAB_ROLE_KEY);
60434
+ if (!raw) return null;
60435
+ const parsed = JSON.parse(raw);
60436
+ if (parsed.username !== username) return null;
60437
+ return parsed.role === "leader" || parsed.role === "member" ? parsed.role : null;
60438
+ } catch {
60439
+ return null;
60440
+ }
60441
+ }
60442
+ function clearTabRole(storage = defaultSessionStorage()) {
60443
+ if (!storage) return;
60444
+ try {
60445
+ storage.removeItem(TAB_ROLE_KEY);
60446
+ } catch {
60447
+ }
60448
+ }
60449
+ function takeoverDelayMs(random = Math.random()) {
60450
+ return TAKEOVER_GRACE_MS + Math.floor(random * TAKEOVER_JITTER_MS);
60451
+ }
60179
60452
  const DEFAULT_CLICK_TO_CALL_INTERVAL_MS = 4e3;
60180
60453
  const BUSY_INTERVAL_FACTOR = 3;
60181
60454
  const DISPATCH_COOLDOWN_MS = 6e3;
@@ -60398,7 +60671,7 @@ function ClickToCallAgent({ enabled = true, pollIntervalMs, sessionToken, onTask
60398
60671
  }, [enabled, pollIntervalMs, sessionToken]);
60399
60672
  return null;
60400
60673
  }
60401
- const WIDGET_VERSION = "2.6.0";
60674
+ const WIDGET_VERSION = "2.7.0";
60402
60675
  let currentSipWebSocket = null;
60403
60676
  function sendSipUnregisterIfPossible(ws) {
60404
60677
  if (ws.readyState !== WebSocket.OPEN) return;
@@ -60524,9 +60797,23 @@ function useDialerConfig() {
60524
60797
  return context;
60525
60798
  }
60526
60799
  const SdkCleanupManager = forwardRef(
60527
- function SdkCleanupManager2({ children: children2 }, ref) {
60800
+ function SdkCleanupManager2({ children: children2, onSessionDropped }, ref) {
60528
60801
  const operator = useCallOperator();
60529
60802
  const controls = useCallOperatorControls();
60803
+ const operatorState = useCallOperatorState();
60804
+ const droppedRef = useRef(onSessionDropped);
60805
+ droppedRef.current = onSessionDropped;
60806
+ useCallOperatorEventListener("userSessionDropped", (data) => {
60807
+ var _a2;
60808
+ const originator = (data == null ? void 0 : data.originator) ?? null;
60809
+ (_a2 = droppedRef.current) == null ? void 0 : _a2.call(droppedRef, originator, describeSessionDrop(originator));
60810
+ });
60811
+ const startupError = (operatorState == null ? void 0 : operatorState.state) === "startupError" ? operatorState.error ?? null : null;
60812
+ useEffect(() => {
60813
+ var _a2;
60814
+ if (startupError !== "userIsAlreadyConnectedOnAnotherDevice") return;
60815
+ (_a2 = droppedRef.current) == null ? void 0 : _a2.call(droppedRef, "anotherDevice", describeStartupError(startupError));
60816
+ }, [startupError]);
60530
60817
  useImperativeHandle(ref, () => ({
60531
60818
  goOffline: () => {
60532
60819
  console.log("[Callix Dialer] 📴 Chamando goOffline() para enviar SIP UNREGISTER...");
@@ -60547,16 +60834,18 @@ const SdkCleanupManager = forwardRef(
60547
60834
  }
60548
60835
  }
60549
60836
  }), [operator, controls]);
60837
+ const controlsRef = useRef(controls);
60838
+ controlsRef.current = controls;
60550
60839
  useEffect(() => {
60551
60840
  return () => {
60552
60841
  console.log("[Callix Dialer] 🔌 SdkCleanupManager desmontando, chamando goOffline()...");
60553
60842
  try {
60554
- controls.goOffline();
60843
+ controlsRef.current.goOffline();
60555
60844
  } catch (err) {
60556
60845
  console.warn("[Callix Dialer] ⚠️ Erro no cleanup automático:", err);
60557
60846
  }
60558
60847
  };
60559
- }, [controls]);
60848
+ }, []);
60560
60849
  return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: children2 });
60561
60850
  }
60562
60851
  );
@@ -60695,6 +60984,13 @@ const getStorageKeys = (username) => ({
60695
60984
  SKIP_MANUAL_QUALIFICATION: `callix_dialer_${username}_skip_manual_qualification`
60696
60985
  });
60697
60986
  const LAST_USERNAME_KEY = "callix_dialer_last_username";
60987
+ function readStoredToken(user) {
60988
+ try {
60989
+ return localStorage.getItem(getStorageKeys(user).SESSION_TOKEN);
60990
+ } catch {
60991
+ return null;
60992
+ }
60993
+ }
60698
60994
  async function fetchSkipQualification(domain, token) {
60699
60995
  var _a2, _b;
60700
60996
  try {
@@ -60724,10 +61020,35 @@ function DialerWrapper({ options, containerEl }) {
60724
61020
  const [isDisconnecting, setIsDisconnecting] = useState(false);
60725
61021
  const presetUsername = options.presetUsername ? options.presetUsername.trim() : null;
60726
61022
  const uiVariant = options.uiVariant ?? "default";
60727
- const autoLogin = options.autoLogin === true;
60728
61023
  const portalTarget = useMemo(() => options.portalTarget ?? containerEl ?? null, [options.portalTarget, containerEl]);
60729
- const autoLoginAttemptedRef = useRef(false);
60730
- const lastPresetRef = useRef(presetUsername);
61024
+ const tabIdRef = useRef("");
61025
+ if (!tabIdRef.current) tabIdRef.current = newTabId();
61026
+ const [tabRole, setTabRole] = useState(null);
61027
+ const tabRoleRef = useRef(null);
61028
+ tabRoleRef.current = tabRole;
61029
+ const [tabDecided, setTabDecided] = useState(false);
61030
+ const [tabNote, setTabNote] = useState(null);
61031
+ const [engineWaiting, setEngineWaiting] = useState(false);
61032
+ const busRef = useRef(null);
61033
+ const takeoverTimerRef = useRef(null);
61034
+ const isAuthenticatedRef = useRef(false);
61035
+ isAuthenticatedRef.current = isAuthenticated;
61036
+ const isLoadingRef = useRef(false);
61037
+ isLoadingRef.current = isLoading;
61038
+ const logoffPendingRef = useRef(false);
61039
+ const handleLoginRef = useRef(async () => void 0);
61040
+ const handleLogoutRef = useRef(async () => void 0);
61041
+ const cancelTakeover = useCallback(() => {
61042
+ if (takeoverTimerRef.current) {
61043
+ clearTimeout(takeoverTimerRef.current);
61044
+ takeoverTimerRef.current = null;
61045
+ }
61046
+ }, []);
61047
+ useEffect(() => {
61048
+ if (options.autoLogin) {
61049
+ 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).");
61050
+ }
61051
+ }, [options.autoLogin]);
60731
61052
  const currentSessionRef = useRef({
60732
61053
  apiUrl: options.apiUrl || "/api/user-sdk-session",
60733
61054
  token: null
@@ -60759,7 +61080,7 @@ function DialerWrapper({ options, containerEl }) {
60759
61080
  setError(`${notice.title}: ${notice.detail}${notice.action ? ` ${notice.action}` : ""}`);
60760
61081
  setIsAuthenticated(false);
60761
61082
  setSessionToken(null);
60762
- autoLoginAttemptedRef.current = true;
61083
+ setTabNote(null);
60763
61084
  });
60764
61085
  client2.connect();
60765
61086
  engineRef.current = client2;
@@ -60776,9 +61097,19 @@ function DialerWrapper({ options, containerEl }) {
60776
61097
  []
60777
61098
  );
60778
61099
  useEffect(() => {
61100
+ let unloadHandled = false;
60779
61101
  const handleBeforeUnload = () => {
60780
- if (engineModeRef.current) return;
61102
+ var _a2;
61103
+ if (engineModeRef.current || unloadHandled) return;
61104
+ unloadHandled = true;
61105
+ setTimeout(() => {
61106
+ unloadHandled = false;
61107
+ }, 2e3);
60781
61108
  const { apiUrl, token } = currentSessionRef.current;
61109
+ if (tabRoleRef.current === "leader") {
61110
+ (_a2 = busRef.current) == null ? void 0 : _a2.post({ type: "leaving" });
61111
+ if (busRef.current) releaseLeader(busRef.current.username, tabIdRef.current);
61112
+ }
60782
61113
  if (token) {
60783
61114
  const data = JSON.stringify({ logout: true });
60784
61115
  const logoutUrl = `${apiUrl}?logout=true`;
@@ -60797,8 +61128,10 @@ function DialerWrapper({ options, containerEl }) {
60797
61128
  }
60798
61129
  };
60799
61130
  window.addEventListener("beforeunload", handleBeforeUnload);
61131
+ window.addEventListener("pagehide", handleBeforeUnload);
60800
61132
  return () => {
60801
61133
  window.removeEventListener("beforeunload", handleBeforeUnload);
61134
+ window.removeEventListener("pagehide", handleBeforeUnload);
60802
61135
  };
60803
61136
  }, []);
60804
61137
  useEffect(() => {
@@ -60836,8 +61169,239 @@ function DialerWrapper({ options, containerEl }) {
60836
61169
  setUsername(presetUsername);
60837
61170
  }
60838
61171
  }, [presetUsername, username]);
61172
+ const engineDomain = useCallback(
61173
+ () => options.domain || new URL(options.apiUrl || "/api/user-sdk-session", window.location.href).host,
61174
+ [options.domain, options.apiUrl]
61175
+ );
61176
+ const enterEngine = useCallback(async (user, domain, token) => {
61177
+ if (token) {
61178
+ const skip = await fetchSkipQualification(domain, token);
61179
+ if (skip !== null) {
61180
+ setSkipManualQualification(skip);
61181
+ try {
61182
+ localStorage.setItem(getStorageKeys(user).SKIP_MANUAL_QUALIFICATION, String(skip));
61183
+ } catch {
61184
+ }
61185
+ }
61186
+ }
61187
+ try {
61188
+ localStorage.setItem(LAST_USERNAME_KEY, user);
61189
+ if (token) {
61190
+ const STORAGE_KEYS = getStorageKeys(user);
61191
+ localStorage.setItem(STORAGE_KEYS.SESSION_TOKEN, token);
61192
+ localStorage.setItem(STORAGE_KEYS.DOMAIN, domain);
61193
+ localStorage.setItem(STORAGE_KEYS.TIMESTAMP, Date.now().toString());
61194
+ }
61195
+ } catch {
61196
+ }
61197
+ setSessionToken(token);
61198
+ setCallixDomain(domain);
61199
+ setUsername(user);
61200
+ setIsAuthenticated(true);
61201
+ setError(null);
61202
+ setTabNote(null);
61203
+ setProviderKey(Date.now());
61204
+ }, []);
61205
+ const joinEngine = useCallback(
61206
+ async (user) => {
61207
+ setIsLoading(true);
61208
+ setError(null);
61209
+ setLoginStatus("Entrando na sessão do motor...");
61210
+ try {
61211
+ await enterEngine(user, engineDomain(), readStoredToken(user));
61212
+ console.log("[Callix Dialer] Conectado ao motor como", user);
61213
+ } catch (err) {
61214
+ console.error("[Callix Dialer] Erro ao entrar na sessão do motor:", err);
61215
+ setError(err instanceof Error ? err.message : "Erro ao conectar ao motor");
61216
+ } finally {
61217
+ setIsLoading(false);
61218
+ setLoginStatus(null);
61219
+ }
61220
+ },
61221
+ [enterEngine, engineDomain]
61222
+ );
61223
+ useEffect(() => {
61224
+ if (!engineMode || !username || isRestoringSession) return;
61225
+ const user = username;
61226
+ const client2 = ensureEngine(user);
61227
+ setEngineWaiting(!client2.getState().view);
61228
+ const fallback = setTimeout(() => setEngineWaiting(false), 15e3);
61229
+ let joining = false;
61230
+ const sync = () => {
61231
+ const { view, sessionLost } = client2.getState();
61232
+ if (!view) return;
61233
+ setEngineWaiting(false);
61234
+ if (view.engine === "stopped" || view.engine === "crashed") logoffPendingRef.current = false;
61235
+ const hasSession = (view.engine === "running" || view.engine === "starting") && !sessionLost;
61236
+ if (hasSession && !logoffPendingRef.current && !isAuthenticatedRef.current && !isLoadingRef.current && !joining) {
61237
+ joining = true;
61238
+ console.log("[Callix Dialer] o motor já segura a sessão deste agente; entrando por aqui também");
61239
+ void joinEngine(user).finally(() => {
61240
+ joining = false;
61241
+ });
61242
+ } else if (view.engine === "stopped" && isAuthenticatedRef.current && !isLoadingRef.current) {
61243
+ console.log("[Callix Dialer] o motor desligou (Sair em outra aba ou servidor reiniciado); voltando à tela de login");
61244
+ setIsAuthenticated(false);
61245
+ setSessionToken(null);
61246
+ setCallixDomain(null);
61247
+ setTabNote("O discador foi encerrado em outra aba ou o motor foi reiniciado.");
61248
+ }
61249
+ };
61250
+ sync();
61251
+ const off = client2.subscribe(sync);
61252
+ return () => {
61253
+ off();
61254
+ clearTimeout(fallback);
61255
+ };
61256
+ }, [engineMode, username, isRestoringSession, ensureEngine, joinEngine]);
61257
+ const scheduleTakeoverRef = useRef(null);
61258
+ useEffect(() => {
61259
+ if (engineMode || !username) {
61260
+ setTabDecided(true);
61261
+ return;
61262
+ }
61263
+ setTabDecided(false);
61264
+ const user = username;
61265
+ const bus = new DialerTabBus({
61266
+ username: user,
61267
+ tabId: tabIdRef.current,
61268
+ status: () => ({ role: tabRoleRef.current, authenticated: isAuthenticatedRef.current })
61269
+ });
61270
+ busRef.current = bus;
61271
+ const becomeMember = () => {
61272
+ if (isAuthenticatedRef.current || tabRoleRef.current === "leader") return;
61273
+ setTabRole("member");
61274
+ saveTabRole(user, "member");
61275
+ setTabNote(null);
61276
+ setError(null);
61277
+ setTabDecided(true);
61278
+ };
61279
+ const scheduleTakeover = () => {
61280
+ cancelTakeover();
61281
+ takeoverTimerRef.current = setTimeout(() => {
61282
+ takeoverTimerRef.current = null;
61283
+ if (tabRoleRef.current !== "member" || isAuthenticatedRef.current || isLoadingRef.current) return;
61284
+ const current = readLeader(user);
61285
+ if (current && current.tabId !== tabIdRef.current) return;
61286
+ if (!claimLeader(user, tabIdRef.current)) return;
61287
+ bus.post({ type: "claim" });
61288
+ console.log("[Callix Dialer] a aba com o discador fechou; esta aba assume a conexão");
61289
+ void handleLoginRef.current(user);
61290
+ }, takeoverDelayMs());
61291
+ };
61292
+ scheduleTakeoverRef.current = scheduleTakeover;
61293
+ const off = bus.on((message) => {
61294
+ switch (message.type) {
61295
+ case "login":
61296
+ case "claim":
61297
+ cancelTakeover();
61298
+ becomeMember();
61299
+ break;
61300
+ case "leaving":
61301
+ if (tabRoleRef.current === "member" && !isAuthenticatedRef.current) scheduleTakeover();
61302
+ break;
61303
+ case "takeover":
61304
+ if (tabRoleRef.current === "leader") {
61305
+ console.log("[Callix Dialer] outra aba pediu o discador; soltando a conexão");
61306
+ void handleLogoutRef.current({ broadcast: false, keepDecision: true });
61307
+ }
61308
+ break;
61309
+ case "logout":
61310
+ cancelTakeover();
61311
+ void handleLogoutRef.current({ broadcast: false });
61312
+ setTabNote("Você saiu do discador em outra aba.");
61313
+ break;
61314
+ case "dropped": {
61315
+ cancelTakeover();
61316
+ releaseLeader(user, tabIdRef.current);
61317
+ setTabRole(null);
61318
+ clearTabRole();
61319
+ const notice = describeSessionDrop(message.originator);
61320
+ setError(`${notice.title}: ${notice.detail}${notice.action ? ` ${notice.action}` : ""}`);
61321
+ break;
61322
+ }
61323
+ }
61324
+ });
61325
+ return () => {
61326
+ off();
61327
+ bus.close();
61328
+ cancelTakeover();
61329
+ scheduleTakeoverRef.current = null;
61330
+ if (busRef.current === bus) busRef.current = null;
61331
+ setTabDecided(false);
61332
+ };
61333
+ }, [engineMode, username, cancelTakeover]);
61334
+ useEffect(() => {
61335
+ if (engineMode || !username || isAuthenticated || tabDecided || isRestoringSession || isLoading) return;
61336
+ const bus = busRef.current;
61337
+ if (!bus) return;
61338
+ const user = username;
61339
+ const stored = readTabRole(user);
61340
+ if (stored === "leader") {
61341
+ cancelTakeover();
61342
+ setTabDecided(true);
61343
+ if (claimLeader(user, tabIdRef.current)) {
61344
+ bus.post({ type: "claim" });
61345
+ console.log("[Callix Dialer] esta aba segurava o discador antes de recarregar; reconectando");
61346
+ void handleLoginRef.current(user);
61347
+ } else {
61348
+ setTabRole("member");
61349
+ saveTabRole(user, "member");
61350
+ }
61351
+ return;
61352
+ }
61353
+ let cancelled = false;
61354
+ void bus.probe().then((pongs) => {
61355
+ var _a2;
61356
+ if (cancelled) return;
61357
+ setTabDecided(true);
61358
+ const leaderLock = readLeader(user);
61359
+ if (findLeader(pongs) || leaderLock && leaderLock.tabId !== tabIdRef.current || stored === "member" && anyDialerTab(pongs)) {
61360
+ setTabRole("member");
61361
+ saveTabRole(user, "member");
61362
+ } else if (stored === "member") {
61363
+ setTabRole("member");
61364
+ (_a2 = scheduleTakeoverRef.current) == null ? void 0 : _a2.call(scheduleTakeoverRef);
61365
+ } else {
61366
+ setTabRole(null);
61367
+ clearTabRole();
61368
+ }
61369
+ });
61370
+ return () => {
61371
+ cancelled = true;
61372
+ };
61373
+ }, [engineMode, username, isAuthenticated, tabDecided, isRestoringSession, isLoading, cancelTakeover]);
61374
+ useEffect(() => {
61375
+ if (engineMode || tabRole !== "leader" || !username) return;
61376
+ const user = username;
61377
+ claimLeader(user, tabIdRef.current);
61378
+ const timer = setInterval(() => claimLeader(user, tabIdRef.current), LEADER_HEARTBEAT_MS);
61379
+ return () => clearInterval(timer);
61380
+ }, [engineMode, tabRole, username]);
61381
+ useEffect(() => {
61382
+ if (engineMode || tabRole !== "member" || !username) return;
61383
+ const user = username;
61384
+ const check = () => {
61385
+ if (document.visibilityState !== "visible" || takeoverTimerRef.current) return;
61386
+ const bus = busRef.current;
61387
+ if (!bus) return;
61388
+ void bus.probe().then((pongs) => {
61389
+ var _a2;
61390
+ if (tabRoleRef.current !== "member" || isAuthenticatedRef.current || takeoverTimerRef.current) return;
61391
+ if (findLeader(pongs) || readLeader(user)) return;
61392
+ console.log("[Callix Dialer] a aba com o discador não responde; esta aba vai assumir");
61393
+ (_a2 = scheduleTakeoverRef.current) == null ? void 0 : _a2.call(scheduleTakeoverRef);
61394
+ });
61395
+ };
61396
+ document.addEventListener("visibilitychange", check);
61397
+ window.addEventListener("focus", check);
61398
+ return () => {
61399
+ document.removeEventListener("visibilitychange", check);
61400
+ window.removeEventListener("focus", check);
61401
+ };
61402
+ }, [engineMode, tabRole, username]);
60839
61403
  const handleLogin = useCallback(async (user) => {
60840
- var _a2, _b;
61404
+ var _a2, _b, _c, _d;
60841
61405
  setIsLoading(true);
60842
61406
  setError(null);
60843
61407
  setLoginStatus("Preparando login...");
@@ -60846,17 +61410,14 @@ function DialerWrapper({ options, containerEl }) {
60846
61410
  const engineApiUrl = options.apiUrl || "/api/user-sdk-session";
60847
61411
  try {
60848
61412
  setLoginStatus("Conectando ao motor...");
61413
+ logoffPendingRef.current = false;
60849
61414
  const client2 = ensureEngine(user);
60850
61415
  const view = await client2.waitForView(15e3);
60851
- const domain = options.domain || new URL(engineApiUrl, window.location.href).host;
61416
+ const domain = engineDomain();
60852
61417
  const engineHasSession = (view.engine === "running" || view.engine === "starting") && !view.sessionLost;
60853
61418
  let token = null;
60854
61419
  if (engineHasSession) {
60855
- try {
60856
- token = localStorage.getItem(getStorageKeys(user).SESSION_TOKEN);
60857
- } catch {
60858
- token = null;
60859
- }
61420
+ token = readStoredToken(user);
60860
61421
  console.log("[Callix Dialer] motor já tem a sessão do agente; entrando sem novo login");
60861
61422
  } else {
60862
61423
  setLoginStatus("Autenticando no Callix...");
@@ -60875,32 +61436,7 @@ function DialerWrapper({ options, containerEl }) {
60875
61436
  client2.setSession({ domain, userSessionToken: token });
60876
61437
  setLoginStatus("Subindo o motor...");
60877
61438
  }
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) {
60891
- const STORAGE_KEYS = getStorageKeys(user);
60892
- localStorage.setItem(STORAGE_KEYS.SESSION_TOKEN, token);
60893
- localStorage.setItem(STORAGE_KEYS.DOMAIN, domain);
60894
- localStorage.setItem(STORAGE_KEYS.TIMESTAMP, Date.now().toString());
60895
- }
60896
- } catch {
60897
- }
60898
- setSessionToken(token);
60899
- setCallixDomain(domain);
60900
- setUsername(user);
60901
- setIsAuthenticated(true);
60902
- setError(null);
60903
- setProviderKey(Date.now());
61439
+ await enterEngine(user, domain, token);
60904
61440
  console.log("[Callix Dialer] Conectado ao motor como", user);
60905
61441
  } catch (err) {
60906
61442
  console.error("[Callix Dialer] Erro ao entrar pelo motor:", err);
@@ -60913,6 +61449,20 @@ function DialerWrapper({ options, containerEl }) {
60913
61449
  }
60914
61450
  return;
60915
61451
  }
61452
+ if (!claimLeader(user, tabIdRef.current)) {
61453
+ console.log("[Callix Dialer] outra aba já segura a conexão deste usuário; esta aba vira membro");
61454
+ setTabRole("member");
61455
+ saveTabRole(user, "member");
61456
+ setTabDecided(true);
61457
+ setUsername(user);
61458
+ setIsLoading(false);
61459
+ setLoginStatus(null);
61460
+ return;
61461
+ }
61462
+ (_a2 = busRef.current) == null ? void 0 : _a2.post({ type: "claim" });
61463
+ cancelTakeover();
61464
+ setTabRole("leader");
61465
+ saveTabRole(user, "leader");
60916
61466
  const apiUrl = options.apiUrl || "/api/user-sdk-session";
60917
61467
  let previousToken;
60918
61468
  if (typeof window !== "undefined") {
@@ -61007,6 +61557,9 @@ function DialerWrapper({ options, containerEl }) {
61007
61557
  setError(null);
61008
61558
  setProviderKey(Date.now());
61009
61559
  console.log("[Callix Dialer] Nova providerKey gerada para forçar recriação do SDK");
61560
+ setTabNote(null);
61561
+ setTabDecided(true);
61562
+ (_b = busRef.current) == null ? void 0 : _b.post({ type: "login" });
61010
61563
  try {
61011
61564
  const configUrl = `https://${resolvedDomain}/sdk/v1/client/user-service/config`;
61012
61565
  setLoginStatus("Carregando configurações...");
@@ -61021,7 +61574,7 @@ function DialerWrapper({ options, containerEl }) {
61021
61574
  if (configResponse.ok) {
61022
61575
  const configData = await configResponse.json();
61023
61576
  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;
61577
+ const shouldSkipQualification = ((_d = (_c = configData == null ? void 0 : configData.service) == null ? void 0 : _c.outboundOptions) == null ? void 0 : _d.skipQualification) === true;
61025
61578
  console.log("[Callix Dialer] ✅ skipQualification configurado:", shouldSkipQualification);
61026
61579
  setSkipManualQualification(shouldSkipQualification);
61027
61580
  if (typeof window !== "undefined") {
@@ -61055,53 +61608,52 @@ function DialerWrapper({ options, containerEl }) {
61055
61608
  setError(err instanceof Error ? err.message : "Erro ao autenticar");
61056
61609
  setIsAuthenticated(false);
61057
61610
  setSessionToken(null);
61611
+ releaseLeader(user, tabIdRef.current);
61612
+ setTabRole(null);
61613
+ clearTabRole();
61058
61614
  } finally {
61059
61615
  setIsLoading(false);
61060
61616
  setLoginStatus(null);
61061
61617
  }
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]);
61618
+ }, [options.apiUrl, options.domain, options.engineUrl, isAuthenticated, sessionToken, ensureEngine, enterEngine, engineDomain, cancelTakeover]);
61077
61619
  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;
61620
+ handleLoginRef.current = handleLogin;
61621
+ }, [handleLogin]);
61622
+ const handleLogout = useCallback(async (opts) => {
61623
+ var _a2, _b;
61624
+ const broadcast = (opts == null ? void 0 : opts.broadcast) !== false;
61625
+ const keepDecision = (opts == null ? void 0 : opts.keepDecision) === true;
61087
61626
  const currentUsername = username;
61627
+ cancelTakeover();
61088
61628
  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));
61629
+ if (isAuthenticatedRef.current) {
61630
+ logoffPendingRef.current = true;
61631
+ (_a2 = engineRef.current) == null ? void 0 : _a2.command({ name: "logoff" });
61632
+ }
61633
+ } else {
61634
+ if (broadcast) (_b = busRef.current) == null ? void 0 : _b.post({ type: "logout" });
61635
+ if (cleanupRef.current) {
61636
+ console.log("[Callix Dialer] 📴 Logout: Chamando goOffline()...");
61637
+ cleanupRef.current.goOffline();
61638
+ await new Promise((resolve) => setTimeout(resolve, 500));
61639
+ console.log("[Callix Dialer] 🛑 Logout: Chamando stop()...");
61640
+ cleanupRef.current.stop();
61641
+ await new Promise((resolve) => setTimeout(resolve, 500));
61642
+ }
61643
+ if (currentUsername) releaseLeader(currentUsername, tabIdRef.current);
61097
61644
  }
61098
- setUsername(presetUsername ?? null);
61099
61645
  setIsAuthenticated(false);
61100
61646
  setSessionToken(null);
61101
61647
  setCallixDomain(null);
61102
61648
  setError(null);
61103
- autoLoginAttemptedRef.current = true;
61104
- lastPresetRef.current = presetUsername ?? null;
61649
+ if (keepDecision) {
61650
+ setTabRole("member");
61651
+ if (currentUsername) saveTabRole(currentUsername, "member");
61652
+ return;
61653
+ }
61654
+ setTabRole(null);
61655
+ clearTabRole();
61656
+ setUsername(presetUsername ?? null);
61105
61657
  if (typeof window !== "undefined" && currentUsername) {
61106
61658
  try {
61107
61659
  const STORAGE_KEYS = getStorageKeys(currentUsername);
@@ -61114,14 +61666,53 @@ function DialerWrapper({ options, containerEl }) {
61114
61666
  console.warn("[Callix Dialer] Não foi possível limpar sessão:", err);
61115
61667
  }
61116
61668
  }
61117
- }, [username, presetUsername, options.engineUrl]);
61118
- const pendingAutoLogin = autoLogin && Boolean(username) && !isAuthenticated && !error2 && !autoLoginAttemptedRef.current;
61669
+ }, [username, presetUsername, options.engineUrl, cancelTakeover]);
61670
+ useEffect(() => {
61671
+ handleLogoutRef.current = handleLogout;
61672
+ }, [handleLogout]);
61673
+ const handleTakeOver = useCallback(async () => {
61674
+ const user = username;
61675
+ const bus = busRef.current;
61676
+ if (!user || !bus) return;
61677
+ setIsLoading(true);
61678
+ setError(null);
61679
+ setLoginStatus("Trazendo o discador para esta aba...");
61680
+ bus.post({ type: "takeover" });
61681
+ await new Promise((resolve) => setTimeout(resolve, 1500));
61682
+ const current = readLeader(user);
61683
+ if (current && current.tabId !== tabIdRef.current) {
61684
+ console.log("[Callix Dialer] a outra aba não soltou a conexão; continuando como membro");
61685
+ setIsLoading(false);
61686
+ setLoginStatus(null);
61687
+ return;
61688
+ }
61689
+ await handleLoginRef.current(user);
61690
+ }, [username]);
61691
+ const handleLogoutAll = useCallback(() => {
61692
+ void handleLogoutRef.current();
61693
+ }, []);
61694
+ const handleSdkSessionDropped = useCallback(
61695
+ (originator, notice) => {
61696
+ var _a2;
61697
+ const user = username;
61698
+ if (user) releaseLeader(user, tabIdRef.current);
61699
+ (_a2 = busRef.current) == null ? void 0 : _a2.post({ type: "dropped", originator });
61700
+ setTabRole(null);
61701
+ clearTabRole();
61702
+ setError(`${notice.title}: ${notice.detail}${notice.action ? ` ${notice.action}` : ""}`);
61703
+ setIsAuthenticated(false);
61704
+ setSessionToken(null);
61705
+ setCallixDomain(null);
61706
+ },
61707
+ [username]
61708
+ );
61709
+ const deciding = Boolean(username) && !isAuthenticated && (engineMode ? engineWaiting : !tabDecided);
61119
61710
  const loginConfirmation = useMemo(() => {
61120
- if (isAuthenticated || isLoading || isRestoringSession || isDisconnecting || !username || pendingAutoLogin) {
61711
+ if (isAuthenticated || isLoading || isRestoringSession || isDisconnecting || !username || deciding) {
61121
61712
  return null;
61122
61713
  }
61123
- return { username, locked: Boolean(presetUsername) };
61124
- }, [isAuthenticated, isLoading, isRestoringSession, isDisconnecting, username, presetUsername, pendingAutoLogin]);
61714
+ return { username, locked: Boolean(presetUsername), elsewhere: !engineMode && tabRole === "member", note: tabNote };
61715
+ }, [isAuthenticated, isLoading, isRestoringSession, isDisconnecting, username, presetUsername, deciding, engineMode, tabRole, tabNote]);
61125
61716
  const handleCancelLoginConfirmation = useCallback(() => {
61126
61717
  setUsername(null);
61127
61718
  setError(null);
@@ -61161,11 +61752,13 @@ function DialerWrapper({ options, containerEl }) {
61161
61752
  username,
61162
61753
  onLogin: handleLogin,
61163
61754
  onLogout: handleLogout,
61164
- isLoadingSession: isLoading || isRestoringSession || pendingAutoLogin,
61755
+ isLoadingSession: isLoading || isRestoringSession || deciding,
61165
61756
  sessionError: error2,
61166
61757
  sessionStatus: loginStatus,
61167
61758
  loginConfirmation,
61168
61759
  onCancelLoginConfirmation: handleCancelLoginConfirmation,
61760
+ onTakeOverSession: handleTakeOver,
61761
+ onLogoutAllTabs: handleLogoutAll,
61169
61762
  variant: uiVariant,
61170
61763
  portalTarget,
61171
61764
  density: options.density,
@@ -61210,7 +61803,7 @@ function DialerWrapper({ options, containerEl }) {
61210
61803
  {
61211
61804
  domain: callixDomain,
61212
61805
  userSessionToken: sessionToken,
61213
- children: /* @__PURE__ */ jsxRuntimeExports.jsxs(SdkCleanupManager, { ref: cleanupRef, children: [
61806
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs(SdkCleanupManager, { ref: cleanupRef, onSessionDropped: handleSdkSessionDropped, children: [
61214
61807
  /* @__PURE__ */ jsxRuntimeExports.jsx(
61215
61808
  DialerComponent,
61216
61809
  {