|
1 | 1 | /*! |
2 | 2 | * Simple origin server for the Cloudflare Workers demo. |
3 | | - * Serves static HTML pages that the Worker proxies and modifies. |
| 3 | + * Generates HTML pages from a shared template that the Worker proxies and modifies. |
4 | 4 | * |
5 | 5 | * Usage: node origin/server.js |
6 | 6 | * Serves on http://localhost:8888 |
7 | 7 | */ |
8 | 8 |
|
9 | 9 | const http = require('http'); |
10 | | -const fs = require('fs'); |
11 | | -const path = require('path'); |
12 | 10 |
|
13 | 11 | const PORT = 8888; |
14 | | -const PAGES_DIR = path.join(__dirname, 'pages'); |
| 12 | + |
| 13 | +// Page definitions — only the unique content per route |
| 14 | +const PAGES = { |
| 15 | + '/': { |
| 16 | + title: 'Convert Edge Demo', |
| 17 | + body: ` |
| 18 | + <div class="card"> |
| 19 | + <h1>Cloudflare Workers Demo</h1> |
| 20 | + <p>This page is served by a simple origin server and proxied through a Cloudflare Worker.</p> |
| 21 | + <p>The Worker runs Convert A/B tests at the edge and modifies HTML before delivery — zero flicker.</p> |
| 22 | + </div> |
| 23 | + <div class="card" style="background:#e8f4fd;border-left:4px solid #2196F3"> |
| 24 | + <strong>Tip:</strong> Visit the <a href="/events">Events</a>, <a href="/statistics">Statistics</a>, |
| 25 | + or <a href="/pricing">Pricing</a> pages to see experiments in action. |
| 26 | + The home page has no experiments configured. |
| 27 | + </div>` |
| 28 | + }, |
| 29 | + '/events': { |
| 30 | + title: 'Events - Convert Edge Demo', |
| 31 | + heading: 'Events', |
| 32 | + description: 'Experience: <code>test-experience-ab-fullstack-1</code> | Location: <code>events</code>', |
| 33 | + featureLabel: 'Feature Status' |
| 34 | + }, |
| 35 | + '/statistics': { |
| 36 | + title: 'Statistics - Convert Edge Demo', |
| 37 | + heading: 'Statistics', |
| 38 | + description: 'Runs all matching experiences | Feature: <code>feature-4</code> | Location: <code>statistics</code>', |
| 39 | + featureLabel: 'Feature Status (feature-4)' |
| 40 | + }, |
| 41 | + '/pricing': { |
| 42 | + title: 'Pricing - Convert Edge Demo', |
| 43 | + heading: 'Pricing', |
| 44 | + description: |
| 45 | + 'Runs all matching experiences | Feature: <code>feature-5</code> | Location: <code>pricing</code></p>' + |
| 46 | + '<p>Experiences on this route: <code>test-experience-ab-fullstack-1</code> and <code>test-experience-ab-fullstack-4</code>', |
| 47 | + featureLabel: 'Feature Status (feature-5)' |
| 48 | + } |
| 49 | +}; |
| 50 | + |
| 51 | +// Shared layout template |
| 52 | +function renderPage(page) { |
| 53 | + const nav = `<nav> |
| 54 | + <a href="/">Home</a> |
| 55 | + <a href="/events">Events</a> |
| 56 | + <a href="/statistics">Statistics</a> |
| 57 | + <a href="/pricing">Pricing</a> |
| 58 | + </nav>`; |
| 59 | + |
| 60 | + // Home page uses custom body; experiment pages use a standard layout |
| 61 | + const content = page.body || ` |
| 62 | + <div class="card"> |
| 63 | + <h1>${page.heading}</h1> |
| 64 | + <p>${page.description}</p> |
| 65 | + </div> |
| 66 | + <div class="card"> |
| 67 | + <h3>Experiment Results</h3> |
| 68 | + <div id="experiment-results"> |
| 69 | + <span class="placeholder">No experiment bucketing yet — this content is replaced by the Worker.</span> |
| 70 | + </div> |
| 71 | + </div> |
| 72 | + <div class="card"> |
| 73 | + <h3>${page.featureLabel}</h3> |
| 74 | + <div id="feature-status"> |
| 75 | + <span class="placeholder">No feature flag evaluated.</span> |
| 76 | + </div> |
| 77 | + </div> |
| 78 | + <div class="card"> |
| 79 | + <h3>Variation Caption</h3> |
| 80 | + <div id="variation-caption"> |
| 81 | + <span class="placeholder">Original caption (not modified)</span> |
| 82 | + </div> |
| 83 | + </div>`; |
| 84 | + |
| 85 | + return `<!DOCTYPE html> |
| 86 | +<html lang="en"> |
| 87 | +<head> |
| 88 | + <meta charset="UTF-8"> |
| 89 | + <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 90 | + <title>${page.title}</title> |
| 91 | + <style> |
| 92 | + body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; background: #f5f5f5; } |
| 93 | + nav { background: #333; padding: 10px 20px; border-radius: 8px; margin-bottom: 20px; } |
| 94 | + nav a { color: #fff; text-decoration: none; margin-right: 20px; } |
| 95 | + nav a:hover { text-decoration: underline; } |
| 96 | + .card { background: #fff; border-radius: 8px; padding: 20px; margin-bottom: 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } |
| 97 | + h1 { color: #333; } |
| 98 | + .placeholder { color: #999; font-style: italic; } |
| 99 | + .enabled { color: #4CAF50; font-weight: bold; } |
| 100 | + </style> |
| 101 | +</head> |
| 102 | +<body> |
| 103 | + ${nav} |
| 104 | + ${content} |
| 105 | +</body> |
| 106 | +</html>`; |
| 107 | +} |
15 | 108 |
|
16 | 109 | const server = http.createServer((req, res) => { |
17 | | - // Map URL path to HTML file |
18 | | - let filePath; |
19 | | - const pathname = req.url.split('?')[0]; // strip query string |
20 | | - |
21 | | - switch (pathname) { |
22 | | - case '/': |
23 | | - filePath = path.join(PAGES_DIR, 'index.html'); |
24 | | - break; |
25 | | - case '/events': |
26 | | - filePath = path.join(PAGES_DIR, 'events.html'); |
27 | | - break; |
28 | | - case '/statistics': |
29 | | - filePath = path.join(PAGES_DIR, 'statistics.html'); |
30 | | - break; |
31 | | - case '/pricing': |
32 | | - filePath = path.join(PAGES_DIR, 'pricing.html'); |
33 | | - break; |
34 | | - default: |
35 | | - res.writeHead(404, {'Content-Type': 'text/html'}); |
36 | | - res.end('<h1>404 Not Found</h1>'); |
37 | | - return; |
| 110 | + const pathname = req.url.split('?')[0]; |
| 111 | + const page = PAGES[pathname]; |
| 112 | + |
| 113 | + if (!page) { |
| 114 | + res.writeHead(404, {'Content-Type': 'text/html'}); |
| 115 | + res.end('<h1>404 Not Found</h1>'); |
| 116 | + return; |
38 | 117 | } |
39 | 118 |
|
40 | | - fs.readFile(filePath, 'utf8', (err, data) => { |
41 | | - if (err) { |
42 | | - res.writeHead(500, {'Content-Type': 'text/plain'}); |
43 | | - res.end('Internal Server Error'); |
44 | | - return; |
45 | | - } |
46 | | - res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'}); |
47 | | - res.end(data); |
48 | | - }); |
| 119 | + res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'}); |
| 120 | + res.end(renderPage(page)); |
49 | 121 | }); |
50 | 122 |
|
51 | 123 | server.listen(PORT, () => { |
|
0 commit comments