callix-dialer-widget 2.8.1 → 2.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/WIDGET-README.md CHANGED
@@ -145,7 +145,20 @@ Pausas no modo motor (2.8.1): a lista de pausas é a do serviço no Callix, traz
145
145
  do motor; a pausa escolhida vai no comando `pause` e o discador mostra o nome dela. Com um
146
146
  motor anterior ao 2.8.1 a lista cai numa pausa única ("Pausa") e o motor usa a configurada
147
147
  nele. O widget também informa a própria versão ao host, que a registra no log: é o jeito de
148
- achar navegadores com um build antigo em cache.
148
+ achar navegadores com um build antigo em cache. No console da página, `CallixDialer.version`
149
+ diz qual build está carregado.
150
+
151
+ Comandos durante uma queda da conexão (2.8.2): um clique dado enquanto a conexão com o host
152
+ está caída ou reconectando (rede instável) não se perde mais em silêncio. O comando fica
153
+ guardado, a tela avisa que a conexão está voltando, e ele é enviado no reconectar, se ainda
154
+ tiver menos de 15 s; mais velho que isso é descartado, para não atingir outra chamada. Antes,
155
+ um "encerrar" dado nesse momento deixava a chamada ativa no motor.
156
+
157
+ Espelho e F5 no meio da chamada (2.8.3): a tela de chamada mostra o número da chamada em
158
+ curso informado pelo SDK ou pelo motor quando ninguém digitou nada naquele teclado (antes
159
+ aparecia "(--) ----- ----" na aba espelho). Click-to-call com o agente em pausa (2.8.3): o
160
+ agente sai da pausa, espera o Callix liberá-lo (até 8 s) e disca; se não liberar, a tarefa
161
+ volta à fila. No modo motor, um `dial` recebido em pausa faz o mesmo dentro do motor.
149
162
 
150
163
  Sem `engineUrl` nada muda: o SDK continua rodando no navegador como sempre.
151
164
 
