From c91a669209f3e6356e7d1b10fbf8060d7f2c87c3 Mon Sep 17 00:00:00 2001 From: Vitor Date: Fri, 24 Jul 2026 18:37:54 -0300 Subject: [PATCH 1/3] fix(storefront): Refresh passport token before loading checkout app when near expiry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a recurring customer opens checkout with a passport token expiring within 2 minutes, the storefront-app would load with the stale token, causing fetchCustomer to 401 and leaving the user stuck on the "Complete seu cadastro" screen instead of the login or checkout step. Now checks synchronously if the stored token needs refresh before loading app.js. Only users in the ~2-minute expiry window experience a brief extra delay (Firebase authStateReady + /_api/passport/token). All other users — new, anonymous, or with a fresh token — load immediately with no change in behavior. Co-Authored-By: Claude Sonnet 4.6 --- .../storefront/src/lib/scripts/vbeta-app.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/storefront/src/lib/scripts/vbeta-app.ts b/packages/storefront/src/lib/scripts/vbeta-app.ts index e7a45e125..c43269c3b 100644 --- a/packages/storefront/src/lib/scripts/vbeta-app.ts +++ b/packages/storefront/src/lib/scripts/vbeta-app.ts @@ -321,6 +321,10 @@ if (!import.meta.env.SSR) { resolve(getAuth()); }); }); + const storedTokenExpiresAt = session.auth ? new Date(session.auth.expires).getTime() : 0; + const storedTokenNeedsRefresh = storedTokenExpiresAt > 0 + && storedTokenExpiresAt - Date.now() <= 10 * 1000; + const rejectAfter = (ms: number) => new Promise((_, reject) => { setTimeout(() => reject(new Error('timeout')), ms); }); if (window.location.hash.includes('account')) { initializingAuth .then(async (firebaseAuth) => { @@ -337,6 +341,23 @@ if (!import.meta.env.SSR) { console.error(err); loadAppScript(); }); + } else if (storedTokenNeedsRefresh) { + Promise.race([initializingAuth, rejectAfter(10000)]) + .then(async (firebaseAuth) => { + await Promise.race([firebaseAuth.authStateReady(), rejectAfter(5000)]); + if (!isAuthReady.value) { + await Promise.race([ + new Promise((res) => { + const u = watch(isAuthReady, (v) => { + if (v) { u(); res(); } + }); + }), + rejectAfter(5000), + ]); + } + loadAppScript(); + }) + .catch(() => loadAppScript()); } else { loadAppScript(); } From 3c530e5889568963160b9a1fc5869a265905fb78 Mon Sep 17 00:00:00 2001 From: Leonardo Matos Date: Sun, 2 Aug 2026 11:55:32 -0300 Subject: [PATCH 2/3] fix(storefront): Update checkout app to retry customer fetch on token refresh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @ecomplus/storefront-app 2.0.0-beta.228 replaces the fixed 1500ms fetchCustomer retry in account.js with the ecomPassport "login" event (ecomplus/storefront#1298), complementing the token refresh gate added here — the retry now fires exactly when the renewed token lands. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01D6txsobQ3CPAo1yYK7XDab --- packages/storefront/src/lib/scripts/vbeta-app.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/storefront/src/lib/scripts/vbeta-app.ts b/packages/storefront/src/lib/scripts/vbeta-app.ts index c43269c3b..700e89e1b 100644 --- a/packages/storefront/src/lib/scripts/vbeta-app.ts +++ b/packages/storefront/src/lib/scripts/vbeta-app.ts @@ -274,7 +274,7 @@ if (!import.meta.env.SSR) { const appScript = document.createElement('script'); appScript.src = src || (window as any)._appScriptSrc - || 'https://cdn.jsdelivr.net/npm/@ecomplus/storefront-app@2.0.0-beta.227/dist/lib/js/app.js'; + || 'https://cdn.jsdelivr.net/npm/@ecomplus/storefront-app@2.0.0-beta.228/dist/lib/js/app.js'; appScript.onload = () => { setTimeout(() => { watchAppRoutes(); From 3889a3cd0fe8758c3b6d8c00285d4b05228cf9c5 Mon Sep 17 00:00:00 2001 From: Leonardo Matos Date: Sun, 2 Aug 2026 12:14:33 -0300 Subject: [PATCH 3/3] chore(storefront): Simplify checkout token refresh gate Reuse the isAuthenticated computed (same 10s margin) instead of duplicating the expiry math, collapse the three nested Promise.race timeouts into a single 10s overall cap, and fix lint max-len warning. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01D6txsobQ3CPAo1yYK7XDab --- .../storefront/src/lib/scripts/vbeta-app.ts | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/packages/storefront/src/lib/scripts/vbeta-app.ts b/packages/storefront/src/lib/scripts/vbeta-app.ts index 700e89e1b..68b8f5496 100644 --- a/packages/storefront/src/lib/scripts/vbeta-app.ts +++ b/packages/storefront/src/lib/scripts/vbeta-app.ts @@ -321,10 +321,6 @@ if (!import.meta.env.SSR) { resolve(getAuth()); }); }); - const storedTokenExpiresAt = session.auth ? new Date(session.auth.expires).getTime() : 0; - const storedTokenNeedsRefresh = storedTokenExpiresAt > 0 - && storedTokenExpiresAt - Date.now() <= 10 * 1000; - const rejectAfter = (ms: number) => new Promise((_, reject) => { setTimeout(() => reject(new Error('timeout')), ms); }); if (window.location.hash.includes('account')) { initializingAuth .then(async (firebaseAuth) => { @@ -341,23 +337,24 @@ if (!import.meta.env.SSR) { console.error(err); loadAppScript(); }); - } else if (storedTokenNeedsRefresh) { - Promise.race([initializingAuth, rejectAfter(10000)]) - .then(async (firebaseAuth) => { - await Promise.race([firebaseAuth.authStateReady(), rejectAfter(5000)]); - if (!isAuthReady.value) { - await Promise.race([ - new Promise((res) => { - const u = watch(isAuthReady, (v) => { - if (v) { u(); res(); } - }); - }), - rejectAfter(5000), - ]); - } - loadAppScript(); - }) - .catch(() => loadAppScript()); + } else if (session.auth && !isAuthenticated.value) { + // Stored passport token is stale: wait for Firebase auth to renew it + // before app.js starts fetching with it + const waitingTokenRefresh = initializingAuth.then(async (firebaseAuth) => { + await firebaseAuth.authStateReady(); + if (isAuthReady.value) return; + await new Promise((resolve) => { + const unwatch = watch(isAuthReady, (ready) => { + if (!ready) return; + unwatch(); + resolve(); + }); + }); + }); + Promise.race([ + waitingTokenRefresh.catch(console.error), + new Promise((resolve) => { setTimeout(resolve, 10000); }), + ]).then(() => loadAppScript()); } else { loadAppScript(); }