callix-dialer-widget 2.7.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/WIDGET-README.md
CHANGED
|
@@ -119,18 +119,27 @@ 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`
|
|
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
|
|
130
|
-
motor, que sobe o SDK no servidor.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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.
|
|
134
143
|
|
|
135
144
|
Sem `engineUrl` nada muda: o SDK continua rodando no navegador como sempre.
|
|
136
145
|
|
|
@@ -43725,6 +43725,13 @@ function requireClientSdkReact() {
|
|
|
43725
43725
|
return clientSdkReact;
|
|
43726
43726
|
}
|
|
43727
43727
|
var clientSdkReactExports = requireClientSdkReact();
|
|
43728
|
+
class EngineAccessError extends Error {
|
|
43729
|
+
constructor(code, message) {
|
|
43730
|
+
super(message);
|
|
43731
|
+
this.code = code;
|
|
43732
|
+
this.name = "EngineAccessError";
|
|
43733
|
+
}
|
|
43734
|
+
}
|
|
43728
43735
|
class EngineSignal {
|
|
43729
43736
|
constructor(value) {
|
|
43730
43737
|
this.value = value;
|
|
@@ -43791,7 +43798,8 @@ class EngineClient {
|
|
|
43791
43798
|
outputMuted: false,
|
|
43792
43799
|
outputVolume: 100,
|
|
43793
43800
|
transcript: INITIAL_TRANSCRIPT,
|
|
43794
|
-
sessionLost: null
|
|
43801
|
+
sessionLost: null,
|
|
43802
|
+
rejected: null
|
|
43795
43803
|
};
|
|
43796
43804
|
}
|
|
43797
43805
|
// ------------------------------------------------------------------ store
|
|
@@ -43824,7 +43832,16 @@ class EngineClient {
|
|
|
43824
43832
|
if (this.ws !== ws) return;
|
|
43825
43833
|
this.reconnectDelay = RECONNECT_MIN_MS;
|
|
43826
43834
|
this.setState({ wsState: "open" });
|
|
43827
|
-
const
|
|
43835
|
+
const sessionToken = !this.session && this.options.sessionToken ? this.options.sessionToken() : null;
|
|
43836
|
+
const hello = {
|
|
43837
|
+
type: "hello",
|
|
43838
|
+
role: "tab",
|
|
43839
|
+
agentId: this.agentId,
|
|
43840
|
+
...this.options.token ? { token: this.options.token } : {},
|
|
43841
|
+
...sessionToken ? { sessionToken } : {},
|
|
43842
|
+
label: this.options.label ?? "Discador",
|
|
43843
|
+
...this.session ? { session: this.session } : {}
|
|
43844
|
+
};
|
|
43828
43845
|
this.send(hello);
|
|
43829
43846
|
this.log("conectado ao host do motor");
|
|
43830
43847
|
};
|
|
@@ -43871,18 +43888,30 @@ class EngineClient {
|
|
|
43871
43888
|
for (const waiter of this.viewWaiters.splice(0)) clearTimeout(waiter.timer);
|
|
43872
43889
|
this.setState({ wsState: "closed", tabId: null, engaged: false });
|
|
43873
43890
|
}
|
|
43874
|
-
/**
|
|
43875
|
-
|
|
43876
|
-
|
|
43891
|
+
/**
|
|
43892
|
+
* A view atual, ou a próxima que o host mandar (welcome/view). Com `accept`, espera uma
|
|
43893
|
+
* view que passe no filtro (ex.: motor subindo depois de entregar a sessão). Rejeita com
|
|
43894
|
+
* `EngineAccessError` se o host recusou esta aba (`rejected`).
|
|
43895
|
+
*/
|
|
43896
|
+
waitForView(timeoutMs = 15e3, accept, options) {
|
|
43897
|
+
const rejected = this.state.rejected;
|
|
43898
|
+
if (rejected) return Promise.reject(new EngineAccessError(rejected.code, rejected.message));
|
|
43899
|
+
if (!(options == null ? void 0 : options.fresh) && this.state.view && this.state.tabId && (!accept || accept(this.state.view))) return Promise.resolve(this.state.view);
|
|
43877
43900
|
return new Promise((resolve, reject) => {
|
|
43878
43901
|
const timer = setTimeout(() => {
|
|
43879
43902
|
const index = this.viewWaiters.findIndex((w) => w.timer === timer);
|
|
43880
43903
|
if (index >= 0) this.viewWaiters.splice(index, 1);
|
|
43881
43904
|
reject(new Error("o motor não respondeu"));
|
|
43882
43905
|
}, timeoutMs);
|
|
43883
|
-
this.viewWaiters.push({ resolve, timer });
|
|
43906
|
+
this.viewWaiters.push({ resolve, reject, accept, timer });
|
|
43884
43907
|
});
|
|
43885
43908
|
}
|
|
43909
|
+
rejectWaiters(error2) {
|
|
43910
|
+
for (const waiter of this.viewWaiters.splice(0)) {
|
|
43911
|
+
clearTimeout(waiter.timer);
|
|
43912
|
+
waiter.reject(error2);
|
|
43913
|
+
}
|
|
43914
|
+
}
|
|
43886
43915
|
send(message) {
|
|
43887
43916
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) this.ws.send(JSON.stringify(message));
|
|
43888
43917
|
}
|
|
@@ -43890,7 +43919,7 @@ class EngineClient {
|
|
|
43890
43919
|
switch (message.type) {
|
|
43891
43920
|
case "welcome": {
|
|
43892
43921
|
if (Array.isArray(message.iceServers) && message.iceServers.length > 0) this.iceServers = message.iceServers;
|
|
43893
|
-
this.setState({ tabId: message.tabId });
|
|
43922
|
+
this.setState({ tabId: message.tabId, rejected: null });
|
|
43894
43923
|
this.applyView(message.view);
|
|
43895
43924
|
this.log(`sou a aba ${message.tabId}`);
|
|
43896
43925
|
if (this.options.autoEngage !== false && message.view.engagedTabId !== message.tabId) this.engage();
|
|
@@ -43908,6 +43937,19 @@ class EngineClient {
|
|
|
43908
43937
|
void this.onSignal(message.payload);
|
|
43909
43938
|
break;
|
|
43910
43939
|
case "error":
|
|
43940
|
+
if (message.code === "unauthorized") {
|
|
43941
|
+
this.setState({ lastError: message.message, rejected: { code: message.code, message: message.message } });
|
|
43942
|
+
this.session = null;
|
|
43943
|
+
this.reconnectDelay = RECONNECT_MAX_MS;
|
|
43944
|
+
this.log(`acesso recusado pelo host: ${message.message}`);
|
|
43945
|
+
this.rejectWaiters(new EngineAccessError(message.code, message.message));
|
|
43946
|
+
break;
|
|
43947
|
+
}
|
|
43948
|
+
if (message.code === "session-changed") {
|
|
43949
|
+
this.session = null;
|
|
43950
|
+
this.log("a sessão deste agente foi trocada por um login novo; reconectando com a credencial atual");
|
|
43951
|
+
break;
|
|
43952
|
+
}
|
|
43911
43953
|
this.setState({ lastError: message.message });
|
|
43912
43954
|
this.log(`erro do host: ${message.message}`);
|
|
43913
43955
|
break;
|
|
@@ -43945,6 +43987,10 @@ class EngineClient {
|
|
|
43945
43987
|
if (call && typeof call.muted === "boolean" && call.muted !== this.state.inputMuted && call.state !== "ended") patch.inputMuted = call.muted;
|
|
43946
43988
|
this.setState(patch);
|
|
43947
43989
|
for (const waiter of this.viewWaiters.splice(0)) {
|
|
43990
|
+
if (waiter.accept && !waiter.accept(view)) {
|
|
43991
|
+
this.viewWaiters.push(waiter);
|
|
43992
|
+
continue;
|
|
43993
|
+
}
|
|
43948
43994
|
clearTimeout(waiter.timer);
|
|
43949
43995
|
waiter.resolve(view);
|
|
43950
43996
|
}
|
|
@@ -43957,10 +44003,24 @@ class EngineClient {
|
|
|
43957
44003
|
};
|
|
43958
44004
|
}
|
|
43959
44005
|
// ------------------------------------------------------------------ sessão e comandos
|
|
43960
|
-
/**
|
|
44006
|
+
/**
|
|
44007
|
+
* Modo sdk: o widget fez login no back-end e entrega a sessão ao host. Com a conexão
|
|
44008
|
+
* aberta manda `session`; recusada ou caída, reconecta agora apresentando a sessão no hello
|
|
44009
|
+
* (é assim que um login novo substitui a sessão de outro navegador).
|
|
44010
|
+
*/
|
|
43961
44011
|
setSession(session) {
|
|
43962
44012
|
this.session = session;
|
|
43963
|
-
this.
|
|
44013
|
+
this.setState({ rejected: null, sessionLost: null });
|
|
44014
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
44015
|
+
this.send({ type: "session", session });
|
|
44016
|
+
return;
|
|
44017
|
+
}
|
|
44018
|
+
if (this.reconnectTimer) {
|
|
44019
|
+
clearTimeout(this.reconnectTimer);
|
|
44020
|
+
this.reconnectTimer = null;
|
|
44021
|
+
}
|
|
44022
|
+
this.reconnectDelay = RECONNECT_MIN_MS;
|
|
44023
|
+
this.connect();
|
|
43964
44024
|
}
|
|
43965
44025
|
engage() {
|
|
43966
44026
|
this.send({ type: "engage" });
|
|
@@ -60671,7 +60731,7 @@ function ClickToCallAgent({ enabled = true, pollIntervalMs, sessionToken, onTask
|
|
|
60671
60731
|
}, [enabled, pollIntervalMs, sessionToken]);
|
|
60672
60732
|
return null;
|
|
60673
60733
|
}
|
|
60674
|
-
const WIDGET_VERSION = "2.
|
|
60734
|
+
const WIDGET_VERSION = "2.8.0";
|
|
60675
60735
|
let currentSipWebSocket = null;
|
|
60676
60736
|
function sendSipUnregisterIfPossible(ws) {
|
|
60677
60737
|
if (ws.readyState !== WebSocket.OPEN) return;
|
|
@@ -61070,7 +61130,10 @@ function DialerWrapper({ options, containerEl }) {
|
|
|
61070
61130
|
current == null ? void 0 : current.close();
|
|
61071
61131
|
const client2 = new EngineClient({
|
|
61072
61132
|
url: options.engineUrl ?? "",
|
|
61073
|
-
token: options.engineToken
|
|
61133
|
+
token: options.engineToken || void 0,
|
|
61134
|
+
// credencial da aba: o token da sessão do Callix guardado neste navegador (lido a cada
|
|
61135
|
+
// hello, para a reconexão depois de uma troca de sessão pegar o token novo)
|
|
61136
|
+
sessionToken: () => readStoredToken(user),
|
|
61074
61137
|
agentId: user,
|
|
61075
61138
|
label: "Discador",
|
|
61076
61139
|
log: (message) => console.log("[Callix Dialer/motor]", message)
|
|
@@ -61227,8 +61290,26 @@ function DialerWrapper({ options, containerEl }) {
|
|
|
61227
61290
|
setEngineWaiting(!client2.getState().view);
|
|
61228
61291
|
const fallback = setTimeout(() => setEngineWaiting(false), 15e3);
|
|
61229
61292
|
let joining = false;
|
|
61293
|
+
let rejectedNote = false;
|
|
61230
61294
|
const sync = () => {
|
|
61231
|
-
const { view, sessionLost } = client2.getState();
|
|
61295
|
+
const { view, sessionLost, rejected } = client2.getState();
|
|
61296
|
+
if (!rejected && rejectedNote) {
|
|
61297
|
+
rejectedNote = false;
|
|
61298
|
+
setTabNote(null);
|
|
61299
|
+
}
|
|
61300
|
+
if (rejected) {
|
|
61301
|
+
rejectedNote = true;
|
|
61302
|
+
setEngineWaiting(false);
|
|
61303
|
+
if (isLoadingRef.current) return;
|
|
61304
|
+
if (isAuthenticatedRef.current) {
|
|
61305
|
+
console.log("[Callix Dialer] o motor recusou esta aba: a sessão do agente foi trocada por um login em outro lugar");
|
|
61306
|
+
setIsAuthenticated(false);
|
|
61307
|
+
setSessionToken(null);
|
|
61308
|
+
setCallixDomain(null);
|
|
61309
|
+
}
|
|
61310
|
+
setTabNote(rejected.message);
|
|
61311
|
+
return;
|
|
61312
|
+
}
|
|
61232
61313
|
if (!view) return;
|
|
61233
61314
|
setEngineWaiting(false);
|
|
61234
61315
|
if (view.engine === "stopped" || view.engine === "crashed") logoffPendingRef.current = false;
|
|
@@ -61412,9 +61493,15 @@ function DialerWrapper({ options, containerEl }) {
|
|
|
61412
61493
|
setLoginStatus("Conectando ao motor...");
|
|
61413
61494
|
logoffPendingRef.current = false;
|
|
61414
61495
|
const client2 = ensureEngine(user);
|
|
61415
|
-
|
|
61496
|
+
let view = null;
|
|
61497
|
+
try {
|
|
61498
|
+
view = await client2.waitForView(15e3);
|
|
61499
|
+
} catch (err) {
|
|
61500
|
+
if (!(err instanceof EngineAccessError)) throw err;
|
|
61501
|
+
console.log("[Callix Dialer] o motor recusou a credencial desta aba; fazendo login novo que substitui a sessão de lá");
|
|
61502
|
+
}
|
|
61416
61503
|
const domain = engineDomain();
|
|
61417
|
-
const engineHasSession = (view.engine === "running" || view.engine === "starting") && !view.sessionLost;
|
|
61504
|
+
const engineHasSession = view !== null && (view.engine === "running" || view.engine === "starting") && !view.sessionLost;
|
|
61418
61505
|
let token = null;
|
|
61419
61506
|
if (engineHasSession) {
|
|
61420
61507
|
token = readStoredToken(user);
|
|
@@ -61433,8 +61520,18 @@ function DialerWrapper({ options, containerEl }) {
|
|
|
61433
61520
|
}
|
|
61434
61521
|
const data = await response.json();
|
|
61435
61522
|
token = data.userSessionToken;
|
|
61523
|
+
try {
|
|
61524
|
+
const STORAGE_KEYS = getStorageKeys(user);
|
|
61525
|
+
localStorage.setItem(LAST_USERNAME_KEY, user);
|
|
61526
|
+
localStorage.setItem(STORAGE_KEYS.SESSION_TOKEN, token);
|
|
61527
|
+
localStorage.setItem(STORAGE_KEYS.DOMAIN, domain);
|
|
61528
|
+
localStorage.setItem(STORAGE_KEYS.TIMESTAMP, Date.now().toString());
|
|
61529
|
+
} catch {
|
|
61530
|
+
}
|
|
61436
61531
|
client2.setSession({ domain, userSessionToken: token });
|
|
61437
61532
|
setLoginStatus("Subindo o motor...");
|
|
61533
|
+
const up = await client2.waitForView(2e4, (v) => v.engine === "running" || v.engine === "starting" || Boolean(v.sessionLost), { fresh: true });
|
|
61534
|
+
if (up.sessionLost) throw new Error(up.sessionLost.message);
|
|
61438
61535
|
}
|
|
61439
61536
|
await enterEngine(user, domain, token);
|
|
61440
61537
|
console.log("[Callix Dialer] Conectado ao motor como", user);
|