/**
* Every sign-in screen, side by side, reloading as you edit.
*
* bun run preview # from apps/auth, then open http://localhost:1338
*
* No database, no mail, no cookies. Each state is the real screen value — the
* code provider's own `request` called with a made-up state, and the issuer's
* screens from `ui/issuer.ts` — drawn by the real renderer with the real
* theme. What differs from production is only how the page was reached.
*
* `preview-watch.ts` restarts this process when the UI's source changes —
* the renderer, the stylesheet, the copy, the issuer's screens. Each page polls
* for the restart and reloads itself, so saving a file is the whole loop.
*
* A state that is missing here is a state nobody will look at before it
* ships. Adding a screen to the flow means adding it to `STATES`.
*/
import type { CodeProviderState } from '@nestri/auth/provider/code';
import { CodeUI } from '@nestri/auth/ui/code';
import { IssuerScreens } from '@nestri/auth/ui/issuer';
import { MARK_CODE, MARK_GITHUB, MARK_GOOGLE } from '@nestri/auth/ui/mark';
import { HtmlRenderer } from '@nestri/auth/ui/render';
import type { Screen } from '@nestri/auth/ui/screen';
import { deviceConfirm } from '../src/index.js';
import { THEME_NESTRI } from '../src/theme.js';
const PORT = Number(process.env.PORT ?? 1338);
const BOOT = crypto.randomUUID();
const renderer = HtmlRenderer({ theme: THEME_NESTRI });
// Matches `USER_CODE_LENGTH` in the issuer, which is local to it.
const DEVICE_CODE_LENGTH = 8;
const code = CodeUI({ sendCode: async () => {} });
const req = new Request(`http://localhost:${PORT}/`);
const email = 'someone@example.com';
const sent = {
type: 'code',
code: '123456',
claims: { email },
flow: 'x',
expires: 0
} satisfies CodeProviderState;
interface State {
id: string;
group: string;
label: string;
screen: () => Promise | Screen;
}
const STATES: State[] = [
// Email code — what everyone sees.
{
id: 'email',
group: 'Email',
label: 'Enter email',
screen: () => code.request(req, { type: 'start' }, new FormData())
},
{
id: 'email-invalid',
group: 'Email',
label: 'Bad address',
screen: () =>
code.request(req, { type: 'start' }, new FormData(), {
type: 'invalid_claim',
key: 'email',
value: 'nope'
})
},
{
id: 'email-rate-limit',
group: 'Email',
label: 'Rate limited (email)',
screen: () =>
code.request(req, { type: 'start' }, new FormData(), {
type: 'rate_limit'
})
},
{
id: 'code',
group: 'Email',
label: 'Code sent',
screen: () => code.request(req, sent, new FormData())
},
{
id: 'code-resent',
group: 'Email',
label: 'Code resent',
screen: () => code.request(req, { ...sent, resend: true }, new FormData())
},
{
id: 'code-invalid',
group: 'Email',
label: 'Wrong code',
screen: () => code.request(req, sent, new FormData(), { type: 'invalid_code' })
},
{
id: 'code-rate-limit',
group: 'Email',
label: 'Rate limited (code)',
screen: () => code.request(req, sent, new FormData(), { type: 'rate_limit' })
},
// Device grant — a TV or a CLI signing in through a browser.
{
id: 'device-enter',
group: 'Device',
label: 'Enter device code',
screen: () => IssuerScreens.deviceEnter(DEVICE_CODE_LENGTH)
},
{
id: 'device-confirm',
group: 'Device',
label: 'Is this you?',
screen: () =>
IssuerScreens.deviceConfirm({
userCode: 'BCDF3467',
csrf: 'x'
})
},
{
id: 'device-confirm-installer',
group: 'Device',
label: 'Add a host?',
screen: () =>
IssuerScreens.deviceConfirm({
userCode: 'BCDF3467',
csrf: 'x',
wording: deviceConfirm('installer')
})
},
{
id: 'device-approved',
group: 'Device',
label: 'Approved',
screen: () => IssuerScreens.deviceApproved()
},
{
id: 'device-denied',
group: 'Device',
label: 'Denied',
screen: () => IssuerScreens.deviceDenied()
},
{
id: 'device-invalid',
group: 'Device',
label: 'Code not valid',
screen: () => IssuerScreens.deviceInvalidCode()
},
{
id: 'device-too-many',
group: 'Device',
label: 'Too many tries',
screen: () => IssuerScreens.deviceTooManyTries()
},
{
id: 'device-expired',
group: 'Device',
label: 'Expired',
screen: () => IssuerScreens.deviceExpired()
},
{
id: 'device-answered',
group: 'Device',
label: 'Already answered',
screen: () => IssuerScreens.deviceAlreadyAnswered()
},
{
id: 'device-forged',
group: 'Device',
label: 'Forged form',
screen: () => IssuerScreens.deviceForgedForm()
},
// Reached only by accident in production, which is why they need looking at.
{
id: 'unknown-state',
group: 'Errors',
label: 'Sign-in expired',
screen: () => IssuerScreens.unknownState('The browser was in an unknown state.')
},
{
// Not shown today: with one provider the chooser redirects straight
// to it. Kept so a second provider does not arrive to an unstyled page.
id: 'choose',
group: 'Errors',
label: 'Provider chooser (unused)',
screen: () => ({
kind: 'choose',
options: [
{ href: '#', label: 'Continue with Email', mark: MARK_CODE },
{ href: '#', label: 'Continue with GitHub', mark: MARK_GITHUB },
{ href: '#', label: 'Continue with Google', mark: MARK_GOOGLE }
]
})
}
];
/** Polls for a restart of this process and reloads when it sees one. */
const RELOAD = ``;
async function page(state: State): Promise {
const res = renderer.render(await state.screen(), req);
const html = (await res.text()).replace('
Auth previewsaves reload every frame · click a title to open full size
${tiles}
${RELOAD}
', `${RELOAD}`);
// Always 200, so a 400 screen is not mistaken for a broken preview.
return new Response(html, {
headers: { 'Content-Type': 'text/html; charset=utf-8' }
});
}
function gallery(): Response {
const groups = [...new Set(STATES.map((s) => s.group))];
const tiles = groups
.map(
(group) =>
`