Files
recflare/apps/www/src/client/DeviceLink.tsx
T
Crasho 26270e9c25 [studio] beta studio (#114)
* rrs stuff

* studio service yay

* rrs whitelist

* moree studiooooooo fellowwww

* rec room studio room loading yay

* fixes yay

* fix 2 because idk  yayuh

* Update cdn.app.ts

* ccontent length fix?

* Preserve content-length and upgrade metadata

Return a real zero-filled byte array for CDN HEAD responses so the runtime preserves Content-Length while still sending no payload. In notify, re-create the Request with its original headers, set OWNER_HEADER from the validated token, and forward that Request to hub.fetch to avoid losing WebSocket upgrade metadata (which could trigger 500s in Durable Objects). These changes ensure the editor's size checks and WebSocket upgrades remain reliable.

* fix x524234

* shirt thing?

* [auth] remove grant table as we already have audit_log for now

* rename table to generic device_grant

* cleanup/lint

---------

Co-authored-by: Devin Zuczek <devin.zuczek@gmail.com>
2026-10-05 14:39:22 -04:00

93 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useState } from 'react'
import { call, useAction, where } from './api'
/**
* The page RecFlare Studio opens after `POST /connect/deviceauthorization`.
*
* Studio shows `verification_uri` (`/device`) and opens `verification_uri_complete`
* (`/device?user_code=…`) in the browser. The API that created the code stays on
* `auth`; this page only approves or denies it, with the website session's token.
*/
export function DeviceLinkPage({
account,
search,
navigate,
}: {
/** undefined while the session is still being checked, null when signed out. */
account: { username: string } | null | undefined
search: string
navigate: (to: string) => void
}) {
const fromQuery = new URLSearchParams(search).get('user_code') ?? ''
const [code, setCode] = useState(fromQuery)
const { pending, error, done, run } = useAction()
useEffect(() => {
if (account !== null) return
const next = `/device${search}`
navigate(`/login?next=${encodeURIComponent(next)}`)
}, [account, navigate, search])
if (!account) {
return (
<main className="shell">
<p className="muted">{account === undefined ? 'Loading…' : 'Redirecting to sign in…'}</p>
</main>
)
}
const submit = (path: 'approve' | 'deny') => {
void run(async () => {
await call(`${where().auth}/connect/device/${path}`, {
authed: true,
form: { user_code: code.trim() },
})
return path === 'approve'
? 'Studio is signed in. You can close this tab and go back to the editor.'
: 'Studio was not signed in. You can close this tab.'
})
}
return (
<main className="shell">
<section className="card">
<h2>Sign in to RecFlare Studio</h2>
<p className="muted">
The editor asked to use <strong>{account.username}</strong>. The code below is the one
Studio is showing. Allowing it signs the editor into this account.
</p>
<form
onSubmit={(e) => {
e.preventDefault()
submit('approve')
}}
>
<label>
Code
<input
value={code}
autoComplete="off"
spellCheck={false}
onChange={(e) => setCode(e.target.value)}
required
/>
</label>
<button type="submit" disabled={pending || done !== ''}>
Allow
</button>
</form>
<button
type="button"
disabled={pending || done !== '' || code.trim() === ''}
onClick={() => submit('deny')}
>
Don’t allow
</button>
{error && <p className="error">{error}</p>}
{done && <p className="ok">{done}</p>}
</section>
</main>
)
}