locqr/website/index.html

161 lines
6.8 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>LOCQR test site</title>
<style>
/* Intentionally bare (website/claude.md) — readability only, no application logic. */
body { font-family: system-ui, sans-serif; max-width: 640px; margin: 40px auto; padding: 0 16px; color: #111; }
h1 { font-size: 20px; }
form { display: flex; flex-direction: column; gap: 8px; max-width: 280px; margin-bottom: 32px; }
input, button { padding: 8px; font-size: 14px; }
button { cursor: pointer; }
#log { font-family: ui-monospace, monospace; font-size: 12px; background: #f5f5f5; border: 1px solid #ddd;
padding: 12px; height: 320px; overflow-y: auto; white-space: pre-wrap; }
#log .entry { border-bottom: 1px solid #e5e5e5; padding: 4px 0; }
#log .time { color: #888; }
</style>
</head>
<body>
<h1>LOCQR test site</h1>
<p id="status">Loading registration certificate…</p>
<form id="login-form">
<input id="username" type="text" placeholder="Username" readonly />
<input id="password" type="password" placeholder="Password" readonly />
<button id="sign-in-btn" type="button" disabled>Sign in with LOCQR</button>
<button id="log-out-btn" type="button" style="display: none;">Log out (this site only)</button>
</form>
<h2 style="font-size: 14px;">Event log</h2>
<div id="log"></div>
<script type="module">
import locqr from '/sdk/index.js';
const logEl = document.getElementById('log');
const statusEl = document.getElementById('status');
// "Every SDK event, Promise resolution, rejection, and error — including
// the full payload — is appended to the log in real time with a
// timestamp." (website/claude.md — this is the primary debugging tool.)
function log(label, payload) {
const time = new Date().toISOString().split('T')[1].replace('Z', '');
const entry = document.createElement('div');
entry.className = 'entry';
entry.innerHTML = `<span class="time">${time}</span> <strong>${label}</strong> ${payload !== undefined ? JSON.stringify(payload) : ''}`;
logEl.appendChild(entry);
logEl.scrollTop = logEl.scrollHeight;
}
// Dev extension ID, derived from extension/key.pem (dev.md, "Extension ID
// in development"). Stable as long as key.pem doesn't change.
const DEV_EXTENSION_ID = 'daijigbjegngkjckedcbhhdjkampjgia';
// Stands in for this site's own real session mechanism (a cookie, a
// server-checked session — whatever it already uses). LOCQR has no
// opinion on this and doesn't provide it; reportSession() just needs
// *something* to report, and localStorage is the simplest thing that
// survives a refresh for demo purposes.
const SESSION_KEY = 'locqr-test-session';
const hasSimulatedSession = () => localStorage.getItem(SESSION_KEY) === 'active';
const setSimulatedSession = (active) => {
if (active) localStorage.setItem(SESSION_KEY, 'active');
else localStorage.removeItem(SESSION_KEY);
logOutBtn.style.display = active ? '' : 'none';
};
async function main() {
log('page:loaded');
let cert;
try {
// Registration cert served by the backend rather than hand-copied
// into this page (website/claude.md leaves the mechanism open;
// server/claude.md's GET /domain/registration implements this option).
const response = await fetch('https://api.locqr.dev:3000/domain/registration?domain=test.locqr.dev');
if (!response.ok) throw new Error(`registration fetch failed: ${response.status}`);
({ cert } = await response.json());
log('cert:fetched', { length: cert.length });
} catch (err) {
statusEl.textContent = 'Could not fetch registration certificate — is the server running?';
log('cert:fetch_error', { message: String(err) });
return;
}
statusEl.textContent = 'Initializing LOCQR…';
log('locqr:init', { extensionId: DEV_EXTENSION_ID });
locqr.on('ready', (payload) => log('event:ready', payload));
locqr.on('run:started', (payload) => log('event:run:started', payload));
locqr.on('run:delivered', (payload) => {
log('event:run:delivered', payload);
// Fills the form here too, not just inside the Sign-in button's own
// requestCredential() promise — this event fires for a credential
// delivered from *any* trigger (e.g. the extension popup's own
// Sign in button), and the form should reflect that regardless of
// which side started the run (sdk/claude.md: "sites using the
// event model do not need to call requestCredential()").
usernameEl.value = payload.credential.username;
passwordEl.value = payload.credential.password;
setSimulatedSession(true);
locqr.reportSession({ active: true });
log('reportSession', { active: true });
});
locqr.on('run:error', (payload) => log('event:run:error', payload));
locqr.init({ cert, extensionId: DEV_EXTENSION_ID });
try {
const result = await locqr.ready;
statusEl.textContent = `Ready — features: ${result.features.join(', ')}`;
log('ready:resolved', result);
signInBtn.disabled = false;
const active = hasSimulatedSession();
logOutBtn.style.display = active ? '' : 'none';
locqr.reportSession({ active });
log('reportSession', { active });
} catch (error) {
statusEl.textContent = `Not ready — ${error.code}${error.reason ? ' / ' + error.reason : ''}`;
log('ready:rejected', error);
}
}
const signInBtn = document.getElementById('sign-in-btn');
const logOutBtn = document.getElementById('log-out-btn');
const usernameEl = document.getElementById('username');
const passwordEl = document.getElementById('password');
logOutBtn.addEventListener('click', () => {
setSimulatedSession(false);
usernameEl.value = '';
passwordEl.value = '';
locqr.reportSession({ active: false });
log('reportSession', { active: false });
});
signInBtn.addEventListener('click', async () => {
signInBtn.disabled = true;
usernameEl.value = '';
passwordEl.value = '';
log('requestCredential:called');
try {
// Field population happens in the run:delivered event listener
// above, not here — that fires for a delivery from any trigger,
// this promise only resolves for one started by this exact click.
const credential = await locqr.requestCredential();
log('requestCredential:resolved', { username: credential.username });
} catch (error) {
statusEl.textContent = `Sign-in didn't complete — ${error.code}${error.reason ? ' / ' + error.reason : ''}`;
log('requestCredential:rejected', error);
} finally {
signInBtn.disabled = false;
}
});
main();
</script>
</body>
</html>