Designing CampCart: A Lightweight, Offline-First Commerce Widget
Simplifying Independent E-commerce
Adding e-commerce to a static website usually requires loading heavy, tracking-laden third-party widgets or maintaining a complex backend database. These solutions introduce security vulnerabilities, drag down performance, and profile visitors.
CampCart provides an alternative: a zero-dependency, secure shopping cart widget running purely in the browser.
We wanted to prove that you can build a complete, highly-responsive shopping cart experience using purely static files and client-side cryptography.
Under the Hood: Pure Client-Side State
CampCart relies on a robust JavaScript state machine that manages product entries, quantities, and price calculations on the client. It persists cart items in localStorage securely to preserve the shopping state across page reloads.
Because static sites do not have a server to dynamically calculate prices and generate tokens, they are traditionally prone to price-tampering attacks — where a malicious user edits client-side JS values to checkout with cheaper amounts.
Solving Price-Tampering via Cryptographic Signing
To thwart client-side manipulation without running an active database server, CampCart uses cryptographic payload verification.
When you initialize your static store, product definitions (including IDs, titles, and unit prices) are declared alongside a pre-computed signature generated by your publishing suite using a private key.
When a user initiates checkout:
- The widget bundles the items, counts, and prices into a payload.
- The payload and the signatures are sent to our lightweight, stateless payment forwarding API.
- The API verifies the cryptographic signatures against the store owner's public key. If a price value doesn't match the signature, the checkout transaction is instantly rejected.
Here is how the verification signature is verified inside the serverless API:
// Web Crypto API method to verify static store product detailsasync function verifyProductSignature(publicKeyPem, productPayload, signatureBase64) {const enc = new TextEncoder();const rawKey = pemToDer(publicKeyPem);const publicKey = await window.crypto.subtle.importKey("spki",rawKey,{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },false,["verify"]);const data = enc.encode(JSON.stringify({id: productPayload.id,price: productPayload.price,currency: productPayload.currency}));const signature = Uint8Array.from(atob(signatureBase64), c => c.charCodeAt(0));return await window.crypto.subtle.verify("RSASSA-PKCS1-v1_5",publicKey,signature,data);}// Helper to convert PEM string back to binary DERfunction pemToDer(pem) {const pemHeader = "-----BEGIN PUBLIC KEY-----";const pemFooter = "-----END PUBLIC KEY-----";const pemContents = pem.substring(pemHeader.length, pem.length - pemFooter.length);const cleanPem = pemContents.replace(/\s+/g, "");return Uint8Array.from(atob(cleanPem), c => c.charCodeAt(0)).buffer;}
Performance Benchmarks
By eliminating database handshakes and network overhead during browsing:
- Time-to-Interactive (TTI): Remains under 0.2 seconds on standard mobile devices.
- Payload Weight: The entire CampCart engine is a single file weighing under 12KB (minified and gzipped).
- Integrations: Mounts as a native Web Component (
<camp-cart>), making it compatible with any static site builder.