Fix login/tenant create race
This commit is contained in:
@@ -6,7 +6,7 @@ import { EventStore } from "applesauce-core"
|
||||
import { createEventLoaderForStore } from "applesauce-loaders/loaders"
|
||||
import { RelayPool } from "applesauce-relay"
|
||||
import { NostrConnectSigner } from "applesauce-signers"
|
||||
import { getIdentity, getTenant, listPlans, listTenantInvoices, listTenantRelays, registerAccountGetter, type Plan } from "@/lib/api"
|
||||
import { createTenant, getIdentity, getTenant, listPlans, listTenantInvoices, listTenantRelays, registerAccountGetter, type Plan } from "@/lib/api"
|
||||
|
||||
export type UnsignedEvent = {
|
||||
kind: number
|
||||
@@ -56,10 +56,15 @@ export const [identity, { refetch: refetchIdentity, mutate: setIdentity }] = cre
|
||||
)
|
||||
|
||||
// Shared billing reads, fetched once per session and consumed by the dashboard
|
||||
// shell, the billing page, and the billing-prompt surface. Keyed on the active
|
||||
// pubkey so they refetch on account switch; refetchBilling() refreshes them all
|
||||
// after a mutation (payment, method update, plan change).
|
||||
const billingKey = () => account()?.pubkey
|
||||
// shell, the billing page, and the billing-prompt surface. They're gated on
|
||||
// billingPubkey rather than the active account directly: the tenant row is
|
||||
// provisioned lazily on first login, so getTenant/listTenantInvoices 404 until
|
||||
// ensureSessionTenant() has created it. billingPubkey is reset on activation and
|
||||
// re-set once the tenant is ensured, so the reads still refetch on account switch
|
||||
// without racing ahead of provisioning. refetchBilling() refreshes them all after
|
||||
// a mutation (payment, method update, plan change).
|
||||
const [billingPubkey, setBillingPubkey] = createSignal<string>()
|
||||
const billingKey = () => billingPubkey()
|
||||
|
||||
export const [billingTenant, { refetch: refetchBillingTenant }] = createResource(billingKey, getTenant)
|
||||
export const [billingInvoices, { refetch: refetchBillingInvoices }] = createResource(billingKey, listTenantInvoices)
|
||||
@@ -71,6 +76,31 @@ export function refetchBilling() {
|
||||
void refetchBillingRelays()
|
||||
}
|
||||
|
||||
// Ensure the active pubkey's tenant row exists, then unlock billing reads. The
|
||||
// tenant is created lazily on first login, so this must run before any
|
||||
// tenant-scoped read. The in-flight promise is shared so the login flow (which
|
||||
// awaits it to roll back on failure) and the activation subscriber below don't
|
||||
// double-provision; createTenant is itself idempotent.
|
||||
let tenantEnsure: { pubkey: string; promise: Promise<void> } | undefined
|
||||
|
||||
export function ensureSessionTenant(): Promise<void> {
|
||||
const pubkey = account()?.pubkey
|
||||
if (!pubkey) return Promise.resolve()
|
||||
if (tenantEnsure?.pubkey === pubkey) return tenantEnsure.promise
|
||||
|
||||
const promise = (async () => {
|
||||
try {
|
||||
await createTenant()
|
||||
if (account()?.pubkey === pubkey) setBillingPubkey(pubkey)
|
||||
} finally {
|
||||
if (tenantEnsure?.pubkey === pubkey) tenantEnsure = undefined
|
||||
}
|
||||
})()
|
||||
|
||||
tenantEnsure = { pubkey, promise }
|
||||
return promise
|
||||
}
|
||||
|
||||
// Deferred to avoid circular-import TDZ errors (api.ts <-> state.ts)
|
||||
queueMicrotask(() => {
|
||||
try {
|
||||
@@ -97,5 +127,10 @@ queueMicrotask(() => {
|
||||
}
|
||||
|
||||
refetchIdentity()
|
||||
|
||||
// Lock billing reads until the new account's tenant is ensured, so they never
|
||||
// fire against a not-yet-provisioned tenant during signup.
|
||||
setBillingPubkey(undefined)
|
||||
if (account) void ensureSessionTenant().catch(() => {})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,8 +4,7 @@ import { ExtensionAccount, NostrConnectAccount, PasswordAccount, PrivateKeyAccou
|
||||
import { PasswordSigner } from "applesauce-signers"
|
||||
import QrScanner from "qr-scanner"
|
||||
import QRCode from "qrcode"
|
||||
import { accountManager, identity, PLATFORM_NAME } from "@/lib/state"
|
||||
import { createTenant } from "@/lib/api"
|
||||
import { accountManager, ensureSessionTenant, identity, PLATFORM_NAME } from "@/lib/state"
|
||||
import useMinLoading from "@/components/useMinLoading"
|
||||
|
||||
const NIP46_RELAYS = ['wss://bucket.coracle.social', 'wss://ephemeral.snowflare.cc']
|
||||
@@ -71,7 +70,7 @@ export default function Login(props: LoginPageProps = {}) {
|
||||
accountManager.addAccount(account)
|
||||
accountManager.setActive(account)
|
||||
try {
|
||||
await createTenant()
|
||||
await ensureSessionTenant()
|
||||
} catch (e) {
|
||||
accountManager.removeAccount(account)
|
||||
throw e
|
||||
|
||||
Reference in New Issue
Block a user