crates/monetize-site-buy/src/assets.rs
//! **The buy pages' browser scripts, ONE copy for every product site** (owner 2026-10-23):
//! Gunnar's sign-up page script (`auth/signup.html`) and welcome page script
//! (`auth/welcome.html`), moved here as they are, and the order page a vendor sends a buyer
//! back to (`/paid/{reference}`, `/cancel/{reference}`: [`paid_js`], T16). A site supplies only its [`Settings`] (its
//! names, its product word, how a request carries its session) and draws its own look; its UI
//! server serves what [`buy_js`] and [`welcome_js`] render. No product server serves them.
//!
//! The scripts carry `@@NAME@@` words where a site differs; each is filled in inside a
//! single-quoted JavaScript string, escaped.
/// Gunnar's sign-up page script, with the sites' words to fill in.
pub const BUY_JS: &str = include_str!("../assets/buy.js");
/// Gunnar's welcome page script, with the sites' words to fill in.
pub const WELCOME_JS: &str = include_str!("../assets/welcome.js");
/// The order page's script (`/paid/{reference}`, `/cancel/{reference}`): Gunnar's FollowOrder,
/// with the sites' words to fill in (TASKS-4-v2 T16, A28 M11: Holger's hand port and Gunnar's
/// wasm panes were two copies that drifted).
pub const PAID_JS: &str = include_str!("../assets/paid.js");
/// **The elements a site's order page carries** for [`paid_js`] to fill: the cancelled box,
/// the steps, the headline and its sentence, the change and room lines, the progress line, the
/// pay area, the fault line and the foot. The site's page draws the look around them.
pub const PAID_IDS: [&str; 10] = ["cancelled", "steps", "headline", "detail", "change", "room", "progress", "pay", "fault", "foot"];
/// **Where the order page leads**, beside a site's [`Settings`].
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Back {
/// "Try again" (an order that is gone, or a cancelled checkout): Holger's
/// `/signup?plan=account`, Gunnar's `/account/storage`.
pub again: String,
/// "Back to your account": Holger's `/plan`, Gunnar's `/account`.
pub account: String,
/// The sign-in page a signed-out reader is sent to, `?next=` the order's address: `/login`.
pub login: String,
}
/// **A site's settings block**: everything that differs between the sites' buy pages.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Settings {
/// The product as the buyer reads it: "Gunnar", "Holger".
pub product: String,
/// The prefix of the page's browser-storage keys: `gunnar_`, `holger_`.
pub storage: String,
/// The free account's plan id: `gunnar/account`.
pub free_account: String,
/// The free row's note, in the owner's words for the product ("Free: clone only. Pushing
/// needs a paid plan.").
pub free_note: String,
/// The free account's card-check line, the vendor's and the product's words ("A card check
/// at Stripe (0 kr) proves you are a person."); empty: none shown (RA5-41).
pub card_check: String,
/// The beta line on every tab ("Beta: we replicate your data, but cannot guarantee it.");
/// empty: none shown (RA5-41).
pub beta_line: String,
/// Where an instance is managed, its company appended: `/signup?plan=private&renew=`.
pub manage: String,
/// The `?plan=` word, beside Gunnar's `10gb`, that opens the account choice (`account`).
pub account_plan: String,
/// What the site is to the buyer, in "Nothing is sold by this …": `console`, `site`.
pub here: String,
/// Where this tab keeps the session as a bearer (`holger-bearer-token`); empty: the site's
/// cookie carries it.
pub bearer_key: String,
/// The site's own "who is here": `/auth/status`, or Holger's `/-/signup`.
pub status_url: String,
/// ★ A32-24: how [`Self::status_url`] answers — empty: the fields `/auth/status` names;
/// [`STATUS_LINKS`]: a sign-up links answer (`signed_in`, `account`, `account_url`, …),
/// which the script reads as those fields. The script branches on this, never on a path.
pub status_shape: String,
/// The shop's word on what it sells here (`/auth/buy/offer`); empty: `status_url` says it.
pub offer_url: String,
/// Where a refused or unknown visitor is told to write; empty: the site's front page.
pub contact: String,
}
/// [`Settings::status_shape`] of a site whose "who is here" answers sign-up links.
pub const STATUS_LINKS: &str = "links";
/// Pure: `v` inside a single-quoted JavaScript string.
fn quoted(v: &str) -> String {
v.chars()
.map(|c| match c {
'\\' => "\\\\".to_owned(),
'\'' => "\\'".to_owned(),
'\n' => "\\n".to_owned(),
'\r' => "\\r".to_owned(),
'<' => "\\x3c".to_owned(),
c => c.to_string(),
})
.collect()
}
fn fill(template: &str, s: &Settings) -> String {
fill_with(template, s, &Back::default())
}
fn fill_with(template: &str, s: &Settings, b: &Back) -> String {
let words = [
("AGAIN", &b.again),
("ACCOUNT", &b.account),
("LOGIN", &b.login),
("PRODUCT", &s.product),
("STORAGE", &s.storage),
("FREE_ACCOUNT", &s.free_account),
("FREE_NOTE", &s.free_note),
("CARD_CHECK", &s.card_check),
("BETA_LINE", &s.beta_line),
("MANAGE", &s.manage),
("ACCOUNT_PLAN", &s.account_plan),
("HERE", &s.here),
("BEARER_KEY", &s.bearer_key),
("STATUS_URL", &s.status_url),
("STATUS_SHAPE", &s.status_shape),
("OFFER_URL", &s.offer_url),
("CONTACT", &s.contact),
];
words.iter().fold(template.to_owned(), |t, (k, v)| t.replace(&format!("@@{k}@@"), "ed(v)))
}
/// **The sign-up page's script for a site.**
pub fn buy_js(s: &Settings) -> String {
fill(BUY_JS, s)
}
/// **The welcome page's script for a site.**
pub fn welcome_js(s: &Settings) -> String {
fill(WELCOME_JS, s)
}
/// **The order page's script for a site**: its words from `s` (the product, the bearer key)
/// and its ways on from `b`.
pub fn paid_js(s: &Settings, b: &Back) -> String {
fill_with(PAID_JS, s, b)
}
#[cfg(test)]
mod tests {
use super::*;
fn gunnar() -> Settings {
Settings {
product: "Gunnar".into(),
storage: "gunnar_".into(),
free_account: "gunnar/account".into(),
free_note: "Free, with no commitment.".into(),
manage: "/signup?plan=private&renew=".into(),
account_plan: "10gb".into(),
here: "console".into(),
status_url: "/auth/status".into(),
contact: "desk@mock.invalid".into(),
..Settings::default()
}
}
/// ★ **Every word a site differs in is filled in, and nothing else is left to fill.**
#[test]
fn a_sites_scripts_carry_its_words_and_nothing_left_to_fill() {
for js in [buy_js(&gunnar()), welcome_js(&gunnar())] {
assert!(!js.contains("@@"), "a word left unfilled");
}
let buy = buy_js(&gunnar());
assert!(buy.contains("out.push({ mode: 'private', words: TAB_PRIVATE });") && buy.contains("const TAB_PRIVATE = 'New private instance';"), "BUY-ONE-2's tab name");
assert!(buy.contains("const CARD_CHECK_KEY = 'gunnar_card_check';") && buy.contains("plan: 'gunnar/account', next: '/' }"));
assert!(buy.contains("const SITE = { bearer: '', status: '/auth/status', statusShape: '', offer: '' };"));
// Gunnar's own lines, as its console's tests read them (the plans are asked with no
// session; `10gb` opens the account).
assert!(buy.contains("fetch('/auth/signup/ladder', { credentials: 'same-origin' })"));
assert!(buy.contains("const asked = (q.get('plan') || '').toLowerCase();") && buy.contains("asked === '10gb' ? (page.status.authenticated ? 'grow-account' : 'account')"));
let welcome = welcome_js(&gunnar());
assert!(welcome.contains("const CONTACT = 'desk@mock.invalid';") && welcome.contains("'?subject=Gunnar%20signup'"));
}
/// ★ **RA5-41: the free row's note, the card-check line and the beta line are the site's.**
/// The shared script hard-coded Gunnar's words ("Stripe (0 kr)", "Pushing needs a paid
/// plan"), so a Holger site, or one on another vendor or currency, told its buyer them, and
/// the site's `free_note` silently did nothing. Each is filled from the site's settings; an
/// empty card-check or beta line shows none.
#[test]
fn the_free_note_card_check_and_beta_line_are_the_sites_words() {
let holger = Settings {
product: "Holger".into(),
free_note: "Free: read and install only. Publishing needs a paid plan.".into(),
card_check: "A card check at the vendor proves you are a person.".into(),
beta_line: "Beta: Holger replicates your packages, but cannot guarantee them.".into(),
..gunnar()
};
let js = buy_js(&holger);
for own in ["Free: read and install only. Publishing needs a paid plan.", "A card check at the vendor proves you are a person.", "Beta: Holger replicates your packages, but cannot guarantee them."] {
assert!(js.contains(own), "the site's words: {own}");
}
for gunnars in ["Pushing needs a paid plan", "(0 kr)", "Beta: we replicate your data"] {
assert!(!js.contains(gunnars), "Gunnar's words on another site: {gunnars}");
}
let quiet = buy_js(&Settings { card_check: String::new(), beta_line: String::new(), ..holger });
assert!(quiet.contains("const BETA_LINE = '';"), "no beta line set: none shown");
}
/// ★ **The welcome page keeps no settle bound and no settle words of its own** (RA4-19): it
/// shows the shop's sentences, and the one the shop sends ahead for its silence.
#[test]
fn the_welcome_page_has_no_settle_bound_or_words_of_its_own() {
let js = welcome_js(&gunnar());
for own in ["LONGER_MS", "2 * 60 * 1000", "this takes a few seconds", "your payment is safe", "Nothing has been charged, and this page keeps checking"] {
assert!(!js.contains(own), "the page keeps its own settle rule or words: {own:?}");
}
assert!(js.contains("body.words_if_silent") && js.contains("body.silent_after_ms"), "the page shows the sentence the shop sends ahead");
}
/// ★ **The order page's script is ONE copy for every site** (T16, A28 M11): a site's words
/// and its ways on are filled in, nothing is left to fill, and it follows the order over the
/// shared relay's doors.
#[test]
fn the_order_pages_script_carries_a_sites_words_and_ways_on() {
let back = Back { again: "/account/storage".into(), account: "/account".into(), login: "/login".into() };
let js = paid_js(&gunnar(), &back);
assert!(!js.contains("@@"), "a word left unfilled");
assert!(js.contains("const BUY = { product: 'Gunnar', bearerKey: '', again: '/account/storage', account: '/account', login: '/login' };"), "{}", &js[..1200]);
assert!(js.contains("'/api/account/subscription/status?reference='") && js.contains("'/api/account/subscription/cancel'"));
for id in PAID_IDS {
assert!(js.contains(&format!("$('{id}')")), "the script fills #{id}");
}
let holger = Settings { product: "Holger".into(), bearer_key: "holger-bearer-token".into(), ..gunnar() };
let js = paid_js(&holger, &Back { again: "/signup?plan=account".into(), account: "/plan".into(), login: "/login".into() });
assert!(js.contains("product: 'Holger', bearerKey: 'holger-bearer-token', again: '/signup?plan=account', account: '/plan'"));
}
/// ★ **A32-24: the script reads a sign-up links answer where the SITE says so** (its
/// `status_shape`), whatever path it is served at: no product's path decides it.
#[test]
fn the_status_shape_is_the_sites_setting_not_a_path() {
let links = Settings { status_url: "/who".into(), status_shape: STATUS_LINKS.into(), ..gunnar() };
let js = buy_js(&links);
assert!(js.contains("status: '/who', statusShape: 'links'"), "the site's word is filled in");
assert!(js.contains("if (SITE.statusShape !== 'links') return l;"), "the script branches on the setting");
assert!(!BUY_JS.contains("SITE.status !=="), "never on the status path itself");
}
/// ★ **A word is a word, never code**: a quote, a backslash or a `</script>` in a setting
/// stays inside its string.
#[test]
fn a_setting_never_breaks_out_of_its_string() {
let s = Settings { product: "O'Neil\\</script>".into(), ..gunnar() };
let js = buy_js(&s);
assert!(js.contains("'New private O\\'Neil\\\\\\x3c/script>'"), "escaped");
assert!(!js.contains("</script>"));
}
}