feat: initial SleepGuard implementation

Wake-on-demand proxy + agent system with SvelteKit dashboard.
Monorepo: shared types, proxy (Hono + http-proxy), agent (monitors + locks), web (SvelteKit SPA).

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Vadim Sobinin
2026-02-10 13:46:51 +03:00
commit 852e01df39
64 changed files with 4864 additions and 0 deletions

View File

@@ -0,0 +1,84 @@
import type { UpSnapAuthResponse } from '@sleepguard/shared';
import { config } from '../config.js';
let token: string | null = null;
async function authenticate(): Promise<string> {
const res = await fetch(
`${config.upsnap.url}/api/collections/users/auth-with-password`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
identity: config.upsnap.username,
password: config.upsnap.password,
}),
}
);
if (!res.ok) {
throw new Error(`UpSnap auth failed: ${res.status} ${await res.text()}`);
}
const data = (await res.json()) as UpSnapAuthResponse;
token = data.token;
return token;
}
async function getToken(): Promise<string> {
if (!token) {
return authenticate();
}
return token;
}
async function upSnapFetch(path: string, options: RequestInit = {}): Promise<Response> {
let authToken = await getToken();
const doFetch = (t: string) =>
fetch(`${config.upsnap.url}${path}`, {
...options,
headers: {
'Content-Type': 'application/json',
Authorization: t,
...options.headers,
},
});
let res = await doFetch(authToken);
// Token expired — re-auth once
if (res.status === 401 || res.status === 403) {
authToken = await authenticate();
res = await doFetch(authToken);
}
return res;
}
export async function wakeDevice(): Promise<void> {
const res = await upSnapFetch(
`/api/collections/devices/records/${config.upsnap.deviceId}`,
{ method: 'PATCH', body: JSON.stringify({ status: 'on' }) }
);
// UpSnap may also have a dedicated wake endpoint — try both approaches
if (!res.ok) {
// Fallback: POST to wake endpoint
const res2 = await upSnapFetch(`/api/upsnap/wake/${config.upsnap.deviceId}`, {
method: 'GET',
});
if (!res2.ok) {
throw new Error(`UpSnap wake failed: ${res2.status}`);
}
}
}
export async function shutdownDevice(): Promise<void> {
const res = await upSnapFetch(`/api/upsnap/shutdown/${config.upsnap.deviceId}`, {
method: 'GET',
});
if (!res.ok) {
throw new Error(`UpSnap shutdown failed: ${res.status}`);
}
}