diff --git a/src/main.ts b/src/main.ts index aceb242..cad0ff8 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,14 @@ async function renderApp() { initAccordion(); initMobile(); + // Asynchronous resolve + for (const item of allData) { + item.resolve?.() + .then(value => updateTile(item, value)) + .catch(() => updateTile(item, 'Unavailable')); + item.live?.(value => updateTile(item, value)); + } + // 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..041f97c 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 () => { 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/index.ts b/src/modules/index.ts index ede133d..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 { updateSensorStates } 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 ddf7490..3e73674 100644 --- a/src/modules/phone.ts +++ b/src/modules/phone.ts @@ -1,4 +1,5 @@ import type { FingerprintData } from './types'; +import { streamSensor } from './sensor'; export function getPhoneFingerprint(): FingerprintData[] { const data: FingerprintData[] = []; @@ -55,20 +56,23 @@ export function getPhoneFingerprint(): FingerprintData[] { { category: 'Sensors', key: 'Accelerometer', - value: 'Checking...', - tooltip: 'Measures acceleration of the device along 3 axes. Placeholder—actual permission checked asynchronously.' + value: 'Waiting...', + live: streamSensor('Accelerometer'), + tooltip: 'Measures acceleration of the device along 3 axes. Obtained from an Accelerometer object.' }, { category: 'Sensors', key: 'Gyroscope', - value: 'Checking...', - tooltip: 'Measures rotation rate around the device axes. Placeholder—actual permission checked asynchronously.' + value: 'Waiting...', + live: streamSensor('Gyroscope'), + tooltip: 'Measures rotation rate around the device axes. Obtained from a Gyroscope object.' }, { category: 'Sensors', key: 'Magnetometer', - value: 'Checking...', - tooltip: 'Detects the magnetic field around the device. Placeholder—actual permission checked asynchronously.' + value: 'Waiting...', + 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 aaf17bb..cd33898 100644 --- a/src/modules/sensor.ts +++ b/src/modules/sensor.ts @@ -1,49 +1,21 @@ -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; +// 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)) { + set('Not supported'); + return; } - let SensorClass: any = (window as any)[s.className]; - let sensor: any; - try { - sensor = new SensorClass({ frequency: 1 }); + const SensorClass: any = (window as any)[name]; + const 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(); + set(`${sensor.x.toFixed(2)}, ${sensor.y.toFixed(2)}, ${sensor.z.toFixed(2)}`); }); - 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.`; - sensor.stop(); - }); - + sensor.addEventListener('error', () => set('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.`; - } - }, 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.`; + set(err?.name === 'SecurityError' ? 'Permission Required' : 'Blocked'); } - } + }; } diff --git a/src/modules/types.ts b/src/modules/types.ts index e8a150d..2ed93a6 100644 --- a/src/modules/types.ts +++ b/src/modules/types.ts @@ -2,5 +2,9 @@ export interface FingerprintData { category: string; key: string; 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; } 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;