Resolve account subscription sync state
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
buildAuthMePath,
|
buildAuthMePath,
|
||||||
mergeAccountAuthSnapshot,
|
mergeAccountAuthSnapshot,
|
||||||
|
shouldResolveAccountUnknownWithFullProfile,
|
||||||
} from "@/lib/auth-snapshot";
|
} from "@/lib/auth-snapshot";
|
||||||
import type { AuthSnapshotLike } from "@/lib/auth-snapshot";
|
import type { AuthSnapshotLike } from "@/lib/auth-snapshot";
|
||||||
|
|
||||||
@@ -64,4 +65,33 @@ export function runTests() {
|
|||||||
inactive.subscription_active === false,
|
inactive.subscription_active === false,
|
||||||
"account snapshot merge must still accept a confirmed inactive subscription response",
|
"account snapshot merge must still accept a confirmed inactive subscription response",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
shouldResolveAccountUnknownWithFullProfile(
|
||||||
|
{ authenticated: true, subscription_active: null },
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
"account center must resolve an unknown local-user subscription snapshot with the full auth profile",
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
!shouldResolveAccountUnknownWithFullProfile(
|
||||||
|
{ authenticated: true, subscription_active: false },
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
"account center must not re-resolve an explicitly inactive subscription snapshot",
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
!shouldResolveAccountUnknownWithFullProfile(
|
||||||
|
{ authenticated: false, subscription_active: null },
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
"account center must not re-resolve an unauthenticated backend snapshot",
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
!shouldResolveAccountUnknownWithFullProfile(
|
||||||
|
{ authenticated: true, subscription_active: null },
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
"account center must only resolve unknown subscription snapshots when a local Supabase user exists",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -259,6 +259,13 @@ export function runTests() {
|
|||||||
hookSource.includes("retriedBackendJson"),
|
hookSource.includes("retriedBackendJson"),
|
||||||
"account snapshot loader must retry with a refreshed Supabase token when local user exists but /api/auth/me reports unauthenticated",
|
"account snapshot loader must retry with a refreshed Supabase token when local user exists but /api/auth/me reports unauthenticated",
|
||||||
);
|
);
|
||||||
|
assert(
|
||||||
|
hookSource.includes("shouldResolveAccountUnknownWithFullProfile") &&
|
||||||
|
hookSource.includes("backendJson = await readAuthSnapshot(latestHeaders);") &&
|
||||||
|
hookSource.indexOf("shouldResolveAccountUnknownWithFullProfile") <
|
||||||
|
hookSource.indexOf("setBackend((previous) => mergeAccountAuthSnapshot(previous, backendJson))"),
|
||||||
|
"account snapshot loader must resolve unknown entitlement-scope results through full auth/me before rendering account status",
|
||||||
|
);
|
||||||
assert(
|
assert(
|
||||||
hookSource.includes("refreshEntitlementAfterPayment") &&
|
hookSource.includes("refreshEntitlementAfterPayment") &&
|
||||||
paymentFlowSource.includes("refreshEntitlementAfterPayment") &&
|
paymentFlowSource.includes("refreshEntitlementAfterPayment") &&
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type { User } from "@supabase/supabase-js";
|
|||||||
import {
|
import {
|
||||||
buildAuthMePath,
|
buildAuthMePath,
|
||||||
mergeAccountAuthSnapshot,
|
mergeAccountAuthSnapshot,
|
||||||
|
shouldResolveAccountUnknownWithFullProfile,
|
||||||
} from "@/lib/auth-snapshot";
|
} from "@/lib/auth-snapshot";
|
||||||
import { getSupabaseBrowserClient } from "@/lib/supabase/client";
|
import { getSupabaseBrowserClient } from "@/lib/supabase/client";
|
||||||
|
|
||||||
@@ -244,9 +245,19 @@ export function useAccountPayment(params: UseAccountPaymentParams) {
|
|||||||
// an unauthenticated backend snapshot as a temporary sync state.
|
// an unauthenticated backend snapshot as a temporary sync state.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let resolvedWithFullProfile = false;
|
||||||
|
if (shouldResolveAccountUnknownWithFullProfile(backendJson, Boolean(localUser))) {
|
||||||
|
try {
|
||||||
|
backendJson = await readAuthSnapshot(latestHeaders);
|
||||||
|
resolvedWithFullProfile = true;
|
||||||
|
} catch {
|
||||||
|
// Keep the explicit unknown state only when the full account profile
|
||||||
|
// cannot be read; the refresh button can retry the same path.
|
||||||
|
}
|
||||||
|
}
|
||||||
setBackend((previous) => mergeAccountAuthSnapshot(previous, backendJson));
|
setBackend((previous) => mergeAccountAuthSnapshot(previous, backendJson));
|
||||||
setUpdatedAt(new Date().toISOString());
|
setUpdatedAt(new Date().toISOString());
|
||||||
if (backendJson.authenticated !== false) {
|
if (backendJson.authenticated !== false && !resolvedWithFullProfile) {
|
||||||
void readAuthSnapshot(latestHeaders)
|
void readAuthSnapshot(latestHeaders)
|
||||||
.then((fullJson) => {
|
.then((fullJson) => {
|
||||||
setBackend((previous) =>
|
setBackend((previous) =>
|
||||||
|
|||||||
@@ -40,6 +40,17 @@ export function isUnknownSubscriptionSnapshot(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function shouldResolveAccountUnknownWithFullProfile(
|
||||||
|
snapshot: AuthSnapshotLike | null | undefined,
|
||||||
|
localUserPresent: boolean,
|
||||||
|
) {
|
||||||
|
return Boolean(
|
||||||
|
localUserPresent &&
|
||||||
|
snapshot?.authenticated !== false &&
|
||||||
|
isUnknownSubscriptionSnapshot(snapshot),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function mergeAccountAuthSnapshot<T extends AuthSnapshotLike>(
|
export function mergeAccountAuthSnapshot<T extends AuthSnapshotLike>(
|
||||||
previous: T | null | undefined,
|
previous: T | null | undefined,
|
||||||
next: T,
|
next: T,
|
||||||
|
|||||||
Reference in New Issue
Block a user