|
4 | 4 | <title>LNA / Mixed Content Test</title> |
5 | 5 | <style> |
6 | 6 | body { font-family: monospace; padding: 20px; background: #1e1e1e; color: #d4d4d4; } |
7 | | - h2 { color: #569cd6; } |
8 | | - .test { margin: 8px 0; padding: 8px; border-radius: 4px; } |
| 7 | + h2 { color: #569cd6; margin-top: 24px; } |
| 8 | + .test { margin: 6px 0; padding: 8px; border-radius: 4px; } |
9 | 9 | .pass { background: #1e3a1e; color: #4ec94e; } |
10 | 10 | .fail { background: #3a1e1e; color: #e94e4e; } |
11 | 11 | .wait { background: #3a3a1e; color: #e9e94e; } |
12 | | - input, button { font-family: monospace; font-size: 14px; padding: 6px 12px; } |
13 | | - button { cursor: pointer; background: #569cd6; color: #1e1e1e; border: none; border-radius: 4px; } |
| 12 | + input, button, select { font-family: monospace; font-size: 14px; padding: 6px 12px; } |
| 13 | + button { cursor: pointer; background: #569cd6; color: #1e1e1e; border: none; border-radius: 4px; margin: 4px; } |
14 | 14 | #results { margin-top: 20px; } |
15 | 15 | .info { color: #888; font-size: 12px; } |
| 16 | + .section { margin-top: 16px; padding-top: 8px; border-top: 1px solid #444; } |
16 | 17 | </style> |
17 | 18 | </head> |
18 | 19 | <body> |
19 | 20 | <h2>Chrome LNA / Mixed Content Integration Test</h2> |
20 | 21 | <p class="info"> |
21 | 22 | Page protocol: <strong id="pageProto"></strong> | |
22 | | - Chrome version: <strong id="chromeVer"></strong> |
| 23 | + Chrome: <strong id="chromeVer"></strong> | |
| 24 | + Secure context: <strong id="secureCtx"></strong> |
23 | 25 | </p> |
24 | 26 |
|
25 | | - <label>Target: <input id="target" value="http://192.168.15.17:7780/api/v1/health" size="60"></label> |
26 | | - <button onclick="runAll()">Run All Tests</button> |
| 27 | + <div> |
| 28 | + <label>Host: <input id="host" value="192.168.15.17" size="20"></label> |
| 29 | + <label>Port: <input id="port" value="7780" size="6"></label> |
| 30 | + <label>Path: <input id="path" value="/api/v1/health" size="25"></label> |
| 31 | + </div> |
| 32 | + <div style="margin-top: 8px;"> |
| 33 | + <button onclick="runAll()">▶ Run All Tests</button> |
| 34 | + <button onclick="results.innerHTML=''">Clear</button> |
| 35 | + </div> |
27 | 36 |
|
28 | 37 | <div id="results"></div> |
29 | 38 |
|
30 | 39 | <script> |
31 | 40 | document.getElementById('pageProto').textContent = location.protocol; |
32 | | - const match = navigator.userAgent.match(/Chrome\/(\d+)/); |
33 | | - document.getElementById('chromeVer').textContent = match ? match[1] : 'unknown'; |
| 41 | + const m = navigator.userAgent.match(/Chrome\/(\d+)/); |
| 42 | + document.getElementById('chromeVer').textContent = m ? m[1] : 'N/A'; |
| 43 | + document.getElementById('secureCtx').textContent = String(window.isSecureContext); |
34 | 44 |
|
35 | 45 | const results = document.getElementById('results'); |
36 | 46 |
|
37 | | - function log(id, status, msg) { |
38 | | - const el = document.getElementById(id); |
39 | | - if (el) { |
40 | | - el.className = 'test ' + status; |
41 | | - el.textContent = `[${status.toUpperCase()}] ${el.dataset.label}: ${msg}`; |
42 | | - } |
| 47 | + function getUrl(proto) { |
| 48 | + const h = document.getElementById('host').value; |
| 49 | + const p = document.getElementById('port').value; |
| 50 | + const path = document.getElementById('path').value; |
| 51 | + return `${proto}://${h}:${p}${path}`; |
| 52 | + } |
| 53 | + |
| 54 | + function addSection(title) { |
| 55 | + const div = document.createElement('div'); |
| 56 | + div.className = 'section'; |
| 57 | + div.innerHTML = `<strong style="color:#569cd6">${title}</strong>`; |
| 58 | + results.appendChild(div); |
43 | 59 | } |
44 | 60 |
|
45 | 61 | function addTest(id, label) { |
46 | 62 | const div = document.createElement('div'); |
47 | 63 | div.id = id; |
48 | 64 | div.dataset.label = label; |
49 | 65 | div.className = 'test wait'; |
50 | | - div.textContent = `[WAIT] ${label}: running...`; |
| 66 | + div.textContent = `⏳ ${label}`; |
51 | 67 | results.appendChild(div); |
52 | 68 | } |
53 | 69 |
|
54 | | - async function runTest(id, label, url, init) { |
| 70 | + function log(id, ok, msg) { |
| 71 | + const el = document.getElementById(id); |
| 72 | + if (!el) return; |
| 73 | + el.className = 'test ' + (ok ? 'pass' : 'fail'); |
| 74 | + el.textContent = `${ok ? '✅' : '❌'} ${el.dataset.label}: ${msg}`; |
| 75 | + } |
| 76 | + |
| 77 | + async function test(id, label, url, init, timeoutMs = 8000) { |
55 | 78 | addTest(id, label); |
56 | | - const opts = JSON.stringify(init || {}); |
| 79 | + const ctrl = new AbortController(); |
| 80 | + const timer = setTimeout(() => ctrl.abort(), timeoutMs); |
| 81 | + const optsStr = init ? JSON.stringify(init) : '{}'; |
57 | 82 | try { |
58 | | - const start = performance.now(); |
59 | | - const res = await fetch(url, init); |
60 | | - const ms = Math.round(performance.now() - start); |
| 83 | + const t0 = performance.now(); |
| 84 | + const res = await fetch(url, { ...init, signal: ctrl.signal }); |
| 85 | + const ms = Math.round(performance.now() - t0); |
| 86 | + clearTimeout(timer); |
61 | 87 | if (res.type === 'opaque') { |
62 | | - log(id, 'pass', `opaque response (no-cors) — ${ms}ms | opts: ${opts}`); |
| 88 | + log(id, true, `opaque (no-cors OK) ${ms}ms — ${optsStr}`); |
63 | 89 | } else { |
64 | | - const body = await res.text().catch(() => ''); |
65 | | - log(id, 'pass', `${res.status} ${res.statusText} — ${ms}ms | body: ${body.slice(0,100)} | opts: ${opts}`); |
| 90 | + const txt = await res.text().catch(() => ''); |
| 91 | + log(id, true, `${res.status} ${ms}ms — body: ${txt.slice(0,80)} — ${optsStr}`); |
66 | 92 | } |
67 | 93 | } catch (err) { |
68 | | - log(id, 'fail', `${err.name}: ${err.message} | opts: ${opts}`); |
| 94 | + clearTimeout(timer); |
| 95 | + const kind = err.name === 'AbortError' ? 'TIMEOUT' : err.name; |
| 96 | + log(id, false, `${kind}: ${err.message} — ${optsStr}`); |
69 | 97 | } |
70 | 98 | } |
71 | 99 |
|
72 | 100 | async function runAll() { |
73 | 101 | results.innerHTML = ''; |
74 | | - const url = document.getElementById('target').value; |
75 | | - |
76 | | - // Parse URL to build HTTPS variant |
77 | | - let httpsUrl; |
78 | | - try { |
79 | | - const u = new URL(url); |
80 | | - u.protocol = 'https:'; |
81 | | - httpsUrl = u.toString(); |
82 | | - } catch { httpsUrl = url.replace('http://', 'https://'); } |
83 | | - |
84 | | - // Test 1: Plain fetch (no options) |
85 | | - await runTest('t1', 'Plain fetch (no options)', url); |
86 | | - |
87 | | - // Test 2: mode: no-cors |
88 | | - await runTest('t2', 'mode: no-cors', url, { mode: 'no-cors' }); |
89 | | - |
90 | | - // Test 3: targetAddressSpace: private |
91 | | - await runTest('t3', 'targetAddressSpace: private', url, { targetAddressSpace: 'private' }); |
92 | | - |
93 | | - // Test 4: targetAddressSpace: local |
94 | | - await runTest('t4', 'targetAddressSpace: local', url, { targetAddressSpace: 'local' }); |
95 | | - |
96 | | - // Test 5: no-cors + targetAddressSpace: private |
97 | | - await runTest('t5', 'no-cors + targetAddressSpace: private', url, { mode: 'no-cors', targetAddressSpace: 'private' }); |
98 | | - |
99 | | - // Test 6: no-cors + targetAddressSpace: local |
100 | | - await runTest('t6', 'no-cors + targetAddressSpace: local', url, { mode: 'no-cors', targetAddressSpace: 'local' }); |
101 | | - |
102 | | - // Test 7: HTTPS variant (plain) |
103 | | - await runTest('t7', 'HTTPS variant (plain fetch)', httpsUrl); |
104 | | - |
105 | | - // Test 8: HTTPS + no-cors |
106 | | - await runTest('t8', 'HTTPS variant + no-cors', httpsUrl, { mode: 'no-cors' }); |
107 | | - |
108 | | - // Summary |
109 | | - const summary = document.createElement('div'); |
110 | | - summary.style.marginTop = '20px'; |
111 | | - summary.style.color = '#569cd6'; |
112 | | - summary.innerHTML = '<h2>Done — check DevTools console for additional warnings/errors</h2>'; |
113 | | - results.appendChild(summary); |
| 102 | + const httpUrl = getUrl('http'); |
| 103 | + const httpsUrl = getUrl('https'); |
| 104 | + |
| 105 | + // --- Section 1: HTTP target --- |
| 106 | + addSection(`HTTP target: ${httpUrl}`); |
| 107 | + await test('h1', 'Plain fetch', httpUrl); |
| 108 | + await test('h2', 'mode: no-cors', httpUrl, { mode: 'no-cors' }); |
| 109 | + await test('h3', 'targetAddressSpace: private', httpUrl, { targetAddressSpace: 'private' }); |
| 110 | + await test('h4', 'targetAddressSpace: local', httpUrl, { targetAddressSpace: 'local' }); |
| 111 | + await test('h5', 'no-cors + targetAddressSpace: private', httpUrl, { mode: 'no-cors', targetAddressSpace: 'private' }); |
| 112 | + |
| 113 | + // --- Section 2: HTTPS target --- |
| 114 | + addSection(`HTTPS target: ${httpsUrl}`); |
| 115 | + await test('s1', 'Plain fetch', httpsUrl); |
| 116 | + await test('s2', 'mode: no-cors', httpsUrl, { mode: 'no-cors' }); |
| 117 | + await test('s3', 'targetAddressSpace: private', httpsUrl, { targetAddressSpace: 'private' }); |
| 118 | + |
| 119 | + // --- Summary --- |
| 120 | + addSection('Done — also check DevTools Console tab for mixed content / CORS warnings'); |
114 | 121 | } |
115 | 122 | </script> |
116 | 123 | </body> |
|
0 commit comments