diff --git a/library/components/qa/QaCorner.vue b/library/components/qa/QaCorner.vue index d4ade24dd..341a96b29 100644 --- a/library/components/qa/QaCorner.vue +++ b/library/components/qa/QaCorner.vue @@ -28,6 +28,51 @@ const mapLabel = computed(() => const open = ref(false) function toggle() { open.value = !open.value } +// ── Start a QA session: mint a gateway token → create a qhoot session → open room ── +const starting = ref(false) +const startErr = ref('') +async function startSession() { + starting.value = true; startErr.value = '' + const log = (...a) => { try { console.log('[QaCorner:start]', ...a) } catch (_) {} } + try { + // 1. resolve the org slug (prefer one the user can admin) + const orgsRes = await fetch('/api/orgs', { credentials: 'include' }) + if (!orgsRes.ok) throw new Error('orgs ' + orgsRes.status) + const orgs = (await orgsRes.json())?.orgs || [] + const org = orgs.find(o => (o.roles || []).some(r => /admin|owner|manage/i.test(String(r)))) || orgs[0] + if (!org?.slug) throw new Error('no org membership') + log('org', org.slug) + // 2. mint an RS256 gateway token from the OIDC session + const mintRes = await fetch(`/api/o/${encodeURIComponent(org.slug)}/mcp/mint`, { + method: 'POST', credentials: 'include', + headers: { 'content-type': 'application/json' }, body: JSON.stringify({}) + }) + const mintText = await mintRes.text() + if (!mintRes.ok) throw new Error('mint ' + mintRes.status + ': ' + mintText.slice(0, 160)) + // The mint returns an OAuth-shaped body: the JWT is `access_token` (not `token`). + const token = (() => { try { const j = JSON.parse(mintText); return j.access_token || j.token || '' } catch { return '' } })() + log('minted token?', !!token, 'len', token && token.length) + if (!token) throw new Error('mint returned no token') + // 3. create the qhoot QA session (same-origin /sessions, Bearer) + const crRes = await fetch('/sessions', { + method: 'POST', + headers: { authorization: 'Bearer ' + token, 'content-type': 'application/json' }, + body: JSON.stringify({ visibility: 'public', kind: 'qa', question: 'QA on ' + mapLabel.value }) + }) + const crText = await crRes.text() + if (!crRes.ok) throw new Error('create-session ' + crRes.status + ': ' + crText.slice(0, 200)) + const sess = (() => { try { return JSON.parse(crText) } catch { return {} } })() + const code = sess.code || sess.Code || sess.id || sess.ID + log('session created', code) + if (!code) throw new Error('no session code returned') + // 4. open the multiplayer room + window.open('/qhoot/assembly.html?c=' + encodeURIComponent(code), '_blank') + } catch (e) { + startErr.value = String(e?.message || e) + try { console.error('[QaCorner:start] FAILED:', e) } catch (_) {} + } finally { starting.value = false } +} + // ── auth ────────────────────────────────────────────────────────────────── const authState = ref('checking') // checking | in | out | blocked const authUser = ref(null) @@ -108,8 +153,11 @@ useShadowStyles(`