@@ -43754,6 +43754,8 @@ const INITIAL_TRANSCRIPT = { segments: [], status: null, insight: null, interims
43754
43754
  const MAX_SEGMENTS = 300;
43755
43755
  const RECONNECT_MIN_MS = 500;
43756
43756
  const RECONNECT_MAX_MS = 5e3;
43757
+ const PENDING_COMMANDS_MAX = 10;
43758
+ const PENDING_COMMANDS_TTL_MS = 15e3;
43757
43759
  function engineWsUrl(baseUrl) {
43758
43760
  const url = new URL(baseUrl, typeof window !== "undefined" ? window.location.href : "http://localhost");
43759
43761
  url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
@@ -43771,6 +43773,7 @@ class EngineClient {
43771
43773
  this.viewWaiters = [];
43772
43774
  this.ws = null;
43773
43775
  this.reconnectDelay = RECONNECT_MIN_MS;
43776
+ this.pendingCommands = [];
43774
43777
  this.reconnectTimer = null;
43775
43778
  this.closed = false;
43776
43779
  this.iceServers = [{ urls: "stun:stun.l.google.com:19302" }];
@@ -43921,6 +43924,7 @@ class EngineClient {
43921
43924
  case "welcome": {
43922
43925
  if (Array.isArray(message.iceServers) && message.iceServers.length > 0) this.iceServers = message.iceServers;
43923
43926
  this.setState({ tabId: message.tabId, rejected: null });
43927
+ this.flushPendingCommands();
43924
43928
  this.applyView(message.view);
43925
43929
  this.log(`sou a aba ${message.tabId}`);
43926
43930
  if (this.options.autoEngage !== false && message.view.engagedTabId !== message.tabId) this.engage();
@@ -44039,8 +44043,29 @@ class EngineClient {
44039
44043
  this.send({ type: "release" });
44040
44044
  }
44041
44045
  command(command) {
44042
- this.send({ type: "command", command });
44043
- this.log(`comando: ${command.name}${command.phone ? ` ${command.phone}` : ""}`);
44046
+ if (this.ws && this.ws.readyState === WebSocket.OPEN && this.state.tabId) {
44047
+ this.send({ type: "command", command });
44048
+ this.log(`comando: ${command.name}${command.phone ? ` ${command.phone}` : ""}`);
44049
+ return;
44050
+ }
44051
+ this.pendingCommands.push({ command, at: Date.now() });
44052
+ if (this.pendingCommands.length > PENDING_COMMANDS_MAX) this.pendingCommands.shift();
44053
+ this.log(`comando: ${command.name} guardado até a conexão com o motor voltar`);
44054
+ this.setState({ lastError: "Sem conexão com o motor neste momento; o comando será enviado assim que ela voltar." });
44055
+ this.connect();
44056
+ }
44057
+ /** manda os comandos guardados que ainda fazem sentido (os velhos poderiam atingir outra chamada) */
44058
+ flushPendingCommands() {
44059
+ const now = Date.now();
44060
+ const pending = this.pendingCommands.splice(0);
44061
+ for (const entry of pending) {
44062
+ if (now - entry.at > PENDING_COMMANDS_TTL_MS) {
44063
+ this.log(`comando ${entry.command.name} descartado: a conexão demorou demais para voltar`);
44064
+ continue;
44065
+ }
44066
+ this.send({ type: "command", command: entry.command });
44067
+ this.log(`comando: ${entry.command.name} (enviado depois da reconexão)`);
44068
+ }
44044
44069
  }
44045
44070
  // ------------------------------------------------------------------ áudio
44046
44071
  setInputMuted(muted) {
@@ -58710,7 +58735,7 @@ function AuthenticatedMobileDialer({
58710
58735
  showClientInfoModal = false,
58711
58736
  density
58712
58737
  }) {
58713
- var _a2, _b, _c, _d, _e;
58738
+ var _a2, _b, _c, _d, _e, _f;
58714
58739
  const { skipManualQualification } = useDialerConfig();
58715
58740
  const [dialValue, setDialValue] = useState("");
58716
58741
  const [activeScreen, setActiveScreen] = useState("dialer");
@@ -58750,6 +58775,7 @@ function AuthenticatedMobileDialer({
58750
58775
  const currentCall = useCallOperatorCurrentCall();
58751
58776
  const { hangup, answer, reject } = useCallOperatorCurrentCallControls();
58752
58777
  const callInfo = useCallOperatorCurrentCallInfo();
58778
+ const activeCallNumber = ((_a2 = currentCall == null ? void 0 : currentCall.data) == null ? void 0 : _a2.destinationNumber) || (currentCall == null ? void 0 : currentCall.callerNumber) || "";
58753
58779
  const rawSuccessQualifications = useCallOperatorSuccessQualifications();
58754
58780
  const rawDiscardQualifications = useCallOperatorDiscardQualifications();
58755
58781
  const successQualifications = useMemo(() => rawSuccessQualifications ?? [], [rawSuccessQualifications]);
@@ -59324,6 +59350,8 @@ function AuthenticatedMobileDialer({
59324
59350
  phoneNumber = callInfo.info.campaignContact.phoneNumber;
59325
59351
  } else if (dialValue) {
59326
59352
  phoneNumber = dialValue;
59353
+ } else if (activeCallNumber) {
59354
+ phoneNumber = activeCallNumber;
59327
59355
  }
59328
59356
  const cleanNumber = phoneNumber.replace(/[^\d+]/g, "");
59329
59357
  let whatsappNumber = cleanNumber;
@@ -59339,7 +59367,7 @@ function AuthenticatedMobileDialer({
59339
59367
  const whatsappUrl = `https://wa.me/${whatsappNumber.replace("+", "")}?text=${encodedMessage}`;
59340
59368
  console.log("[MobileDialer] Abrindo WhatsApp com template:", whatsappUrl);
59341
59369
  window.open(whatsappUrl, "_blank");
59342
- }, [callInfo, dialValue, userData, username, contactData.finalData]);
59370
+ }, [callInfo, dialValue, userData, username, contactData.finalData, activeCallNumber]);
59343
59371
  const handleGoOnBreak = useCallback(
59344
59372
  async (breakId) => {
59345
59373
  if (!goOnBreak) return;
@@ -59407,8 +59435,9 @@ function AuthenticatedMobileDialer({
59407
59435
  if ((callInfo == null ? void 0 : callInfo.type) === "campaign" && ((_a3 = callInfo.info.campaignContact) == null ? void 0 : _a3.phoneNumber)) {
59408
59436
  return formatPhoneForDisplay(callInfo.info.campaignContact.phoneNumber);
59409
59437
  }
59410
- return formattedDial || "(--) ----- ----";
59411
- }, [callInfo, formattedDial]);
59438
+ if (formattedDial) return formattedDial;
59439
+ return activeCallNumber ? formatPhoneForDisplay(activeCallNumber) : "(--) ----- ----";
59440
+ }, [callInfo, formattedDial, activeCallNumber]);
59412
59441
  const callStatusLabel = useMemo(() => {
59413
59442
  if (isConnecting) return "Ligando...";
59414
59443
  if (isInCall) return "Em chamada";
@@ -59423,7 +59452,7 @@ function AuthenticatedMobileDialer({
59423
59452
  if (isOnBreak) return "paused";
59424
59453
  return "available";
59425
59454
  }, [isError, isOnBreak]);
59426
- const activeBreakName = (operatorState == null ? void 0 : operatorState.state) === "onBreak" ? (_a2 = operatorState.break) == null ? void 0 : _a2.name : void 0;
59455
+ const activeBreakName = (operatorState == null ? void 0 : operatorState.state) === "onBreak" ? (_b = operatorState.break) == null ? void 0 : _b.name : void 0;
59427
59456
  const statusBadgeText = useMemo(() => {
59428
59457
  if (isError) return (dialerNotice == null ? void 0 : dialerNotice.source) === "device" ? dialerNotice.title : "Erro";
59429
59458
  if (isOnBreak) return currentBreakName || activeBreakName || "Em pausa";
@@ -59566,15 +59595,15 @@ function AuthenticatedMobileDialer({
59566
59595
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "client-card-header", children: [
59567
59596
  /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-user-circle client-avatar" }),
59568
59597
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "client-main-info", children: [
59569
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "client-name", id: "clientName", children: findValue(contactData.finalData, ["DECISOR", "NOME", "CLIENTE", "PROSPECT"]) ?? ((_b = callInfo.info.campaignContact) == null ? void 0 : _b.label) ?? "--" }),
59570
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "client-company", id: "clientCompany", children: findValue(contactData.finalData, ["EMPRESA", "RAZAO SOCIAL", "RAZÃO SOCIAL", "RAZAO_SOCIAL"]) ?? ((_c = callInfo.info.campaign) == null ? void 0 : _c.name) ?? "--" })
59598
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "client-name", id: "clientName", children: findValue(contactData.finalData, ["DECISOR", "NOME", "CLIENTE", "PROSPECT"]) ?? ((_c = callInfo.info.campaignContact) == null ? void 0 : _c.label) ?? "--" }),
59599
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "client-company", id: "clientCompany", children: findValue(contactData.finalData, ["EMPRESA", "RAZAO SOCIAL", "RAZÃO SOCIAL", "RAZAO_SOCIAL"]) ?? ((_d = callInfo.info.campaign) == null ? void 0 : _d.name) ?? "--" })
59571
59600
  ] })
59572
59601
  ] }),
59573
59602
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "client-card-divider" }),
59574
59603
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "client-info-list", children: [
59575
59604
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "client-info-row", children: [
59576
59605
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "client-info-label", children: "Socio" }),
59577
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "client-info-value", children: findValue(contactData.finalData, ["SOCIO", "PROSPECT", "DECISOR", "NOME", "CLIENTE"]) ?? ((_d = callInfo.info.campaignContact) == null ? void 0 : _d.label) ?? "--" })
59606
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "client-info-value", children: findValue(contactData.finalData, ["SOCIO", "PROSPECT", "DECISOR", "NOME", "CLIENTE"]) ?? ((_e = callInfo.info.campaignContact) == null ? void 0 : _e.label) ?? "--" })
59578
59607
  ] }),
