diff options
| author | Adam Soltys <[email protected]> | 2026-02-16 11:51:25 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-02-16 11:51:25 -0800 |
| commit | e39bf4f977bba2952a7de4a307b14c5ec862a3a7 (patch) | |
| tree | 1eac038796d376ad073eae6246bf43f15454bd66 /src/lib | |
| parent | fe104c2364de35bcdb2a10ca469cd25c12b04289 (diff) | |
| download | coinos-ui-e39bf4f977bba2952a7de4a307b14c5ec862a3a7.tar.gz coinos-ui-e39bf4f977bba2952a7de4a307b14c5ec862a3a7.zip | |
passkey auth
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/ark.ts | 47 | ||||
| -rw-r--r-- | src/lib/passkey.ts | 14 | ||||
| -rw-r--r-- | src/lib/utils.ts | 38 |
3 files changed, 68 insertions, 31 deletions
diff --git a/src/lib/ark.ts b/src/lib/ark.ts index a90236da..a3939a40 100644 --- a/src/lib/ark.ts +++ b/src/lib/ark.ts @@ -52,10 +52,13 @@ export const getVtxos = async () => { return wallet.getVtxos({ spendableOnly: false }); }; -export const syncTransactions = async (aid: string) => { +const _syncTransactions = async (aid: string) => { const wallet = await getWallet(); if (!wallet) return; + const walletAddr = await wallet.getAddress(); + console.log("arkSync: wallet address", walletAddr); + const transactions: any[] = []; const history = await wallet.getTransactionHistory(); @@ -91,9 +94,17 @@ export const syncTransactions = async (aid: string) => { }); } - if (!transactions.length) return; + const bal = await wallet.getBalance(); + const balance = (bal.available || 0) + (bal.preconfirmed || 0); - return post("/post/ark/sync", { transactions, aid }); + return post("/post/ark/sync", { transactions, aid, balance }); +}; + +export const syncTransactions = async (aid: string) => { + if (arkSending) return; + const p = _syncTransactions(aid); + syncPromise = p; + try { return await p; } finally { if (syncPromise === p) syncPromise = null; } }; export const vtxoKey = (v: { txid: string; vout: number }) => `${v.txid}:${v.vout}`; @@ -113,8 +124,9 @@ export const addProcessedVtxos = (keys: string[]) => { localStorage.setItem("processedArkVtxos", JSON.stringify(arr)); }; -// Flag to suppress notifyIncomingFunds during sends +// Flag to suppress sync during sends export let arkSending = false; +let syncPromise: Promise<any> | null = null; const withTimeout = <T>(promise: Promise<T>, ms: number, label: string) => Promise.race([ @@ -128,23 +140,22 @@ export const sendArk = async (address: string, amount: number) => { const wallet = await getWallet(); if (!wallet) throw new Error("Ark wallet not available"); - // Refresh expired VTXOs before attempting to send - const balance = await wallet.getBalance(); - if (balance.available < amount && balance.recoverable > 0) { - console.log("ark send: refreshing expired VTXOs before send"); - await refresh(); - } - - const freshBalance = await wallet.getBalance(); - if (freshBalance.available < amount) { - throw new Error(`Insufficient funds: ${freshBalance.available} available, need ${amount}`); - } - - // Suppress incoming notifications during send to prevent - // change VTXOs from being credited as incoming payments + // Suppress sync polling and wait for any in-flight sync to finish arkSending = true; + if (syncPromise) await syncPromise.catch(() => {}); try { + // Refresh expired VTXOs before attempting to send + const balance = await wallet.getBalance(); + if (balance.available < amount && balance.recoverable > 0) { + console.log("ark send: refreshing expired VTXOs before send"); + await refresh(); + } + + const freshBalance = await wallet.getBalance(); + if (freshBalance.available < amount) { + throw new Error(`Insufficient funds: ${freshBalance.available} available, need ${amount}`); + } let txid; try { txid = await withTimeout( diff --git a/src/lib/passkey.ts b/src/lib/passkey.ts new file mode 100644 index 00000000..228b254e --- /dev/null +++ b/src/lib/passkey.ts @@ -0,0 +1,14 @@ +import { startRegistration, startAuthentication } from "@simplewebauthn/browser"; +import { post } from "$lib/utils"; + +export async function registerPasskey() { + const options = await post("/passkey/register/options", {}); + const registration = await startRegistration({ optionsJSON: options }); + await post("/passkey/register/verify", registration); +} + +export async function loginWithPasskey() { + const { challengeId, ...options } = await post("/passkey/login/options", {}); + const authentication = await startAuthentication({ optionsJSON: options }); + return { credential: authentication, challengeId }; +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index a96db03d..28b63038 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -508,29 +508,41 @@ export const register = async ( }; if (ip) headers["cf-connecting-ip"] = ip; - let sk; + let sk, token; try { - ({ sk } = await post("/register", { user }, headers)); + ({ sk, token } = await post("/register", { user }, headers)); } catch (e) { ({ message: error } = e as Error); } - try { - await login(user, cookies, ip, host, extraHeaders); + if (user.password) { + try { + await login(user, cookies, ip, host, extraHeaders); + error = null; + } catch (e) { + const { message } = e as Error; + error ||= message; + } + } else if (token) { + const maxAge = 380 * 24 * 60 * 60; + const expires = new Date(); + expires.setSeconds(expires.getSeconds() + maxAge); + const opts = { path: "/", expires }; + cookies.set("username", user.username, opts); + cookies.set("token", token, opts); error = null; - } catch (e) { - const { message } = e as Error; - error ||= message; } const expires = new Date(); expires.setSeconds(expires.getSeconds() + 21000000); - cookies.set("sk", sk, { - path: "/", - expires, - httpOnly: false, - sameSite: "lax", - }); + if (sk) { + cookies.set("sk", sk, { + path: "/", + expires, + httpOnly: false, + sameSite: "lax", + }); + } if (error) return svelteFail(400, { error }); redirect(303, loginRedirect || `/${user.username}`); |
