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/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
- declare function refreshTokenSilently(authUrl: string, projectId: number): Promise<LoginResponse>;
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
- declare function refreshTokenSilently(authUrl: string, projectId: number): Promise<LoginResponse>;
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 timer = setTimeout(() => {
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 || event.data.type !== AUTH_MESSAGE_TYPE) return;
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(timer);
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 timer = setTimeout(() => {
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(timer);
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 timer = setTimeout(() => {
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(timer);
1626
+ clearTimeout(timer2);
1515
1627
  ws = socket;
1516
1628
  resolve(socket);
1517
1629
  };
1518
1630
  socket.onerror = () => {
1519
1631
  if (!opened) {
1520
- clearTimeout(timer);
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(timer);
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) {