161 lines
6.8 KiB
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>
|