owner_app/app.js
Read-only source from the unchanged 0.3.4 kit. Existing license terms apply.
(function () {
"use strict";
let currentWallet = null;
let currentPacket = null;
let currentAnchor = null;
let currentReport = null;
let createdPacket = null;
let createdAnchor = null;
function element(id) { return document.getElementById(id); }
function downloadJson(filename, value) {
const blob = new Blob([JSON.stringify(value, null, 2) + "\n"], { type: "application/json" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
link.remove();
setTimeout(() => URL.revokeObjectURL(url), 1000);
}
async function readJsonFile(input) {
if (!input.files || input.files.length !== 1) throw new Error("Choose one JSON file first.");
if (input.files[0].size > 1024 * 1024) throw new Error("File exceeds the 1 MiB limit for identity JSON files.");
try {
return JSON.parse(await input.files[0].text());
} catch (_error) {
throw new Error("The selected file is not valid JSON.");
}
}
function setBusy(button, busy, label) {
if (!button.dataset.label) button.dataset.label = button.textContent;
button.disabled = busy;
button.textContent = busy ? label : button.dataset.label;
}
function showError(container, error) {
container.classList.remove("hidden");
container.classList.add("error");
container.replaceChildren();
const heading = document.createElement("h3");
heading.textContent = "Unable to continue";
const message = document.createElement("p");
message.textContent = error instanceof Error ? error.message : String(error);
container.append(heading, message);
}
element("generate").addEventListener("click", async function () {
const button = this;
const result = element("create-result");
result.classList.add("hidden");
element("create-error").classList.add("hidden");
try {
const password = element("new-password").value;
if (password !== element("confirm-password").value) throw new Error("The two passwords do not match.");
setBusy(button, true, "Generating securely…");
currentWallet = await AILCrypto.generateWallet({
display_name: element("display-name").value,
principal: element("principal").value,
identity_anchor: element("identity-anchor").value
}, password);
currentPacket = await AILCrypto.createIdentityPacket(currentWallet, password);
currentAnchor = await AILCrypto.createPublicAnchor(currentWallet);
createdPacket = currentPacket;
createdAnchor = currentAnchor;
result.classList.remove("hidden", "error");
element("created-key-id").textContent = "Key fingerprint: " + currentWallet.key.key_id;
element("new-password").value = "";
element("confirm-password").value = "";
} catch (error) {
currentWallet = null;
currentPacket = null;
currentAnchor = null;
showError(element("create-error"), error);
} finally {
setBusy(button, false, "");
}
});
element("download-wallet").addEventListener("click", function () {
if (!currentWallet) return;
downloadJson("AIL_Owner_Wallet_PRIVATE.ailwallet.json", currentWallet);
});
element("download-first-packet").addEventListener("click", function () {
if (!createdPacket) return;
downloadJson("AIL_Identity_Packet.json", createdPacket);
});
element("download-public-anchor").addEventListener("click", function () {
if (!createdAnchor) return;
downloadJson("ail-owner.json", createdAnchor);
});
element("sign-packet").addEventListener("click", async function () {
const button = this;
const result = element("sign-result");
result.classList.add("hidden");
element("sign-error").classList.add("hidden");
try {
setBusy(button, true, "Verifying and signing…");
const wallet = await readJsonFile(element("wallet-file"));
const newAnchor = element("sign-anchor").value.trim();
const anchorOverride = newAnchor || undefined;
currentPacket = await AILCrypto.createIdentityPacket(wallet, element("wallet-password").value, undefined, anchorOverride);
currentAnchor = await AILCrypto.createPublicAnchor(wallet, anchorOverride);
result.classList.remove("hidden", "error");
element("signed-summary").textContent = currentPacket.subject.display_name + " · " + currentPacket.public_key.fingerprint;
element("wallet-password").value = "";
} catch (error) {
currentPacket = null;
currentAnchor = null;
showError(element("sign-error"), error);
} finally {
setBusy(button, false, "");
}
});
element("download-signed-packet").addEventListener("click", function () {
if (!currentPacket) return;
downloadJson("AIL_Identity_Packet.json", currentPacket);
});
element("download-updated-anchor").addEventListener("click", function () {
if (!currentAnchor) return;
downloadJson("ail-owner.json", currentAnchor);
});
element("verify-packet").addEventListener("click", async function () {
const button = this;
const result = element("verify-result");
currentReport = null;
element("download-report").disabled = true;
try {
setBusy(button, true, "Verifying…");
const packet = await readJsonFile(element("packet-file"));
const expected = element("trusted-fingerprint").value.trim();
const check = expected
? await AILCrypto.verifyTrustedFingerprint(packet, expected)
: await AILCrypto.verifyIdentityPacket(packet);
let anchorCheck = null;
if (check.signature_status === "ail:cryptographically_verified" && element("anchor-file").files.length) {
const anchor = await readJsonFile(element("anchor-file"));
anchorCheck = await AILCrypto.comparePublicAnchor(packet, anchor, packet.subject.identity_anchor);
}
const passed = check.valid && (!anchorCheck || anchorCheck.valid);
currentReport = { format: "AIL-VERIFICATION-REPORT/0.3.4", checked_at: new Date().toISOString(),
valid: passed, signature: check, supplied_anchor: anchorCheck,
note: "Unsigned local check report. No legal identity or external permissions are established." };
element("download-report").disabled = false;
result.classList.remove("hidden", "error");
result.classList.toggle("error", !passed);
result.replaceChildren();
const heading = document.createElement("h3");
heading.textContent = !passed ? "Verification failed — review details"
: expected ? "Signature valid · matches expected key"
: "Signature valid · identity remains self-declared";
const details = document.createElement("pre");
details.textContent = [
"Declared name: " + (check.display_name || "unverified"),
"Signature: " + (check.signature_status || "ail:rejected"),
"Expected key: " + (check.trusted_key_status || "not checked"),
"Identity assurance: " + (check.identity_assurance || "not established"),
"Fingerprint: " + (check.key_id || "unavailable"),
check.reason,
anchorCheck ? "Supplied anchor: " + anchorCheck.reason : "Supplied anchor: not checked"
].join("\n");
result.append(heading, details);
} catch (error) {
showError(result, error);
} finally {
setBusy(button, false, "");
}
});
element("download-report").addEventListener("click", function () {
if (currentReport) downloadJson("AIL_Verification_Report.json", currentReport);
});
for (const id of ["packet-file", "anchor-file", "trusted-fingerprint"]) {
element(id).addEventListener("input", function () {
currentReport = null;
element("download-report").disabled = true;
element("verify-result").classList.add("hidden");
});
}
element("clear-session").addEventListener("click", function () {
if (!confirm("Have you saved your private wallet backup? Clear loaded files and generated results from this page?")) return;
currentWallet = currentPacket = currentAnchor = currentReport = createdPacket = createdAnchor = null;
document.querySelectorAll("input").forEach((input) => { input.value = ""; });
for (const id of ["create-result", "create-error", "sign-result", "sign-error", "verify-result"]) element(id).classList.add("hidden");
element("download-report").disabled = true;
element("session-status").textContent = "Session cleared. Files you downloaded remain on your device.";
});
element("copy-prompt").addEventListener("click", async function () {
const button = this;
try {
await navigator.clipboard.writeText(element("ai-prompt").textContent);
button.textContent = "Copied";
setTimeout(() => { button.textContent = "Copy this message"; }, 1400);
} catch (_error) {
button.textContent = "Select and copy the message above";
}
});
})();