59579
59608
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "client-info-row", children: [
59580
59609
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "client-info-label", children: "Empresa" }),
@@ -59771,7 +59800,7 @@ function AuthenticatedMobileDialer({
59771
59800
  {
59772
59801
  isOpen: isClientInfoModalOpen,
59773
59802
  campaignCallInfo,
59774
- campaignContactPhoneId: ((_e = currentCall == null ? void 0 : currentCall.data) == null ? void 0 : _e.campaignContactPhoneId) ?? null,
59803
+ campaignContactPhoneId: ((_f = currentCall == null ? void 0 : currentCall.data) == null ? void 0 : _f.campaignContactPhoneId) ?? null,
59775
59804
  callDuration: callDurationSeconds,
59776
59805
  isCallActive: isInCall || isConnecting,
59777
59806
  onClose: () => setIsClientInfoModalOpen(false)
@@ -60538,6 +60567,7 @@ const BUSY_INTERVAL_FACTOR = 3;
60538
60567
  const DISPATCH_COOLDOWN_MS = 6e3;
60539
60568
  const CALL_START_TIMEOUT_MS = 4e3;
60540
60569
  const CALL_START_CHECK_MS = 100;
60570
+ const LEAVE_BREAK_TIMEOUT_MS = 8e3;
60541
60571
  const MAX_BACKOFF_MS = 6e4;
60542
60572
  const NOT_CONFIGURED_INTERVAL_MS = 5 * 6e4;
60543
60573
  const NOT_CONFIGURED_CODES = /* @__PURE__ */ new Set(["STORE_NOT_CONFIGURED", "SERVER_NOT_CONFIGURED"]);
@@ -60553,11 +60583,13 @@ function buildHeaders(sessionToken) {
60553
60583
  const delay = (ms2) => new Promise((resolve) => setTimeout(resolve, ms2));
60554
60584
  function ClickToCallAgent({ enabled = true, pollIntervalMs, sessionToken, onTask }) {
60555
60585
  const operatorState = useCallOperatorState();
60556
- const { makeManualCall } = useCallOperatorControls();
60586
+ const { makeManualCall, becomeAvailable } = useCallOperatorControls();
60557
60587
  const stateRef = useRef(operatorState == null ? void 0 : operatorState.state);
60558
60588
  stateRef.current = operatorState == null ? void 0 : operatorState.state;
60559
60589
  const makeManualCallRef = useRef(makeManualCall);
60560
60590
  makeManualCallRef.current = makeManualCall;
60591
+ const becomeAvailableRef = useRef(becomeAvailable);
60592
+ becomeAvailableRef.current = becomeAvailable;
60561
60593
  const onTaskRef = useRef(onTask);
60562
60594
  onTaskRef.current = onTask;
60563
60595
  const tickerRef = useRef(null);
@@ -60619,6 +60651,16 @@ function ClickToCallAgent({ enabled = true, pollIntervalMs, sessionToken, onTask
60619
60651
  console.error("[ClickToCall] não foi possível confirmar a tarefa", options.taskId, "; o servidor a marcará como não confirmada");
60620
60652
  return false;
60621
60653
  };
60654
+ const waitForOperatorState = (predicate, timeoutMs) => new Promise((resolve) => {
60655
+ const deadline = Date.now() + timeoutMs;
60656
+ const check = () => {
60657
+ if (disposed) return resolve(false);
60658
+ if (predicate(stateRef.current)) return resolve(true);
60659
+ if (Date.now() >= deadline) return resolve(false);
60660
+ setTimeout(check, CALL_START_CHECK_MS);
60661
+ };
60662
+ check();
60663
+ });
60622
60664
  const waitForCallStart = () => new Promise((resolve) => {
60623
60665
  const deadline = Date.now() + CALL_START_TIMEOUT_MS;
60624
60666
  const check = () => {
@@ -60652,6 +60694,23 @@ function ClickToCallAgent({ enabled = true, pollIntervalMs, sessionToken, onTask
60652
60694
  } catch (error2) {
60653
60695
  console.warn("[ClickToCall] onTask lançou erro:", error2);
60654
60696
  }
60697
+ if (presenceFromOperatorState(stateRef.current) === "on_break") {
60698
+ console.log("[ClickToCall] operador em pausa: saindo da pausa para ligar");
60699
+ try {
60700
+ becomeAvailableRef.current();
60701
+ } catch (error2) {
60702
+ const message = error2 instanceof Error ? error2.message : String(error2);
60703
+ console.error("[ClickToCall] becomeAvailable falhou:", message);
60704
+ await ack({ taskId: task.id, status: "failed", error: `Não deu para sair da pausa: ${message}`, retry: true });
60705
+ return;
60706
+ }
60707
+ const available = await waitForOperatorState((state2) => state2 === "idle", LEAVE_BREAK_TIMEOUT_MS);
60708
+ if (!available) {
60709
+ console.warn("[ClickToCall] o operador não saiu da pausa a tempo (estado", stateRef.current, "): devolvendo à fila");
60710
+ await ack({ taskId: task.id, status: "failed", error: `Operador não saiu da pausa (${stateRef.current ?? "desconhecido"})`, retry: true });
60711
+ return;
60712
+ }
60713
+ }
60655
60714
  try {
60656
60715
  makeManualCallRef.current(task.phone);
60657
60716
  } catch (error2) {
@@ -60755,7 +60814,7 @@ function ClickToCallAgent({ enabled = true, pollIntervalMs, sessionToken, onTask
60755
60814
  }, [enabled, pollIntervalMs, sessionToken]);
60756
60815
  return null;
60757
60816
  }
60758
- const WIDGET_VERSION = "2.8.1";
60817
+ const WIDGET_VERSION = "2.8.3";
60759
60818
  let currentSipWebSocket = null;
60760
60819
  function sendSipUnregisterIfPossible(ws) {
60761
60820
  if (ws.readyState !== WebSocket.OPEN) return;