feat: move creator pubkey to env, fix mobile TTS + signer, button loaders

Security:
- Move CREATOR_PUBKEY from hardcoded constant to BOTFIGHTS_CREATOR_PUBKEYS
  env var. Shared isCreatorPubkey() in constants.ts used by auth, admin,
  tournaments. Frontend checks authorization via API, not client-side.

Mobile fixes:
- Nostr signer: poll for window.nostr up to 3s (Amber injects late).
- TTS: auto-unlock AudioContext on first user interaction via
  installAutoUnlock() on fight page mount.

UX:
- Add loading spinners to "I BUILD BOTS" and "I FIGHT MYSELF" buttons.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-09 15:31:58 +00:00
co-authored by Claude Opus 4.6
parent dd3cbdae7f
commit 6dc50f5d5d
7 changed files with 55 additions and 40 deletions
+11 -9
View File
@@ -1,10 +1,8 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue'
import { ref, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { useNostr } from '../composables/useNostr'
const CREATOR_PUBKEY = 'da5e0c1b646bdb13c2300f805b0ca3e5afe5b052c594ce78bac8978d21c3fa39'
interface Stats {
uptime: number
rssBytes: number
@@ -51,7 +49,7 @@ interface Fight {
const { pubkey } = useNostr()
const router = useRouter()
const isAuthorized = computed(() => pubkey.value === CREATOR_PUBKEY)
const isAuthorized = ref(false)
const stats = ref<Stats | null>(null)
const bots = ref<Bot[]>([])
const fights = ref<Fight[]>([])
@@ -119,11 +117,15 @@ function botName(id: string): string {
}
onMounted(async () => {
if (!isAuthorized.value) {
router.replace('/')
return
}
await Promise.all([fetchStats(), fetchBots(), fetchFights()])
if (!pubkey.value) { router.replace('/'); return }
// Check authorization via API — no hardcoded pubkeys in frontend
try {
const res = await fetch('/api/admin/stats', { headers: headers() })
if (!res.ok) { router.replace('/'); return }
stats.value = await res.json()
isAuthorized.value = true
} catch { router.replace('/'); return }
await Promise.all([fetchBots(), fetchFights()])
isLoading.value = false
refreshTimer = setInterval(fetchStats, 15_000)
})
+7 -1
View File
@@ -156,7 +156,13 @@ async function initLiveScene() {
liveScene.startMusic()
}
announceDeepIntro()
await liveScene.playEntrance()
try {
await liveScene.playEntrance()
} catch (err) {
console.warn('[FightPage] entrance failed:', err)
}
// Safety: ensure fighters are visible after entrance (prevents invisible characters on mobile)
liveScene._resetPositions()
}
liveLogItems.value.push(