callix-dialer-widget 2.7.0 → 2.8.1

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
@@ -119,18 +119,33 @@ CallixDialer.init({
119
119
  apiUrl: 'https://callix-zeta.vercel.app/api/user-sdk-session', // continua: é onde o login é feito
120
120
  uiVariant: 'mobile',
121
121
  engineUrl: 'https://motor.exemplo.com', // host do motor (HTTPS; o navegador exige origem segura para o microfone)
122
- engineToken: '...', // ENGINE_TAB_TOKEN do host
123
122
  });
124
123
  ```
125
124
 
126
- Também pelo HTML: `data-callix-engine-url` e `data-callix-engine-token` no container.
125
+ Também pelo HTML: `data-callix-engine-url` no container. Só isso: desde a 2.8.0 o widget não
126
+ precisa do token do host (`engineToken`). A credencial da aba é a própria sessão do Callix que
127
+ o widget cria em `apiUrl` e guarda no navegador: uma aba nova ou um F5 no mesmo navegador
128
+ apresenta esse token e entra na sessão que o motor roda; outro navegador ou computador não o
129
+ tem, vê "Entrar" com o aviso de que o discador está em uso em outro lugar, e ao entrar faz um
130
+ login novo que substitui a sessão de lá (como o Callix web faz). O host confirma no Callix
131
+ toda sessão entregue antes de subir o motor com ela. `engineToken` continua aceito, mas só
132
+ serve para uso administrativo (página `tab/` do motor, roteiros); não o coloque em página
133
+ pública.
127
134
 
128
135
  Fluxo de login no modo motor: o widget conecta ao host com o usuário; se o motor já segura
129
- a sessão desse agente, entra direto. Senão, faz o login em `apiUrl` e entrega a sessão ao
130
- motor, que sobe o SDK no servidor. O widget nunca cria uma sessão por cima de uma viva: o
131
- login exclusivo do Callix derrubaria a que o motor está usando. Se o Callix derrubar a
132
- sessão (login no Callix web, supervisor, bloqueio), o widget volta à tela de login com o
133
- motivo e não reentra sozinho.
136
+ a sessão desse agente e a aba tem a credencial dela, entra direto. Senão, faz o login em
137
+ `apiUrl` e entrega a sessão ao motor, que sobe o SDK no servidor. No mesmo navegador o
138
+ widget nunca cria uma sessão por cima de uma viva (o login exclusivo do Callix derrubaria a
139
+ que o motor está usando); de outro navegador ele só cria depois que o agente clica em
140
+ Entrar, avisado de que encerra a sessão de lá. Se o Callix derrubar a sessão (login no
141
+ Callix web, supervisor, bloqueio), o widget volta à tela de login com o motivo e não reentra
142
+ sozinho.
143
+
144
+ Pausas no modo motor (2.8.1): a lista de pausas é a do serviço no Callix, trazida pela vista
145
+ do motor; a pausa escolhida vai no comando `pause` e o discador mostra o nome dela. Com um
146
+ motor anterior ao 2.8.1 a lista cai numa pausa única ("Pausa") e o motor usa a configurada
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.
134
149
 
135
150
  Sem `engineUrl` nada muda: o SDK continua rodando no navegador como sempre.
136
151
 
@@ -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,17 @@ 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
+ ...this.options.version ? { version: this.options.version } : {},
43842
+ ...sessionToken ? { sessionToken } : {},
43843
+ label: this.options.label ?? "Discador",
43844
+ ...this.session ? { session: this.session } : {}
43845
+ };
43828
43846
  this.send(hello);
43829
43847
  this.log("conectado ao host do motor");
43830
43848
  };
@@ -43871,18 +43889,30 @@ class EngineClient {
43871
43889
  for (const waiter of this.viewWaiters.splice(0)) clearTimeout(waiter.timer);
43872
43890
  this.setState({ wsState: "closed", tabId: null, engaged: false });
43873
43891
  }
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);
43892
+ /**
43893
+ * A view atual, ou a próxima que o host mandar (welcome/view). Com `accept`, espera uma
43894
+ * view que passe no filtro (ex.: motor subindo depois de entregar a sessão). Rejeita com
43895
+ * `EngineAccessError` se o host recusou esta aba (`rejected`).
43896
+ */
43897
+ waitForView(timeoutMs = 15e3, accept, options) {
43898
+ const rejected = this.state.rejected;
43899
+ if (rejected) return Promise.reject(new EngineAccessError(rejected.code, rejected.message));
43900
+ 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
43901
  return new Promise((resolve, reject) => {
43878
43902
  const timer = setTimeout(() => {
43879
43903
  const index = this.viewWaiters.findIndex((w) => w.timer === timer);
43880
43904
  if (index >= 0) this.viewWaiters.splice(index, 1);
43881
43905
  reject(new Error("o motor não respondeu"));
43882
43906
  }, timeoutMs);
43883
- this.viewWaiters.push({ resolve, timer });
43907
+ this.viewWaiters.push({ resolve, reject, accept, timer });
43884
43908
  });
43885
43909
  }
43910
+ rejectWaiters(error2) {
43911
+ for (const waiter of this.viewWaiters.splice(0)) {
43912
+ clearTimeout(waiter.timer);
43913
+ waiter.reject(error2);
43914
+ }
43915
+ }
43886
43916
  send(message) {
43887
43917
  if (this.ws && this.ws.readyState === WebSocket.OPEN) this.ws.send(JSON.stringify(message));
43888
43918
  }
@@ -43890,7 +43920,7 @@ class EngineClient {
43890
43920
  switch (message.type) {
43891
43921
  case "welcome": {
43892
43922
  if (Array.isArray(message.iceServers) && message.iceServers.length > 0) this.iceServers = message.iceServers;
43893
- this.setState({ tabId: message.tabId });
43923
+ this.setState({ tabId: message.tabId, rejected: null });
43894
43924
  this.applyView(message.view);
43895
43925
  this.log(`sou a aba ${message.tabId}`);
43896
43926
  if (this.options.autoEngage !== false && message.view.engagedTabId !== message.tabId) this.engage();
@@ -43908,6 +43938,19 @@ class EngineClient {
43908
43938
  void this.onSignal(message.payload);
43909
43939
  break;
43910
43940
  case "error":
43941
+ if (message.code === "unauthorized") {
43942
+ this.setState({ lastError: message.message, rejected: { code: message.code, message: message.message } });
43943
+ this.session = null;
43944
+ this.reconnectDelay = RECONNECT_MAX_MS;
43945
+ this.log(`acesso recusado pelo host: ${message.message}`);
43946
+ this.rejectWaiters(new EngineAccessError(message.code, message.message));
43947
+ break;
43948
+ }
43949
+ if (message.code === "session-changed") {
43950
+ this.session = null;
43951
+ this.log("a sessão deste agente foi trocada por um login novo; reconectando com a credencial atual");
43952
+ break;
43953
+ }
43911
43954
  this.setState({ lastError: message.message });
43912
43955
  this.log(`erro do host: ${message.message}`);
43913
43956
  break;
@@ -43945,6 +43988,10 @@ class EngineClient {
43945
43988
  if (call && typeof call.muted === "boolean" && call.muted !== this.state.inputMuted && call.state !== "ended") patch.inputMuted = call.muted;
43946
43989
  this.setState(patch);
43947
43990
  for (const waiter of this.viewWaiters.splice(0)) {
43991
+ if (waiter.accept && !waiter.accept(view)) {
43992
+ this.viewWaiters.push(waiter);
43993
+ continue;
43994
+ }
43948
43995
  clearTimeout(waiter.timer);
43949
43996
  waiter.resolve(view);
43950
43997
  }
@@ -43957,10 +44004,33 @@ class EngineClient {
43957
44004
  };
43958
44005
  }
43959
44006
  // ------------------------------------------------------------------ sessão e comandos
43960
- /** modo sdk: o widget fez login no back-end e entrega a sessão ao host */
44007
+ /**
44008
+ * Modo sdk: o widget fez login no back-end e entrega a sessão ao host. Com a conexão
44009
+ * aberta manda `session`; recusada ou caída, reconecta agora apresentando a sessão no hello
44010
+ * (é assim que um login novo substitui a sessão de outro navegador).
44011
+ */
43961
44012
  setSession(session) {
43962
44013
  this.session = session;
43963
- this.send({ type: "session", session });
44014
+ this.setState({ rejected: null, sessionLost: null });
44015
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
44016
+ if (this.state.tabId) {
44017
+ this.send({ type: "session", session });
44018
+ return;
44019
+ }
44020
+ const stale = this.ws;
44021
+ this.ws = null;
44022
+ stale.onopen = null;
44023
+ stale.onmessage = null;
44024
+ stale.onclose = null;
44025
+ stale.close();
44026
+ this.setState({ wsState: "closed" });
44027
+ }
44028
+ if (this.reconnectTimer) {
44029
+ clearTimeout(this.reconnectTimer);
44030
+ this.reconnectTimer = null;
44031
+ }
44032
+ this.reconnectDelay = RECONNECT_MIN_MS;
44033
+ this.connect();
43964
44034
  }
43965
44035
  engage() {
43966
44036
  this.send({ type: "engage" });
@@ -44159,6 +44229,18 @@ function useEngineState(client2) {
44159
44229
  return useSyncExternalStore(client2.subscribe.bind(client2), client2.getState.bind(client2), client2.getState.bind(client2));
44160
44230
  }
44161
44231
  const ENGINE_BREAKS = [{ id: 0, name: "Pausa" }];
44232
+ function breaksFromView(view) {
44233
+ var _a2;
44234
+ const breaks = (_a2 = view == null ? void 0 : view.snapshot) == null ? void 0 : _a2.leg.breaks;
44235
+ return breaks && breaks.length > 0 ? breaks.map((b) => ({ id: b.id, name: b.name })) : ENGINE_BREAKS;
44236
+ }
44237
+ function currentBreakFromView(view) {
44238
+ var _a2;
44239
+ return ((_a2 = view.snapshot) == null ? void 0 : _a2.leg.break) ?? breaksFromView(view)[0];
44240
+ }
44241
+ function pauseCommand(breakId) {
44242
+ return typeof breakId === "number" ? { name: "pause", breakId } : { name: "pause" };
44243
+ }
44162
44244
  function toCallQualification(q) {
44163
44245
  return { id: q.id, name: q.name, isSystemQualification: false, systemQualificationType: 0 };
44164
44246
  }
@@ -44291,7 +44373,7 @@ function operatorStateFromView(view, call) {
44291
44373
  case "afterCall":
44292
44374
  return { state: "afterCall" };
44293
44375
  case "onBreak":
44294
- return { state: "onBreak", break: ENGINE_BREAKS[0] };
44376
+ return { state: "onBreak", break: currentBreakFromView(view) };
44295
44377
  case "offline":
44296
44378
  return { state: "offline" };
44297
44379
  case "error":
@@ -44374,7 +44456,7 @@ function useEngineControls(client2) {
44374
44456
  if (status === "onBreak") client2.command({ name: "resume" });
44375
44457
  },
44376
44458
  goOffline: () => void 0,
44377
- enterOnBreak: () => client2.command({ name: "pause" }),
44459
+ enterOnBreak: (breakId) => client2.command(pauseCommand(breakId)),
44378
44460
  makeManualCall: (phoneNumber) => client2.command({ name: "dial", phone: phoneNumber }),
44379
44461
  finishAfterCall: (data) => {
44380
44462
  if (typeof data.qualificationId === "number") client2.command({ name: "qualify", qualificationId: data.qualificationId, result: data.result === "failure" ? "discard" : "success" });
@@ -44428,7 +44510,8 @@ const operators = /* @__PURE__ */ new WeakMap();
44428
44510
  function engineOperator(client2) {
44429
44511
  let operator = operators.get(client2);
44430
44512
  if (!operator) {
44431
- const breaks = new EngineSignal(ENGINE_BREAKS);
44513
+ const breaks = new EngineSignal(breaksFromView(client2.getState().view));
44514
+ client2.subscribe(() => breaks.set(breaksFromView(client2.getState().view)));
44432
44515
  operator = {
44433
44516
  breaks,
44434
44517
  user: client2.userSignal,
@@ -44443,7 +44526,7 @@ function engineOperator(client2) {
44443
44526
  var _a2, _b;
44444
44527
  if (((_b = (_a2 = client2.getState().view) == null ? void 0 : _a2.snapshot) == null ? void 0 : _b.leg.status) === "onBreak") client2.command({ name: "resume" });
44445
44528
  },
44446
- enterOnBreak: () => client2.command({ name: "pause" }),
44529
+ enterOnBreak: (breakId) => client2.command(pauseCommand(breakId)),
44447
44530
  makeManualCall: (phoneNumber) => client2.command({ name: "dial", phone: phoneNumber }),
44448
44531
  finishAfterCall: (data) => {
44449
44532
  if (typeof data.qualificationId === "number") client2.command({ name: "qualify", qualificationId: data.qualificationId, result: data.result === "failure" ? "discard" : "success" });
@@ -58627,7 +58710,7 @@ function AuthenticatedMobileDialer({
58627
58710
  showClientInfoModal = false,
58628
58711
  density
58629
58712
  }) {
58630
- var _a2, _b, _c, _d;
58713
+ var _a2, _b, _c, _d, _e;
58631
58714
  const { skipManualQualification } = useDialerConfig();
58632
58715
  const [dialValue, setDialValue] = useState("");
58633
58716
  const [activeScreen, setActiveScreen] = useState("dialer");
@@ -59340,11 +59423,12 @@ function AuthenticatedMobileDialer({
59340
59423
  if (isOnBreak) return "paused";
59341
59424
  return "available";
59342
59425
  }, [isError, isOnBreak]);
59426
+ const activeBreakName = (operatorState == null ? void 0 : operatorState.state) === "onBreak" ? (_a2 = operatorState.break) == null ? void 0 : _a2.name : void 0;
59343
59427
  const statusBadgeText = useMemo(() => {
59344
59428
  if (isError) return (dialerNotice == null ? void 0 : dialerNotice.source) === "device" ? dialerNotice.title : "Erro";
59345
- if (isOnBreak) return currentBreakName || "Em pausa";
59429
+ if (isOnBreak) return currentBreakName || activeBreakName || "Em pausa";
59346
59430
  return "Disponível";
59347
- }, [dialerNotice, isError, isOnBreak, currentBreakName]);
59431
+ }, [dialerNotice, isError, isOnBreak, currentBreakName, activeBreakName]);
59348
59432
  const pauseDurationLabel = useMemo(() => {
59349
59433
  const hours = Math.floor(pauseDurationSeconds / 3600);
59350
59434
  const minutes = Math.floor(pauseDurationSeconds % 3600 / 60).toString().padStart(2, "0");
@@ -59482,15 +59566,15 @@ function AuthenticatedMobileDialer({
59482
59566
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "client-card-header", children: [
59483
59567
  /* @__PURE__ */ jsxRuntimeExports.jsx("i", { className: "fas fa-user-circle client-avatar" }),
59484
59568
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "client-main-info", children: [
59485
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "client-name", id: "clientName", children: findValue(contactData.finalData, ["DECISOR", "NOME", "CLIENTE", "PROSPECT"]) ?? ((_a2 = callInfo.info.campaignContact) == null ? void 0 : _a2.label) ?? "--" }),
59486
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "client-company", id: "clientCompany", children: findValue(contactData.finalData, ["EMPRESA", "RAZAO SOCIAL", "RAZÃO SOCIAL", "RAZAO_SOCIAL"]) ?? ((_b = callInfo.info.campaign) == null ? void 0 : _b.name) ?? "--" })
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) ?? "--" })
59487
59571
  ] })
59488
59572
  ] }),
59489
59573
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "client-card-divider" }),
59490
59574
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "client-info-list", children: [
59491
59575
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "client-info-row", children: [
59492
59576
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "client-info-label", children: "Socio" }),
59493
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "client-info-value", children: findValue(contactData.finalData, ["SOCIO", "PROSPECT", "DECISOR", "NOME", "CLIENTE"]) ?? ((_c = callInfo.info.campaignContact) == null ? void 0 : _c.label) ?? "--" })
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) ?? "--" })
59494
59578
  ] }),
59495
59579
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "client-info-row", children: [
59496
59580
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "client-info-label", children: "Empresa" }),
@@ -59687,7 +59771,7 @@ function AuthenticatedMobileDialer({
59687
59771
  {
59688
59772
  isOpen: isClientInfoModalOpen,
59689
59773
  campaignCallInfo,
59690
- campaignContactPhoneId: ((_d = currentCall == null ? void 0 : currentCall.data) == null ? void 0 : _d.campaignContactPhoneId) ?? null,
59774
+ campaignContactPhoneId: ((_e = currentCall == null ? void 0 : currentCall.data) == null ? void 0 : _e.campaignContactPhoneId) ?? null,
59691
59775
  callDuration: callDurationSeconds,
59692
59776
  isCallActive: isInCall || isConnecting,
59693
59777
  onClose: () => setIsClientInfoModalOpen(false)
@@ -60671,7 +60755,7 @@ function ClickToCallAgent({ enabled = true, pollIntervalMs, sessionToken, onTask
60671
60755
  }, [enabled, pollIntervalMs, sessionToken]);
60672
60756
  return null;
60673
60757
  }
60674
- const WIDGET_VERSION = "2.7.0";
60758
+ const WIDGET_VERSION = "2.8.1";
60675
60759
  let currentSipWebSocket = null;
60676
60760
  function sendSipUnregisterIfPossible(ws) {
60677
60761
  if (ws.readyState !== WebSocket.OPEN) return;
@@ -61070,7 +61154,11 @@ function DialerWrapper({ options, containerEl }) {
61070
61154
  current == null ? void 0 : current.close();
61071
61155
  const client2 = new EngineClient({
61072
61156
  url: options.engineUrl ?? "",
61073
- token: options.engineToken ?? "",
61157
+ token: options.engineToken || void 0,
61158
+ version: WIDGET_VERSION,
61159
+ // credencial da aba: o token da sessão do Callix guardado neste navegador (lido a cada
61160
+ // hello, para a reconexão depois de uma troca de sessão pegar o token novo)
61161
+ sessionToken: () => readStoredToken(user),
61074
61162
  agentId: user,
61075
61163
  label: "Discador",
61076
61164
  log: (message) => console.log("[Callix Dialer/motor]", message)
@@ -61227,8 +61315,26 @@ function DialerWrapper({ options, containerEl }) {
61227
61315
  setEngineWaiting(!client2.getState().view);
61228
61316
  const fallback = setTimeout(() => setEngineWaiting(false), 15e3);
61229
61317
  let joining = false;
61318
+ let rejectedNote = false;
61230
61319
  const sync = () => {
61231
- const { view, sessionLost } = client2.getState();
61320
+ const { view, sessionLost, rejected } = client2.getState();
61321
+ if (!rejected && rejectedNote) {
61322
+ rejectedNote = false;
61323
+ setTabNote(null);
61324
+ }
61325
+ if (rejected) {
61326
+ rejectedNote = true;
61327
+ setEngineWaiting(false);
61328
+ if (isLoadingRef.current) return;
61329
+ if (isAuthenticatedRef.current) {
61330
+ console.log("[Callix Dialer] o motor recusou esta aba: a sessão do agente foi trocada por um login em outro lugar");
61331
+ setIsAuthenticated(false);
61332
+ setSessionToken(null);
61333
+ setCallixDomain(null);
61334
+ }
61335
+ setTabNote(rejected.message);
61336
+ return;
61337
+ }
61232
61338
  if (!view) return;
61233
61339
  setEngineWaiting(false);
61234
61340
  if (view.engine === "stopped" || view.engine === "crashed") logoffPendingRef.current = false;
@@ -61412,9 +61518,15 @@ function DialerWrapper({ options, containerEl }) {
61412
61518
  setLoginStatus("Conectando ao motor...");
61413
61519
  logoffPendingRef.current = false;
61414
61520
  const client2 = ensureEngine(user);
61415
- const view = await client2.waitForView(15e3);
61521
+ let view = null;
61522
+ try {
61523
+ view = await client2.waitForView(15e3);
61524
+ } catch (err) {
61525
+ if (!(err instanceof EngineAccessError)) throw err;
61526
+ console.log("[Callix Dialer] o motor recusou a credencial desta aba; fazendo login novo que substitui a sessão de lá");
61527
+ }
61416
61528
  const domain = engineDomain();
61417
- const engineHasSession = (view.engine === "running" || view.engine === "starting") && !view.sessionLost;
61529
+ const engineHasSession = view !== null && (view.engine === "running" || view.engine === "starting") && !view.sessionLost;
61418
61530
  let token = null;
61419
61531
  if (engineHasSession) {
61420
61532
  token = readStoredToken(user);
@@ -61433,8 +61545,18 @@ function DialerWrapper({ options, containerEl }) {
61433
61545
  }
61434
61546
  const data = await response.json();
61435
61547
  token = data.userSessionToken;
61548
+ try {
61549
+ const STORAGE_KEYS = getStorageKeys(user);
61550
+ localStorage.setItem(LAST_USERNAME_KEY, user);
61551
+ localStorage.setItem(STORAGE_KEYS.SESSION_TOKEN, token);
61552
+ localStorage.setItem(STORAGE_KEYS.DOMAIN, domain);
61553
+ localStorage.setItem(STORAGE_KEYS.TIMESTAMP, Date.now().toString());
61554
+ } catch {
61555
+ }
61436
61556
  client2.setSession({ domain, userSessionToken: token });
61437
61557
  setLoginStatus("Subindo o motor...");
61558
+ const up = await client2.waitForView(2e4, (v) => v.engine === "running" || v.engine === "starting" || Boolean(v.sessionLost), { fresh: true });
61559
+ if (up.sessionLost) throw new Error(up.sessionLost.message);
61438
61560
  }
61439
61561
  await enterEngine(user, domain, token);
61440
61562
  console.log("[Callix Dialer] Conectado ao motor como", user);