fix: avoid terminal cold-start paywall race
This commit is contained in:
@@ -967,8 +967,13 @@ function ScanTerminalScreen() {
|
||||
}
|
||||
} catch {}
|
||||
setProAccess(createEmptyAccess(false));
|
||||
} else if (event === "TOKEN_REFRESHED" || event === "SIGNED_IN") {
|
||||
} else if (
|
||||
event === "INITIAL_SESSION" ||
|
||||
event === "TOKEN_REFRESHED" ||
|
||||
event === "SIGNED_IN"
|
||||
) {
|
||||
try {
|
||||
if (!session?.access_token) return;
|
||||
const payload = await loadAuthProfile(session?.access_token);
|
||||
setProAccess((prev) => mergeAccessStateWithAuthPayload(prev, payload));
|
||||
} catch {}
|
||||
|
||||
@@ -79,4 +79,43 @@ export async function runTests() {
|
||||
cookieOnlyResult.user_id === "cookie-user",
|
||||
"terminal auth bootstrap should accept an authenticated cookie profile immediately",
|
||||
);
|
||||
|
||||
const delayedBearerSession = deferred<{ data: { session: { access_token: string } } }>();
|
||||
let coldStartSettled = false;
|
||||
const coldStartResultPromise = loadTerminalAuthProfile({
|
||||
hasSupabasePublicEnv: true,
|
||||
getSession: () => delayedBearerSession.promise,
|
||||
loadAuthProfile: (accessToken) => {
|
||||
const token = String(accessToken || "");
|
||||
if (!token) {
|
||||
return Promise.resolve({
|
||||
authenticated: true,
|
||||
user_id: "cookie-user",
|
||||
subscription_active: null,
|
||||
degraded_auth_profile: true,
|
||||
});
|
||||
}
|
||||
return Promise.resolve({
|
||||
authenticated: true,
|
||||
user_id: "bearer-paid-user",
|
||||
subscription_active: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
coldStartResultPromise.then(() => {
|
||||
coldStartSettled = true;
|
||||
});
|
||||
await flushMicrotasks();
|
||||
assert(
|
||||
coldStartSettled === false,
|
||||
"terminal auth bootstrap must not show a cold-start paywall from a degraded cookie profile before bearer auth has a chance to confirm Pro",
|
||||
);
|
||||
|
||||
delayedBearerSession.resolve({ data: { session: { access_token: "paid-token" } } });
|
||||
const coldStartResult = await coldStartResultPromise;
|
||||
assert(
|
||||
coldStartResult.user_id === "bearer-paid-user" &&
|
||||
coldStartResult.subscription_active === true,
|
||||
"terminal auth bootstrap should prefer the bearer-confirmed active Pro profile over a degraded cookie profile",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -76,4 +76,10 @@ export function runTests() {
|
||||
signedOutBlock.indexOf("setProAccess(createEmptyAccess(false))"),
|
||||
"terminal auth listener must re-check the current Supabase session before clearing access on SIGNED_OUT events",
|
||||
);
|
||||
assert(
|
||||
dashboardSource.includes('event === "INITIAL_SESSION"') &&
|
||||
dashboardSource.indexOf('event === "INITIAL_SESSION"') <
|
||||
dashboardSource.indexOf('event === "TOKEN_REFRESHED"'),
|
||||
"terminal auth listener must hydrate access from Supabase INITIAL_SESSION events during first navigation from the landing page",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,12 @@ function firstKnownProfile(cookieResult: SettledProfile, bearerResult: SettledPr
|
||||
};
|
||||
}
|
||||
|
||||
function canResolveProfileImmediately(
|
||||
payload: TerminalAuthProfilePayload | null,
|
||||
): payload is TerminalAuthProfilePayload {
|
||||
return payload?.authenticated === true && payload.subscription_active === true;
|
||||
}
|
||||
|
||||
export async function loadTerminalAuthProfile({
|
||||
getSession,
|
||||
hasSupabasePublicEnv,
|
||||
@@ -56,7 +62,7 @@ export async function loadTerminalAuthProfile({
|
||||
});
|
||||
|
||||
const resolveIfAuthenticated = (payload: TerminalAuthProfilePayload | null) => {
|
||||
if (!payload?.authenticated || resolvedAuthenticated) return;
|
||||
if (!canResolveProfileImmediately(payload) || resolvedAuthenticated) return;
|
||||
resolvedAuthenticated = true;
|
||||
resolveAuthenticated?.(payload);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user