Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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 => {
Expand Down
10 changes: 6 additions & 4 deletions src/modules/battery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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().' },
];
}
2 changes: 1 addition & 1 deletion src/modules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
16 changes: 10 additions & 6 deletions src/modules/phone.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { FingerprintData } from './types';
import { streamSensor } from './sensor';

export function getPhoneFingerprint(): FingerprintData[] {
const data: FingerprintData[] = [];
Expand Down Expand Up @@ -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',
Expand Down
52 changes: 12 additions & 40 deletions src/modules/sensor.ts
Original file line number Diff line number Diff line change
@@ -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');
}
}
};
}
4 changes: 4 additions & 0 deletions src/modules/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>;
// If set, pushes repeated value updates to the tile after render
live?: (set: (value: string) => void) => void;
tooltip?: string;
}
11 changes: 11 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<FingerprintData, 'category' | 'key'>): 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;

Expand Down