From e5f37b003d93a17c2a9958697269b2d3228d8830 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Wed, 4 Mar 2026 23:06:35 -0800 Subject: tuning --- src/routes/(app)/+layout.server.ts | 51 +++++++---------- src/routes/(app)/+layout.svelte | 8 +-- src/routes/(app)/[username]/(profile)/+page.svelte | 2 +- src/routes/(app)/account/new/+page.svelte | 16 ++++-- src/routes/(app)/invoice/+page.server.ts | 2 +- src/routes/(app)/invoice/[id]/+layout.server.ts | 61 -------------------- src/routes/(app)/invoice/[id]/+layout.ts | 66 ++++++++++++++++++++++ src/routes/(app)/invoice/[id]/+page.svelte | 4 +- src/routes/(app)/invoice/[id]/tip/+page.server.ts | 5 +- .../(app)/send/ark/[address]/+layout.server.ts | 15 +++-- .../(app)/send/ark/[address]/+page.server.ts | 16 ++++-- src/routes/(app)/send/ark/[address]/+page.svelte | 4 +- .../[amount]/[...feeRate]/+page.server.ts | 14 ++++- .../[address]/[amount]/[...feeRate]/+page.svelte | 4 +- src/routes/(static)/login/+page.svelte | 13 +++++ src/routes/+layout.server.ts | 9 +-- src/routes/+layout.svelte | 13 ++--- 17 files changed, 168 insertions(+), 135 deletions(-) delete mode 100644 src/routes/(app)/invoice/[id]/+layout.server.ts create mode 100644 src/routes/(app)/invoice/[id]/+layout.ts (limited to 'src/routes') diff --git a/src/routes/(app)/+layout.server.ts b/src/routes/(app)/+layout.server.ts index f863e026..09d6f841 100644 --- a/src/routes/(app)/+layout.server.ts +++ b/src/routes/(app)/+layout.server.ts @@ -1,54 +1,45 @@ import { auth, get, isInvalidTokenError, sleep } from "$lib/utils"; -import { error, redirect } from "@sveltejs/kit"; +import { redirect } from "@sveltejs/kit"; -export async function load({ cookies, url, params }) { - const { pathname } = url; - const { id, username } = params; - const token = cookies.get("token"); +export async function load({ cookies, depends }) { + depends("app:user"); - const isSweep = pathname.match(/^\/fund\/[^/]+\/sweep/); - if (!token && !isSweep) redirect(307, "/login"); + const token = cookies.get("token"); let user; let hasArk = false; if (token) { - try { - user = await get("/me", auth(cookies)); - } catch (e) { + const authHeaders = auth(cookies); + + // Fetch user and accounts in parallel + const [userResult, accounts] = await Promise.all([ + get("/me", authHeaders).catch((e) => ({ _error: e })), + get("/accounts", authHeaders).catch(() => []), + ]); + + if (userResult?._error) { + const e = userResult._error; const { message } = e as Error; if (message.startsWith("Rate")) { await sleep(3000); try { - user = await get("/me", auth(cookies)); + user = await get("/me", authHeaders); } catch (retryError) { - if (isInvalidTokenError(retryError) && pathname !== "/logout") { + if (isInvalidTokenError(retryError)) { redirect(307, "/logout"); } throw retryError; } - } else if (isInvalidTokenError(e) && pathname !== "/logout") { + } else if (isInvalidTokenError(e)) { redirect(307, "/logout"); } else { throw e; } + } else { + user = userResult; } - hasArk = (await get("/accounts", auth(cookies))).some((a: any) => a.type === "ark"); - } - - let subject; - if (url.pathname.includes("/invoice") && id) { - try { - ({ user: subject } = await get(`/invoice/${id}`)); - } catch (e) { - console.log("unable to fetch invoice", id, e); - } - } else if (username) { - try { - subject = await get(`/users/${username}`); - } catch { - error(500, "Unable to retrieve user account data"); - } + hasArk = accounts.some((a: any) => a.type === "ark"); } if (user?.needsMigration) { @@ -57,5 +48,5 @@ export async function load({ cookies, url, params }) { const theme = cookies.get("theme") || "light"; - return { user, token, subject, theme, hasArk }; + return { user, token, theme, hasArk }; } diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 991171be..5a9397b5 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -29,14 +29,14 @@ import Nostr from "$comp/Nostr.svelte"; import Password from "$comp/Password.svelte"; import { s, f, toFiat, success, getCookie, warning, post } from "$lib/utils"; - import { t, locale, loading } from "$lib/translations"; + import { t, locale } from "$lib/translations"; import { goto, invalidate, afterNavigate } from "$app/navigation"; let { data, children } = $props(); let user = $derived(data.user); - let subject = $derived(data.subject); + let subject = $derived(($page.data as any).subject); let token = $derived(data.token); let theme = $derived(data.theme); @@ -332,7 +332,5 @@
- {#if !$loading} - {@render children?.()} - {/if} + {@render children?.()}
diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte index 123a14e8..9979d77c 100644 --- a/src/routes/(app)/[username]/(profile)/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/+page.svelte @@ -124,7 +124,7 @@ {/if} {#if user?.id && user.id === subject.id} -
+
{#each accounts as account} {/each} diff --git a/src/routes/(app)/account/new/+page.svelte b/src/routes/(app)/account/new/+page.svelte index 4365dedd..310660a5 100644 --- a/src/routes/(app)/account/new/+page.svelte +++ b/src/routes/(app)/account/new/+page.svelte @@ -3,9 +3,10 @@ import { t } from "$lib/translations"; import Spinner from "$comp/Spinner.svelte"; import { bytesToHex } from "@noble/hashes/utils.js"; - import { fail, post, versions } from "$lib/utils"; + import { fail, post, versions, wait } from "$lib/utils"; import { goto } from "$app/navigation"; import { arkServerUrl } from "$lib/ark"; + import { password as pw, passwordPrompt } from "$lib/store"; import { HDKey } from "@scure/bip32"; import { entropyToMnemonic, mnemonicToSeed } from "@scure/bip39"; import { wordlist } from "@scure/bip39/wordlists/english.js"; @@ -19,11 +20,16 @@ creatingArk = true; try { const { getWalletEntropy } = await import("$lib/walletEntropy"); - const entropy = await getWalletEntropy(); + let entropy = await getWalletEntropy(); + if (!entropy) { - fail($t("accounts.failedToDecryptSeed") as string); - creatingArk = false; - return; + if (!$pw) $passwordPrompt = true; + await wait(() => !!$pw); + const { deriveAuthKeypair, deriveWalletEntropy } = await import("$lib/deriveAuthKey"); + const { sk } = await deriveAuthKeypair(user.username, $pw!); + entropy = await deriveWalletEntropy(sk); + const { rememberPrfKey, defaultRememberForMs } = await import("$lib/passwordCache"); + rememberPrfKey(entropy, defaultRememberForMs); } const { SingleKey, Wallet } = await import("@arkade-os/sdk"); diff --git a/src/routes/(app)/invoice/+page.server.ts b/src/routes/(app)/invoice/+page.server.ts index 9cf2088a..db54ff8b 100644 --- a/src/routes/(app)/invoice/+page.server.ts +++ b/src/routes/(app)/invoice/+page.server.ts @@ -5,7 +5,7 @@ import { error, redirect } from "@sveltejs/kit"; export const load = async ({ cookies, parent, url }) => { const aid = url.searchParams.get("aid") || cookies.get("aid"); - if (aid) cookies.set("aid", aid, { path: "/", maxAge: 86400 }); + if (aid) cookies.set("aid", aid, { path: "/", maxAge: 86400, httpOnly: false }); let { subject, user } = await parent(); const lnurl = url.searchParams.has("lnurl"); diff --git a/src/routes/(app)/invoice/[id]/+layout.server.ts b/src/routes/(app)/invoice/[id]/+layout.server.ts deleted file mode 100644 index 24b7f1b0..00000000 --- a/src/routes/(app)/invoice/[id]/+layout.server.ts +++ /dev/null @@ -1,61 +0,0 @@ -import getRates from "$lib/rates"; -import { get } from "$lib/utils"; -import { redirect } from "@sveltejs/kit"; - -export async function load({ cookies, depends, params, url, parent }) { - depends("app:invoice"); - - const token = cookies.get("token"); - let { subject, user } = await parent(); - const { id } = params; - let invoice; - - if (id === "lnurl") { - const rates = await getRates(); - invoice = { - aid: user.id, - uid: user.id, - amount: 0, - tip: 0, - rate: rates[user?.currency || "USD"], - text: "", - hash: "", - type: "lightning", - items: [], - memo: "", - pending: 0, - received: 0, - user: { id: user.id, username: user.username, currency: user.currency }, - }; - } else if (id) { - invoice = await get(`/invoice/${id}`); - const options = !!url.searchParams.get("options"); - let { amount, pending, received } = invoice; - amount = parseInt(amount); - - const paid = - (!amount && (pending || received)) || - (amount > 0 && (pending >= amount || received >= amount)); - if (paid && !url.pathname.endsWith("paid")) { - redirect(307, `/invoice/${id}/paid` + (options ? "?options=true" : "")); - } - - subject = invoice.user; - - const aid = cookies.get("aid") || user?.id; - if ( - user && - invoice.uid !== user?.id && - !(url.pathname.includes("tip") || url.pathname.includes("memo")) && - !options - ) { - if (invoice.prompt && invoice.tip === null) redirect(307, `/invoice/${id}/tip`); - else { - redirect(307, `/send/invoice/${id}`); - } - } - } - - const theme = cookies.get("theme") || "light"; - return { id, invoice, subject, user, token, theme }; -} diff --git a/src/routes/(app)/invoice/[id]/+layout.ts b/src/routes/(app)/invoice/[id]/+layout.ts new file mode 100644 index 00000000..411486c7 --- /dev/null +++ b/src/routes/(app)/invoice/[id]/+layout.ts @@ -0,0 +1,66 @@ +import { get } from "$lib/utils"; +import { redirect } from "@sveltejs/kit"; + +export async function load({ params, url, parent, depends }) { + depends("app:invoice"); + + const { user, token, theme } = await parent(); + const { id } = params; + let invoice; + let subject; + + if (id === "lnurl") { + subject = user; + invoice = { + aid: user.id, + uid: user.id, + amount: 0, + tip: 0, + rate: 0, + text: `${user.username}@${url.host}`, + hash: "", + type: "lightning", + items: [], + memo: "", + pending: 0, + received: 0, + user: { id: user.id, username: user.username, currency: user.currency }, + }; + } else if (id) { + invoice = await get(`/invoice/${id}`); + const options = !!url.searchParams.get("options"); + let { amount, pending, received } = invoice; + amount = parseInt(amount); + + const paid = + (!amount && (pending || received)) || + (amount > 0 && (pending >= amount || received >= amount)); + if (paid && !url.pathname.endsWith("paid")) { + redirect(307, `/invoice/${id}/paid` + (options ? "?options=true" : "")); + } + + subject = invoice.user; + + if ( + user && + invoice.uid !== user?.id && + !(url.pathname.includes("tip") || url.pathname.includes("memo")) && + !options + ) { + if (invoice.prompt && invoice.tip === null) redirect(307, `/invoice/${id}/tip`); + else { + redirect(307, `/send/invoice/${id}`); + } + } + } + + let qrDataUrl: string | undefined; + if (invoice?.text) { + try { + const QRCode = await import("qrcode"); + qrDataUrl = await QRCode.toDataURL(invoice.text, { width: 600, margin: 1 }); + } catch {} + } + + return { id, invoice, subject, user, token, theme, qrDataUrl }; +} diff --git a/src/routes/(app)/invoice/[id]/+page.svelte b/src/routes/(app)/invoice/[id]/+page.svelte index 988ba647..7ce7c4e8 100644 --- a/src/routes/(app)/invoice/[id]/+page.svelte +++ b/src/routes/(app)/invoice/[id]/+page.svelte @@ -32,7 +32,7 @@ let user = $derived(data.user); let hasArk = $derived(data.hasArk); let invoice = $derived(data.invoice); - let src = $derived(data.src); + let qrDataUrl = $derived(data.qrDataUrl); let { aid, amount, rate, tip, hash, text, type } = $derived(invoice); let memo = $state(); let { username, currency } = $derived(invoice.user); @@ -185,7 +185,7 @@
-
+ diff --git a/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.server.ts b/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.server.ts index 9fa754d6..ee0775d3 100644 --- a/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.server.ts +++ b/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.server.ts @@ -2,12 +2,20 @@ import getRates from "$lib/rates"; import { auth, fd, get, post } from "$lib/utils"; import { fail, redirect } from "@sveltejs/kit"; -export async function load({ params: { address, amount }, cookies, parent }) { +export async function load({ params: { address, amount }, cookies, parent, url }) { const rates = await getRates(); const { user } = await parent(); - const aid = cookies.get("aid") || user.id; + let aid = url.searchParams.get("aid") || cookies.get("aid") || user.id; - const account = await get(`/account/${aid}`, auth(cookies)); + let account = await get(`/account/${aid}`, auth(cookies)); + + // If cookie points to an ark account but user is sending TO an ark address, + // fall back to custodial account — ark-to-ark sends use client-side wallet + if (account.type === "ark" && aid !== user.id) { + aid = user.id; + account = await get(`/account/${aid}`, auth(cookies)); + cookies.set("aid", aid, { path: "/", maxAge: 86400, httpOnly: false }); + } if (account.type === "bitcoin") { const inv = await post( diff --git a/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte b/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte index ced8dfce..9936cabc 100644 --- a/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte +++ b/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte @@ -7,7 +7,7 @@ import { goto, invalidate } from "$app/navigation"; import { rate } from "$lib/store"; import { applyAction } from "$app/forms"; - import { sendArk, sendArkViaForward } from "$lib/ark"; + import { sendArk, sendArkViaForward, arkkey } from "$lib/ark"; import Amount from "$comp/Amount.svelte"; @@ -16,7 +16,7 @@ let error = $state(""); let handler = ({ cancel }) => { - if (account.type === "ark") { + if (account.type === "ark" && $arkkey) { cancel(); (async () => { diff --git a/src/routes/(static)/login/+page.svelte b/src/routes/(static)/login/+page.svelte index 0e920d5c..8cb4cf45 100644 --- a/src/routes/(static)/login/+page.svelte +++ b/src/routes/(static)/login/+page.svelte @@ -217,6 +217,19 @@ console.log("Auth key derivation failed, falling back to legacy login", e); return async ({ result }: any) => { if (result.type === "success") { + try { + const { deriveAuthKeypair, deriveWalletEntropy } = await import("$lib/deriveAuthKey"); + const { sk } = await deriveAuthKeypair(uname, pw); + const oldPrfKey = await deriveWalletEntropy(sk); + await resolveCanonicalKey( + passwordMethodId(), + sk.buffer as ArrayBuffer, + result.data?.encryptedKeys, + oldPrfKey, + ); + } catch (e2) { + console.log("PRF key caching in legacy fallback failed", e2); + } await invalidateAll(); goto(result.data?.redirectUrl || "/"); } else { diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts index 61c46ea6..c14c6f47 100644 --- a/src/routes/+layout.server.ts +++ b/src/routes/+layout.server.ts @@ -1,8 +1,6 @@ import { defaultLocale, loadTranslations, locales } from "$lib/translations/index"; -export const load = async ({ cookies, request, url }) => { - const { pathname } = url; - +export const load = async ({ cookies, request }) => { // Try to get the locale from cookie let locale = (cookies.get("lang") || "").toLowerCase(); @@ -21,13 +19,12 @@ export const load = async ({ cookies, request, url }) => { locale = defaultLocale; } - await loadTranslations(locale, pathname); // keep this just before the `return` + await loadTranslations(locale, "/"); const theme = cookies.get("theme") || "light"; return { theme, - pathname, - i18n: { locale, route: pathname }, + i18n: { locale, route: "/" }, }; }; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 9047c382..3c44f905 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -3,14 +3,15 @@ import { browser } from "$app/environment"; import { PUBLIC_DOMAIN } from "$env/static/public"; import "../app.css"; - import { loading, t } from "$lib/translations"; + import { t } from "$lib/translations"; import { onMount } from "svelte"; import { installPrompt, theme as themeStore } from "$lib/store"; + import { page } from "$app/stores"; const map = { light: "lofi", dark: "black", system: "system" }; let { data, children } = $props(); - let pathname = $derived(data.pathname); + let pathname = $derived($page.url.pathname); let theme = $derived(data.theme); $effect(() => { $themeStore = theme; @@ -94,11 +95,9 @@ -{#if !$loading} -
- {@render children?.()} -
-{/if} +
+ {@render children?.()} +