fix(app): stabilize auth bootstrap and direct backend integration
This commit is contained in:
@@ -30,6 +30,10 @@ const openApiDocument = buildOpenApiDocument();
|
|||||||
const trustedOrigins = process.env.BETTER_AUTH_TRUSTED_ORIGINS
|
const trustedOrigins = process.env.BETTER_AUTH_TRUSTED_ORIGINS
|
||||||
? process.env.BETTER_AUTH_TRUSTED_ORIGINS.split(',').map((origin) => origin.trim()).filter(Boolean)
|
? process.env.BETTER_AUTH_TRUSTED_ORIGINS.split(',').map((origin) => origin.trim()).filter(Boolean)
|
||||||
: [];
|
: [];
|
||||||
|
const corsMiddleware = cors({
|
||||||
|
origin: trustedOrigins.length > 0 ? trustedOrigins : true,
|
||||||
|
credentials: true,
|
||||||
|
});
|
||||||
|
|
||||||
const buildMinioConnectOrigin = (): string | null => {
|
const buildMinioConnectOrigin = (): string | null => {
|
||||||
const endpoint = process.env.MINIO_ENDPOINT?.trim();
|
const endpoint = process.env.MINIO_ENDPOINT?.trim();
|
||||||
@@ -71,8 +75,6 @@ app.get('/openapi.json', (_req, res) => {
|
|||||||
|
|
||||||
app.use('/docs', swaggerUi.serve, swaggerUi.setup(openApiDocument));
|
app.use('/docs', swaggerUi.serve, swaggerUi.setup(openApiDocument));
|
||||||
|
|
||||||
app.all('/api/auth/*splat', toNodeHandler(auth));
|
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
helmet({
|
helmet({
|
||||||
contentSecurityPolicy: {
|
contentSecurityPolicy: {
|
||||||
@@ -88,12 +90,8 @@ app.use(
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
app.use(
|
app.use(corsMiddleware);
|
||||||
cors({
|
app.all('/api/auth/*splat', corsMiddleware, toNodeHandler(auth));
|
||||||
origin: trustedOrigins.length > 0 ? trustedOrigins : true,
|
|
||||||
credentials: true,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
app.use(rateLimit({ keyPrefix: 'global', windowMs: 60_000, max: 400 }));
|
app.use(rateLimit({ keyPrefix: 'global', windowMs: 60_000, max: 400 }));
|
||||||
app.use(requestContext);
|
app.use(requestContext);
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en" class="dark" data-theme="black" style="background:#0a0a0c; color-scheme: dark">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#0a0a0c" />
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
<body data-sveltekit-preload-data="hover">
|
<body
|
||||||
|
data-sveltekit-preload-data="hover"
|
||||||
|
class="h-screen overflow-hidden flex bg-[#0a0a0c] text-gray-200"
|
||||||
|
style="background:#0a0a0c; color:#e5e7eb"
|
||||||
|
>
|
||||||
<div style="display: contents">%sveltekit.body%</div>
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -2,6 +2,17 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { getAppState } from './store';
|
import { getAppState } from './store';
|
||||||
|
|
||||||
|
const rawBackendUrl = import.meta.env.VITE_BACKEND_URL ?? 'http://localhost:3000';
|
||||||
|
const backendUrl = rawBackendUrl.replace(/\/+$/, '');
|
||||||
|
|
||||||
|
const toBackendUrl = (path) => {
|
||||||
|
if (/^https?:\/\//i.test(path)) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${backendUrl}${path.startsWith('/') ? path : `/${path}`}`;
|
||||||
|
};
|
||||||
|
|
||||||
const request = async (path, options = {}) => {
|
const request = async (path, options = {}) => {
|
||||||
const { deviceToken } = getAppState();
|
const { deviceToken } = getAppState();
|
||||||
const headers = { 'Content-Type': 'application/json' };
|
const headers = { 'Content-Type': 'application/json' };
|
||||||
@@ -10,8 +21,9 @@ const request = async (path, options = {}) => {
|
|||||||
headers.Authorization = `Bearer ${deviceToken}`;
|
headers.Authorization = `Bearer ${deviceToken}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(path, {
|
const response = await fetch(toBackendUrl(path), {
|
||||||
...options,
|
...options,
|
||||||
|
credentials: 'include',
|
||||||
headers: {
|
headers: {
|
||||||
...headers,
|
...headers,
|
||||||
...(options.headers || {})
|
...(options.headers || {})
|
||||||
@@ -26,6 +38,8 @@ const request = async (path, options = {}) => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getBackendUrl = () => backendUrl;
|
||||||
|
|
||||||
export const api = {
|
export const api = {
|
||||||
request,
|
request,
|
||||||
auth: {
|
auth: {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { io } from 'socket.io-client';
|
import { io } from 'socket.io-client';
|
||||||
|
|
||||||
import { api } from './api';
|
import { api, getBackendUrl } from './api';
|
||||||
import { createMotionDetector } from './motion-detector';
|
import { createMotionDetector } from './motion-detector';
|
||||||
import { getAppState, patchAppState, resetAppState, setAppState } from './store';
|
import { getAppState, patchAppState, resetAppState, setAppState } from './store';
|
||||||
|
|
||||||
@@ -14,6 +14,13 @@ const PAGE_PATHS = {
|
|||||||
activity: '/activity',
|
activity: '/activity',
|
||||||
settings: '/settings'
|
settings: '/settings'
|
||||||
};
|
};
|
||||||
|
const DEVICE_STORAGE_KEY = 'mobileSimDevice';
|
||||||
|
const INVALID_DEVICE_TOKEN_ERRORS = new Set([
|
||||||
|
'Missing device token',
|
||||||
|
'Invalid device token',
|
||||||
|
'Device not found',
|
||||||
|
'Token role does not match device role'
|
||||||
|
]);
|
||||||
|
|
||||||
const MOTION_DETECTION_SETTINGS_STORAGE_KEY = 'securecam-motion-detection-settings';
|
const MOTION_DETECTION_SETTINGS_STORAGE_KEY = 'securecam-motion-detection-settings';
|
||||||
const MOTION_DETECTION_PROFILES = {
|
const MOTION_DETECTION_PROFILES = {
|
||||||
@@ -253,6 +260,151 @@ const navigateToScreen = (screen, options = {}) => {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const setConnectedStreamSessionIds = () => {
|
const setConnectedStreamSessionIds = () => {
|
||||||
setAppState({ connectedStreamSessionIds: Array.from(connectedPeers) });
|
setAppState({ connectedStreamSessionIds: Array.from(connectedPeers) });
|
||||||
};
|
};
|
||||||
@@ -1267,7 +1419,10 @@ const connectSocket = () => {
|
|||||||
if (!deviceToken) return;
|
if (!deviceToken) return;
|
||||||
|
|
||||||
if (socket) socket.disconnect();
|
if (socket) socket.disconnect();
|
||||||
socket = io({ auth: { token: deviceToken } });
|
socket = io(getBackendUrl(), {
|
||||||
|
auth: { token: deviceToken },
|
||||||
|
withCredentials: true
|
||||||
|
});
|
||||||
|
|
||||||
socket.on('connect', () => {
|
socket.on('connect', () => {
|
||||||
setAppState({ socketConnected: true });
|
setAppState({ socketConnected: true });
|
||||||
@@ -1286,6 +1441,20 @@ const connectSocket = () => {
|
|||||||
applyMotionDetectionReadiness();
|
applyMotionDetectionReadiness();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on('connect_error', (error) => {
|
||||||
|
const message = error?.message || 'Realtime connection failed';
|
||||||
|
setAppState({ socketConnected: false });
|
||||||
|
addActivity('System', `Realtime connection failed: ${message}`);
|
||||||
|
|
||||||
|
if (INVALID_DEVICE_TOKEN_ERRORS.has(message)) {
|
||||||
|
void invalidateSavedDevice('Saved device is invalid for this account. Please register this browser again.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pushToast(message, 'error');
|
||||||
|
applyMotionDetectionReadiness();
|
||||||
|
});
|
||||||
|
|
||||||
socket.on('command:received', async (payload) => {
|
socket.on('command:received', async (payload) => {
|
||||||
addActivity('Command', `Received ${payload.commandType}`);
|
addActivity('Command', `Received ${payload.commandType}`);
|
||||||
|
|
||||||
@@ -1519,6 +1688,17 @@ const cleanupConnectionState = async () => {
|
|||||||
requestedStreams.clear();
|
requestedStreams.clear();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const invalidateSavedDevice = async (message, options = {}) => {
|
||||||
|
const { showToast = true } = options;
|
||||||
|
clearSavedDeviceRecord();
|
||||||
|
await cleanupConnectionState();
|
||||||
|
clearDeviceState();
|
||||||
|
if (showToast) {
|
||||||
|
pushToast(message || 'Saved device is no longer valid. Please register this browser again.', 'error');
|
||||||
|
}
|
||||||
|
navigateToScreen('onboarding', { replace: true });
|
||||||
|
};
|
||||||
|
|
||||||
const enforceRouteForSession = () => {
|
const enforceRouteForSession = () => {
|
||||||
const state = getAppState();
|
const state = getAppState();
|
||||||
const page = pageFromPath(window.location.pathname);
|
const page = pageFromPath(window.location.pathname);
|
||||||
@@ -1554,7 +1734,7 @@ const init = async () => {
|
|||||||
if (initPromise) return initPromise;
|
if (initPromise) return initPromise;
|
||||||
|
|
||||||
initPromise = (async () => {
|
initPromise = (async () => {
|
||||||
setAppState({ page: pageFromPath(window.location.pathname) });
|
setAppState({ page: pageFromPath(window.location.pathname), loading: true });
|
||||||
if (navigator.mediaDevices?.addEventListener) {
|
if (navigator.mediaDevices?.addEventListener) {
|
||||||
navigator.mediaDevices.addEventListener('devicechange', onMediaDeviceChange);
|
navigator.mediaDevices.addEventListener('devicechange', onMediaDeviceChange);
|
||||||
}
|
}
|
||||||
@@ -1562,39 +1742,24 @@ const init = async () => {
|
|||||||
document.addEventListener('visibilitychange', onVisibilityChange);
|
document.addEventListener('visibilitychange', onVisibilityChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
const saved = localStorage.getItem('mobileSimDevice');
|
|
||||||
if (saved) {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(saved);
|
|
||||||
setAppState({
|
|
||||||
device: parsed.device,
|
|
||||||
deviceToken: parsed.deviceToken,
|
|
||||||
onboardingForm: {
|
|
||||||
...getAppState().onboardingForm,
|
|
||||||
name: parsed.device?.name ?? '',
|
|
||||||
role: parsed.device?.role ?? 'client'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load saved device', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setAppState({ motionDetection: loadMotionDetectionSettings() });
|
setAppState({ motionDetection: loadMotionDetectionSettings() });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const session = await api.auth.getSession();
|
const session = await api.auth.getSession();
|
||||||
if (session?.session) {
|
if (session?.session) {
|
||||||
setAppState({ session });
|
setAppState({ session });
|
||||||
if (getAppState().deviceToken) {
|
const restoredSavedDevice = await restoreSavedDeviceForSession(session);
|
||||||
|
if (restoredSavedDevice) {
|
||||||
connectSocket();
|
connectSocket();
|
||||||
startPolling();
|
startPolling();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setAppState({ session: null });
|
setAppState({ session: null });
|
||||||
|
clearDeviceState();
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
setAppState({ session: null });
|
setAppState({ session: null });
|
||||||
|
clearDeviceState();
|
||||||
}
|
}
|
||||||
|
|
||||||
enforceRouteForSession();
|
enforceRouteForSession();
|
||||||
@@ -1606,10 +1771,10 @@ const init = async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
})()
|
})().finally(() => {
|
||||||
.finally(() => {
|
setAppState({ loading: false });
|
||||||
initPromise = null;
|
initPromise = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
return initPromise;
|
return initPromise;
|
||||||
};
|
};
|
||||||
@@ -1667,7 +1832,8 @@ const actions = {
|
|||||||
setAppState({ session, authForm: { ...state.authForm, password: '' } });
|
setAppState({ session, authForm: { ...state.authForm, password: '' } });
|
||||||
pushToast(`Welcome, ${session.user.name}`, 'success');
|
pushToast(`Welcome, ${session.user.name}`, 'success');
|
||||||
|
|
||||||
if (getAppState().deviceToken) {
|
const restoredSavedDevice = await restoreSavedDeviceForSession(session);
|
||||||
|
if (restoredSavedDevice) {
|
||||||
connectSocket();
|
connectSocket();
|
||||||
startPolling();
|
startPolling();
|
||||||
navigateToScreen('home', { role: getAppState().device?.role });
|
navigateToScreen('home', { role: getAppState().device?.role });
|
||||||
@@ -1704,8 +1870,12 @@ const actions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await api.devices.register(payload);
|
const result = await api.devices.register(payload);
|
||||||
setAppState({ device: result.device, deviceToken: result.deviceToken });
|
applySavedDeviceState(result.device, result.deviceToken);
|
||||||
localStorage.setItem('mobileSimDevice', JSON.stringify({ device: result.device, deviceToken: result.deviceToken }));
|
persistSavedDeviceRecord({
|
||||||
|
device: result.device,
|
||||||
|
deviceToken: result.deviceToken,
|
||||||
|
userId: getAppState().session?.user?.id ?? null
|
||||||
|
});
|
||||||
|
|
||||||
pushToast('Device Registered', 'success');
|
pushToast('Device Registered', 'success');
|
||||||
connectSocket();
|
connectSocket();
|
||||||
@@ -1717,27 +1887,17 @@ const actions = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
loadSavedDevice() {
|
loadSavedDevice() {
|
||||||
const saved = localStorage.getItem('mobileSimDevice');
|
const session = getAppState().session;
|
||||||
if (!saved) {
|
if (!session) {
|
||||||
pushToast('No saved device found', 'info');
|
pushToast('Please sign in before loading a saved device', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
void restoreSavedDeviceForSession(session, { showMissingToast: true, showInvalidToast: true }).then((restored) => {
|
||||||
const parsed = JSON.parse(saved);
|
if (restored) {
|
||||||
setAppState({
|
pushToast('Loaded saved device', 'success');
|
||||||
device: parsed.device,
|
}
|
||||||
deviceToken: parsed.deviceToken,
|
});
|
||||||
onboardingForm: {
|
|
||||||
...getAppState().onboardingForm,
|
|
||||||
name: parsed.device?.name ?? '',
|
|
||||||
role: parsed.device?.role ?? 'client'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
pushToast('Loaded saved device', 'success');
|
|
||||||
} catch {
|
|
||||||
pushToast('Saved device is invalid', 'error');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async signOut() {
|
async signOut() {
|
||||||
@@ -1748,7 +1908,7 @@ const actions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await cleanupConnectionState();
|
await cleanupConnectionState();
|
||||||
localStorage.removeItem('mobileSimDevice');
|
clearSavedDeviceRecord();
|
||||||
const keep = { page: 'auth', toasts: [] };
|
const keep = { page: 'auth', toasts: [] };
|
||||||
resetAppState(keep);
|
resetAppState(keep);
|
||||||
pushToast('Signed Out', 'info');
|
pushToast('Signed Out', 'info');
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const createInitialState = () => ({
|
|||||||
activityLog: [],
|
activityLog: [],
|
||||||
cameraSessions: {},
|
cameraSessions: {},
|
||||||
connectedStreamSessionIds: [],
|
connectedStreamSessionIds: [],
|
||||||
loading: false,
|
loading: true,
|
||||||
isRegistering: false,
|
isRegistering: false,
|
||||||
authForm: {
|
authForm: {
|
||||||
email: '',
|
email: '',
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import tailwindcss from '@tailwindcss/vite';
|
|||||||
import { sveltekit } from '@sveltejs/kit/vite';
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
|
|
||||||
const backendTarget = process.env.BACKEND_URL ?? 'http://localhost:3000';
|
const backendTarget = process.env.BACKEND_URL ?? 'http://localhost:3000';
|
||||||
|
const enableProxy = process.env.USE_VITE_PROXY === 'true';
|
||||||
|
|
||||||
const proxiedPaths = [
|
const proxiedPaths = [
|
||||||
'/api',
|
'/api',
|
||||||
@@ -31,7 +32,7 @@ const proxy = Object.fromEntries(
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [tailwindcss(), sveltekit()],
|
plugins: [tailwindcss(), sveltekit()],
|
||||||
server: {
|
server: {
|
||||||
proxy
|
proxy: enableProxy ? proxy : undefined
|
||||||
},
|
},
|
||||||
test: {
|
test: {
|
||||||
expect: { requireAssertions: true },
|
expect: { requireAssertions: true },
|
||||||
|
|||||||
Reference in New Issue
Block a user