learning_ai_common_plat/packages/diagnostics-client/src/device.ts
saravanakumardb1 8acb8db7d7 feat(diagnostics-client): implement Phase 2.1 TypeScript SDK
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)
2026-03-03 08:35:00 -08:00

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);
};
}