feat(app): add controller, state store, and API client modules
This commit is contained in:
72
WebApp/src/lib/app/api.js
Normal file
72
WebApp/src/lib/app/api.js
Normal file
@@ -0,0 +1,72 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
// @ts-nocheck
|
||||
import { getAppState } from './store';
|
||||
|
||||
const request = async (path, options = {}) => {
|
||||
const { deviceToken } = getAppState();
|
||||
const headers = { 'Content-Type': 'application/json' };
|
||||
|
||||
if (deviceToken) {
|
||||
headers.Authorization = `Bearer ${deviceToken}`;
|
||||
}
|
||||
|
||||
const response = await fetch(path, {
|
||||
...options,
|
||||
headers: {
|
||||
...headers,
|
||||
...(options.headers || {})
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || data.error || response.statusText || 'Request failed');
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const api = {
|
||||
request,
|
||||
auth: {
|
||||
signUp: (data) => request('/api/auth/sign-up/email', { method: 'POST', body: JSON.stringify(data) }),
|
||||
signIn: (data) => request('/api/auth/sign-in/email', { method: 'POST', body: JSON.stringify(data) }),
|
||||
getSession: () => request('/api/auth/get-session'),
|
||||
signOut: () => request('/api/auth/sign-out', { method: 'POST', body: JSON.stringify({}) })
|
||||
},
|
||||
devices: {
|
||||
register: (data) => request('/devices/register', { method: 'POST', body: JSON.stringify(data) }),
|
||||
list: () => request('/devices'),
|
||||
update: (deviceId, data) => request(`/devices/${deviceId}`, { method: 'PATCH', body: JSON.stringify(data) }),
|
||||
listLinks: () => request('/device-links'),
|
||||
link: (cameraDeviceId, clientDeviceId) =>
|
||||
request('/device-links', { method: 'POST', body: JSON.stringify({ cameraDeviceId, clientDeviceId }) }),
|
||||
unlink: (linkId) => request(`/device-links/${linkId}`, { method: 'DELETE' })
|
||||
},
|
||||
streams: {
|
||||
request: (cameraDeviceId) =>
|
||||
request('/streams/request', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ cameraDeviceId, reason: 'on_demand' })
|
||||
}),
|
||||
accept: (id) => request(`/streams/${id}/accept`, { method: 'POST', body: JSON.stringify({}) }),
|
||||
end: (id) => request(`/streams/${id}/end`, { method: 'POST', body: JSON.stringify({ reason: 'completed' }) }),
|
||||
getPublishCreds: (id) => request(`/streams/${id}/publish-credentials`),
|
||||
getSubscribeCreds: (id) => request(`/streams/${id}/subscribe-credentials`)
|
||||
},
|
||||
events: {
|
||||
startMotion: () =>
|
||||
request('/events/motion/start', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ title: 'Simulated Motion', triggeredBy: 'motion' })
|
||||
}),
|
||||
endMotion: (id) => request(`/events/${id}/motion/end`, { method: 'POST', body: JSON.stringify({ status: 'completed' }) }),
|
||||
finalizeRecording: (id, payload) => request(`/recordings/${id}/finalize`, { method: 'POST', body: JSON.stringify(payload) })
|
||||
},
|
||||
ops: {
|
||||
listRecordings: () => request('/recordings/me/list'),
|
||||
getRecordingDownloadUrl: (recordingId) => request(`/recordings/${recordingId}/download-url`),
|
||||
listNotifications: () => request('/push-notifications/me')
|
||||
}
|
||||
};
|
||||
|
||||
1555
WebApp/src/lib/app/controller.js
Normal file
1555
WebApp/src/lib/app/controller.js
Normal file
File diff suppressed because it is too large
Load Diff
66
WebApp/src/lib/app/store.js
Normal file
66
WebApp/src/lib/app/store.js
Normal file
@@ -0,0 +1,66 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
// @ts-nocheck
|
||||
import { derived, get, writable } from 'svelte/store';
|
||||
|
||||
export const createInitialState = () => ({
|
||||
page: 'auth',
|
||||
session: null,
|
||||
device: null,
|
||||
deviceToken: null,
|
||||
socketConnected: false,
|
||||
isMotionActive: false,
|
||||
cameraStatus: 'idle',
|
||||
cameraPreviewReady: false,
|
||||
linkedCameras: [],
|
||||
recordings: [],
|
||||
motionNotifications: [],
|
||||
activeCameraDeviceId: null,
|
||||
activeStreamSessionId: null,
|
||||
openLinkedCameraMenuId: null,
|
||||
activityLog: [],
|
||||
cameraSessions: {},
|
||||
connectedStreamSessionIds: [],
|
||||
loading: false,
|
||||
isRegistering: false,
|
||||
authForm: {
|
||||
email: '',
|
||||
password: '',
|
||||
name: ''
|
||||
},
|
||||
onboardingForm: {
|
||||
name: '',
|
||||
role: 'client',
|
||||
pushToken: ''
|
||||
},
|
||||
toasts: [],
|
||||
recordingModal: {
|
||||
open: false,
|
||||
title: 'Recording Playback',
|
||||
url: ''
|
||||
},
|
||||
clientStreamMode: 'none',
|
||||
clientFallbackFrame: '',
|
||||
clientPlaceholderText: 'Select a camera to view',
|
||||
lastError: null
|
||||
});
|
||||
|
||||
export const appState = writable(createInitialState());
|
||||
|
||||
export const setAppState = (partial) => {
|
||||
appState.update((state) => ({ ...state, ...partial }));
|
||||
};
|
||||
|
||||
export const patchAppState = (updater) => {
|
||||
appState.update((state) => ({ ...state, ...updater(state) }));
|
||||
};
|
||||
|
||||
export const getAppState = () => get(appState);
|
||||
|
||||
export const resetAppState = (keep = {}) => {
|
||||
appState.set({ ...createInitialState(), ...keep });
|
||||
};
|
||||
|
||||
export const unreadNotificationsCount = derived(appState, ($state) =>
|
||||
$state.motionNotifications.reduce((count, notification) => count + (notification.isRead ? 0 : 1), 0)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user