From 5d71453625412d22376e16caea05100653dabc87 Mon Sep 17 00:00:00 2001 From: rarangur Date: Sun, 21 Jun 2026 08:26:10 +0200 Subject: [PATCH 1/3] Resolve battery and sensor values asynchronously after render --- src/main.ts | 15 ++++++----- src/modules/battery.ts | 10 +++++--- src/modules/index.ts | 2 +- src/modules/phone.ts | 4 +++ src/modules/sensor.ts | 56 ++++++++++++++---------------------------- src/modules/types.ts | 2 ++ src/utils.ts | 11 +++++++++ 7 files changed, 52 insertions(+), 48 deletions(-) diff --git a/src/main.ts b/src/main.ts index aceb242..75362ee 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,10 +7,10 @@ import { getCPUBenchmark, getSpeechVoices, getClientHints, getTouchInfo, getFullScreenInfo, getTimerResolution, getPhoneFingerprint, getStorageInfo, murmurhash3, getNavigatorInfo, getMediaDevices, getPermissionsStatus, getInputInfo, getCSSFeatures, getConnectionInfo, - getWebGPUInfo, getPWAInfo, updateSensorStates, getMediaCapabilities, getWebCodecs, getWasmFeatures, + getWebGPUInfo, getPWAInfo, getMediaCapabilities, getWebCodecs, getWasmFeatures, getPrivacyInfo, getJavascriptInfo, getIntlFingerprint } from './modules'; -import { initMobile, safeId, createTile, safePush } from './utils'; +import { initMobile, safeId, createTile, safePush, updateTile } from './utils'; import { getIcon, initIcons } from './icons'; import { initAccordion, createInfo } from './info'; @@ -77,10 +77,6 @@ async function renderApp() { await safePush(allData, getIntlFingerprint); await safePush(allData, getSpeechVoices); - // Sensors - await updateSensorStates(allData); - - const STABLE_KEYS = new Set([ 'User Agent', 'Platform', 'Hardware Threads', 'Device Memory', 'Screen Resolution', 'Screen Pixel Ratio', 'Language', 'Languages', @@ -140,6 +136,13 @@ async function renderApp() { initAccordion(); initMobile(); + // Asynchronous resolve + for (const item of allData) { + item.resolve?.() + .then(value => updateTile(item, value)) + .catch(() => updateTile(item, 'Unavailable')); + } + // Smooth scroll + active nav document.querySelectorAll('.nav-item').forEach(link => { link.addEventListener('click', e => { diff --git a/src/modules/battery.ts b/src/modules/battery.ts index d5e4486..ab02422 100644 --- a/src/modules/battery.ts +++ b/src/modules/battery.ts @@ -10,10 +10,12 @@ export function getBatteryInfo(): FingerprintData[] { }]; } - // Placeholder; real values can be updated asynchronously if desired + // Call getBattery() once here so all three resolvers await the same promise + // instead of each requesting the battery separately. + const battery = (navigator as any).getBattery(); return [ - { category: 'Hardware', key: 'Battery Level', value: 'Loading...', tooltip: 'Shows current battery level as a percentage. Obtained via navigator.getBattery().' }, - { category: 'Hardware', key: 'Charging Status', value: 'Loading...', tooltip: 'Indicates if the device is currently charging. Obtained via navigator.getBattery().' }, - { category: 'Hardware', key: 'Discharging Time', value: 'Loading...', tooltip: 'Estimated time until battery is empty. Obtained via navigator.getBattery().' }, + { category: 'Hardware', key: 'Battery Level', value: 'Loading...', resolve: async () => `${Math.round((await battery).level * 100)}%`, tooltip: 'Shows current battery level as a percentage. Obtained via navigator.getBattery().' }, + { category: 'Hardware', key: 'Charging Status', value: 'Loading...', resolve: async () => (await battery).charging ? 'Charging' : 'Not charging', tooltip: 'Indicates if the device is currently charging. Obtained via navigator.getBattery().' }, + { category: 'Hardware', key: 'Discharging Time', value: 'Loading...', resolve: async () => `${(await battery).dischargingTime}s`, tooltip: 'Estimated time until battery is empty. Obtained via navigator.getBattery().' }, ]; } diff --git a/src/modules/index.ts b/src/modules/index.ts index ede133d..f963ed8 100644 --- a/src/modules/index.ts +++ b/src/modules/index.ts @@ -27,7 +27,7 @@ export { getCSSFeatures } from './cssFeatures'; export { getConnectionInfo } from './connection'; export { getWebGPUInfo } from './webgpu'; export { getPWAInfo } from './pwa'; -export { updateSensorStates } from './sensor'; +export { resolveSensor } from './sensor'; export { getMediaCapabilities } from './mediaCapabilities'; export { getWebCodecs } from './webcodecs'; export { getWasmFeatures } from './webassembly'; diff --git a/src/modules/phone.ts b/src/modules/phone.ts index ddf7490..7e113ca 100644 --- a/src/modules/phone.ts +++ b/src/modules/phone.ts @@ -1,4 +1,5 @@ import type { FingerprintData } from './types'; +import { resolveSensor } from './sensor'; export function getPhoneFingerprint(): FingerprintData[] { const data: FingerprintData[] = []; @@ -56,18 +57,21 @@ export function getPhoneFingerprint(): FingerprintData[] { category: 'Sensors', key: 'Accelerometer', value: 'Checking...', + resolve: resolveSensor('Accelerometer'), tooltip: 'Measures acceleration of the device along 3 axes. Placeholder—actual permission checked asynchronously.' }, { category: 'Sensors', key: 'Gyroscope', value: 'Checking...', + resolve: resolveSensor('Gyroscope'), tooltip: 'Measures rotation rate around the device axes. Placeholder—actual permission checked asynchronously.' }, { category: 'Sensors', key: 'Magnetometer', value: 'Checking...', + resolve: resolveSensor('Magnetometer'), tooltip: 'Detects the magnetic field around the device. Placeholder—actual permission checked asynchronously.' }, { diff --git a/src/modules/sensor.ts b/src/modules/sensor.ts index aaf17bb..afa3217 100644 --- a/src/modules/sensor.ts +++ b/src/modules/sensor.ts @@ -1,49 +1,31 @@ -import type { FingerprintData } from './types'; - -export async function updateSensorStates(allData: FingerprintData[]) { - const sensors = [ - { key: 'Accelerometer', className: 'Accelerometer' }, - { key: 'Gyroscope', className: 'Gyroscope' }, - { key: 'Magnetometer', className: 'Magnetometer' } - ]; - - for (const s of sensors) { - const idx = allData.findIndex(d => d.category === 'Sensors' && d.key === s.key); - if (idx === -1) continue; - - if (!(s.className in window)) { - allData[idx].value = 'Not supported'; - allData[idx].tooltip = `The ${s.key} is not supported. API not present in this browser.`; - continue; +// Wraps the event-based Sensor API in a promise so it fits FingerprintData.resolve. +export function resolveSensor(name: string): () => Promise { + return () => new Promise(resolve => { + if (!(name in window)) { + resolve('Not supported'); + return; } - let SensorClass: any = (window as any)[s.className]; - let sensor: any; - try { - sensor = new SensorClass({ frequency: 1 }); - sensor.addEventListener('reading', () => { - allData[idx].value = 'Available'; - allData[idx].tooltip = `${s.key} is available and accessible. Detected by creating a sensor instance and receiving readings.`; - sensor.stop(); - }); - sensor.addEventListener('error', () => { - allData[idx].value = 'Blocked or No Permission'; - allData[idx].tooltip = `${s.key} is blocked or requires permission. Detected by sensor error events.`; + const SensorClass: any = (window as any)[name]; + const sensor = new SensorClass({ frequency: 1 }); + let settled = false; + const done = (value: string) => { + if (settled) return; + settled = true; sensor.stop(); - }); + resolve(value); + }; + sensor.addEventListener('reading', () => done('Available')); + sensor.addEventListener('error', () => done('Blocked or No Permission')); sensor.start(); setTimeout(() => { - if (allData[idx].value === 'Checking...') { - allData[idx].value = 'Blocked / Requires Permission'; - allData[idx].tooltip = `${s.key} did not respond. Possibly blocked or permission required.`; - } + done('Blocked / Requires Permission'); }, 500); } catch (err: any) { - allData[idx].value = err.name === 'SecurityError' ? 'Permission Required' : 'Blocked'; - allData[idx].tooltip = `${s.key} cannot be accessed. Caught during sensor initialization.`; + resolve(err?.name === 'SecurityError' ? 'Permission Required' : 'Blocked'); } - } + }); } diff --git a/src/modules/types.ts b/src/modules/types.ts index e8a150d..471ebd4 100644 --- a/src/modules/types.ts +++ b/src/modules/types.ts @@ -2,5 +2,7 @@ export interface FingerprintData { category: string; key: string; value: string; + // If set, its result replaces `value` in the tile after render + resolve?: () => Promise; tooltip?: string; } diff --git a/src/utils.ts b/src/utils.ts index cd4d802..7bcb7fa 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -56,9 +56,20 @@ export async function safePush(allData: FingerprintData[], fn: () => Fingerprint } +// Stable identity for a tile (key alone isn't unique across categories). +export function tileId(item: Pick): string { + return `${item.category}::${item.key}`; +} + +export function updateTile(item: FingerprintData, value: string): void { + const valueEl = document.querySelector(`.tile[data-id="${CSS.escape(tileId(item))}"] .tile-value`); + if (valueEl) valueEl.textContent = value; +} + export function createTile(item: FingerprintData) { const tile = document.createElement('div'); tile.className = 'tile'; + tile.dataset.id = tileId(item); const shortValue = item.value.length > 168 ? item.value.slice(0, 165) + ' ...' : item.value; From 4d2d2af00340c749e16781aa297af167ae8a9a37 Mon Sep 17 00:00:00 2001 From: rarangur Date: Wed, 24 Jun 2026 15:35:45 +0200 Subject: [PATCH 2/3] fix tooltips and handling of Infinity in battery --- src/modules/battery.ts | 2 +- src/modules/phone.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/modules/battery.ts b/src/modules/battery.ts index ab02422..041f97c 100644 --- a/src/modules/battery.ts +++ b/src/modules/battery.ts @@ -16,6 +16,6 @@ export function getBatteryInfo(): FingerprintData[] { return [ { category: 'Hardware', key: 'Battery Level', value: 'Loading...', resolve: async () => `${Math.round((await battery).level * 100)}%`, tooltip: 'Shows current battery level as a percentage. Obtained via navigator.getBattery().' }, { category: 'Hardware', key: 'Charging Status', value: 'Loading...', resolve: async () => (await battery).charging ? 'Charging' : 'Not charging', tooltip: 'Indicates if the device is currently charging. Obtained via navigator.getBattery().' }, - { category: 'Hardware', key: 'Discharging Time', value: 'Loading...', resolve: async () => `${(await battery).dischargingTime}s`, tooltip: 'Estimated time until battery is empty. Obtained via navigator.getBattery().' }, + { category: 'Hardware', key: 'Discharging Time', value: 'Loading...', resolve: async () => { const t = (await battery).dischargingTime; return isFinite(t) ? `${t}s` : 'Unknown'; }, tooltip: 'Estimated time until battery is empty. Obtained via navigator.getBattery().' }, ]; } diff --git a/src/modules/phone.ts b/src/modules/phone.ts index 7e113ca..4ce778d 100644 --- a/src/modules/phone.ts +++ b/src/modules/phone.ts @@ -56,23 +56,23 @@ export function getPhoneFingerprint(): FingerprintData[] { { category: 'Sensors', key: 'Accelerometer', - value: 'Checking...', + value: 'Waiting...', resolve: resolveSensor('Accelerometer'), - tooltip: 'Measures acceleration of the device along 3 axes. Placeholder—actual permission checked asynchronously.' + tooltip: 'Measures acceleration of the device along 3 axes. Detected by instantiating Accelerometer() and listening for a first reading.' }, { category: 'Sensors', key: 'Gyroscope', - value: 'Checking...', + value: 'Waiting...', resolve: resolveSensor('Gyroscope'), - tooltip: 'Measures rotation rate around the device axes. Placeholder—actual permission checked asynchronously.' + tooltip: 'Measures rotation rate around the device axes. Detected by instantiating Gyroscope() and listening for a first reading.' }, { category: 'Sensors', key: 'Magnetometer', - value: 'Checking...', + value: 'Waiting...', resolve: resolveSensor('Magnetometer'), - tooltip: 'Detects the magnetic field around the device. Placeholder—actual permission checked asynchronously.' + tooltip: 'Detects the magnetic field around the device. Detected by instantiating Magnetometer() and listening for a first reading.' }, { category: 'Sensors', From 4ccdb5415c7f7198c6f302159a97f48e2493f802 Mon Sep 17 00:00:00 2001 From: rarangur Date: Wed, 24 Jun 2026 17:08:44 +0200 Subject: [PATCH 3/3] show data from sensors live every second if available --- src/main.ts | 1 + src/modules/index.ts | 2 +- src/modules/phone.ts | 14 +++++++------- src/modules/sensor.ts | 30 ++++++++++-------------------- src/modules/types.ts | 2 ++ 5 files changed, 21 insertions(+), 28 deletions(-) diff --git a/src/main.ts b/src/main.ts index 75362ee..cad0ff8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -141,6 +141,7 @@ async function renderApp() { item.resolve?.() .then(value => updateTile(item, value)) .catch(() => updateTile(item, 'Unavailable')); + item.live?.(value => updateTile(item, value)); } // Smooth scroll + active nav diff --git a/src/modules/index.ts b/src/modules/index.ts index f963ed8..3cdc793 100644 --- a/src/modules/index.ts +++ b/src/modules/index.ts @@ -27,7 +27,7 @@ export { getCSSFeatures } from './cssFeatures'; export { getConnectionInfo } from './connection'; export { getWebGPUInfo } from './webgpu'; export { getPWAInfo } from './pwa'; -export { resolveSensor } from './sensor'; +export { streamSensor } from './sensor'; export { getMediaCapabilities } from './mediaCapabilities'; export { getWebCodecs } from './webcodecs'; export { getWasmFeatures } from './webassembly'; diff --git a/src/modules/phone.ts b/src/modules/phone.ts index 4ce778d..3e73674 100644 --- a/src/modules/phone.ts +++ b/src/modules/phone.ts @@ -1,5 +1,5 @@ import type { FingerprintData } from './types'; -import { resolveSensor } from './sensor'; +import { streamSensor } from './sensor'; export function getPhoneFingerprint(): FingerprintData[] { const data: FingerprintData[] = []; @@ -57,22 +57,22 @@ export function getPhoneFingerprint(): FingerprintData[] { category: 'Sensors', key: 'Accelerometer', value: 'Waiting...', - resolve: resolveSensor('Accelerometer'), - tooltip: 'Measures acceleration of the device along 3 axes. Detected by instantiating Accelerometer() and listening for a first reading.' + live: streamSensor('Accelerometer'), + tooltip: 'Measures acceleration of the device along 3 axes. Obtained from an Accelerometer object.' }, { category: 'Sensors', key: 'Gyroscope', value: 'Waiting...', - resolve: resolveSensor('Gyroscope'), - tooltip: 'Measures rotation rate around the device axes. Detected by instantiating Gyroscope() and listening for a first reading.' + live: streamSensor('Gyroscope'), + tooltip: 'Measures rotation rate around the device axes. Obtained from a Gyroscope object.' }, { category: 'Sensors', key: 'Magnetometer', value: 'Waiting...', - resolve: resolveSensor('Magnetometer'), - tooltip: 'Detects the magnetic field around the device. Detected by instantiating Magnetometer() and listening for a first reading.' + live: streamSensor('Magnetometer'), + tooltip: 'Detects the magnetic field around the device. Obtained from a Magnetometer object.' }, { category: 'Sensors', diff --git a/src/modules/sensor.ts b/src/modules/sensor.ts index afa3217..cd33898 100644 --- a/src/modules/sensor.ts +++ b/src/modules/sensor.ts @@ -1,31 +1,21 @@ -// Wraps the event-based Sensor API in a promise so it fits FingerprintData.resolve. -export function resolveSensor(name: string): () => Promise { - return () => new Promise(resolve => { +// Streams a sensor's x/y/z readings to the tile, or reports why it can't. +export function streamSensor(name: string): (set: (value: string) => void) => void { + return set => { if (!(name in window)) { - resolve('Not supported'); + set('Not supported'); return; } try { const SensorClass: any = (window as any)[name]; const sensor = new SensorClass({ frequency: 1 }); - let settled = false; - const done = (value: string) => { - if (settled) return; - settled = true; - sensor.stop(); - resolve(value); - }; - - sensor.addEventListener('reading', () => done('Available')); - sensor.addEventListener('error', () => done('Blocked or No Permission')); + sensor.addEventListener('reading', () => { + set(`${sensor.x.toFixed(2)}, ${sensor.y.toFixed(2)}, ${sensor.z.toFixed(2)}`); + }); + sensor.addEventListener('error', () => set('Blocked or No Permission')); sensor.start(); - - setTimeout(() => { - done('Blocked / Requires Permission'); - }, 500); } catch (err: any) { - resolve(err?.name === 'SecurityError' ? 'Permission Required' : 'Blocked'); + set(err?.name === 'SecurityError' ? 'Permission Required' : 'Blocked'); } - }); + }; } diff --git a/src/modules/types.ts b/src/modules/types.ts index 471ebd4..2ed93a6 100644 --- a/src/modules/types.ts +++ b/src/modules/types.ts @@ -4,5 +4,7 @@ export interface FingerprintData { value: string; // If set, its result replaces `value` in the tile after render resolve?: () => Promise; + // If set, pushes repeated value updates to the tile after render + live?: (set: (value: string) => void) => void; tooltip?: string; }