Fix terminal auth bootstrap and account checkout gate
This commit is contained in:
@@ -410,7 +410,6 @@ export function AccountCenter() {
|
|||||||
const canTrialUpgrade = Boolean(isSubscribed && isTrialSubscription);
|
const canTrialUpgrade = Boolean(isSubscribed && isTrialSubscription);
|
||||||
const canOpenCheckoutOverlay = Boolean(
|
const canOpenCheckoutOverlay = Boolean(
|
||||||
paymentFeatureReady &&
|
paymentFeatureReady &&
|
||||||
!isSubscriptionUnknown &&
|
|
||||||
(canTrialUpgrade || !isSubscribed || showExpiringSoon || showExpiredReminder),
|
(canTrialUpgrade || !isSubscribed || showExpiringSoon || showExpiredReminder),
|
||||||
);
|
);
|
||||||
const subscriptionStatusTitle = showExpiredReminder
|
const subscriptionStatusTitle = showExpiredReminder
|
||||||
|
|||||||
@@ -253,6 +253,17 @@ export function runTests() {
|
|||||||
),
|
),
|
||||||
"account center must distinguish unknown subscription sync state from a confirmed unsubscribed account",
|
"account center must distinguish unknown subscription sync state from a confirmed unsubscribed account",
|
||||||
);
|
);
|
||||||
|
assert(
|
||||||
|
!accountCenterSource.includes("paymentFeatureReady &&\n !isSubscriptionUnknown") &&
|
||||||
|
!accountCenterSource.includes("paymentFeatureReady &&\r\n !isSubscriptionUnknown"),
|
||||||
|
"account center must not block checkout when an authenticated user's subscription status is temporarily unknown",
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
accountFeatureSource.includes("状态待确认") &&
|
||||||
|
accountFeatureSource.includes("刷新或直接续费") &&
|
||||||
|
!accountFeatureSource.includes("订阅状态正在同步,请稍后刷新。"),
|
||||||
|
"account unknown subscription copy must tell users to refresh or renew instead of waiting indefinitely",
|
||||||
|
);
|
||||||
assert(
|
assert(
|
||||||
hookSource.includes("backendJson.authenticated === false") &&
|
hookSource.includes("backendJson.authenticated === false") &&
|
||||||
hookSource.includes("refreshSession()") &&
|
hookSource.includes("refreshSession()") &&
|
||||||
|
|||||||
@@ -165,10 +165,10 @@ export function createAccountCopy(isEn: boolean): Record<string, string> {
|
|||||||
: "钱包已绑定,正在创建订单并发起支付...",
|
: "钱包已绑定,正在创建订单并发起支付...",
|
||||||
proMember: "PRO MEMBER",
|
proMember: "PRO MEMBER",
|
||||||
proPendingSync: isEn ? "Activated (pending sync)" : "已开通(待同步)",
|
proPendingSync: isEn ? "Activated (pending sync)" : "已开通(待同步)",
|
||||||
subscriptionChecking: isEn ? "Checking subscription" : "订阅同步中",
|
subscriptionChecking: isEn ? "Status pending" : "状态待确认",
|
||||||
subscriptionUnknown: isEn
|
subscriptionUnknown: isEn
|
||||||
? "Subscription status is syncing. Please refresh shortly."
|
? "Subscription status is not confirmed yet. Refresh or renew directly."
|
||||||
: "订阅状态正在同步,请稍后刷新。",
|
: "订阅状态暂未确认,可刷新或直接续费。",
|
||||||
noProSubscription: isEn ? "No Pro subscription" : "暂无 Pro 订阅",
|
noProSubscription: isEn ? "No Pro subscription" : "暂无 Pro 订阅",
|
||||||
proEndsSoonTitle: isEn ? "Pro renewal due soon" : "Pro 即将到期",
|
proEndsSoonTitle: isEn ? "Pro renewal due soon" : "Pro 即将到期",
|
||||||
proEndsSoonBody: isEn
|
proEndsSoonBody: isEn
|
||||||
|
|||||||
@@ -228,6 +228,46 @@ export async function runTests() {
|
|||||||
"terminal auth bootstrap must not resolve to an anonymous paywall when a bearer session exists but the auth profile request is transiently failing",
|
"terminal auth bootstrap must not resolve to an anonymous paywall when a bearer session exists but the auth profile request is transiently failing",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const unresolvedSession = deferred<{ data: { session: { access_token: string } | null } }>();
|
||||||
|
let anonymousResolvedBeforeSession = false;
|
||||||
|
try {
|
||||||
|
const maybeAnonymous = await loadTerminalAuthProfile({
|
||||||
|
hasSupabasePublicEnv: true,
|
||||||
|
getSession: () => unresolvedSession.promise,
|
||||||
|
timeoutMs: 1,
|
||||||
|
loadAuthProfile: (accessToken) => {
|
||||||
|
assert(!accessToken, "cookie-only anonymous probe should not receive a bearer token");
|
||||||
|
return Promise.resolve({
|
||||||
|
authenticated: false,
|
||||||
|
subscription_active: false,
|
||||||
|
points: 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
anonymousResolvedBeforeSession = maybeAnonymous.authenticated === false;
|
||||||
|
} catch {}
|
||||||
|
assert(
|
||||||
|
!anonymousResolvedBeforeSession,
|
||||||
|
"terminal auth bootstrap must not show the signed-out gate while the local Supabase session is still unresolved",
|
||||||
|
);
|
||||||
|
|
||||||
|
const signedOutProfile = await loadTerminalAuthProfile({
|
||||||
|
hasSupabasePublicEnv: true,
|
||||||
|
getSession: () => Promise.resolve({ data: { session: null } }),
|
||||||
|
loadAuthProfile: (accessToken) => {
|
||||||
|
assert(!accessToken, "signed-out profile should be resolved through the cookie probe only");
|
||||||
|
return Promise.resolve({
|
||||||
|
authenticated: false,
|
||||||
|
subscription_active: false,
|
||||||
|
points: 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert(
|
||||||
|
signedOutProfile.authenticated === false,
|
||||||
|
"terminal auth bootstrap must still resolve a confirmed signed-out browser as unauthenticated",
|
||||||
|
);
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
isSubscriptionRequiredBackendResponse(
|
isSubscriptionRequiredBackendResponse(
|
||||||
403,
|
403,
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ export async function loadTerminalAuthProfile({
|
|||||||
| null = null;
|
| null = null;
|
||||||
let latestCookiePayload: TerminalAuthProfilePayload | null = null;
|
let latestCookiePayload: TerminalAuthProfilePayload | null = null;
|
||||||
let latestBearerPayload: TerminalAuthProfilePayload | null = null;
|
let latestBearerPayload: TerminalAuthProfilePayload | null = null;
|
||||||
|
let sessionSettled = !hasSupabasePublicEnv;
|
||||||
|
|
||||||
const authenticatedProfile = new Promise<TerminalAuthProfilePayload>((resolve) => {
|
const authenticatedProfile = new Promise<TerminalAuthProfilePayload>((resolve) => {
|
||||||
resolveAuthenticated = resolve;
|
resolveAuthenticated = resolve;
|
||||||
@@ -119,6 +120,7 @@ export async function loadTerminalAuthProfile({
|
|||||||
(async () => {
|
(async () => {
|
||||||
if (!hasSupabasePublicEnv) return null;
|
if (!hasSupabasePublicEnv) return null;
|
||||||
const sessionResult = await getSession();
|
const sessionResult = await getSession();
|
||||||
|
sessionSettled = true;
|
||||||
if (resolvedAuthenticated) return null;
|
if (resolvedAuthenticated) return null;
|
||||||
const accessToken = String(
|
const accessToken = String(
|
||||||
sessionResult?.data?.session?.access_token || "",
|
sessionResult?.data?.session?.access_token || "",
|
||||||
@@ -142,6 +144,14 @@ export async function loadTerminalAuthProfile({
|
|||||||
resolve(latestBearerPayload);
|
resolve(latestBearerPayload);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
latestCookiePayload?.authenticated === false &&
|
||||||
|
hasSupabasePublicEnv &&
|
||||||
|
!sessionSettled
|
||||||
|
) {
|
||||||
|
reject(new Error("Terminal auth bootstrap timeout"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (latestCookiePayload) {
|
if (latestCookiePayload) {
|
||||||
resolve(latestCookiePayload);
|
resolve(latestCookiePayload);
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user