New package @bytelyst/diagnostics-client with: - DiagnosticsClient: singleton with polling for active sessions - BreadcrumbTrail: ring buffer (max 100) for timeline - NetworkInterceptor: fetch wrapper for HTTP capture - DeviceState: memory, storage, network collection - 21 Vitest tests (all passing)
87 lines
2.5 KiB
TypeScript
87 lines
2.5 KiB
TypeScript
/**
|
|
* Device state collector — memory, battery, storage, network
|
|
*
|
|
* @module device
|
|
*/
|
|
|
|
import type { DeviceState } from './types.js';
|
|
|
|
// DOM type declarations for ESLint
|
|
type Navigator = {
|
|
onLine: boolean;
|
|
connection?: { effectiveType?: string };
|
|
getBattery?: () => Promise<{ charging: boolean; level: number }>;
|
|
storage?: { estimate(): Promise<{ usage?: number }> };
|
|
};
|
|
declare const navigator: Navigator;
|
|
declare const performance: { memory?: { usedJSHeapSize: number } };
|
|
interface Window {
|
|
addEventListener: (type: string, listener: () => void) => void;
|
|
removeEventListener: (type: string, listener: () => void) => void;
|
|
}
|
|
declare const window: Window;
|
|
|
|
/**
|
|
* Collect current device state
|
|
* Best-effort: some APIs may not be available in all environments
|
|
*/
|
|
export function collectDeviceState(): DeviceState {
|
|
const state: DeviceState = {
|
|
isOnline: navigator.onLine ?? true,
|
|
};
|
|
|
|
// Network type (experimental API)
|
|
const connection = (navigator as { connection?: { effectiveType?: string } }).connection;
|
|
if (connection) {
|
|
state.networkType = connection.effectiveType ?? 'unknown';
|
|
}
|
|
|
|
// Battery API (experimental, not widely supported)
|
|
// Note: Battery API is deprecated but still useful for diagnostics
|
|
const battery = (
|
|
navigator as { getBattery?: () => Promise<{ charging: boolean; level: number }> }
|
|
).getBattery;
|
|
if (battery) {
|
|
// We'll return a promise, but sync API can't wait
|
|
// Store last known value if available
|
|
}
|
|
|
|
// Memory (Chrome-only experimental)
|
|
const memory = (performance as { memory?: { usedJSHeapSize: number } }).memory;
|
|
if (memory) {
|
|
state.memoryMB = Math.round(memory.usedJSHeapSize / 1024 / 1024);
|
|
}
|
|
|
|
// Storage (async, but we'll fire-and-forget)
|
|
if (navigator.storage && navigator.storage.estimate) {
|
|
navigator.storage
|
|
.estimate()
|
|
.then(estimate => {
|
|
if (estimate.usage !== undefined) {
|
|
state.storageMB = Math.round(estimate.usage / 1024 / 1024);
|
|
}
|
|
})
|
|
.catch(() => {
|
|
// Ignore errors
|
|
});
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
* Subscribe to online/offline events
|
|
*/
|
|
export function subscribeToConnectivity(callback: (isOnline: boolean) => void): () => void {
|
|
const handleOnline = () => callback(true);
|
|
const handleOffline = () => callback(false);
|
|
|
|
window.addEventListener('online', handleOnline);
|
|
window.addEventListener('offline', handleOffline);
|
|
|
|
return () => {
|
|
window.removeEventListener('online', handleOnline);
|
|
window.removeEventListener('offline', handleOffline);
|
|
};
|
|
}
|