From a3079c2451e5079655e9a366dbcb60864ea09bd2 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Wed, 11 Feb 2026 11:58:12 +0100 Subject: [PATCH] bug-fixes --- public/healthz.json | 4 +- src/lib/services/auth/nip07-signer.ts | 31 +++++++++++ src/lib/utils/pwa-detection.ts | 26 +++++++++ src/routes/login/+page.svelte | 78 ++++++++++++++++++++++++--- 4 files changed, 131 insertions(+), 8 deletions(-) create mode 100644 src/lib/utils/pwa-detection.ts diff --git a/public/healthz.json b/public/healthz.json index d7404bc..afa4000 100644 --- a/public/healthz.json +++ b/public/healthz.json @@ -2,7 +2,7 @@ "status": "ok", "service": "aitherboard", "version": "0.3.0", - "buildTime": "2026-02-11T10:52:47.456Z", + "buildTime": "2026-02-11T10:58:05.118Z", "gitCommit": "unknown", - "timestamp": 1770807167456 + "timestamp": 1770807485118 } \ No newline at end of file diff --git a/src/lib/services/auth/nip07-signer.ts b/src/lib/services/auth/nip07-signer.ts index 1df4b15..064555e 100644 --- a/src/lib/services/auth/nip07-signer.ts +++ b/src/lib/services/auth/nip07-signer.ts @@ -16,6 +16,37 @@ export function isNIP07Available(): boolean { return typeof window !== 'undefined' && 'nostr' in window; } +/** + * Wait for NIP-07 to become available (useful for PWA mode where extensions may inject with delay) + * @param timeout Maximum time to wait in milliseconds (default: 3000ms) + * @param interval Check interval in milliseconds (default: 100ms) + * @returns Promise that resolves to true if NIP-07 becomes available, false if timeout + */ +export async function waitForNIP07( + timeout: number = 3000, + interval: number = 100 +): Promise { + if (isNIP07Available()) { + return true; + } + + return new Promise((resolve) => { + const startTime = Date.now(); + const checkInterval = setInterval(() => { + if (isNIP07Available()) { + clearInterval(checkInterval); + resolve(true); + return; + } + + if (Date.now() - startTime >= timeout) { + clearInterval(checkInterval); + resolve(false); + } + }, interval); + }); +} + /** * Get NIP-07 signer */ diff --git a/src/lib/utils/pwa-detection.ts b/src/lib/utils/pwa-detection.ts new file mode 100644 index 0000000..53a0ddd --- /dev/null +++ b/src/lib/utils/pwa-detection.ts @@ -0,0 +1,26 @@ +/** + * PWA detection utilities + */ + +/** + * Check if the app is running as a PWA (installed/standalone mode) + */ +export function isPWAInstalled(): boolean { + if (typeof window === 'undefined') return false; + + // Check for standalone display mode (most common) + const isStandalone = window.matchMedia('(display-mode: standalone)').matches; + + // Check for iOS standalone mode + const isIOSStandalone = (window.navigator as any).standalone === true; + + return isStandalone || isIOSStandalone; +} + +/** + * Get the current URL to open in browser + */ +export function getBrowserURL(): string { + if (typeof window === 'undefined') return '/'; + return window.location.href; +} diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte index ecabad8..cce93b8 100644 --- a/src/routes/login/+page.svelte +++ b/src/routes/login/+page.svelte @@ -8,12 +8,25 @@ import { sessionManager } from '../../lib/services/auth/session-manager.js'; import { page } from '$app/stores'; import Icon from '../../lib/components/ui/Icon.svelte'; + import { isPWAInstalled, getBrowserURL } from '../../lib/utils/pwa-detection.js'; onMount(async () => { await nostrClient.initialize(); // Check NIP-07 availability dynamically - const { isNIP07Available } = await import('../../lib/services/auth/nip07-signer.js'); + const { isNIP07Available, waitForNIP07 } = await import('../../lib/services/auth/nip07-signer.js'); + + // Initial check nip07Available = isNIP07Available(); + + // In PWA mode, wait a bit for extensions to inject (some extensions inject with delay) + if (isPWA && !nip07Available) { + // Wait up to 2 seconds for NIP-07 to become available + const available = await waitForNIP07(2000); + if (available) { + nip07Available = true; + } + } + await loadStoredKeys(); }); @@ -21,6 +34,26 @@ let loading = $state(false); let activeTab = $state<'nip07' | 'nsec' | 'anonymous'>('nip07'); let nip07Available = $state(false); + let isPWA = $state(false); + + $effect(() => { + isPWA = isPWAInstalled(); + // Re-check NIP-07 availability when PWA state changes + if (isPWA && !nip07Available) { + (async () => { + const { isNIP07Available, waitForNIP07 } = await import('../../lib/services/auth/nip07-signer.js'); + const available = await waitForNIP07(2000); + if (available) { + nip07Available = true; + } + })(); + } + }); + + function openInBrowser() { + const url = getBrowserURL(); + window.open(url, '_blank'); + } // Stored keys let storedNsecKeys = $state>([]); @@ -65,15 +98,29 @@ } async function loginWithNIP07() { - if (!nip07Available) { - error = 'NIP-07 extension not available. Please install a Nostr extension like Alby or nos2x.'; - return; - } - loading = true; error = null; try { + // Re-check NIP-07 availability (extensions might have injected since last check) + const { isNIP07Available, waitForNIP07 } = await import('../../lib/services/auth/nip07-signer.js'); + + // If not available, wait a bit for it to become available (especially in PWA mode) + if (!isNIP07Available()) { + const available = await waitForNIP07(2000); + if (!available) { + if (isPWA) { + error = 'NIP-07 extension not available in PWA mode. Browser extensions may not be accessible in standalone mode. Try opening in your regular browser, or use nsec/anonymous login.'; + } else { + error = 'NIP-07 extension not available. Please install a Nostr extension like Alby or nos2x.'; + } + loading = false; + return; + } + // Update availability state + nip07Available = true; + } + await authenticateWithNIP07(); // Redirect to stored route or default to home const redirectRoute = sessionManager.getLogoutRedirect() || '/'; @@ -337,6 +384,25 @@

Login using a Nostr browser extension (Alby, nos2x, etc.)

+ {#if isPWA && !nip07Available} +
+

+ PWA Mode: Browser extensions may not be accessible in PWA standalone mode, depending on your browser. + The app will attempt to detect your Nostr extension, but if it's not found, you can: +

+
    +
  • Try the login button anyway (some browsers support extensions in PWA mode)
  • +
  • Open aitherboard in your regular browser to use extensions
  • +
  • Use nsec or anonymous login instead
  • +
+ +
+ {/if}