diff options
| author | Adam Soltys <[email protected]> | 2026-02-28 18:30:33 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-02-28 18:30:33 -0800 |
| commit | 41467c51e2def5c8932e1dd967f3cfe04e0cc563 (patch) | |
| tree | ff2322fc5573ac32c3a6420d705bddd7fefc15f0 /src | |
| parent | ba715da66afed730180750b567cc3b956a0c9181 (diff) | |
| download | coinos-ui-41467c51e2def5c8932e1dd967f3cfe04e0cc563.tar.gz coinos-ui-41467c51e2def5c8932e1dd967f3cfe04e0cc563.zip | |
service worker for ark wallet refresh
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/InvoiceData.svelte | 4 | ||||
| -rw-r--r-- | src/lib/ark.ts | 64 | ||||
| -rw-r--r-- | src/routes/(app)/+layout.svelte | 17 | ||||
| -rw-r--r-- | src/service-worker.js | 17 |
4 files changed, 94 insertions, 8 deletions
diff --git a/src/components/InvoiceData.svelte b/src/components/InvoiceData.svelte index 88a535a5..0e5be51f 100644 --- a/src/components/InvoiceData.svelte +++ b/src/components/InvoiceData.svelte @@ -29,9 +29,7 @@ <a href={link}> <img src={`/qr/${encodeURIComponent(invoice.text)}/raw`} - class="z-10 border-4 border-white opacity-0 transition-opacity duration-300" - class:opacity-100={loaded} - onload={load} + class="z-10 border-4 border-white" alt={txt} /> </a> diff --git a/src/lib/ark.ts b/src/lib/ark.ts index 1a779f32..c5212f70 100644 --- a/src/lib/ark.ts +++ b/src/lib/ark.ts @@ -1,6 +1,6 @@ import { PUBLIC_COINOS_NETWORK } from "$env/static/public"; import { persistLocal } from "$lib/store"; -import { post } from "$lib/utils"; +import { post, versions } from "$lib/utils"; import { get } from "svelte/store"; export const arkServerUrl = { @@ -64,6 +64,31 @@ export const getWallet = async () => { return walletInstance; }; +export const autoUnlockArk = async () => { + if (get(arkkey)) return; + const { getWalletEntropy } = await import("$lib/walletEntropy"); + const entropy = await getWalletEntropy(); + if (!entropy) return; + try { + const [{ HDKey }, { entropyToMnemonic, mnemonicToSeed }, { wordlist }] = await Promise.all([ + import("@scure/bip32"), + import("@scure/bip39"), + import("@scure/bip39/wordlists/english.js"), + ]); + const { bytesToHex } = await import("@noble/hashes/utils.js"); + const ent = new Uint8Array(entropy).slice(0, 16); + const mnemonic = entropyToMnemonic(ent, wordlist); + const seed = await mnemonicToSeed(mnemonic); + const master = HDKey.fromMasterSeed(seed, versions); + const child = master.derive("m/86'/0'/0'/0/0"); + const key = bytesToHex(child.privateKey!); + arkkey.set(key); + } catch (e) { + console.error("Ark auto-unlock failed:", e); + } +}; + + const toHex = (bytes: Uint8Array) => Array.from(bytes) .map((b) => b.toString(16).padStart(2, "0")) @@ -339,7 +364,7 @@ export const sendArk = async (address: string, amount: number) => { const freshBalance = await wallet.getBalance(); const preSendTotal = (freshBalance.available || 0) + (freshBalance.preconfirmed || 0); if (freshBalance.available < amount) { - throw new Error(`Insufficient funds: ${freshBalance.available} available, need ${amount}`); + throw new Error(`Insufficient funds: ${freshBalance.available} available, need ${amount}\nLocked balance: ${freshBalance.boarding || 0} boarding, ${freshBalance.preconfirmed || 0} preconfirmed, ${freshBalance.recoverable || 0} recoverable`); } let txid; @@ -463,3 +488,38 @@ export const settle = async () => { const vtxos = await wallet.getVtxos({ spendableOnly: false }); if (vtxos?.length) addProcessedVtxos(vtxos.map(vtxoKey)); }; + +// Service Worker wallet for background fund notifications +let swWallet: any; +let swWalletKey: string | undefined; + +export const getServiceWorkerWallet = async () => { + const key = get(arkkey); + if (!key) { + await cleanupServiceWorkerWallet(); + return; + } + if (swWallet && swWalletKey === key) return swWallet; + if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) return; + + try { + const { ServiceWorkerWallet, SingleKey } = await loadArkSdk(); + swWallet = await ServiceWorkerWallet.setup({ + serviceWorkerPath: "/service-worker.js", + arkServerUrl: arkServerUrl!, + identity: SingleKey.fromHex(key), + }); + swWalletKey = key; + return swWallet; + } catch (e) { + console.error("[ark] Service worker wallet init failed:", e); + } +}; + +export const cleanupServiceWorkerWallet = async () => { + if (swWallet) { + try { await swWallet.clear(); } catch {} + } + swWallet = undefined; + swWalletKey = undefined; +}; diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 500880e8..a7224a15 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -1,5 +1,5 @@ <script lang="ts"> - import { getWallet, subscribeToAsp, syncTransactions, settle, refresh, arkkey, arkaid, sendArk, arkSending } from "$lib/ark"; + import { getWallet, subscribeToAsp, syncTransactions, settle, refresh, arkkey, arkaid, sendArk, arkSending, autoUnlockArk, getServiceWorkerWallet, cleanupServiceWorkerWallet } from "$lib/ark"; import { SvelteToast } from "@zerodevx/svelte-toast"; import { onDestroy, onMount } from "svelte"; import { close, connect, send, socket } from "$lib/socket"; @@ -137,6 +137,18 @@ refresh().catch((e) => console.error("Ark refresh failed:", e)); settle().catch((e) => console.error("Ark settle failed:", e)); + // Init service worker wallet for background fund notifications + getServiceWorkerWallet().catch((e) => console.error("SW wallet init:", e)); + + // Listen for VTXO/UTXO updates from the service worker + if ("serviceWorker" in navigator) { + navigator.serviceWorker.addEventListener("message", (event) => { + if (event.data?.type === "VTXO_UPDATE" || event.data?.type === "UTXO_UPDATE") { + arkSync("serviceWorker"); + } + }); + } + // Periodically recover expired VTXOs if (arkRefreshTimer) clearInterval(arkRefreshTimer); arkRefreshTimer = setInterval(() => { @@ -167,6 +179,8 @@ localStorage.removeItem("arkkey:uid"); } + // Auto-unlock ark wallet if silent entropy is available + if (!$arkkey) autoUnlockArk(); } // if (window.NDEFReader) { @@ -222,6 +236,7 @@ clearTimeout(checkTimer); if (arkRefreshTimer) clearInterval(arkRefreshTimer); aspStopFunc?.(); + cleanupServiceWorkerWallet(); } }); </script> diff --git a/src/service-worker.js b/src/service-worker.js index 9d33d322..a366b9d8 100644 --- a/src/service-worker.js +++ b/src/service-worker.js @@ -1,6 +1,19 @@ -self.addEventListener("install", () => {}); -self.addEventListener("activate", () => {}); +import { Worker } from "@arkade-os/sdk"; + +const worker = new Worker(); +// Only add message listener - we handle install/activate ourselves +worker.start(false).catch(console.error); + +self.addEventListener("install", () => { + self.skipWaiting(); +}); + +self.addEventListener("activate", (event) => { + event.waitUntil(self.clients.claim()); +}); + self.addEventListener("fetch", () => {}); + self.addEventListener("push", (event) => { const { title, body, url } = event.data.json(); const icon = "/images/icon.png"; |
