mitra-interactions-sdk 1.0.69 → 1.0.70
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/README.md +791 -779
- package/dist/index.d.mts +16 -2
- package/dist/index.d.ts +16 -2
- package/dist/index.js +129 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +129 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +52 -52
package/dist/index.d.mts
CHANGED
|
@@ -1042,6 +1042,13 @@ interface MitraConfig {
|
|
|
1042
1042
|
projectId?: number;
|
|
1043
1043
|
/** Callback chamado quando o token é renovado automaticamente (após 401/403). Recebe a nova sessão. */
|
|
1044
1044
|
onTokenRefresh?: (session: LoginResponse) => void;
|
|
1045
|
+
/**
|
|
1046
|
+
* Callback chamado quando a sessão acabou e não há mais como renovar: o token
|
|
1047
|
+
* de uma sessão com duração limitada venceu e a renovação foi recusada. O app
|
|
1048
|
+
* deve limpar a sessão guardada e levar o usuário ao login. Nunca é chamado
|
|
1049
|
+
* para token de validade longa nem em erro de permissão (403 comum).
|
|
1050
|
+
*/
|
|
1051
|
+
onSessionExpired?: () => void;
|
|
1045
1052
|
}
|
|
1046
1053
|
/**
|
|
1047
1054
|
* Configura o SDK globalmente e retorna uma instância configurada.
|
|
@@ -1102,8 +1109,15 @@ declare function loginMitra(method: 'email' | 'google' | 'microsoft' | 'mitra',
|
|
|
1102
1109
|
/**
|
|
1103
1110
|
* Renova o token silenciosamente via iframe invisível.
|
|
1104
1111
|
* Se já houver um refresh em andamento, reaproveita a mesma promise.
|
|
1105
|
-
|
|
1106
|
-
|
|
1112
|
+
*
|
|
1113
|
+
* `currentToken` é o token que o app tem agora. Quando a página de auth pede
|
|
1114
|
+
* (aperto de mão `mitra-auth-ready`), ele é entregue ao iframe por postMessage.
|
|
1115
|
+
* Sem isso, app em domínio próprio não renova: o navegador particiona o
|
|
1116
|
+
* localStorage do iframe de outro site e a página de auth não acha o token que
|
|
1117
|
+
* gravou no login. Página de auth antiga ignora o pedido e renova pelo token
|
|
1118
|
+
* guardado, como sempre.
|
|
1119
|
+
*/
|
|
1120
|
+
declare function refreshTokenSilently(authUrl: string, projectId: number, currentToken?: string): Promise<LoginResponse>;
|
|
1107
1121
|
/**
|
|
1108
1122
|
* Cria conta via email usando iframe silencioso.
|
|
1109
1123
|
* Após sucesso, o usuário recebe um código de verificação por email.
|
package/dist/index.d.ts
CHANGED
|
@@ -1042,6 +1042,13 @@ interface MitraConfig {
|
|
|
1042
1042
|
projectId?: number;
|
|
1043
1043
|
/** Callback chamado quando o token é renovado automaticamente (após 401/403). Recebe a nova sessão. */
|
|
1044
1044
|
onTokenRefresh?: (session: LoginResponse) => void;
|
|
1045
|
+
/**
|
|
1046
|
+
* Callback chamado quando a sessão acabou e não há mais como renovar: o token
|
|
1047
|
+
* de uma sessão com duração limitada venceu e a renovação foi recusada. O app
|
|
1048
|
+
* deve limpar a sessão guardada e levar o usuário ao login. Nunca é chamado
|
|
1049
|
+
* para token de validade longa nem em erro de permissão (403 comum).
|
|
1050
|
+
*/
|
|
1051
|
+
onSessionExpired?: () => void;
|
|
1045
1052
|
}
|
|
1046
1053
|
/**
|
|
1047
1054
|
* Configura o SDK globalmente e retorna uma instância configurada.
|
|
@@ -1102,8 +1109,15 @@ declare function loginMitra(method: 'email' | 'google' | 'microsoft' | 'mitra',
|
|
|
1102
1109
|
/**
|
|
1103
1110
|
* Renova o token silenciosamente via iframe invisível.
|
|
1104
1111
|
* Se já houver um refresh em andamento, reaproveita a mesma promise.
|
|
1105
|
-
|
|
1106
|
-
|
|
1112
|
+
*
|
|
1113
|
+
* `currentToken` é o token que o app tem agora. Quando a página de auth pede
|
|
1114
|
+
* (aperto de mão `mitra-auth-ready`), ele é entregue ao iframe por postMessage.
|
|
1115
|
+
* Sem isso, app em domínio próprio não renova: o navegador particiona o
|
|
1116
|
+
* localStorage do iframe de outro site e a página de auth não acha o token que
|
|
1117
|
+
* gravou no login. Página de auth antiga ignora o pedido e renova pelo token
|
|
1118
|
+
* guardado, como sempre.
|
|
1119
|
+
*/
|
|
1120
|
+
declare function refreshTokenSilently(authUrl: string, projectId: number, currentToken?: string): Promise<LoginResponse>;
|
|
1107
1121
|
/**
|
|
1108
1122
|
* Cria conta via email usando iframe silencioso.
|
|
1109
1123
|
* Após sucesso, o usuário recebe um código de verificação por email.
|
package/dist/index.js
CHANGED
|
@@ -120,19 +120,28 @@ async function loginMitra(method, options) {
|
|
|
120
120
|
return doLogin(method, options);
|
|
121
121
|
}
|
|
122
122
|
var REFRESH_TIMEOUT_MS = 15e3;
|
|
123
|
+
var AUTH_HANDOFF_READY_TYPE = "mitra-auth-ready";
|
|
124
|
+
var AUTH_HANDOFF_TOKEN_TYPE = "mitra-auth-refresh-token";
|
|
123
125
|
var _refreshPromise = null;
|
|
124
|
-
function openAuthIframe(url, expectedOrigin) {
|
|
126
|
+
function openAuthIframe(url, expectedOrigin, handoffToken) {
|
|
125
127
|
return new Promise((resolve, reject) => {
|
|
126
128
|
const iframe = document.createElement("iframe");
|
|
127
129
|
iframe.style.display = "none";
|
|
128
130
|
iframe.src = url;
|
|
129
|
-
const
|
|
131
|
+
const timer2 = setTimeout(() => {
|
|
130
132
|
cleanup();
|
|
131
133
|
reject(new Error("Token refresh timeout"));
|
|
132
134
|
}, REFRESH_TIMEOUT_MS);
|
|
133
135
|
function onMessage(event) {
|
|
134
136
|
if (event.origin !== expectedOrigin) return;
|
|
135
|
-
if (!event.data
|
|
137
|
+
if (!event.data) return;
|
|
138
|
+
if (event.data.type === AUTH_HANDOFF_READY_TYPE) {
|
|
139
|
+
if (handoffToken && iframe.contentWindow && event.source === iframe.contentWindow) {
|
|
140
|
+
iframe.contentWindow.postMessage({ type: AUTH_HANDOFF_TOKEN_TYPE, token: handoffToken }, expectedOrigin);
|
|
141
|
+
}
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (event.data.type !== AUTH_MESSAGE_TYPE) return;
|
|
136
145
|
cleanup();
|
|
137
146
|
if (event.data.success) {
|
|
138
147
|
resolve(event.data.data);
|
|
@@ -142,20 +151,20 @@ function openAuthIframe(url, expectedOrigin) {
|
|
|
142
151
|
}
|
|
143
152
|
function cleanup() {
|
|
144
153
|
window.removeEventListener("message", onMessage);
|
|
145
|
-
clearTimeout(
|
|
154
|
+
clearTimeout(timer2);
|
|
146
155
|
if (iframe.parentNode) iframe.parentNode.removeChild(iframe);
|
|
147
156
|
}
|
|
148
157
|
window.addEventListener("message", onMessage);
|
|
149
158
|
document.body.appendChild(iframe);
|
|
150
159
|
});
|
|
151
160
|
}
|
|
152
|
-
async function refreshTokenSilently(authUrl, projectId) {
|
|
161
|
+
async function refreshTokenSilently(authUrl, projectId, currentToken) {
|
|
153
162
|
if (_refreshPromise) return _refreshPromise;
|
|
154
163
|
_refreshPromise = (async () => {
|
|
155
164
|
try {
|
|
156
165
|
const origin = getOriginFromUrl(authUrl);
|
|
157
|
-
const url = buildAuthUrl(authUrl, "refresh", projectId);
|
|
158
|
-
const response = await openAuthIframe(url, origin);
|
|
166
|
+
const url = buildAuthUrl(authUrl, "refresh", projectId) + (currentToken ? "&handoff=1" : "");
|
|
167
|
+
const response = await openAuthIframe(url, origin, currentToken);
|
|
159
168
|
return response;
|
|
160
169
|
} finally {
|
|
161
170
|
_refreshPromise = null;
|
|
@@ -177,7 +186,7 @@ function openSilentAuthIframe(authUrl, projectId, credentials) {
|
|
|
177
186
|
}
|
|
178
187
|
const iframe = document.createElement("iframe");
|
|
179
188
|
iframe.style.display = "none";
|
|
180
|
-
const
|
|
189
|
+
const timer2 = setTimeout(() => {
|
|
181
190
|
cleanup();
|
|
182
191
|
reject(new Error("Timeout na autentica\xE7\xE3o silenciosa"));
|
|
183
192
|
}, SILENT_TIMEOUT_MS);
|
|
@@ -201,7 +210,7 @@ function openSilentAuthIframe(authUrl, projectId, credentials) {
|
|
|
201
210
|
}
|
|
202
211
|
function cleanup() {
|
|
203
212
|
window.removeEventListener("message", onMessage);
|
|
204
|
-
clearTimeout(
|
|
213
|
+
clearTimeout(timer2);
|
|
205
214
|
if (iframe.parentNode) iframe.parentNode.removeChild(iframe);
|
|
206
215
|
}
|
|
207
216
|
window.addEventListener("message", onMessage);
|
|
@@ -383,6 +392,105 @@ function withServerFunctionProof(headers, proof = readServerFunctionProof()) {
|
|
|
383
392
|
return { ...headers, [SF_EXECUTION_TOKEN_HEADER]: proof };
|
|
384
393
|
}
|
|
385
394
|
|
|
395
|
+
// src/session-watch.ts
|
|
396
|
+
var RENEW_BEFORE_MS = 6e4;
|
|
397
|
+
var RETRY_MS = 1e4;
|
|
398
|
+
var MAX_TIMEOUT_MS = 2147483647;
|
|
399
|
+
var refresher = null;
|
|
400
|
+
var timer = null;
|
|
401
|
+
var listening = false;
|
|
402
|
+
var expiryAtLastAttempt = null;
|
|
403
|
+
var notifiedFor = null;
|
|
404
|
+
function registerSessionRefresher(fn) {
|
|
405
|
+
refresher = fn;
|
|
406
|
+
}
|
|
407
|
+
function limitedSessionExpiry(token) {
|
|
408
|
+
if (!token) return null;
|
|
409
|
+
const payload = decodeJwtPayload(token);
|
|
410
|
+
if (!payload || typeof payload.authTime !== "number" || typeof payload.exp !== "number") return null;
|
|
411
|
+
return payload.exp * 1e3;
|
|
412
|
+
}
|
|
413
|
+
function currentExpiry() {
|
|
414
|
+
if (typeof window === "undefined" || !isConfigured()) return null;
|
|
415
|
+
const config = getConfig();
|
|
416
|
+
if (!config.token || !config.authUrl || config.projectId == null) return null;
|
|
417
|
+
return limitedSessionExpiry(config.token);
|
|
418
|
+
}
|
|
419
|
+
function clearTimer() {
|
|
420
|
+
if (timer) {
|
|
421
|
+
clearTimeout(timer);
|
|
422
|
+
timer = null;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
function startSessionWatch() {
|
|
426
|
+
expiryAtLastAttempt = null;
|
|
427
|
+
notifiedFor = null;
|
|
428
|
+
watchSession();
|
|
429
|
+
}
|
|
430
|
+
function watchSession() {
|
|
431
|
+
clearTimer();
|
|
432
|
+
const expiry = currentExpiry();
|
|
433
|
+
if (expiry == null) return;
|
|
434
|
+
listenForReturn();
|
|
435
|
+
let delay = Math.max(expiry - Date.now() - RENEW_BEFORE_MS, 0);
|
|
436
|
+
if (expiryAtLastAttempt != null && expiry <= expiryAtLastAttempt + 1e3) {
|
|
437
|
+
delay = Math.max(expiry - Date.now() + 1e3, RETRY_MS);
|
|
438
|
+
}
|
|
439
|
+
if (delay > MAX_TIMEOUT_MS) return;
|
|
440
|
+
timer = setTimeout(() => {
|
|
441
|
+
void renewNow();
|
|
442
|
+
}, delay);
|
|
443
|
+
}
|
|
444
|
+
async function renewNow() {
|
|
445
|
+
clearTimer();
|
|
446
|
+
const before = currentExpiry();
|
|
447
|
+
if (before == null || !refresher) return;
|
|
448
|
+
expiryAtLastAttempt = before;
|
|
449
|
+
const renewed = await refresher().catch(() => false);
|
|
450
|
+
if (renewed) return;
|
|
451
|
+
const expiry = currentExpiry();
|
|
452
|
+
if (expiry != null && Date.now() < expiry) {
|
|
453
|
+
timer = setTimeout(() => {
|
|
454
|
+
void renewNow();
|
|
455
|
+
}, RETRY_MS);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
function onReturn() {
|
|
459
|
+
const expiry = currentExpiry();
|
|
460
|
+
if (expiry == null) return;
|
|
461
|
+
const atSessionEnd = expiryAtLastAttempt != null && expiry <= expiryAtLastAttempt + 1e3;
|
|
462
|
+
if (expiry - Date.now() <= RENEW_BEFORE_MS && (!atSessionEnd || Date.now() >= expiry)) {
|
|
463
|
+
void renewNow();
|
|
464
|
+
} else {
|
|
465
|
+
watchSession();
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
function listenForReturn() {
|
|
469
|
+
if (listening || typeof window === "undefined") return;
|
|
470
|
+
listening = true;
|
|
471
|
+
if (typeof document !== "undefined" && typeof document.addEventListener === "function") {
|
|
472
|
+
document.addEventListener("visibilitychange", () => {
|
|
473
|
+
if (document.visibilityState === "visible") onReturn();
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
if (typeof window.addEventListener === "function") {
|
|
477
|
+
window.addEventListener("online", onReturn);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
function reportRefreshFailure() {
|
|
481
|
+
var _a, _b;
|
|
482
|
+
const expiry = currentExpiry();
|
|
483
|
+
if (expiry == null || Date.now() < expiry) return;
|
|
484
|
+
const config = getConfig();
|
|
485
|
+
if (notifiedFor === config.token) return;
|
|
486
|
+
notifiedFor = (_a = config.token) != null ? _a : null;
|
|
487
|
+
clearTimer();
|
|
488
|
+
try {
|
|
489
|
+
(_b = config.onSessionExpired) == null ? void 0 : _b.call(config);
|
|
490
|
+
} catch (e) {
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
386
494
|
// src/client.ts
|
|
387
495
|
function getFetch2() {
|
|
388
496
|
if (typeof globalThis.fetch === "function") {
|
|
@@ -417,14 +525,18 @@ function tryRefreshToken() {
|
|
|
417
525
|
if (refreshInFlight === attempt) refreshInFlight = null;
|
|
418
526
|
};
|
|
419
527
|
attempt.then(clear, clear);
|
|
528
|
+
attempt.then((renewed) => {
|
|
529
|
+
if (!renewed) reportRefreshFailure();
|
|
530
|
+
}, () => void 0);
|
|
420
531
|
return attempt;
|
|
421
532
|
}
|
|
533
|
+
registerSessionRefresher(tryRefreshToken);
|
|
422
534
|
async function doRefreshToken() {
|
|
423
535
|
const config = getConfig();
|
|
424
536
|
if (!config.token || config.projectId == null || !config.baseURL) return false;
|
|
425
537
|
if (!config.authUrl || typeof window === "undefined") return false;
|
|
426
538
|
try {
|
|
427
|
-
const session = await refreshTokenSilently(config.authUrl, config.projectId);
|
|
539
|
+
const session = await refreshTokenSilently(config.authUrl, config.projectId, config.token);
|
|
428
540
|
updateGlobalToken(session.token);
|
|
429
541
|
if (config.onTokenRefresh) config.onTokenRefresh(session);
|
|
430
542
|
return true;
|
|
@@ -1500,7 +1612,7 @@ function openSocket() {
|
|
|
1500
1612
|
return new Promise((resolve, reject) => {
|
|
1501
1613
|
const socket = new WebSocket(url);
|
|
1502
1614
|
let opened = false;
|
|
1503
|
-
const
|
|
1615
|
+
const timer2 = setTimeout(() => {
|
|
1504
1616
|
if (!opened) {
|
|
1505
1617
|
try {
|
|
1506
1618
|
socket.close();
|
|
@@ -1511,13 +1623,13 @@ function openSocket() {
|
|
|
1511
1623
|
}, 15e3);
|
|
1512
1624
|
socket.onopen = () => {
|
|
1513
1625
|
opened = true;
|
|
1514
|
-
clearTimeout(
|
|
1626
|
+
clearTimeout(timer2);
|
|
1515
1627
|
ws = socket;
|
|
1516
1628
|
resolve(socket);
|
|
1517
1629
|
};
|
|
1518
1630
|
socket.onerror = () => {
|
|
1519
1631
|
if (!opened) {
|
|
1520
|
-
clearTimeout(
|
|
1632
|
+
clearTimeout(timer2);
|
|
1521
1633
|
reject(new Error("Agent Chat: erro ao conectar no WebSocket"));
|
|
1522
1634
|
}
|
|
1523
1635
|
};
|
|
@@ -1525,7 +1637,7 @@ function openSocket() {
|
|
|
1525
1637
|
if (ws === socket) ws = null;
|
|
1526
1638
|
const detalhe = closeDetail(event);
|
|
1527
1639
|
if (!opened) {
|
|
1528
|
-
clearTimeout(
|
|
1640
|
+
clearTimeout(timer2);
|
|
1529
1641
|
reject(new Error(`Agent Chat: conex\xE3o fechada${detalhe}`));
|
|
1530
1642
|
}
|
|
1531
1643
|
for (const [id, pending] of pendingRequests) {
|
|
@@ -1900,7 +2012,7 @@ function createMitraInstance(initialConfig) {
|
|
|
1900
2012
|
if (!_config.token || _config.projectId == null || !_config.baseURL) return false;
|
|
1901
2013
|
if (!_config.authUrl || typeof window === "undefined") return false;
|
|
1902
2014
|
try {
|
|
1903
|
-
const session = await refreshTokenSilently(_config.authUrl, _config.projectId);
|
|
2015
|
+
const session = await refreshTokenSilently(_config.authUrl, _config.projectId, _config.token);
|
|
1904
2016
|
_config = { ..._config, token: stripBearer(session.token) };
|
|
1905
2017
|
updateGlobalToken(session.token);
|
|
1906
2018
|
if (_config.onTokenRefresh) _config.onTokenRefresh(session);
|
|
@@ -2310,6 +2422,7 @@ function configureSdkMitra(config = {}) {
|
|
|
2310
2422
|
const chat = window.__mitraChat;
|
|
2311
2423
|
if (chat) chat.init(merged.token);
|
|
2312
2424
|
}
|
|
2425
|
+
startSessionWatch();
|
|
2313
2426
|
return createMitraInstance(globalConfig);
|
|
2314
2427
|
}
|
|
2315
2428
|
function getConfig() {
|
|
@@ -2324,6 +2437,7 @@ function isConfigured() {
|
|
|
2324
2437
|
function updateGlobalToken(token) {
|
|
2325
2438
|
if (globalConfig) {
|
|
2326
2439
|
globalConfig = { ...globalConfig, token: stripBearer(token) };
|
|
2440
|
+
watchSession();
|
|
2327
2441
|
}
|
|
2328
2442
|
}
|
|
2329
2443
|
function inferProjectId(projectId) {
|