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:
84
packages/proxy/src/services/upsnap.ts
Normal file
84
packages/proxy/src/services/upsnap.ts
Normal 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}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user