bug fixes from sxsw
This commit is contained in:
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user