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