bug fixes from sxsw

This commit is contained in:
Dorian
2026-03-14 17:12:41 +00:00
parent dcddc7a5dd
commit b786f68e7a
50 changed files with 1635 additions and 543 deletions
+92 -26
View File
@@ -172,12 +172,21 @@
@approve="store.approveConsent"
@deny="store.denyConsent"
/>
<!-- Nostr identity picker (first-launch for identity-aware apps) -->
<NostrIdentityPicker
:show="showIdentityPicker"
:app-name="store.title || 'App'"
@select="onIdentitySelected"
@cancel="showIdentityPicker = false"
/>
</template>
<script setup lang="ts">
import { ref, watch, onMounted, onBeforeUnmount } from 'vue'
import { useAppLauncherStore } from '@/stores/appLauncher'
import NostrSignConsent from '@/components/NostrSignConsent.vue'
import NostrIdentityPicker from '@/components/NostrIdentityPicker.vue'
import { rpcClient } from '@/api/rpc-client'
interface PaymentRequest {
@@ -197,6 +206,73 @@ const isRefreshing = ref(false)
const iframeLoading = ref(true)
const iframeBlocked = ref(false)
// Nostr identity picker state
const showIdentityPicker = ref(false)
const IDENTITY_STORAGE_KEY = 'archipelago_app_identity_'
interface SelectedIdentity {
id: string
name: string
did: string
pubkey: string
nostr_pubkey?: string
nostr_npub?: string
}
/** Get the stored identity for an app, or null if first launch */
function getStoredIdentity(appUrl: string): SelectedIdentity | null {
try {
const key = IDENTITY_STORAGE_KEY + appUrl.replace(/[^a-z0-9]/gi, '_')
const stored = localStorage.getItem(key)
return stored ? JSON.parse(stored) as SelectedIdentity : null
} catch {
return null
}
}
/** Store the selected identity for an app */
function storeIdentity(appUrl: string, identity: SelectedIdentity) {
try {
const key = IDENTITY_STORAGE_KEY + appUrl.replace(/[^a-z0-9]/gi, '_')
localStorage.setItem(key, JSON.stringify(identity))
} catch { /* ignore */ }
}
/** Handle identity selection from the picker */
function onIdentitySelected(identity: SelectedIdentity) {
showIdentityPicker.value = false
if (store.url) {
storeIdentity(store.url, identity)
}
// Send identity to the iframe
sendSelectedIdentity(identity)
}
/** Send a specific identity to the iframe */
async function sendSelectedIdentity(identity: SelectedIdentity) {
try {
const challenge = `archipelago-identity:${Date.now()}`
const sigRes = await rpcClient.call<{ signature: string }>({
method: 'identity.sign',
params: { id: identity.id, message: challenge }
})
const iframe = iframeRef.value
if (!iframe?.contentWindow) return
iframe.contentWindow.postMessage({
type: 'archipelago:identity',
did: identity.did,
name: identity.name,
pubkey: identity.pubkey,
nostr_pubkey: identity.nostr_pubkey || null,
nostr_npub: identity.nostr_npub || null,
challenge,
signature: sigRes.signature
}, '*')
} catch {
/* identity signing not available */
}
}
// Timers for iframe load detection
let loadTimeoutId: ReturnType<typeof setTimeout> | null = null
let contentCheckId: ReturnType<typeof setTimeout> | null = null
@@ -274,37 +350,27 @@ function checkIframeContent() {
/** Apps that support the Archipelago identity protocol (postMessage) */
function isIdentityAwareApp(url: string): boolean {
return url.includes('indeehub')
return url.includes('indeehub') || url.includes('indeedhub')
}
/** Send the user's default identity to the iframe via postMessage */
/** Send the user's identity to the iframe via postMessage.
* On first launch, shows the identity picker modal.
* On subsequent launches, uses the previously selected identity. */
async function sendIdentityIfSupported() {
if (!store.url || !isIdentityAwareApp(store.url)) return
try {
const res = await rpcClient.call<{ identities: Array<{ id: string; name: string; did: string; pubkey: string; is_default: boolean; nostr_pubkey?: string; nostr_npub?: string }> }>({ method: 'identity.list' })
const defaultId = res.identities?.find(i => i.is_default) || res.identities?.[0]
if (!defaultId) return
// Sign a timestamp challenge to prove ownership
const challenge = `archipelago-identity:${Date.now()}`
const sigRes = await rpcClient.call<{ signature: string }>({
method: 'identity.sign',
params: { id: defaultId.id, message: challenge }
})
const iframe = iframeRef.value
if (!iframe?.contentWindow) return
iframe.contentWindow.postMessage({
type: 'archipelago:identity',
did: defaultId.did,
name: defaultId.name,
pubkey: defaultId.pubkey,
nostr_pubkey: defaultId.nostr_pubkey || null,
nostr_npub: defaultId.nostr_npub || null,
challenge,
signature: sigRes.signature
}, '*')
} catch (e) {
if (import.meta.env.DEV) console.warn('Identity not available — continuing without it', e)
// Check if we have a stored identity for this app
const stored = getStoredIdentity(store.url)
if (stored) {
// Use the previously selected identity
await sendSelectedIdentity(stored)
return
}
// First launch — show the identity picker
showIdentityPicker.value = true
return // Identity will be sent after selection via onIdentitySelected
}
function injectScrollbarHideIfSameOrigin() {