diff options
| author | Adam Soltys <[email protected]> | 2026-03-04 23:06:35 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-03-04 23:06:35 -0800 |
| commit | e5f37b003d93a17c2a9958697269b2d3228d8830 (patch) | |
| tree | e2c3a3c4d1bec48d323e830f5be35d5832845a9a /src | |
| parent | e6882c6a8987e23215a2538b242431ae603c7101 (diff) | |
| download | coinos-ui-e5f37b003d93a17c2a9958697269b2d3228d8830.tar.gz coinos-ui-e5f37b003d93a17c2a9958697269b2d3228d8830.zip | |
tuning
Diffstat (limited to 'src')
46 files changed, 243 insertions, 462 deletions
diff --git a/src/app.css b/src/app.css index 3c12f531..0c11c09c 100644 --- a/src/app.css +++ b/src/app.css @@ -36,12 +36,6 @@ w-12 h-12 sm:w-16 sm:h-16 drop-shadow-xl; } -@font-face { - font-family: "PublicSans"; - src: url("/fonts/Public_Sans/static/PublicSans-Regular.woff2") format("woff2"); - font-display: optional; -} - :root { --toastContainerTop: auto; --toastContainerRight: auto; @@ -51,7 +45,7 @@ } html { - font-family: PublicSans, sans-serif; + font-family: system-ui, sans-serif; } ::selection { diff --git a/src/app.html b/src/app.html index 42d00a9e..9ed29d0c 100644 --- a/src/app.html +++ b/src/app.html @@ -10,7 +10,6 @@ <meta name="mobile-web-app-capable" content="yes" /> <meta name="mobile-web-app-status-bar-style" content="black-translucent" /> <link rel="apple-touch-icon" href="/images/icon-160.png" /> - <link rel="preload" href="/fonts/Public_Sans/static/PublicSans-Regular.woff2" as="font" type="font/woff2" crossorigin /> <link rel="manifest" href="/manifest.webmanifest" /> %sveltekit.head% <script> diff --git a/src/components/Account.svelte b/src/components/Account.svelte index e29e1765..2d4b1e48 100644 --- a/src/components/Account.svelte +++ b/src/components/Account.svelte @@ -23,27 +23,27 @@ let arkBalance = $state(0); let arkLoading = $state(false); - let tryUnlockArk = async (): Promise<string | false> => { - const deriveArkKey = async (key: ArrayBuffer): Promise<string> => { - const [{ HDKey }, { entropyToMnemonic, mnemonicToSeed }, { wordlist }] = await Promise.all([ - import("@scure/bip32"), - import("@scure/bip39"), - import("@scure/bip39/wordlists/english.js"), - ]); - let mnemonicStr: string; - if (account.seed && isPrfEncrypted(account.seed)) { - const ent = await prfDecrypt(key, account.seed); - mnemonicStr = entropyToMnemonic(ent, wordlist); - } else { - const entropy = new Uint8Array(key).slice(0, 16); - mnemonicStr = entropyToMnemonic(entropy, wordlist); - } - const s = await mnemonicToSeed(mnemonicStr); - const master = HDKey.fromMasterSeed(s, versions); - const arkChild = master.derive("m/86'/0'/0'/0/0"); - return bytesToHex(arkChild.privateKey!); - }; + let deriveArkKey = async (key: ArrayBuffer): Promise<string> => { + const [{ HDKey }, { entropyToMnemonic, mnemonicToSeed }, { wordlist }] = await Promise.all([ + import("@scure/bip32"), + import("@scure/bip39"), + import("@scure/bip39/wordlists/english.js"), + ]); + let mnemonicStr: string; + if (account.seed && isPrfEncrypted(account.seed)) { + const ent = await prfDecrypt(key, account.seed); + mnemonicStr = entropyToMnemonic(ent, wordlist); + } else { + const entropy = new Uint8Array(key).slice(0, 16); + mnemonicStr = entropyToMnemonic(entropy, wordlist); + } + const s = await mnemonicToSeed(mnemonicStr); + const master = HDKey.fromMasterSeed(s, versions); + const arkChild = master.derive("m/86'/0'/0'/0/0"); + return bytesToHex(arkChild.privateKey!); + }; + let tryUnlockArk = async (): Promise<string | false> => { // Try cached PRF key or silent nsec const { getWalletEntropy, deriveNostrEntropy } = await import("$lib/walletEntropy"); const entropy = await getWalletEntropy(); @@ -65,24 +65,38 @@ } }; - let setAccount = async (event, url) => { + let aidParam = $derived(`aid=${id}`); + let withAid = (url: string) => url + (url.includes("?") ? "&" : "?") + aidParam; + + let setAccount = async (event, url, requireKey = false) => { event.preventDefault(); event.stopPropagation(); - document.cookie = `aid=${id}; path=/; max-age=86400`; if (isArk) { if ($arkkey && $arkaid === id) { - await goto(url); - } else { + await goto(withAid(url)); + } else if (requireKey) { const key = await tryUnlockArk(); if (key) { - await goto(url); + await goto(withAid(url)); $arkkey = key; $arkaid = id; } + } else { + // Try silent unlock only, navigate regardless + const { getWalletEntropy } = await import("$lib/walletEntropy"); + const entropy = await getWalletEntropy(); + if (entropy) { + try { + const key = await deriveArkKey(entropy); + $arkkey = key; + $arkaid = id; + } catch {} + } + goto(withAid(url)); } } else { $arkkey = undefined; - goto(url); + goto(withAid(url)); } }; @@ -106,8 +120,7 @@ let goSettings = (e) => { e.preventDefault(); e.stopPropagation(); - document.cookie = `aid=${id}; path=/; max-age=86400`; - goto(`/account/${id}`); + goto(`/account/${id}?${aidParam}`); }; </script> @@ -145,7 +158,7 @@ <!-- <PhClockCounterClockwiseBold width="38" /> --> <!-- </a> --> <a - href={`/account/${id}`} + href={`/account/${id}?${aidParam}`} class="hover:opacity-80 relative w-12 h-12 flex items-center justify-center" aria-label="Account settings" onclick={goSettings} @@ -165,14 +178,14 @@ </div> <div class="flex w-full text-xl gap-2"> - <a href={receiveUrl} class="contents" data-sveltekit-preload-data="tap" onclick={(e) => setAccount(e, receiveUrl)}> + <a href={receiveUrl} class="contents" onclick={(e) => e.stopPropagation()}> <button class="btn !w-auto flex-1" data-testid="account-receive"> <PhHandCoinsBold width="32" style="transform: scaleX(-1)" /> <div class="my-auto">{$t("user.dashboard.receive")}</div> </button> </a> - <a href={`/send`} class="contents" data-sveltekit-preload-data="tap" onclick={(e) => setAccount(e, "/send")}> + <a href={withAid("/send")} class="contents" onclick={(e) => setAccount(e, "/send")}> <button type="button" class="btn !w-auto flex-1"> <PhPaperPlaneRightBold width="32" /> <div class="my-auto">{$t("user.dashboard.send")}</div> diff --git a/src/components/AppHeader.svelte b/src/components/AppHeader.svelte index a12c656e..0d70bf49 100644 --- a/src/components/AppHeader.svelte +++ b/src/components/AppHeader.svelte @@ -4,12 +4,12 @@ import { goto } from "$app/navigation"; import Avatar from "$comp/Avatar.svelte"; import Menu from "$comp/Menu.svelte"; - import { loading, t } from "$lib/translations"; + import { t } from "$lib/translations"; import { page } from "$app/stores"; let { user, subject = $bindable() } = $props(); - let w: number | undefined = $state(); + let w: number = $state(globalThis.innerWidth || 1024); $effect(() => (subject = subject || user)); let bg = $derived( @@ -69,7 +69,6 @@ <svelte:window bind:innerWidth={w} /> -{#if !$loading} <header class="bg-gradient-to-r bg-primary bg-accent h-[175px] w-full relative bg-cover mb-20 !z-30" style:background-image={bg} @@ -79,7 +78,7 @@ {#each links as { href, icon: Icon, label }} <a {href} data-sveltekit-preload-data="tap" aria-label={label}> <button class="flex justify-center items-center bg-base-100 p-2 rounded-full w-12 h-12 sm:w-16 sm:h-16 drop-shadow-xl {opacity(href)}" aria-label={label}> - <Icon width={(w ?? 0) > 640 ? 32 : 24} /> + <Icon width={w > 640 ? 32 : 24} /> </button> </a> {/each} @@ -90,15 +89,14 @@ </a> {/if} </nav> - {#if subject} + {#if subject || user} <div class="absolute md:w-[64px] md:mx-auto lg:left-[154px] xl:left-[194px] left-[calc(50vw-64px)] -bottom-[64px] z-30" > - <Avatar user={subject} /> + <Avatar user={subject || user} /> </div> {/if} </header> -{/if} <style> header { diff --git a/src/components/InvoiceActions.svelte b/src/components/InvoiceActions.svelte index 1c34d5ac..65d218fd 100644 --- a/src/components/InvoiceActions.svelte +++ b/src/components/InvoiceActions.svelte @@ -24,6 +24,9 @@ } = $props(); let { aid, account, address_type, type } = $derived(invoice); + let isOwn = $derived(user?.id === invoice?.user?.id); + let isCustodial = $derived(aid === user?.id); + let showTypeSwitch = $derived(isCustodial); </script> <div class="text-secondary space-y-2 text-xl pt-2"> @@ -50,7 +53,7 @@ </button> <div class="flex flex-wrap gap-2 justify-around text-secondary w-full"> - {#if user?.id === invoice?.user?.id} + {#if isOwn} <button class="btn grow !w-auto" onclick={toggleAmount}> <PhPencilSimpleBold width="32" /> <div class="my-auto">{t("payments.setAmount")}</div> @@ -70,43 +73,43 @@ </button> </div> - - - <div class="flex gap-2"> - <button - class="btn flex-nowrap !w-auto grow" - class:bg-base-300={type === types.lightning} - class:text-secondary={type === types.lightning} - onclick={() => { - $amountPrompt = false; - newAmount = undefined; - setType(types.lightning); - }} - > - <div class="bg-black rounded-full w-8 h-8 items-center justify-center flex"> - <div class="m-auto"> - <PhLightningFill width="24" class="text-yellow-300" /> - </div> - </div> - <div class="my-auto text-lg">Lightning</div> - </button> - - {#if !account?.seed && !(account?.pubkey && account?.fingerprint) && account?.type !== "ark"} + {#if showTypeSwitch} + <div class="flex gap-2"> <button class="btn flex-nowrap !w-auto grow" - class:bg-base-300={type === types.bitcoin} - class:text-secondary={type === types.bitcoin} - onclick={() => setType(types.bitcoin)} + class:bg-base-300={type === types.lightning} + class:text-secondary={type === types.lightning} + onclick={() => { + $amountPrompt = false; + newAmount = undefined; + setType(types.lightning); + }} > - <img src="/images/bitcoin.svg" class="w-8" alt="Bitcoin" /> - <div class="my-auto text-lg">Bitcoin</div> + <div class="bg-black rounded-full w-8 h-8 items-center justify-center flex"> + <div class="m-auto"> + <PhLightningFill width="24" class="text-yellow-300" /> + </div> + </div> + <div class="my-auto text-lg">Lightning</div> </button> - {/if} - </div> - <button type="button" class="btn" onclick={toggleType}> - <img src="/images/liquid.svg" class="w-8" alt="Liquid" /> - <img src="/images/cash.png" class="w-8 my-auto" alt="Ecash" /> - <div class="my-auto">{t("payments.moreOptions")}</div> - </button> + {#if !account?.seed && !(account?.pubkey && account?.fingerprint) && account?.type !== "ark"} + <button + class="btn flex-nowrap !w-auto grow" + class:bg-base-300={type === types.bitcoin} + class:text-secondary={type === types.bitcoin} + onclick={() => setType(types.bitcoin)} + > + <img src="/images/bitcoin.svg" class="w-8" alt="Bitcoin" /> + <div class="my-auto text-lg">Bitcoin</div> + </button> + {/if} + </div> + + <button type="button" class="btn" onclick={toggleType}> + <img src="/images/liquid.svg" class="w-8" alt="Liquid" /> + <img src="/images/cash.png" class="w-8 my-auto" alt="Ecash" /> + <div class="my-auto">{t("payments.moreOptions")}</div> + </button> + {/if} </div> diff --git a/src/components/InvoiceData.svelte b/src/components/InvoiceData.svelte index ece30457..a613256b 100644 --- a/src/components/InvoiceData.svelte +++ b/src/components/InvoiceData.svelte @@ -17,6 +17,7 @@ tip, rate, align = "", + qrDataUrl, t, }: any = $props(); @@ -24,9 +25,17 @@ let { memo } = $derived(invoice); let qrSrc = $derived(`/qr/${encodeURIComponent(invoice.text)}/raw`); - let srcs: string[] = $state([]); + let srcs: string[] = $state(qrDataUrl ? [qrDataUrl] : []); + let lastDataUrl = qrDataUrl; $effect(() => { + // Update if the server-provided data URL changed (e.g. new invoice) + if (qrDataUrl && qrDataUrl !== lastDataUrl) { + lastDataUrl = qrDataUrl; + srcs = [qrDataUrl]; + return; + } + if (qrDataUrl && srcs.length) return; const next = qrSrc; if (!srcs.length) { srcs = [next]; diff --git a/src/components/WalletPass.svelte b/src/components/WalletPass.svelte deleted file mode 100644 index a3f00994..00000000 --- a/src/components/WalletPass.svelte +++ /dev/null @@ -1,100 +0,0 @@ -<script lang="ts"> - import PhEyeBold from "virtual:icons/ph/eye-bold"; - import PhEyeSlashBold from "virtual:icons/ph/eye-slash-bold"; - import { preventDefault } from "svelte/legacy"; - - import { focus } from "$lib/utils"; - import { t } from "$lib/translations"; - import { - rememberForOptions, - defaultRememberForMs, - rememberWalletPassword, - forgetWalletPassword, - } from "$lib/passwordCache"; - let { cancel = $bindable(), password = $bindable(), submit } = $props(); - let revealPassword = $state(); - let rememberForMs = $state(defaultRememberForMs); - - let handleSubmit = async () => { - if (rememberForMs) { - rememberWalletPassword(password, rememberForMs); - } else { - forgetWalletPassword(); - } - await submit(); - }; -</script> - -<div class="fixed inset-0 bg-gray-600/50 overflow-y-auto h-full w-full z-20"> - <div class="relative top-1/3 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white space-y-5"> - <h1 class="text-center text-2xl font-semibold">{$t("payments.enterWalletPass")}</h1> - <form onsubmit={preventDefault(handleSubmit)}> - <div class="relative mb-5"> - {#if revealPassword} - <input - name="password" - data-testid="walletpass-input" - type="text" - required - bind:value={password} - autocapitalize="none" - use:focus - /> - {:else} - <input - name="password" - data-testid="walletpass-input" - type="password" - required - bind:value={password} - autocapitalize="none" - use:focus - /> - {/if} - <button - type="button" - onclick={() => (revealPassword = !revealPassword)} - class="absolute right-5 top-1/2" - aria-label={revealPassword ? "Hide password" : "Show password"} - > - {#if revealPassword} - <PhEyeBold width="32" /> - {:else} - <PhEyeSlashBold width="32" /> - {/if} - </button> - </div> - <div class="mb-5 space-y-2"> - <label for="rememberFor" class="text-sm text-gray-500">Remember for</label> - <select - id="rememberFor" - class="w-full" - value={rememberForMs} - onchange={(e) => (rememberForMs = Number((e.target as HTMLSelectElement).value))} - > - {#each rememberForOptions as option} - <option value={option.ms}>{option.label}</option> - {/each} - </select> - </div> - <div class="w-full flex justify-center gap-2"> - <button - type="button" - data-testid="walletpass-cancel" - class="border rounded-2xl font-semibold mx-auto py-5 px-6 hover:opacity-80 mx-auto grow" - onclick={cancel} - onkeydown={cancel} - > - <div class="my-auto">Cancel</div> - </button> - <button - type="submit" - data-testid="walletpass-submit" - class="border rounded-2xl font-semibold mx-auto py-5 px-6 hover:opacity-80 mx-auto grow" - > - <div class="my-auto">Submit</div> - </button> - </div> - </form> - </div> -</div> diff --git a/src/hooks.server.ts b/src/hooks.server.ts index fcb1d461..7acb2a43 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,5 +1,6 @@ import { ipStore } from "$lib/server/ip"; import { setIpGetter } from "$lib/utils"; +import { redirect } from "@sveltejs/kit"; import type { Handle } from "@sveltejs/kit"; setIpGetter(() => ipStore.getStore()); @@ -17,6 +18,26 @@ export const handle: Handle = async ({ event, resolve }) => { } } + // Sync aid cookie from URL params + try { + const aid = event.url.searchParams.get("aid"); + if (aid) event.cookies.set("aid", aid, { path: "/", maxAge: 86400, httpOnly: false }); + } catch {} + + + // Auth check for (app) routes + const isAppRoute = event.route.id?.startsWith("/(app)"); + const token = event.cookies.get("token"); + const { pathname } = event.url; + const isSweep = pathname.match(/^\/fund\/[^/]+\/sweep/); + const isLogout = pathname === "/logout"; + if (isLogout) { + // Clear token before layout load to prevent infinite redirect loops + event.cookies.set("token", "", { expires: new Date(0), path: "/" }); + } else if (isAppRoute && !token && !isSweep) { + redirect(307, "/login"); + } + return ipStore.run(ip, async () => { const response = await resolve(event); const { pathname } = event.url; diff --git a/src/lib/parse.ts b/src/lib/parse.ts index 5e43a6ad..b5ff5f42 100644 --- a/src/lib/parse.ts +++ b/src/lib/parse.ts @@ -12,8 +12,9 @@ export default async (s, host, cookies) => { let amount; let invoice; let user; + const aid = cookies.get("aid"); - if (t.startsWith("ark") || t.startsWith("tark")) redirect(307, `/send/ark/${t}`); + if (t.startsWith("ark") || t.startsWith("tark")) redirect(307, `/send/ark/${t}${aid ? `?aid=${aid}` : ""}`); if (t.startsWith("http")) redirect(307, t); if (t.startsWith(host)) redirect(307, `http://${t}`); diff --git a/src/lib/passwordCache.ts b/src/lib/passwordCache.ts index 0ed36af1..8ea4a9c9 100644 --- a/src/lib/passwordCache.ts +++ b/src/lib/passwordCache.ts @@ -1,69 +1,11 @@ import { browser } from "$app/environment"; -import { writable } from "svelte/store"; - -const PASSWORD_KEY = "walletPassword"; -const EXPIRES_KEY = "walletPasswordExpiresAt"; - -export const rememberForOptions = [ - { label: "user.settings.dontRemember", ms: 0 }, - { label: "user.settings.8hours", ms: 8 * 60 * 60 * 1000 }, - { label: "user.settings.1day", ms: 24 * 60 * 60 * 1000 }, - { label: "user.settings.1week", ms: 7 * 24 * 60 * 60 * 1000 }, - { label: "user.settings.1month", ms: 30 * 24 * 60 * 60 * 1000 }, - { label: "user.settings.1year", ms: 365 * 24 * 60 * 60 * 1000 }, -]; - -export const defaultRememberForMs = 30 * 24 * 60 * 60 * 1000; - -const clearStoredPassword = () => { - if (!browser) return; - localStorage.removeItem(PASSWORD_KEY); - localStorage.removeItem(EXPIRES_KEY); -}; - -const loadStoredPassword = () => { - if (!browser) return undefined; - const password = localStorage.getItem(PASSWORD_KEY) || undefined; - if (!password) return undefined; - const expiresAtRaw = localStorage.getItem(EXPIRES_KEY); - const expiresAt = expiresAtRaw ? Number(expiresAtRaw) : undefined; - if (expiresAt && Date.now() > expiresAt) { - clearStoredPassword(); - return undefined; - } - return password; -}; - -export const walletPassword = writable<string | undefined>(loadStoredPassword()); - -export const rememberWalletPassword = (password: string, durationMs: number) => { - if (!browser) return; - if (!durationMs) { - clearStoredPassword(); - walletPassword.set(undefined); - return; - } - const expiresAt = Date.now() + durationMs; - localStorage.setItem(PASSWORD_KEY, password); - localStorage.setItem(EXPIRES_KEY, String(expiresAt)); - walletPassword.set(password); -}; - -export const forgetWalletPassword = () => { - clearStoredPassword(); - walletPassword.set(undefined); -}; - -export const getRememberedWalletPassword = () => { - const password = loadStoredPassword(); - walletPassword.set(password); - return password; -}; // PRF key cache const PRF_KEY = "prfKey"; const PRF_EXPIRES_KEY = "prfKeyExpiresAt"; +export const defaultRememberForMs = 30 * 24 * 60 * 60 * 1000; + function hexFromBuffer(buf: ArrayBuffer): string { return Array.from(new Uint8Array(buf)) .map((b) => b.toString(16).padStart(2, "0")) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 74e05f17..9f1eec3a 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -46,7 +46,8 @@ export const get = (url: string, headers = {}) => { return fetch(base + url, { headers: { ...ipHeaders(), ...headers } }) .then(async (r) => { if (r.ok) return r.text(); - throw new Error(await r.text()); + const text = await r.text(); + throw new Error(text.replace(/^"|"$/g, "")); }) .then((body) => { try { @@ -84,7 +85,7 @@ export const post = async ( } catch {} if (message) throw new Error(message); - throw new Error(text); + throw new Error(text.replace(/^"|"$/g, "")); }) .then((body) => { let json; diff --git a/src/locales/ar.json b/src/locales/ar.json index de4af690..44c2a25e 100644 --- a/src/locales/ar.json +++ b/src/locales/ar.json @@ -462,12 +462,6 @@ "yourPassword": "وكلمة السر الخاصة بك", "welcome": "مرحباً!", "clickBelow": "انقروا من هنا لينهيوا حسابكم", - "dontRemember": "لا تتذكر", - "8hours": "8 ساعات", - "1day": "يوم واحد", - "1week": "أسبوع واحد", - "1month": "شهر واحد", - "1year": "سنة واحدة", "preferredDM": "DM Preferred التأخيرات", "preferredDMDescription": "رسائل مباشرة عنيت لك سيتم نشرها في هذه المؤخرات للحصول على الـ \"دي إم\" على عدة مسارات، وضع كل عملية نقل على خطها الخاص.", "noDMURLsWarning": "أنتِ لم تُعدي أي مُعادل صالحة لن تكون قادراً على استخدام الـ دي إم.", @@ -495,7 +489,6 @@ "cancel": "إلغاء", "failedToRoute": "ولا يمكن الوصول إلى المتلقي إذا لم يتم الدفع على الطريق. حاول إرسال كمية أصغر أو زيادة احتياطي رسوم الحد الأقصى.", "message": "الرسالة", - "enterWalletPass": "كلمة السر", "legacyVersion": "النسخة الأصلية", "newVersion": "صيغة جديدة", "income": "وردت", diff --git a/src/locales/bn.json b/src/locales/bn.json index 5623697d..34993590 100644 --- a/src/locales/bn.json +++ b/src/locales/bn.json @@ -432,12 +432,6 @@ "yourPassword": "আপনার পাসওয়ার্ড হল %s", "welcome": "স্বাগতম!", "clickBelow": "আপনার অ্যাকাউন্ট সম্পন্ন করার জন্য নীচের ক্লিক করুন", - "dontRemember": "মনে নেই", - "8hours": "৮ ঘন্টা", - "1day": "১ দিন", - "1week": "১ সপ্তাহ", - "1month": "১ মাস", - "1year": "১ বছর", "preferredDM": "পছন্দসই DM ফিক্স", "preferredDMDescription": "সরাসরি বার্তা পাঠানো হয়েছে তোমার কাছে এই রিলে প্রকাশিত হবে। একাধিক রিলে ডিএমস্ পেতে, প্রতিটা রিলে রিলে করে তার নিজের লাইনে.", "noDMURLsWarning": "আপনি কোন বৈধ রিলেআর ঠিকানা নির্দিষ্ট করেননি । তুমি ডিএমএস ব্যবহার করতে পারবেনা.", @@ -465,7 +459,6 @@ "cancel": "বাতিল", "failedToRoute": "টাকা প্রদান করতে ব্যর্থ, সম্ভবত নিম্নলিখিত ঠিকানায় প্রেরিত হবে। সামান্য বড় অঙ্কের টাকা দিয়ে অথবা তার মূল্য বাড়ানোর চেষ্টা করুন।.", "message": "বার্তা", - "enterWalletPass": "ওয়ালেট পাসওয়ার্ড", "legacyVersion": "লিগ্যাসি সংস্করণ", "newVersion": "নতুন সংস্করণ", "income": "গৃহীত", diff --git a/src/locales/de.json b/src/locales/de.json index 69f26469..502947a6 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -371,12 +371,6 @@ "yourPassword": "und ihr passwort ist", "welcome": "Willkommen!", "clickBelow": "Klicken Sie unten, um die Einrichtung Ihres Kontos abzuschließen", - "dontRemember": "Erinnere dich nicht", - "8hours": "8 stunden", - "1day": "1 tag", - "1week": "1 woche", - "1month": "1 monat", - "1year": "1 jahr", "preferredDM": "Bevorzugte DM Verzögerungen", "preferredDMDescription": "Direkte Nachrichten für Sie werden in diesen Relais veröffentlicht. Um DMs auf mehrere Relais zu bekommen, setzen Sie jede Relais-URL auf ihre eigene Linie.", "noDMURLsWarning": "Sie haben keine gültigen Relais-URLs eingestellt. Sie können DMs nicht verwenden.", @@ -511,7 +505,6 @@ "showQr": "QR anzeigen", "copyPr": "Lohnanforderung kopieren", "amountPrompt": "Immer nach dem Betrag fragen", - "enterWalletPass": "Wallet-Passwort", "failedToRoute": "Die Weiterleitung der Zahlung ist fehlgeschlagen. Der Empfänger ist möglicherweise nicht erreichbar. Versuchen Sie, einen kleineren Betrag zu senden oder die maximale Gebührenreserve zu erhöhen.", "ok": "Ok", "cancel": "Abbrechen", diff --git a/src/locales/el.json b/src/locales/el.json index 5ef9e06e..64ee46a9 100644 --- a/src/locales/el.json +++ b/src/locales/el.json @@ -341,12 +341,6 @@ "yourPassword": "και ο κωδικός σας είναι", "welcome": "Καλώς ήρθατε!", "clickBelow": "Κάντε κλικ παρακάτω για να ολοκληρώσετε τη ρύθμιση του λογαριασμού σας", - "dontRemember": "Δεν θυμάμαι", - "8hours": "8 ώρες", - "1day": "1 ημέρα", - "1week": "1 εβδομάδα", - "1month": "1 μήνας", - "1year": "1 έτος", "preferredDM": "Προτιμώμενη DM Ηλεκτρονόμοι", "preferredDMDescription": "Άμεση μηνύματα που προορίζονται για σας θα δημοσιευθεί σε αυτά τα ρελέ. Για να πάρετε DMs σε πολλαπλά ρελέ, βάλτε κάθε URL ρελέ στη δική του γραμμή.", "noDMURLsWarning": "Δεν έχετε ορίσει κανένα έγκυρο URL ρελέ. Δεν θα μπορείτε να χρησιμοποιήσετε DMs.", @@ -472,7 +466,6 @@ "showQr": "Εμφάνιση QR", "copyPr": "Αντιγραφή αιτήματος πληρωμής", "amountPrompt": "Πάντα προτροπή για ποσό", - "enterWalletPass": "Κωδικός πρόσβασης πορτοφολιού", "failedToRoute": "Η δρομολόγηση της πληρωμής απέτυχε, ο παραλήπτης ενδέχεται να μην είναι προσβάσιμος. Δοκιμάστε να στείλετε ένα μικρότερο ποσό ή να αυξήσετε το μέγιστο απόθεμα χρέωσης.", "ok": "Εντάξει", "cancel": "Ακύρωση", diff --git a/src/locales/en.json b/src/locales/en.json index 82e78a81..c6e89bfd 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -440,12 +440,6 @@ "memoPrompt": "Checkout instructions", "memoPromptPlaceholder": "e.g. Please enter your name for the order", "rememberFor": "Remember for", - "dontRemember": "Don't remember", - "8hours": "8 hours", - "1day": "1 day", - "1week": "1 week", - "1month": "1 month", - "1year": "1 year", "import": "Import secret key", "destination": "Auto withdrawal address", "destinationDesc": "The address that withdrawals will be sent to", @@ -576,7 +570,6 @@ "cancel": "Cancel", "failedToRoute": "Failed to route payment, recipient may not be reachable. Try sending a smaller amount or increasing the max fee reserve.", "message": "Message", - "enterWalletPass": "Wallet password", "legacyVersion": "Legacy version", "newVersion": "New version", "income": "Received", diff --git a/src/locales/es.json b/src/locales/es.json index bbe2fa4c..2372641e 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -359,12 +359,6 @@ "yourPassword": "y su contraseña es", "welcome": "¡Bienvenido!", "clickBelow": "Haga clic a continuación para terminar de configurar su cuenta", - "dontRemember": "No lo recuerdo", - "8hours": "8 horas", - "1day": "1 día", - "1week": "1 semana", - "1month": "1 mes", - "1year": "1 año", "preferredDM": "Preferentes DM Relays", "preferredDMDescription": "Mensajes directos destinados a usted serán publicados a estos relés. Para obtener DMs en múltiples relés, ponga cada URL de relé en su propia línea.", "noDMURLsWarning": "No has establecido ninguna URL válida de relé. No podrás usar DMs.", @@ -485,7 +479,6 @@ "showQr": "Mostrar código QR", "copyPr": "Copiar solicitud de pago", "amountPrompt": "Siempre solicite la cantidad", - "enterWalletPass": "Contraseña de la billetera", "failedToRoute": "No se pudo enviar el pago; es posible que no se pueda contactar al destinatario. Intente enviar un monto menor o aumentar la reserva de tarifa máxima.", "ok": "Ok", "cancel": "Cancelar", diff --git a/src/locales/fa.json b/src/locales/fa.json index ac64fe7e..577f2569 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -359,12 +359,6 @@ "yourPassword": "و رمز عبور شما", "welcome": "خوش آمدید!", "clickBelow": "در زیر کلیک کنید تا حساب خود را تنظیم کنید", - "dontRemember": "یادت نره", - "8hours": "8 ساعت", - "1day": "1 روز", - "1week": "1 هفته", - "1month": "1 ماه", - "1year": "1 سال", "preferredDM": "DM ترجیحی رله ها", "preferredDMDescription": "پیام های مستقیم برای شما به این رله ها منتشر خواهد شد. برای دریافت DM ها در چندین رله، هر URL رله را در خط خود قرار دهید.", "noDMURLsWarning": "شما هیچ URL رله معتبر تنظیم نکرده اید. شما قادر به استفاده از DM ها نخواهید بود.", @@ -484,7 +478,6 @@ "showQr": "نمایش QR", "copyPr": "کپی درخواست پرداخت", "amountPrompt": "همیشه مقدار را درخواست کنید", - "enterWalletPass": "رمز کیف پول", "failedToRoute": "مسیر پرداخت انجام نشد، ممکن است گیرنده قابل دسترسی نباشد. سعی کنید مبلغ کمتری ارسال کنید یا حداکثر ذخیره هزینه را افزایش دهید.", "ok": "Ok", "cancel": "لغو", diff --git a/src/locales/fr.json b/src/locales/fr.json index 934577bf..3266fc76 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -358,12 +358,6 @@ "yourPassword": "et votre mot de passe est", "welcome": "Bienvenue !", "clickBelow": "Cliquez ci-dessous pour terminer la configuration de votre compte", - "dontRemember": "Je ne me souviens pas", - "8hours": "8 heures", - "1day": "1 jour", - "1week": "1 semaine", - "1month": "1 mois", - "1year": "1 an", "continueSettingUp": "Configuration du compte", "bip353": "Paiement BIP 353 DNS", "bip353Description": "Faites découvrir votre adresse sous le nom d'utilisateur@coinos. io donc tout portefeuille compatible BIP 353 peut vous payer" @@ -510,7 +504,6 @@ "showQr": "Afficher le QR", "copyPr": "Copie de la demande de paiement", "amountPrompt": "Toujours demander le montant", - "enterWalletPass": "Mot de passe du portefeuille", "failedToRoute": "Impossible d'acheminer le paiement, le destinataire n'est peut-être pas joignable. Essayez d'envoyer un montant inférieur ou d'augmenter les frais de transaction.", "ok": "ok", "cancel": "Annuler", diff --git a/src/locales/hi.json b/src/locales/hi.json index 3e831bfe..c29cf0a6 100644 --- a/src/locales/hi.json +++ b/src/locales/hi.json @@ -462,12 +462,6 @@ "yourPassword": "आपका पासवर्ड है", "welcome": "स्वागत!", "clickBelow": "अपने खाते की स्थापना समाप्त करने के लिए नीचे क्लिक करें", - "dontRemember": "याद मत करो", - "8hours": "8 घंटे", - "1day": "1 दिन", - "1week": "1 सप्ताह", - "1month": "1 महीना", - "1year": "1 वर्ष", "preferredDM": "पसंदीदा DM रिले", "preferredDMDescription": "प्रत्यक्ष संदेश जिसका मतलब है कि आप इन रिले में प्रकाशित होंगे। एकाधिक रिले पर डीएम प्राप्त करने के लिए, प्रत्येक रिले यूआरएल को अपनी लाइन पर डाल दें।.", "noDMURLsWarning": "आपने किसी भी वैध रिले यूआरएल को सेट नहीं किया है। आप डीएम का उपयोग करने में सक्षम नहीं होंगे।.", @@ -495,7 +489,6 @@ "cancel": "रद्द करना", "failedToRoute": "भुगतान करने में विफल, प्राप्तकर्ता पहुंच योग्य नहीं हो सकता है। एक छोटी राशि भेजने या अधिकतम शुल्क आरक्षित बढ़ाने की कोशिश करें।.", "message": "संदेश", - "enterWalletPass": "वॉलेट पासवर्ड", "legacyVersion": "विरासत संस्करण", "newVersion": "नया संस्करण", "income": "प्राप्त करना", diff --git a/src/locales/it.json b/src/locales/it.json index 0e01b1c9..19d682a3 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -462,12 +462,6 @@ "yourPassword": "e la tua password è", "welcome": "Benvenuto!", "clickBelow": "Clicca qui sotto per completare l'impostazione del tuo account", - "dontRemember": "Non ricordo", - "8hours": "8 ore", - "1day": "1 giorno", - "1week": "1 settimana", - "1month": "1 mese", - "1year": "1 anno", "preferredDM": "DM preferenziale Relè", "preferredDMDescription": "Messaggi diretti significati per voi saranno pubblicati a questi relè. Per ottenere DM su relè multipli, mettere ogni URL relè sulla propria linea.", "noDMURLsWarning": "Non hai impostato alcun URL di relè valido. Non sarà in grado di utilizzare DM.", @@ -495,7 +489,6 @@ "cancel": "Annulla", "failedToRoute": "Impossibile inoltrare il pagamento, il destinatario potrebbe non essere raggiungibile. Prova ad inviare un importo inferiore o aumentando la quota di commissione.", "message": "Messaggio", - "enterWalletPass": "Portafoglio password", "legacyVersion": "Versione legacy", "newVersion": "Nuova versione", "income": "Ricevuto", diff --git a/src/locales/ja.json b/src/locales/ja.json index 7023a219..84e2eeaa 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -359,12 +359,6 @@ "yourPassword": "パスワードは", "welcome": "お問い合わせ!", "clickBelow": "アカウントの設定を完了するには、下記をクリックしてください", - "dontRemember": "覚えていない", - "8hours": "8時間", - "1day": "1 日", - "1week": "1週間", - "1month": "1ヶ月", - "1year": "1 年", "preferredDM": "推奨DM リレー", "preferredDMDescription": "これらのリレーに公開されるダイレクトメッセージ。 複数のリレーでDMを取得するには、各リレーURLを各行に置きます.", "noDMURLsWarning": "有効なリレー URL を設定していません。 DMは使用できません.", @@ -484,7 +478,6 @@ "showQr": "QRコードを表示", "copyPr": "支払い請求のコピー", "amountPrompt": "常に金額の入力を求める", - "enterWalletPass": "ウォレットパスワード", "failedToRoute": "支払いのルーティングに失敗しました。受取人に連絡が取れない可能性があります。送金金額を減らすか、最大手数料準備金を増やしてみてください。", "ok": "お問い合わせ", "cancel": "キャンセル", diff --git a/src/locales/ko.json b/src/locales/ko.json index 87feb0f9..9ae9892f 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -462,12 +462,6 @@ "yourPassword": "비밀번호", "welcome": "제품 정보!", "clickBelow": "계정 설정하기", - "dontRemember": "기억하지 마십시오", - "8hours": "8시간", - "1day": "1 일", - "1week": "1 주일", - "1month": "1 개월", - "1year": "1 년", "preferredDM": "선호하는 DM 연락처", "preferredDMDescription": "직접 메시지는이 릴레이에 게시됩니다. 여러 개의 릴레이에 DM을 얻으려면 각 릴레이 URL을 자체 라인에 넣어.", "noDMURLsWarning": "유효한 릴레이 URL을 설정하지 않았습니다. DM을 사용할 수 없습니다.", @@ -495,7 +489,6 @@ "cancel": "이름 *", "failedToRoute": "결제에 실패, 수신자는 도달 할 수 없습니다. 더 작은 금액을 보내거나 최대 수수료 예약을 증가하십시오.", "message": "이름 *", - "enterWalletPass": "지갑 암호", "legacyVersion": "Legacy 버전", "newVersion": "새 버전", "income": "이름 *", diff --git a/src/locales/nl.json b/src/locales/nl.json index d06834de..e1ca21f0 100644 --- a/src/locales/nl.json +++ b/src/locales/nl.json @@ -462,12 +462,6 @@ "yourPassword": "Uw wachtwoord is:", "welcome": "Welkom!", "clickBelow": "Klik hieronder om het aanmaken van uw account af te ronden", - "dontRemember": "Ik weet het niet meer", - "8hours": "8 uur", - "1day": "1 dag", - "1week": "1 week", - "1month": "1 maand", - "1year": "1 jaar", "preferredDM": "Voorkeur DM Relais", "preferredDMDescription": "Directe berichten bedoeld voor u zullen worden gepubliceerd naar deze relais. Om DM's te krijgen op meerdere relais, zet elke relais URL op zijn eigen lijn.", "noDMURLsWarning": "U heeft geen geldige relais-URL's ingesteld. U kunt geen DM's gebruiken.", @@ -495,7 +489,6 @@ "cancel": "Annuleren", "failedToRoute": "Het is mogelijk dat de ontvanger niet bereikbaar is. Probeer het verzenden van een kleiner bedrag of controleer met de ontvanger.", "message": "Notitie", - "enterWalletPass": "Wachtwoord portemonnee", "legacyVersion": "Legacy versie", "newVersion": "Nieuwe versie", "income": "Ontvangen", diff --git a/src/locales/pl.json b/src/locales/pl.json index 313c7df3..b0634514 100644 --- a/src/locales/pl.json +++ b/src/locales/pl.json @@ -462,12 +462,6 @@ "yourPassword": "twoje hasło jest", "welcome": "Witamy!", "clickBelow": "Kliknij poniżej, aby skonfigurować konto", - "dontRemember": "Nie pamiętam", - "8hours": "8 godzin", - "1day": "1 dzień", - "1week": "1 tydzień", - "1month": "1 miesiąc", - "1year": "1 rok", "preferredDM": "Preferowany DM Przekaźniki", "preferredDMDescription": "Bezpośrednie wiadomości przeznaczone dla Ciebie zostaną opublikowane do tych przekaźników. Aby uzyskać DM na wielu przekaźników, umieścić każdy przekaźnik URL na własnej linii.", "noDMURLsWarning": "Nie ustawiłeś żadnych poprawnych adresów przekaźnikowych. Nie będziesz mógł używać DM.", @@ -495,7 +489,6 @@ "cancel": "Anuluj", "failedToRoute": "Nie udało się dokonać płatności za trasę, odbiorca może nie być dostępny. Spróbuj wysłać mniejszą kwotę lub zwiększenie maksymalnej rezerwy opłat.", "message": "Wiadomość", - "enterWalletPass": "Hasło portfela", "legacyVersion": "Wersja w języku litewskim", "newVersion": "Nowa wersja", "income": "Otrzymane", diff --git a/src/locales/pt.json b/src/locales/pt.json index 745c0853..93a03632 100644 --- a/src/locales/pt.json +++ b/src/locales/pt.json @@ -378,12 +378,6 @@ "yourPassword": "e sua senha é", "welcome": "Bem-vindos!", "clickBelow": "Clique abaixo para terminar de configurar sua conta", - "dontRemember": "Não me lembro", - "8hours": "8 horas", - "1day": "1 dia", - "1week": "1 semana", - "1month": "1 mês", - "1year": "1 ano", "preferredDM": "DM Preferido Relés", "preferredDMDescription": "Mensagens diretas destinadas a você serão publicadas para estes relés. Para obter DMs em vários relés, coloque cada URL relé em sua própria linha.", "noDMURLsWarning": "Você não definiu nenhum URL de retransmissão válido. Você não poderá usar DMs.", @@ -506,7 +500,6 @@ "showQr": "Mostrar QR", "copyPr": "Copiar solicitação de pagamento", "amountPrompt": "Sempre solicite o valor", - "enterWalletPass": "Senha da carteira", "failedToRoute": "Falha ao encaminhar o pagamento, o destinatário pode não estar acessível. Tente enviar uma quantia menor ou aumentar a reserva de taxa máxima.", "ok": "Ok", "cancel": "Cancelar", diff --git a/src/locales/ru.json b/src/locales/ru.json index 508f31ee..a4dfdecb 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -359,12 +359,6 @@ "yourPassword": "ваш пароль является", "welcome": "Добро пожаловать!", "clickBelow": "Нажмите ниже, чтобы закончить настройку вашей учетной записи", - "dontRemember": "Не помни", - "8hours": "8 часов", - "1day": "1 день", - "1week": "1 неделя", - "1month": "1 месяц", - "1year": "1 год", "preferredDM": "Предпочтительный DM реле", "preferredDMDescription": "Прямые сообщения, предназначенные для вас, будут опубликованы в этих реле. Чтобы получить DM на нескольких реле, поместите каждый URL реле на свою собственную строку.", "noDMURLsWarning": "Вы не установили никаких действительных URL-адресов. Вы не сможете использовать DM.", @@ -484,7 +478,6 @@ "showQr": "Показать QR-код", "copyPr": "Копировать запрос на оплату", "amountPrompt": "Всегда запрашивать сумму", - "enterWalletPass": "Пароль кошелька", "failedToRoute": "Не удалось направить платеж, получатель может быть недоступен. Попробуйте отправить меньшую сумму или увеличить максимальный резерв комиссии.", "ok": "Хорошо", "cancel": "Отменить", diff --git a/src/locales/th.json b/src/locales/th.json index 0de7077f..db3c19ba 100644 --- a/src/locales/th.json +++ b/src/locales/th.json @@ -432,12 +432,6 @@ "yourPassword": "และรหัสผ่านของคุณ", "welcome": "ยินดีต้อนรับ!", "clickBelow": "คลิกด้านล่างนี้เพื่อทําบัญชีให้เสร็จ", - "dontRemember": "จําไม่ได้", - "8hours": "8 ชั่วโมง", - "1day": "1 วัน", - "1week": "1 สัปดาห์", - "1month": "1 เดือน", - "1year": "1 ปี", "preferredDM": "ค่า DM ที่ต้องการ อําลา", "preferredDMDescription": "ข้อความตรงมีความหมายสําหรับคุณ จะถูกเผยแพร่ไปยังเครื่องส่งเหล่านี้ เพื่อเอา DMs มาวางบนเส้นผ่านหลาย ๆ เส้น วางที่อยู่ URL แต่ละสายบนเส้นของตัวเอง.", "noDMURLsWarning": "คุณยังไม่ได้ตั้งค่าที่อยู่ URL ที่ถูกต้อง คุณจะไม่สามารถใช้ DMs ได้.", @@ -465,7 +459,6 @@ "cancel": "ยกเลิก", "failedToRoute": "การจ่ายเงินตามเส้นทางล้มเหลว อาจไม่สามารถเข้าถึงผู้รับได้ ลอง ส่ง เงิน ที่ มี ค่า น้อย กว่า หรือ ทํา การ เพิ่ม อัตรา ค่า ธรรมเนียม ให้ มาก ที่ สุด.", "message": "จดหมาย", - "enterWalletPass": "รหัสผ่านของกระเป๋าคุมข้อมูล", "legacyVersion": "รุ่นของมรดก", "newVersion": "รุ่นใหม่", "income": "ได้รับ", diff --git a/src/locales/tr.json b/src/locales/tr.json index b2c198b0..054ba0e6 100644 --- a/src/locales/tr.json +++ b/src/locales/tr.json @@ -462,12 +462,6 @@ "yourPassword": "ve şifreniziz", "welcome": "Hoşgeldiniz!", "clickBelow": "Hesabınızı kurmak için aşağıda tıklayın", - "dontRemember": "Unutma", - "8hours": "8 saat 8", - "1day": "1 gün", - "1week": "1 hafta", - "1month": "1 ay", - "1year": "1 yıl", "preferredDM": "Tercih edilen DM Relays", "preferredDMDescription": "Doğrudan Mesajlar bu rölelere yayınlanmanız anlamına geliyordu. DM'leri birden fazla röleğe almak için, her röle URL'yi kendi çizgisine koyun.", "noDMURLsWarning": "Herhangi bir geçerli röle URL ayarlamadınız. DM'leri kullanabileceksiniz.", @@ -495,7 +489,6 @@ "cancel": "Cancel", "failedToRoute": "Yol ödemesine başarısız oldu, alıcı ulaşılamaz olabilir. Daha küçük bir miktar göndermeye veya maksimum ücret rezervini arttırmaya çalışın.", "message": "Mesaj", - "enterWalletPass": "Cüzdan şifresi", "legacyVersion": "Legacy Version", "newVersion": "Yeni sürüm", "income": "Kabul edildi", diff --git a/src/locales/zh.json b/src/locales/zh.json index 6c9cd0af..4e37db9e 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -359,12 +359,6 @@ "yourPassword": "而您的密码是", "welcome": "欢迎光临!", "clickBelow": "点击下面完成您的账户设置", - "dontRemember": "不记得了", - "8hours": "8小时", - "1day": "1天(以千美元计)", - "1week": "1个星期", - "1month": "1个月", - "1year": "1 岁", "preferredDM": "首选管理部 中继", "preferredDMDescription": "给您的直接信件将被发布到这些转发中 。 为了让DMs在多个中继器上,将每个中继URL放入自己的线路.", "noDMURLsWarning": "您尚未设置任何有效的中继 URL 。 你将无法使用DMs.", @@ -484,7 +478,6 @@ "showQr": "显示二维码", "copyPr": "复制付款请求", "amountPrompt": "始终提示金额", - "enterWalletPass": "钱包密码", "failedToRoute": "付款路线失败,收款人可能无法联系到。请尝试发送较少的金额或增加最大费用储备。", "ok": "确认", "cancel": "取消", 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 @@ <main class="pb-4 min-h-dvh" data-theme={theme}> <OfflineBanner /> <AppHeader {user} {subject} /> - {#if !$loading} - {@render children?.()} - {/if} + {@render children?.()} </main> 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 @@ </a> {/if} {#if user?.id && user.id === subject.id} - <div class="space-y-4" data-sveltekit-preload-data="false"> + <div class="space-y-4"> {#each accounts as account} <Account {user} {rates} {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.ts index 24b7f1b0..411486c7 100644 --- a/src/routes/(app)/invoice/[id]/+layout.server.ts +++ b/src/routes/(app)/invoice/[id]/+layout.ts @@ -1,24 +1,23 @@ -import getRates from "$lib/rates"; import { get } from "$lib/utils"; import { redirect } from "@sveltejs/kit"; -export async function load({ cookies, depends, params, url, parent }) { +export async function load({ params, url, parent, depends }) { depends("app:invoice"); - const token = cookies.get("token"); - let { subject, user } = await parent(); + const { user, token, theme } = await parent(); const { id } = params; let invoice; + let subject; if (id === "lnurl") { - const rates = await getRates(); + subject = user; invoice = { aid: user.id, uid: user.id, amount: 0, tip: 0, - rate: rates[user?.currency || "USD"], - text: "", + rate: 0, + text: `${user.username}@${url.host}`, hash: "", type: "lightning", items: [], @@ -42,7 +41,6 @@ export async function load({ cookies, depends, params, url, parent }) { subject = invoice.user; - const aid = cookies.get("aid") || user?.id; if ( user && invoice.uid !== user?.id && @@ -56,6 +54,13 @@ export async function load({ cookies, depends, params, url, parent }) { } } - const theme = cookies.get("theme") || "light"; - return { id, invoice, subject, user, token, theme }; + 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 @@ <div class="invoice container mx-auto max-w-xl px-4 pb-4 space-y-2"> <InvoiceData - {src} + {qrDataUrl} {link} {txt} invoice={displayInvoice} diff --git a/src/routes/(app)/invoice/[id]/tip/+page.server.ts b/src/routes/(app)/invoice/[id]/tip/+page.server.ts index 91ccebca..84b218f3 100644 --- a/src/routes/(app)/invoice/[id]/tip/+page.server.ts +++ b/src/routes/(app)/invoice/[id]/tip/+page.server.ts @@ -4,8 +4,9 @@ import { fail, redirect } from "@sveltejs/kit"; export async function load({ cookies, params, parent }) { const { id } = params; - const rates = await getRates(); - let { invoice, subject, user } = await parent(); + const [rates, { user }] = await Promise.all([getRates(), parent()]); + let invoice = await get(`/invoice/${id}`, auth(cookies)); + const subject = invoice.user; if (!invoice.amount) redirect(307, `/invoice/${invoice.id}`); const pin = cookies.get("pin"); diff --git a/src/routes/(app)/send/ark/[address]/+layout.server.ts b/src/routes/(app)/send/ark/[address]/+layout.server.ts index 6381463a..18fb7aed 100644 --- a/src/routes/(app)/send/ark/[address]/+layout.server.ts +++ b/src/routes/(app)/send/ark/[address]/+layout.server.ts @@ -1,11 +1,18 @@ import { auth, get } from "$lib/utils"; import { redirect } from "@sveltejs/kit"; -export async function load({ cookies, params, parent }) { +export async function load({ cookies, params, parent, url }) { const { user } = await parent(); - const aid = cookies.get("aid") || user.id; + let aid = url.searchParams.get("aid") || cookies.get("aid") || user.id; const { address, amount } = params; - const { balance } = await get(`/account/${aid}`, auth(cookies)); + let account = await get(`/account/${aid}`, auth(cookies)); + + // Fall back to custodial if cookie points to ark account + 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 }); + } let invoice; try { @@ -21,5 +28,5 @@ export async function load({ cookies, params, parent }) { if (recipient?.id !== user.id && recipient.id === invoice.aid) redirect(307, r); } - return { balance }; + return { balance: account.balance }; } diff --git a/src/routes/(app)/send/ark/[address]/+page.server.ts b/src/routes/(app)/send/ark/[address]/+page.server.ts index fdf129a0..83a5050f 100644 --- a/src/routes/(app)/send/ark/[address]/+page.server.ts +++ b/src/routes/(app)/send/ark/[address]/+page.server.ts @@ -1,10 +1,18 @@ import getRates from "$lib/rates"; import { auth, get } from "$lib/utils"; -export async function load({ cookies, parent }) { +export async function load({ cookies, parent, url }) { const { user } = await parent(); const rates = await getRates(); - const aid = cookies.get("aid") || user.id; - const { balance } = await get(`/account/${aid}`, auth(cookies)); - return { balance, rate: rates[user.currency] }; + let aid = url.searchParams.get("aid") || cookies.get("aid") || user.id; + let account = await get(`/account/${aid}`, auth(cookies)); + + // Fall back to custodial if cookie points to ark account + 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 }); + } + + return { balance: account.balance, rate: rates[user.currency] }; } diff --git a/src/routes/(app)/send/ark/[address]/+page.svelte b/src/routes/(app)/send/ark/[address]/+page.svelte index 631f6689..251bca72 100644 --- a/src/routes/(app)/send/ark/[address]/+page.svelte +++ b/src/routes/(app)/send/ark/[address]/+page.svelte @@ -4,7 +4,7 @@ import Numpad from "$comp/Numpad.svelte"; import { page } from "$app/stores"; import { fiat as fiatStore, rate } from "$lib/store"; - import { loc, fail, s, f, sats } from "$lib/utils"; + import { loc, s, f, sats } from "$lib/utils"; let { data } = $props(); let balance = $derived(data.balance); @@ -47,7 +47,7 @@ {/if} </button> - <form action={`/send/ark/${address}/${amount}`} class="contents"> + <form action={`/send/ark/${address}/${amount}${$page.url.search}`} class="contents"> <button bind:this={submit} type="submit" class="btn !w-auto grow btn-accent"> {$t("payments.next")} </button> 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<any>(); - let pathname = $derived(data.pathname); + let pathname = $derived($page.url.pathname); let theme = $derived(data.theme); $effect(() => { $themeStore = theme; @@ -94,11 +95,9 @@ <meta name="twitter:creator" content="@coinoswallet" /> </svelte:head> -{#if !$loading} - <main> - {@render children?.()} - </main> -{/if} +<main> + {@render children?.()} +</main> <style> :global(:root) { |
