Files
serversite/info.html
T
2026-06-16 09:33:16 -04:00

653 lines
26 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>awesome johnruina site</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@500;600;700;800&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
min-height: 100vh;
background: #0f0f0f;
font-family: 'Inter', sans-serif;
color: #ccc;
padding: 4rem 1.5rem 2rem;
}
.top-bar {
position: fixed;
top: 0; left: 0; right: 0;
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 1.5rem;
z-index: 10;
pointer-events: none;
}
.top-bar > * { pointer-events: auto; }
.back-btn {
text-decoration: none;
color: #888;
font-family: 'Inter', sans-serif;
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.5px;
text-transform: uppercase;
transition: color 0.2s;
background: rgba(15,15,15,0.7);
padding: 0.4rem 0.8rem;
border-radius: 6px;
border: 1px solid #333;
}
.back-btn:hover { color: #fff; border-color: #555; }
h1 {
font-size: 1.1rem;
font-weight: 800;
letter-spacing: 1px;
text-transform: uppercase;
color: #888;
margin-bottom: 1.5rem;
}
.section {
margin-bottom: 1.5rem;
}
.section-title {
font-size: 0.65rem;
font-weight: 700;
letter-spacing: 1px;
text-transform: uppercase;
color: #555;
border-bottom: 1px solid #1a1a2e;
padding-bottom: 0.4rem;
margin-bottom: 0.5rem;
}
.row {
display: flex;
gap: 1rem;
padding: 0.35rem 0;
border-bottom: 1px solid #111;
font-size: 0.8rem;
}
.row .key {
color: #666;
font-weight: 600;
min-width: 200px;
flex-shrink: 0;
}
.row .val {
color: #bbb;
font-weight: 500;
word-break: break-all;
}
.row .val.error {
color: #6a3a3a;
}
.row .val.ok {
color: #3a6a3a;
}
.copy-btn {
margin-top: 1rem;
padding: 0.6rem 1.5rem;
background: #1a1a2e;
color: #e0e0e0;
border: 1px solid #333;
border-radius: 6px;
font-family: 'Inter', sans-serif;
font-weight: 700;
font-size: 0.7rem;
cursor: pointer;
transition: all 0.25s;
letter-spacing: 0.5px;
text-transform: uppercase;
}
.copy-btn:hover {
background: #16213e;
border-color: #4a4a8a;
box-shadow: 0 0 20px rgba(74,74,138,0.3);
}
.copy-btn.copied {
border-color: #4a8a4a;
color: #8aca8a;
}
@media (max-width: 600px) {
.row { flex-direction: column; gap: 0.1rem; }
.row .key { min-width: auto; }
}
</style>
</head>
<body>
<div class="top-bar">
<a class="back-btn" href="index.html">← Back</a>
</div>
<h1>Client Info Dump</h1>
<div id="root"></div>
<button class="copy-btn" id="copyBtn">Copy All</button>
<script>
const root = document.getElementById('root');
const copyBtn = document.getElementById('copyBtn');
function section(title) {
const s = document.createElement('div');
s.className = 'section';
const t = document.createElement('div');
t.className = 'section-title';
t.textContent = title;
s.appendChild(t);
root.appendChild(s);
return s;
}
function addRow(sec, key, val, cls) {
const r = document.createElement('div');
r.className = 'row';
const k = document.createElement('span');
k.className = 'key';
k.textContent = key;
const v = document.createElement('span');
v.className = 'val' + (cls ? ' ' + cls : '');
v.textContent = val == null ? '—' : String(val);
r.appendChild(k);
r.appendChild(v);
sec.appendChild(r);
return r;
}
// ── Browser / Navigator ──
const navSec = section('Browser / Navigator');
addRow(navSec, 'User Agent', navigator.userAgent);
addRow(navSec, 'Platform', navigator.platform);
addRow(navSec, 'Language', navigator.language);
addRow(navSec, 'Languages', navigator.languages ? navigator.languages.join(', ') : '—');
addRow(navSec, 'Cookie Enabled', navigator.cookieEnabled);
addRow(navSec, 'Do Not Track', navigator.doNotTrack || 'unspecified');
addRow(navSec, 'Hardware Concurrency', navigator.hardwareConcurrency);
addRow(navSec, 'Device Memory', navigator.deviceMemory ? navigator.deviceMemory + ' GB' : '—');
addRow(navSec, 'Max Touch Points', navigator.maxTouchPoints);
addRow(navSec, 'Online', navigator.onLine);
addRow(navSec, 'WebDriver', navigator.webdriver);
addRow(navSec, 'PDF Viewer Enabled', navigator.pdfViewerEnabled);
addRow(navSec, 'Vendor', navigator.vendor);
addRow(navSec, 'Vendor Sub', navigator.vendorSub || '—');
addRow(navSec, 'Product', navigator.product);
addRow(navSec, 'Product Sub', navigator.productSub || '—');
addRow(navSec, 'App Code Name', navigator.appCodeName);
addRow(navSec, 'App Name', navigator.appName);
addRow(navSec, 'App Version', navigator.appVersion);
addRow(navSec, 'Build ID', navigator.buildID || '—');
addRow(navSec, 'Oscpu', navigator.oscpu || '—');
// ── Screen ──
const scrSec = section('Screen');
addRow(scrSec, 'Width', screen.width);
addRow(scrSec, 'Height', screen.height);
addRow(scrSec, 'Avail Width', screen.availWidth);
addRow(scrSec, 'Avail Height', screen.availHeight);
addRow(scrSec, 'Color Depth', screen.colorDepth + ' bits');
addRow(scrSec, 'Pixel Depth', screen.pixelDepth + ' bits');
addRow(scrSec, 'Orientation', screen.orientation ? screen.orientation.type : '—');
// ── Window ──
const winSec = section('Window');
addRow(winSec, 'Inner Width', window.innerWidth);
addRow(winSec, 'Inner Height', window.innerHeight);
addRow(winSec, 'Outer Width', window.outerWidth);
addRow(winSec, 'Outer Height', window.outerHeight);
addRow(winSec, 'Device Pixel Ratio', window.devicePixelRatio);
addRow(winSec, 'Scroll X', window.scrollX);
addRow(winSec, 'Scroll Y', window.scrollY);
addRow(winSec, 'Screen Left', window.screenLeft);
addRow(winSec, 'Screen Top', window.screenTop);
addRow(winSec, 'Page Visible', !document.hidden);
addRow(winSec, 'Visibility State', document.visibilityState);
addRow(winSec, 'Referrer', document.referrer || '—');
addRow(winSec, 'Title', document.title);
addRow(winSec, 'URL', document.URL);
addRow(winSec, 'Base URI', document.baseURI);
// ── Location ──
const locSec = section('Location');
addRow(locSec, 'Href', window.location.href);
addRow(locSec, 'Protocol', window.location.protocol);
addRow(locSec, 'Host', window.location.host);
addRow(locSec, 'Hostname', window.location.hostname);
addRow(locSec, 'Port', window.location.port || '—');
addRow(locSec, 'Pathname', window.location.pathname);
addRow(locSec, 'Search', window.location.search || '—');
addRow(locSec, 'Hash', window.location.hash || '—');
addRow(locSec, 'Origin', window.location.origin);
// ── Connection ──
if (navigator.connection) {
const connSec = section('Network');
const c = navigator.connection;
addRow(connSec, 'Effective Type', c.effectiveType);
addRow(connSec, 'Downlink', c.downlink + ' Mbps');
addRow(connSec, 'RTT', c.rtt + ' ms');
addRow(connSec, 'Save Data', c.saveData);
addRow(connSec, 'Type', c.type);
}
// ── Plugins ──
const plugSec = section('Plugins');
if (navigator.plugins && navigator.plugins.length) {
for (const p of navigator.plugins) {
addRow(plugSec, p.name, p.filename + ' (' + p.description + ')');
}
} else {
addRow(plugSec, 'Plugins', 'none detected', 'error');
}
// ── MIME Types ──
const mimeSec = section('MIME Types');
if (navigator.mimeTypes && navigator.mimeTypes.length) {
for (const m of navigator.mimeTypes) {
addRow(mimeSec, m.type, m.suffixes + ' — ' + m.description);
}
} else {
addRow(mimeSec, 'MIME Types', 'none detected', 'error');
}
// ── Storage ──
const storSec = section('Storage');
let canLocal = false, canSession = false;
try { localStorage.setItem('__test', '1'); localStorage.removeItem('__test'); canLocal = true; } catch (e) {}
try { sessionStorage.setItem('__test', '1'); sessionStorage.removeItem('__test'); canSession = true; } catch (e) {}
addRow(storSec, 'Local Storage', canLocal ? 'available' : 'unavailable', canLocal ? 'ok' : 'error');
addRow(storSec, 'Session Storage', canSession ? 'available' : 'unavailable', canSession ? 'ok' : 'error');
addRow(storSec, 'Cookies', document.cookie || '(empty)');
// ── WebGL ──
const glSec = section('WebGL');
let glInfo = '—';
let glRenderer = '—';
try {
const c = document.createElement('canvas');
const gl = c.getContext('webgl') || c.getContext('experimental-webgl');
if (gl) {
glInfo = gl.getParameter(gl.VERSION);
glRenderer = gl.getParameter(gl.RENDERER) + ' — ' + gl.getParameter(gl.VENDOR);
const ext = gl.getSupportedExtensions();
addRow(glSec, 'Version', glInfo);
addRow(glSec, 'Renderer', glRenderer);
addRow(glSec, 'Vendor', gl.getParameter(gl.VENDOR));
addRow(glSec, 'Shading Language', gl.getParameter(gl.SHADING_LANGUAGE_VERSION));
addRow(glSec, 'Max Texture Size', gl.getParameter(gl.MAX_TEXTURE_SIZE));
addRow(glSec, 'Max Viewport Dims', gl.getParameter(gl.MAX_VIEWPORT_DIMS).join(' x '));
addRow(glSec, 'Max Vertex Attr', gl.getParameter(gl.MAX_VERTEX_ATTRIBS));
addRow(glSec, 'Max Combined Tex Image Units', gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS));
addRow(glSec, 'Max Fragment Uniform Vectors', gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS));
addRow(glSec, 'Max Varying Vectors', gl.getParameter(gl.MAX_VARYING_VECTORS));
addRow(glSec, 'Max Vertex Uniform Vectors', gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS));
addRow(glSec, 'Max Vertex Tex Image Units', gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS));
addRow(glSec, 'Max Tex Image Units', gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS));
addRow(glSec, 'Aliased Line Width Range', gl.getParameter(gl.ALIASED_LINE_WIDTH_RANGE).join(' - '));
addRow(glSec, 'Aliased Point Size Range', gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE).join(' - '));
addRow(glSec, 'Red Bits', gl.getParameter(gl.RED_BITS));
addRow(glSec, 'Green Bits', gl.getParameter(gl.GREEN_BITS));
addRow(glSec, 'Blue Bits', gl.getParameter(gl.BLUE_BITS));
addRow(glSec, 'Alpha Bits', gl.getParameter(gl.ALPHA_BITS));
addRow(glSec, 'Depth Bits', gl.getParameter(gl.DEPTH_BITS));
addRow(glSec, 'Stencil Bits', gl.getParameter(gl.STENCIL_BITS));
addRow(glSec, 'Supported Extensions', ext.join(', '));
} else {
addRow(glSec, 'WebGL', 'not supported', 'error');
}
} catch (e) {
addRow(glSec, 'WebGL', 'error: ' + e.message, 'error');
}
// ── CPU / Performance ──
const perfSec = section('Performance / Timing');
const perf = performance;
addRow(perfSec, 'Time Origin', perf.timeOrigin);
addRow(perfSec, 'Navigation Type', perf.navigation ? ['navigate', 'reload', 'back_forward', 'prerender'][perf.navigation.type] || '—' : '—');
addRow(perfSec, 'Redirect Count', perf.navigation ? perf.navigation.redirectCount : '—');
if (perf.memory) {
addRow(perfSec, 'JS Heap Size Limit', Math.round(perf.memory.jsHeapSizeLimit / 1024 / 1024) + ' MB');
addRow(perfSec, 'JS Heap Total', Math.round(perf.memory.totalJSHeapSize / 1024 / 1024) + ' MB');
addRow(perfSec, 'JS Heap Used', Math.round(perf.memory.usedJSHeapSize / 1024 / 1024) + ' MB');
}
if (perf.timing) {
const t = perf.timing;
addRow(perfSec, 'Dom Interactive', t.domInteractive ? (t.domInteractive - t.navigationStart) + ' ms' : '—');
addRow(perfSec, 'Dom Complete', t.domComplete ? (t.domComplete - t.navigationStart) + ' ms' : '—');
addRow(perfSec, 'Load Event End', t.loadEventEnd ? (t.loadEventEnd - t.navigationStart) + ' ms' : '—');
}
// ── Battery ──
if (navigator.getBattery) {
navigator.getBattery().then(b => {
const batSec = section('Battery');
addRow(batSec, 'Charging', b.charging);
addRow(batSec, 'Level', Math.round(b.level * 100) + '%');
addRow(batSec, 'Charging Time', b.chargingTime === Infinity ? 'Infinity' : b.chargingTime + ' s');
addRow(batSec, 'Discharging Time', b.dischargingTime === Infinity ? 'Infinity' : b.dischargingTime + ' s');
});
}
// ── Geolocation ──
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(pos => {
const geoSec = section('Geolocation');
addRow(geoSec, 'Latitude', pos.coords.latitude);
addRow(geoSec, 'Longitude', pos.coords.longitude);
addRow(geoSec, 'Accuracy', pos.coords.accuracy + ' m');
addRow(geoSec, 'Altitude', pos.coords.altitude != null ? pos.coords.altitude + ' m' : '—');
addRow(geoSec, 'Altitude Accuracy', pos.coords.altitudeAccuracy != null ? pos.coords.altitudeAccuracy + ' m' : '—');
addRow(geoSec, 'Heading', pos.coords.heading != null ? pos.coords.heading + '°' : '—');
addRow(geoSec, 'Speed', pos.coords.speed != null ? pos.coords.speed + ' m/s' : '—');
}, () => {
const geoSec = section('Geolocation');
addRow(geoSec, 'Geolocation', 'permission denied or unavailable', 'error');
}, { timeout: 5000, enableHighAccuracy: false });
}
// ── Time ──
const timeSec = section('Time');
const now = new Date();
addRow(timeSec, 'Locale String', now.toLocaleString());
addRow(timeSec, 'UTC String', now.toUTCString());
addRow(timeSec, 'ISO String', now.toISOString());
addRow(timeSec, 'Timezone', Intl.DateTimeFormat().resolvedOptions().timeZone);
addRow(timeSec, 'Timezone Offset', -now.getTimezoneOffset() + ' min');
addRow(timeSec, 'Timestamp', now.getTime());
// ── Fonts ──
const fontSec = section('Fonts (system)');
if (document.fonts && document.fonts.ready) {
document.fonts.ready.then(() => {
const fonts = document.fonts;
for (const f of fonts) {
addRow(fontSec, f.family, f.style + ' ' + f.weight + ' ' + f.stretch);
}
if (fonts.size === 0) addRow(fontSec, 'Fonts', 'none loaded');
});
}
// ── Display ──
const dispSec = section('Display / Refresh Rate');
(function measureFrameRate() {
let frames = 0;
let last = performance.now();
function tick() {
frames++;
const now = performance.now();
if (now - last >= 1000) {
addRow(dispSec, 'Refresh Rate (approx)', frames + ' Hz');
return;
}
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
})();
// ── Storage Estimate ──
if (navigator.storage && navigator.storage.estimate) {
navigator.storage.estimate().then(s => {
const storEstSec = section('Storage Estimate');
addRow(storEstSec, 'Quota', Math.round(s.quota / 1024 / 1024) + ' MB');
addRow(storEstSec, 'Usage', Math.round(s.usage / 1024 / 1024) + ' MB');
addRow(storEstSec, 'Usage Details', s.usageDetails ? JSON.stringify(s.usageDetails) : '—');
});
}
// ── Audio ──
const audioSec = section('Audio');
try {
const actx = new (window.AudioContext || window.webkitAudioContext)();
addRow(audioSec, 'Sample Rate', actx.sampleRate + ' Hz');
addRow(audioSec, 'State', actx.state);
addRow(audioSec, 'Max Channels', actx.destination.maxChannelCount || '—');
actx.close();
} catch (e) {
addRow(audioSec, 'Audio', 'not available', 'error');
}
// ── Sensors ──
const sensSec = section('Sensors');
if ('Accelerometer' in window) {
try {
const accel = new Accelerometer({ frequency: 10 });
accel.start();
accel.onreading = () => {
addRow(sensSec, 'Accelerometer', accel.x + ', ' + accel.y + ', ' + accel.z + ' m/s²');
accel.stop();
};
accel.onerror = () => addRow(sensSec, 'Accelerometer', 'not available', 'error');
setTimeout(() => { if (accel.activated === false) { accel.stop(); addRow(sensSec, 'Accelerometer', 'not available', 'error'); } }, 1500);
} catch (e) {
addRow(sensSec, 'Accelerometer', 'not available', 'error');
}
} else {
addRow(sensSec, 'Accelerometer', 'not supported', 'error');
}
if ('Gyroscope' in window) {
try {
const gyro = new Gyroscope({ frequency: 10 });
gyro.start();
gyro.onreading = () => {
addRow(sensSec, 'Gyroscope', gyro.x + ', ' + gyro.y + ', ' + gyro.z + ' °/s');
gyro.stop();
};
gyro.onerror = () => addRow(sensSec, 'Gyroscope', 'not available', 'error');
setTimeout(() => { if (gyro.activated === false) { gyro.stop(); addRow(sensSec, 'Gyroscope', 'not available', 'error'); } }, 1500);
} catch (e) {
addRow(sensSec, 'Gyroscope', 'not available', 'error');
}
} else {
addRow(sensSec, 'Gyroscope', 'not supported', 'error');
}
if ('LinearAccelerationSensor' in window) {
addRow(sensSec, 'Linear Acceleration Sensor', 'supported');
} else {
addRow(sensSec, 'Linear Acceleration Sensor', 'not supported');
}
if ('AbsoluteOrientationSensor' in window) {
addRow(sensSec, 'Absolute Orientation Sensor', 'supported');
} else {
addRow(sensSec, 'Absolute Orientation Sensor', 'not supported');
}
// ── Hardware APIs ──
const hwApiSec = section('Hardware APIs');
addRow(hwApiSec, 'Bluetooth', 'bluetooth' in navigator ? 'available' : 'not available', 'bluetooth' in navigator ? 'ok' : 'error');
addRow(hwApiSec, 'USB', 'usb' in navigator ? 'available' : 'not available', 'usb' in navigator ? 'ok' : 'error');
addRow(hwApiSec, 'NFC', 'nfc' in navigator ? 'available' : 'not available', 'nfc' in navigator ? 'ok' : 'error');
addRow(hwApiSec, 'Serial', 'serial' in navigator ? 'available' : 'not available', 'serial' in navigator ? 'ok' : 'error');
addRow(hwApiSec, 'Vibration', 'vibrate' in navigator ? 'supported' : 'not supported', 'vibrate' in navigator ? 'ok' : 'error');
addRow(hwApiSec, 'Wake Lock', 'wakeLock' in navigator ? 'available' : 'not available');
addRow(hwApiSec, 'Hardware Concurrency', navigator.hardwareConcurrency + ' logical processors');
addRow(hwApiSec, 'Device Memory', navigator.deviceMemory ? navigator.deviceMemory + ' GB' : '—');
if ('keyboard' in navigator) {
addRow(hwApiSec, 'Keyboard API', 'available');
}
// ── WebGL 2 ──
const gl2Sec = section('WebGL 2');
try {
const c2 = document.createElement('canvas');
const gl2 = c2.getContext('webgl2');
if (gl2) {
addRow(gl2Sec, 'Version', gl2.getParameter(gl2.VERSION));
addRow(gl2Sec, 'Renderer', gl2.getParameter(gl2.RENDERER) + ' — ' + gl2.getParameter(gl2.VENDOR));
addRow(gl2Sec, 'Shading Language', gl2.getParameter(gl2.SHADING_LANGUAGE_VERSION));
addRow(gl2Sec, 'Max Texture Size', gl2.getParameter(gl2.MAX_TEXTURE_SIZE));
addRow(gl2Sec, 'Max 3D Texture Size', gl2.getParameter(gl2.MAX_3D_TEXTURE_SIZE));
addRow(gl2Sec, 'Max Array Texture Layers', gl2.getParameter(gl2.MAX_ARRAY_TEXTURE_LAYERS));
addRow(gl2Sec, 'Max Uniform Buffer Bindings', gl2.getParameter(gl2.MAX_UNIFORM_BUFFER_BINDINGS));
addRow(gl2Sec, 'Max Uniform Block Size', gl2.getParameter(gl2.MAX_UNIFORM_BLOCK_SIZE) + ' bytes');
addRow(gl2Sec, 'Max Vertex Uniform Blocks', gl2.getParameter(gl2.MAX_VERTEX_UNIFORM_BLOCKS));
addRow(gl2Sec, 'Max Fragment Uniform Blocks', gl2.getParameter(gl2.MAX_FRAGMENT_UNIFORM_BLOCKS));
addRow(gl2Sec, 'Max Combined Uniform Blocks', gl2.getParameter(gl2.MAX_COMBINED_UNIFORM_BLOCKS));
addRow(gl2Sec, 'Max Texture LOD Bias', gl2.getParameter(gl2.MAX_TEXTURE_LOD_BIAS));
addRow(gl2Sec, 'Max Transform Feedback Sep Attrs', gl2.getParameter(gl2.MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS));
addRow(gl2Sec, 'Max Draw Buffers', gl2.getParameter(gl2.MAX_DRAW_BUFFERS));
addRow(gl2Sec, 'Max Color Attachments', gl2.getParameter(gl2.MAX_COLOR_ATTACHMENTS));
addRow(gl2Sec, 'Max Samples', gl2.getParameter(gl2.MAX_SAMPLES));
} else {
addRow(gl2Sec, 'WebGL 2', 'not supported', 'error');
}
} catch (e) {
addRow(gl2Sec, 'WebGL 2', 'error: ' + e.message, 'error');
}
// ── Network: DNS / IP / Latency ──
const netSec = section('Network (External)');
addRow(netSec, 'Fetching IP', '...');
// Try multiple IP services
const ipServices = [
'https://api.ipify.org?format=json',
'https://api.myip.com',
'https://ip-api.com/json/',
];
(function fetchIP() {
fetch(ipServices[0])
.then(r => r.json())
.then(d => {
const ip = d.ip || d.public_ip || d.query || '—';
addRow(netSec, 'Public IP', ip);
if (d.country) addRow(netSec, 'Country (ipify)', d.country);
})
.catch(() => {
fetch(ipServices[1])
.then(r => r.json())
.then(d => {
if (d.ip) addRow(netSec, 'Public IP', d.ip);
if (d.country) addRow(netSec, 'Country (myip)', d.country);
if (d.cc) addRow(netSec, 'Country Code', d.cc);
})
.catch(() => {
fetch(ipServices[2])
.then(r => r.json())
.then(d => {
if (d.query) addRow(netSec, 'Public IP', d.query);
if (d.country) addRow(netSec, 'Country (ip-api)', d.country);
if (d.regionName) addRow(netSec, 'Region', d.regionName);
if (d.city) addRow(netSec, 'City', d.city);
if (d.isp) addRow(netSec, 'ISP', d.isp);
if (d.org) addRow(netSec, 'Organization', d.org);
if (d.as) addRow(netSec, 'AS', d.as);
if (d.lat && d.lon) addRow(netSec, 'Approx Coordinates', d.lat + ', ' + d.lon);
})
.catch(() => {
addRow(netSec, 'Public IP', 'unreachable', 'error');
});
});
});
})();
// Measure latency to a known host
(function measureLatency() {
const t0 = performance.now();
fetch('https://www.google.com/favicon.ico', { mode: 'no-cors', cache: 'no-store' })
.then(() => {
const ms = (performance.now() - t0).toFixed(0);
addRow(netSec, 'Latency (google.com)', ms + ' ms');
})
.catch(() => {
addRow(netSec, 'Latency (google.com)', 'failed', 'error');
});
})();
// ── Network: Connection Events & Additional Info ──
if (navigator.connection) {
const c = navigator.connection;
addRow(netSec, 'Downlink Max', c.downlinkMax != null ? c.downlinkMax + ' Mbps' : '—');
addRow(netSec, 'Effective Type', c.effectiveType);
addRow(netSec, 'RTT', c.rtt + ' ms');
addRow(netSec, 'Downlink', c.downlink + ' Mbps');
addRow(netSec, 'Save Data', c.saveData);
addRow(netSec, 'Connection Type', c.type);
}
// ── WebRTC ──
const rtcSec = section('WebRTC');
if ('RTCPeerConnection' in window) {
addRow(rtcSec, 'RTCPeerConnection', 'available', 'ok');
// Attempt to detect local IP via STUN (frequently blocked)
try {
const pc = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] });
pc.createDataChannel('');
pc.createOffer().then(offer => pc.setLocalDescription(offer));
pc.onicecandidate = (e) => {
if (e.candidate && e.candidate.candidate) {
const match = e.candidate.candidate.match(/([0-9]{1,3}\.){3}[0-9]{1,3}/);
if (match) {
addRow(rtcSec, 'Local IP (STUN)', match[0]);
pc.close();
}
}
};
setTimeout(() => { if (pc.connectionState === 'new') pc.close(); }, 3000);
} catch (e) {
addRow(rtcSec, 'Local IP', 'blocked', 'error');
}
} else {
addRow(rtcSec, 'RTCPeerConnection', 'not available', 'error');
}
// ── Copy ──
function collectData() {
const rows = root.querySelectorAll('.row');
const lines = [];
let currentSection = '';
for (const r of rows) {
const sec = r.closest('.section');
if (sec) {
const secTitle = sec.querySelector('.section-title');
const st = secTitle ? secTitle.textContent.trim() : '';
if (st && st !== currentSection) {
currentSection = st;
lines.push('[' + st + ']');
}
}
const key = r.querySelector('.key').textContent;
const val = r.querySelector('.val').textContent;
lines.push(key + ': ' + val);
}
return lines.join('\n');
}
copyBtn.addEventListener('click', () => {
const text = collectData();
navigator.clipboard.writeText(text).then(() => {
copyBtn.textContent = 'Copied!';
copyBtn.classList.add('copied');
setTimeout(() => {
copyBtn.textContent = 'Copy All';
copyBtn.classList.remove('copied');
}, 2000);
}).catch(() => {
copyBtn.textContent = 'Failed';
setTimeout(() => { copyBtn.textContent = 'Copy All'; }, 2000);
});
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') window.location.href = 'index.html';
});
</script>
</body>
</html>