Skip to content
This repository was archived by the owner on Oct 27, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 91 additions & 7 deletions site/test_try_page.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { createContext, runInContext } from "node:vm";
const script = readFileSync(new URL("./try/try.js", import.meta.url), "utf8");
const captureScript = readFileSync(new URL("./utm-capture.js", import.meta.url), "utf8");
const html = readFileSync(new URL("./try/index.html", import.meta.url), "utf8");
const DEMO_KEY_URL = "https://api.thisisatest.tech/v1/demo/keys";
const SIGNUP_URL = "https://cqtlzicxmaklslvxderm.supabase.co/functions/v1/amw-test-signup";

function makePage(fetchResponse, options = {}) {
const ids = [
Expand All @@ -14,12 +16,14 @@ function makePage(fetchResponse, options = {}) {
"try-key-result",
"try-api-key",
"try-wallet-id",
"try-email",
"try-email-error",
"copy-try-key",
"copy-try-wallet",
];
const elements = new Map(ids.map((id) => [id, {
disabled: false,
hidden: id === "try-key-result",
hidden: id === "try-key-result" || id === "try-email-error",
textContent: "",
value: "",
listeners: {},
Expand All @@ -46,7 +50,11 @@ function makePage(fetchResponse, options = {}) {
URL,
async fetch(url, request) {
requests.push([url, request]);
return fetchResponse();
if (url === DEMO_KEY_URL) return fetchResponse();
if (url === SIGNUP_URL) {
return options.signupResponse ? options.signupResponse() : { status: 201 };
}
throw new Error(`unexpected fetch URL: ${url}`);
},
navigator: {
clipboard: { async writeText(value) { copies.push(value); } },
Expand Down Expand Up @@ -80,6 +88,20 @@ function makePage(fetchResponse, options = {}) {
};
}

function signupRequests(page) {
return page.requests.filter(([url]) => url === SIGNUP_URL);
}

function signupPayload(page) {
const requests = signupRequests(page);
assert.equal(requests.length, 1);
const [, request] = requests[0];
assert.equal(request.method, "POST");
assert.equal(request.credentials, "omit");
assert.equal(request.headers["Content-Type"], "application/json");
return JSON.parse(request.body);
}

function issued(key = "test_key", wallet = "wallet_test") {
return {
status: 201,
Expand Down Expand Up @@ -146,15 +168,15 @@ function pageTokens(markup) {
return { tags, visibleMainText };
}

test("one empty credential free POST shows key and wallet once and copies both", async () => {
test("one demo POST shows key and wallet once, copies both, and sends one credential free signup", async () => {
const key = "demo_<img src=x onerror=alert(1)>";
const wallet = "wallet_<script>bad()</script>";
const page = makePage(async () => issued(key, wallet));

await page.click("get-try-key");
assert.equal(page.requests.length, 1);
assert.equal(page.requests.length, 2);
const [url, request] = page.requests[0];
assert.equal(url, "https://api.thisisatest.tech/v1/demo/keys");
assert.equal(url, DEMO_KEY_URL);
assert.equal(request.method, "POST");
assert.equal(request.credentials, "omit");
assert.deepEqual(Object.keys(request).sort(), ["credentials", "method"]);
Expand All @@ -165,13 +187,18 @@ test("one empty credential free POST shows key and wallet once and copies both",
const displayed = [...page.elements.values()].map((element) => `${element.value}${element.textContent}`).join(" ");
assert.equal(displayed.split(key).length - 1, 1);
assert.equal(displayed.split(wallet).length - 1, 1);
const payload = signupPayload(page);
assert.deepEqual(payload, { wallet_id: wallet });
assert.equal(Object.hasOwn(payload, "api_key"), false);
assert.equal(JSON.stringify(payload).includes(key), false);

await page.click("copy-try-key");
await page.click("copy-try-wallet");
assert.deepEqual(page.copies, [key, wallet]);
assert.doesNotMatch(page.elements.get("try-key-status").textContent, /[-\u2013\u2014]/);
await page.click("get-try-key");
assert.equal(page.requests.length, 1);
assert.equal(page.requests.length, 2);
assert.equal(signupRequests(page).length, 1);
assert.equal(page.session.size, 0);
});

Expand Down Expand Up @@ -199,6 +226,58 @@ test("stored UTM is overridden by URL values and first external referrer is reta
});
assert.deepEqual(JSON.parse(page.session.get("amwDemoKeyUtm")), JSON.parse(request.body));
assert.equal([...page.session.values()].some((value) => value.includes("test_key")), false);
assert.deepEqual(signupPayload(page), {
wallet_id: "wallet_test",
utm_source: "newsletter",
utm_campaign: "launch",
utm_term: "receipts",
referrer: "https://first.example/landing",
});
});

test("valid optional email is trimmed and sent with wallet attribution", async () => {
const page = makePage(async () => issued("secret_key", "wallet_email"), {
search: "?utm_source=docs",
referrer: "https://partner.example/guide",
});
page.elements.get("try-email").value = " user@example.com ";

await page.click("get-try-key");
assert.deepEqual(signupPayload(page), {
wallet_id: "wallet_email",
email: "user@example.com",
utm_source: "docs",
referrer: "https://partner.example/guide",
});
assert.equal(page.elements.get("try-email-error").textContent, "");
assert.equal(page.elements.get("try-email-error").hidden, true);
});

test("invalid optional email is omitted while wallet signup and key display continue", async () => {
for (const email of ["not-an-email", `${"a".repeat(250)}@x.co`]) {
const page = makePage(async () => issued("secret_key", "wallet_invalid"));
page.elements.get("try-email").value = email;

await page.click("get-try-key");
assert.deepEqual(signupPayload(page), { wallet_id: "wallet_invalid" });
assert.notEqual(page.elements.get("try-email-error").textContent, "");
assert.equal(page.elements.get("try-email-error").hidden, false);
assert.equal(page.elements.get("try-key-result").hidden, false);
assert.equal(shownValue(page.elements.get("try-api-key")), "secret_key");
}
});

test("signup failure is silent and leaves the issued key visible", async () => {
const page = makePage(async () => issued("secret_key", "wallet_failure"), {
signupResponse: async () => { throw new Error("signup unavailable"); },
});

await page.click("get-try-key");
await new Promise((resolve) => setImmediate(resolve));
assert.deepEqual(signupPayload(page), { wallet_id: "wallet_failure" });
assert.equal(page.elements.get("try-key-result").hidden, false);
assert.equal(shownValue(page.elements.get("try-api-key")), "secret_key");
assert.equal(page.elements.get("try-key-status").textContent, "Test key ready. Copy it now.");
});

test("UTM and referrer are capped before storage and request", async () => {
Expand Down Expand Up @@ -245,7 +324,8 @@ test("failed requests leave the key hidden and allow retry", async () => {
assert.ok(page.elements.get("try-key-status").textContent);
assert.doesNotMatch(page.elements.get("try-key-status").textContent, /[-\u2013\u2014]/);
await page.click("get-try-key");
assert.equal(page.requests.length, 2);
assert.equal(page.requests.filter(([url]) => url === DEMO_KEY_URL).length, 2);
assert.equal(signupRequests(page).length, 1);
assert.equal(page.elements.get("try-key-result").hidden, false);
}

Expand Down Expand Up @@ -283,6 +363,10 @@ test("page uses external assets and visible copy contains no dashes", () => {
}
}
assert.ok(openingTags.some((tag) => tag.name === "button" && tag.raw.includes('id="get-try-key"')));
assert.ok(openingTags.some((tag) => tag.name === "label" && tag.raw.includes('for="try-email"')));
assert.ok(openingTags.some((tag) => tag.name === "input" && tag.raw.includes('id="try-email"') && tag.raw.includes('type="email"') && tag.raw.includes('autocomplete="email"')));
assert.ok(visibleMainText.includes("Email (optional)"));
assert.ok(visibleMainText.includes("Leave it blank"));
assert.ok(visibleMainText.includes("Get a free test key"));
for (const id of ["copy-try-key", "copy-try-wallet"]) {
assert.ok(openingTags.some((tag) => tag.name === "button" && tag.raw.includes(`id="${id}"`)));
Expand Down
8 changes: 6 additions & 2 deletions site/try/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@
@@FONT_PRELOADS@@
<link rel="stylesheet" href="/fonts.css?v=@@FONTS_CSS_VERSION@@" />
<link rel="stylesheet" href="/styles.css?v=@@STYLES_CSS_VERSION@@" />
<link rel="stylesheet" href="/try/try.css?v=try-2" />
<link rel="stylesheet" href="/try/try.css?v=try-3" />
<script src="/a11y-preload.js?v=gateway-19"></script>
@@VERCEL_ANALYTICS_SCRIPTS@@
<script defer src="/analytics.js?v=gateway-19"></script>
<script defer src="/utm-capture.js?v=utm-1"></script>
<script defer src="/a11y.js?v=gateway-19"></script>
<script defer src="/try/try.js?v=try-1"></script>
<script defer src="/try/try.js?v=try-2"></script>
</head>
<body class="site-light">
<a class="skip-link" href="#main">Skip to content</a>
Expand All @@ -73,6 +73,10 @@ <h1>Try one governed tool call.</h1>
</div>

<section class="key-panel" aria-label="Get your test key">
<label for="try-email">Email (optional)</label>
<input id="try-email" class="email-input" type="email" autocomplete="email" aria-describedby="try-email-help try-email-error" />
<p id="try-email-help" class="email-help">Add your email if you want updates from us about AMW. Leave it blank to continue without sharing an email.</p>
<p id="try-email-error" class="email-error" role="status" hidden></p>
<button id="get-try-key" class="button button-primary primary-button" type="button">Get a free test key</button>
<p id="try-key-status" role="status" aria-live="polite">No account or payment needed.</p>

Expand Down
21 changes: 21 additions & 0 deletions site/try/try.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,27 @@
cursor: default;
}

.try-page .email-input {
box-sizing: border-box;
width: 100%;
padding: 0.7rem 0.8rem;
border: 1px solid var(--paper-line);
border-radius: var(--radius-sm);
background: var(--paper-shade);
color: var(--paper-ink);
font: inherit;
}

.try-page .email-help,
.try-page .email-error {
margin: 0.45rem 0 1rem;
color: var(--paper-dim);
}

.try-page .email-error[hidden] {
display: none;
}

#try-key-status {
margin: 0.9rem 0 0;
color: var(--paper-dim);
Expand Down
35 changes: 31 additions & 4 deletions site/try/try.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
const result = document.getElementById("try-key-result");
const keyInput = document.getElementById("try-api-key");
const walletInput = document.getElementById("try-wallet-id");
const emailInput = document.getElementById("try-email");
const emailError = document.getElementById("try-email-error");
const UTM_PARAMS = ["utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term"];
const UTM_STORAGE_KEY = "amwDemoKeyUtm";
const UTM_MAX_LENGTH = 128;
Expand Down Expand Up @@ -93,9 +95,34 @@

persistAttribution(captureAttribution());

function onKeyIssued({ wallet_id, fields }) {
// Reserved for an optional signup insert in a later change.
// Never pass the API key to this hook or store it in attribution.
async function onKeyIssued({ wallet_id, fields }) {
const signup = { wallet_id };
for (const key of UTM_PARAMS) {
if (fields[key]) signup[key] = fields[key];
}
if (fields.referrer) signup.referrer = fields.referrer;

const email = emailInput.value.trim();
if (email) {
if (email.length <= 254 && /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) {
signup.email = email;
} else {
emailError.textContent = "That email address was not included. Your test key is ready.";
emailError.hidden = false;
}
}

try {
await fetch("https://cqtlzicxmaklslvxderm.supabase.co/functions/v1/amw-test-signup", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "omit",
keepalive: true,
body: JSON.stringify(signup),
Comment thread
PetrefiedThunder marked this conversation as resolved.
Resolved
});
} catch {
// Signup is optional and never interrupts key issuance.
}
}

issueButton.addEventListener("click", async () => {
Expand Down Expand Up @@ -153,7 +180,7 @@
walletInput.value = data.wallet_id;
result.hidden = false;
status.textContent = "Test key ready. Copy it now.";
onKeyIssued({ wallet_id: data.wallet_id, fields });
void onKeyIssued({ wallet_id: data.wallet_id, fields });
});

async function copyValue(input, label) {
Expand Down
2 changes: 1 addition & 1 deletion site/vercel.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
},
{
"key": "Content-Security-Policy",
"value": "default-src 'self'; base-uri 'none'; object-src 'none'; frame-ancestors 'self'; form-action 'none'; script-src 'self'; style-src 'self'; font-src 'self'; img-src 'self' data:; connect-src 'self' https://vitals.vercel-insights.com https://api.thisisatest.tech; manifest-src 'self'; upgrade-insecure-requests"
"value": "default-src 'self'; base-uri 'none'; object-src 'none'; frame-ancestors 'self'; form-action 'none'; script-src 'self'; style-src 'self'; font-src 'self'; img-src 'self' data:; connect-src 'self' https://vitals.vercel-insights.com https://api.thisisatest.tech https://cqtlzicxmaklslvxderm.supabase.co; manifest-src 'self'; upgrade-insecure-requests"
}
]
},
Expand Down
6 changes: 4 additions & 2 deletions tests/test_site_agent_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -1423,7 +1423,8 @@ def test_site_sends_a_content_security_policy_and_hsts() -> None:
assert policy["script-src"] == "script-src 'self'"
assert policy["connect-src"] == (
"connect-src 'self' https://vitals.vercel-insights.com "
"https://api.thisisatest.tech"
"https://api.thisisatest.tech "
"https://cqtlzicxmaklslvxderm.supabase.co"
)
assert "'unsafe-inline'" not in values["Content-Security-Policy"]
# Typography is self-hosted from /fonts, so neither font CDN is permitted.
Expand Down Expand Up @@ -1469,7 +1470,8 @@ def test_built_demo_key_page_has_the_key_controls_and_api_connection(tmp_path) -
policy = _csp_policy(values["Content-Security-Policy"])
assert policy["connect-src"] == (
"connect-src 'self' https://vitals.vercel-insights.com "
"https://api.thisisatest.tech"
"https://api.thisisatest.tech "
"https://cqtlzicxmaklslvxderm.supabase.co"
)


Expand Down
Loading