Refactor web app controller shared and media modules

This commit is contained in:
2026-04-15 19:00:00 +01:00
parent ec1e54e8f2
commit 78d14cb73f
3 changed files with 1438 additions and 1292 deletions

View File

@@ -0,0 +1,472 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-nocheck
const PAGE_PATHS = {
auth: '/',
onboarding: '/onboarding',
camera: '/camera',
client: '/client',
activity: '/activity',
settings: '/settings'
};
const DEVICE_STORAGE_KEY = 'mobileSimDevice';
const MOTION_DETECTION_SETTINGS_STORAGE_KEY = 'securecam-motion-detection-settings';
export const INVALID_DEVICE_TOKEN_ERRORS = new Set([
'Missing device token',
'Invalid device token',
'Device not found',
'Token role does not match device role'
]);
export const MOTION_DETECTION_PROFILES = {
low_power: {
profile: 'low_power',
label: 'Low Power',
description: 'Least heat and battery usage, slower trigger response.',
sampleIntervalMs: 1400,
burstIntervalMs: 500,
triggerThreshold: 0.15,
releaseThreshold: 0.05,
consecutiveTriggerFrames: 3,
cooldownMs: 12000,
minimumEventMs: 6000
},
balanced: {
profile: 'balanced',
label: 'Balanced',
description: 'Recommended default for a plugged-in foreground browser.',
sampleIntervalMs: 1000,
burstIntervalMs: 300,
triggerThreshold: 0.12,
releaseThreshold: 0.04,
consecutiveTriggerFrames: 3,
cooldownMs: 9000,
minimumEventMs: 6000
},
responsive: {
profile: 'responsive',
label: 'Responsive',
description: 'Faster trigger response with higher CPU and thermal cost.',
sampleIntervalMs: 700,
burstIntervalMs: 220,
triggerThreshold: 0.1,
releaseThreshold: 0.035,
consecutiveTriggerFrames: 2,
cooldownMs: 7000,
minimumEventMs: 6000
}
};
export const MAX_STREAM_DIAGNOSTIC_SESSIONS = 12;
export const MAX_STREAM_DIAGNOSTIC_ENTRIES = 24;
const normalizePath = (path) => path.replace(/\/+$/, '') || '/';
export const pageFromPath = (path) => {
switch (normalizePath(path)) {
case '/onboarding':
return 'onboarding';
case '/camera':
return 'camera';
case '/client':
return 'client';
case '/activity':
return 'activity';
case '/settings':
return 'settings';
default:
return 'auth';
}
};
export const getHomePageKeyForRole = (role) => (role === 'camera' ? 'camera' : 'client');
export const getMotionDetectionProfile = (profile) =>
MOTION_DETECTION_PROFILES[profile] ?? MOTION_DETECTION_PROFILES.balanced;
const getDefaultMotionDetectionState = () => ({
enabled: false,
profile: MOTION_DETECTION_PROFILES.balanced.profile,
state: 'idle',
score: 0,
debug: false,
lastTriggeredAt: null
});
const buildMotionDetectionState = (overrides = {}) => {
const defaults = getDefaultMotionDetectionState();
const nextProfile = getMotionDetectionProfile(overrides.profile ?? defaults.profile);
return {
...defaults,
...nextProfile,
...overrides,
profile: nextProfile.profile
};
};
const sanitizeMotionDetectionSettings = (value) => {
if (!value || typeof value !== 'object') {
return buildMotionDetectionState();
}
return buildMotionDetectionState({
enabled: Boolean(value.enabled),
profile: typeof value.profile === 'string' ? value.profile : undefined,
debug: Boolean(value.debug)
});
};
const normalizeMotionDetectionState = (value) => {
if (!value || typeof value !== 'object') {
return buildMotionDetectionState();
}
return buildMotionDetectionState({
enabled: Boolean(value.enabled),
profile: typeof value.profile === 'string' ? value.profile : undefined,
state: typeof value.state === 'string' ? value.state : undefined,
score: typeof value.score === 'number' ? value.score : undefined,
debug: Boolean(value.debug),
lastTriggeredAt: typeof value.lastTriggeredAt === 'string' ? value.lastTriggeredAt : null
});
};
export const createControllerShared = ({ api, getAppState, setAppState, patchAppState }) => {
const makeId = () => {
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
return crypto.randomUUID();
}
return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
};
const getCurrentPath = () => normalizePath(window.location.pathname);
const getPathForScreen = (screen, role) => {
if (screen === 'home') {
return PAGE_PATHS[getHomePageKeyForRole(role)];
}
return PAGE_PATHS[screen] || null;
};
const navigateToScreen = (screen, options = {}) => {
const { replace = false, role = getAppState().device?.role } = options;
const targetPath = getPathForScreen(screen, role);
if (!targetPath) return false;
if (getCurrentPath() !== normalizePath(targetPath)) {
if (replace) {
window.location.replace(targetPath);
} else {
window.location.assign(targetPath);
}
return true;
}
setAppState({ page: pageFromPath(targetPath) });
return false;
};
const pushToast = (message, type = 'info') => {
const id = makeId();
patchAppState((state) => ({
toasts: [...state.toasts, { id, message, type }].slice(-6)
}));
setTimeout(() => {
patchAppState((state) => ({
toasts: state.toasts.filter((toast) => toast.id !== id)
}));
}, 3200);
};
const removeToast = (id) => {
patchAppState((state) => ({
toasts: state.toasts.filter((toast) => toast.id !== id)
}));
};
const addActivity = (type, message) => {
const item = {
id: makeId(),
type,
message,
createdAt: new Date().toISOString()
};
patchAppState((state) => ({
activityLog: [item, ...state.activityLog].slice(0, 200)
}));
};
const pruneStreamDiagnostics = (diagnostics) => {
const entries = Object.entries(diagnostics || {});
if (entries.length <= MAX_STREAM_DIAGNOSTIC_SESSIONS) {
return diagnostics;
}
return Object.fromEntries(
entries
.sort(
(left, right) =>
new Date(right[1]?.updatedAt || 0).getTime() - new Date(left[1]?.updatedAt || 0).getTime()
)
.slice(0, MAX_STREAM_DIAGNOSTIC_SESSIONS)
);
};
const pushStreamDiagnostic = (streamSessionId, stage, message, level = 'info', meta = {}) => {
if (!streamSessionId || !stage || !message) {
return;
}
const createdAt = new Date().toISOString();
patchAppState((state) => {
const current = state.streamDiagnostics?.[streamSessionId] ?? {
streamSessionId,
cameraDeviceId: null,
updatedAt: createdAt,
entries: []
};
const next = {
...current,
...meta,
streamSessionId,
updatedAt: createdAt,
entries: [
{
id: makeId(),
stage,
message,
level,
createdAt
},
...(current.entries || [])
].slice(0, MAX_STREAM_DIAGNOSTIC_ENTRIES)
};
return {
streamDiagnostics: pruneStreamDiagnostics({
...(state.streamDiagnostics || {}),
[streamSessionId]: next
})
};
});
};
const loadMotionDetectionSettings = () => {
if (typeof localStorage === 'undefined') {
return buildMotionDetectionState();
}
try {
const saved = localStorage.getItem(MOTION_DETECTION_SETTINGS_STORAGE_KEY);
if (!saved) {
return buildMotionDetectionState();
}
return sanitizeMotionDetectionSettings(JSON.parse(saved));
} catch (error) {
console.error('Failed to load motion detection settings', error);
return buildMotionDetectionState();
}
};
const persistMotionDetectionSettings = (motionDetection) => {
if (typeof localStorage === 'undefined') {
return;
}
try {
localStorage.setItem(
MOTION_DETECTION_SETTINGS_STORAGE_KEY,
JSON.stringify({
enabled: Boolean(motionDetection?.enabled),
profile: motionDetection?.profile ?? MOTION_DETECTION_PROFILES.balanced.profile,
debug: Boolean(motionDetection?.debug)
})
);
} catch (error) {
console.error('Failed to save motion detection settings', error);
}
};
const updateMotionDetectionState = (updates) => {
const current = normalizeMotionDetectionState(getAppState().motionDetection);
const next =
typeof updates === 'function'
? normalizeMotionDetectionState({
...current,
...updates(current)
})
: normalizeMotionDetectionState({
...current,
...updates
});
setAppState({ motionDetection: next });
persistMotionDetectionSettings(next);
return next;
};
const readSavedDeviceRecord = () => {
if (typeof localStorage === 'undefined') {
return null;
}
const saved = localStorage.getItem(DEVICE_STORAGE_KEY);
if (!saved) {
return null;
}
try {
return JSON.parse(saved);
} catch (error) {
console.error('Failed to parse saved device', error);
localStorage.removeItem(DEVICE_STORAGE_KEY);
return null;
}
};
const persistSavedDeviceRecord = ({ device, deviceToken, userId }) => {
if (typeof localStorage === 'undefined') {
return;
}
localStorage.setItem(
DEVICE_STORAGE_KEY,
JSON.stringify({
device,
deviceToken,
userId
})
);
};
const clearSavedDeviceRecord = () => {
if (typeof localStorage === 'undefined') {
return;
}
localStorage.removeItem(DEVICE_STORAGE_KEY);
};
const applySavedDeviceState = (device, deviceToken) => {
setAppState({
device,
deviceToken,
onboardingForm: {
...getAppState().onboardingForm,
name: device?.name ?? '',
role: device?.role ?? 'client',
pushToken: ''
}
});
};
const clearDeviceState = () => {
setAppState({
device: null,
deviceToken: null,
socketConnected: false,
isMotionActive: false,
activeMotionSource: null,
cameraStatus: 'idle',
cameraPreviewReady: false,
linkedCameras: [],
recordings: [],
activeCameraDeviceId: null,
activeStreamSessionId: null,
openLinkedCameraMenuId: null,
cameraSessions: {},
connectedStreamSessionIds: [],
clientStreamMode: 'none',
clientPlaceholderText: 'Select a camera to view',
onboardingForm: {
...getAppState().onboardingForm,
name: '',
role: 'client',
pushToken: ''
}
});
};
const restoreSavedDeviceForSession = async (session, options = {}) => {
const { showMissingToast = false, showInvalidToast = false } = options;
const saved = readSavedDeviceRecord();
if (!saved) {
if (showMissingToast) {
pushToast('No saved device found', 'info');
}
return false;
}
const sessionUserId = session?.user?.id;
const savedUserId = typeof saved.userId === 'string' ? saved.userId : null;
const savedDeviceId = saved?.device?.id;
const savedDeviceToken = typeof saved?.deviceToken === 'string' ? saved.deviceToken : '';
if (!sessionUserId || !savedDeviceId || !savedDeviceToken) {
clearSavedDeviceRecord();
clearDeviceState();
if (showInvalidToast) {
pushToast('Saved device is incomplete. Please register again.', 'error');
}
return false;
}
if (savedUserId && savedUserId !== sessionUserId) {
clearSavedDeviceRecord();
clearDeviceState();
if (showInvalidToast) {
pushToast('Saved device belongs to a different account.', 'info');
}
return false;
}
try {
const result = await api.devices.list();
const matchingDevice = result.devices?.find((device) => device.id === savedDeviceId);
if (!matchingDevice) {
clearSavedDeviceRecord();
clearDeviceState();
if (showInvalidToast) {
pushToast('Saved device was not found for this account.', 'info');
}
return false;
}
applySavedDeviceState(matchingDevice, savedDeviceToken);
persistSavedDeviceRecord({
device: matchingDevice,
deviceToken: savedDeviceToken,
userId: sessionUserId
});
return true;
} catch (error) {
console.error('Failed to restore saved device', error);
if (showInvalidToast) {
pushToast('Unable to restore saved device right now.', 'error');
}
return false;
}
};
return {
makeId,
pushToast,
removeToast,
addActivity,
pushStreamDiagnostic,
loadMotionDetectionSettings,
updateMotionDetectionState,
navigateToScreen,
persistSavedDeviceRecord,
clearSavedDeviceRecord,
applySavedDeviceState,
clearDeviceState,
restoreSavedDeviceForSession
};
};