feat(fusion_clock): Web NFC integration on kiosk page
Adds NDEFReader scan loop, onNfcReading tap dispatcher, handleTap state machine, postJson helper, capturePhoto stub (Task 17), and setup wizard activation with error display. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -112,20 +112,99 @@
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
// Setup wizard
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
// Web NFC reader
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
let ndefReader = null;
|
||||
let nfcReady = false;
|
||||
|
||||
async function startNfcReader() {
|
||||
if (!("NDEFReader" in window)) {
|
||||
throw new Error("Web NFC not supported on this browser/device. Use Chrome on Android.");
|
||||
}
|
||||
ndefReader = new NDEFReader();
|
||||
await ndefReader.scan();
|
||||
ndefReader.addEventListener("reading", onNfcReading);
|
||||
ndefReader.addEventListener("readingerror", () => {
|
||||
console.warn("[nfc-kiosk] reading error; reader still active");
|
||||
});
|
||||
nfcReady = true;
|
||||
}
|
||||
|
||||
function onNfcReading(event) {
|
||||
// event.serialNumber is the card UID — works for raw MIFARE access cards
|
||||
const uid = (event.serialNumber || "").toUpperCase();
|
||||
if (!uid) return;
|
||||
if (currentState === STATE.ENROLL) {
|
||||
// Enroll Mode handles taps differently (wired up in Task 18)
|
||||
window.__nfcKiosk._onEnrollTap && window.__nfcKiosk._onEnrollTap(uid);
|
||||
return;
|
||||
}
|
||||
if (currentState !== STATE.IDLE) return; // ignore taps mid-result
|
||||
handleTap(uid);
|
||||
}
|
||||
|
||||
async function handleTap(uid) {
|
||||
setState(STATE.PROCESSING);
|
||||
let photoB64 = "";
|
||||
try {
|
||||
photoB64 = await capturePhoto();
|
||||
} catch (e) {
|
||||
console.warn("[nfc-kiosk] camera capture failed", e);
|
||||
// Server enforces photo_required if needed
|
||||
}
|
||||
try {
|
||||
const result = await postJson("/fusion_clock/kiosk/nfc/tap", { card_uid: uid, photo_b64: photoB64 });
|
||||
if (result.error === "debounce") {
|
||||
// silent — back to IDLE
|
||||
setState(STATE.IDLE);
|
||||
return;
|
||||
}
|
||||
setState(STATE.RESULT, result);
|
||||
} catch (e) {
|
||||
setState(STATE.RESULT, { error: "network", message: "No connection. Please try again." });
|
||||
}
|
||||
}
|
||||
|
||||
async function postJson(url, params) {
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ jsonrpc: "2.0", method: "call", params }),
|
||||
});
|
||||
const json = await res.json();
|
||||
return json.result || {};
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
// Camera capture (real implementation in Task 17, stub for now)
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
async function capturePhoto() {
|
||||
return ""; // overridden in Task 17
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
// Setup wizard activation
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
const setupBtn = document.getElementById("nfc_setup_start");
|
||||
if (setupBtn) {
|
||||
setupBtn.addEventListener("click", async () => {
|
||||
// Web NFC + camera activation lives in Tasks 16 + 17
|
||||
setState(STATE.IDLE);
|
||||
try {
|
||||
await startNfcReader();
|
||||
setState(STATE.IDLE);
|
||||
} catch (e) {
|
||||
stateContainer.innerHTML = `
|
||||
<div class="nfc-kiosk__setup">
|
||||
<h2 style="color:#d9374e">Setup failed</h2>
|
||||
<p>${escapeHtml(e.message)}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Expose a tiny API for later tasks
|
||||
window.__nfcKiosk = {
|
||||
setState,
|
||||
STATE,
|
||||
photoRequired,
|
||||
debugEnabled,
|
||||
locationConfigured,
|
||||
setState, STATE, photoRequired, debugEnabled, locationConfigured,
|
||||
handleTap, // exposed for mock-tap debug (Task 19)
|
||||
};
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user