Files
splatoon2.ink/workers/updater/preview/server.mjs
2026-09-13 12:56:40 -07:00

196 lines
5.6 KiB
JavaScript

// UI preview only. No Worker bindings, credentials, or production network requests.
import { createServer } from 'node:http';
import { readFile } from 'node:fs/promises';
import { randomUUID } from 'node:crypto';
const port = Number(process.env.ADMIN_PREVIEW_PORT || 8788);
const hour = Math.floor(Date.now() / 3600000) * 3600000;
const state = {
preview: true,
user: { email: 'Local preview' },
paused: false,
automaticSchedulingEnabled: true,
busy: false,
hourlyAt: hour + 3600000 + 10000,
retryAt: null,
pendingManual: null,
lastManualRun: {
logs: {
lines: [
{ at: hour - 1800000, level: 'info', text: 'Starting social cycle' },
{
at: hour - 1757700,
level: 'error',
text: 'Browser screenshot timed out. The post was not sent.',
},
],
omitted: 0,
},
id: 'previous',
mode: 'social',
ok: false,
status: 'failed',
startedAt: hour - 1800000,
runMs: 42300,
error: 'Browser screenshot timed out. The post was not sent.',
},
lastRun: {
mode: 'both',
ok: true,
startedAt: hour + 10000,
runMs: 18200,
updaters: { ok: true },
social: { ok: true },
},
};
state.runHistory = [
{ ...state.lastRun, trigger: 'scheduled' },
{ ...state.lastManualRun, trigger: 'manual' },
];
createServer(async (request, response) => {
response.setHeader('Cache-Control', 'no-store');
const url = new URL(request.url, `http://127.0.0.1:${port}`);
function json(body, status = 200) {
response.writeHead(status, { 'Content-Type': 'application/json' });
response.end(JSON.stringify(body));
}
try {
if (request.method === 'GET' && ['/', '/admin', '/admin/'].includes(url.pathname)) {
response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
return response.end(
(await readFile(new URL('../src/admin/page.html', import.meta.url), 'utf8')).replaceAll(
'__NONCE__',
'preview',
).replaceAll('__STAGING_HIDDEN__', ''),
);
}
if (request.method === 'GET' && url.pathname === '/admin/api/status')
return json(state);
if (request.method === 'POST' && ['/admin/api/run', '/admin/api/scheduling'].includes(url.pathname)) {
if (request.headers.origin !== `http://${request.headers.host}`)
return json({ error: 'Invalid origin.' }, 403);
if (state.busy)
return json({ error: 'A run is already active.' }, 409);
let body = '';
for await (const chunk of request) {
body += chunk;
if (body.length > 1024)
return json({ error: 'Request too large.' }, 413);
}
const input = JSON.parse(body);
if (url.pathname === '/admin/api/scheduling') {
if (typeof input?.enabled !== 'boolean')
return json({ error: 'Enabled must be a boolean.' }, 400);
state.automaticSchedulingEnabled = input.enabled;
state.hourlyAt = input.enabled ? Math.floor(Date.now() / 3600000) * 3600000 + 3610000 : null;
return json({ ok: true });
}
const { mode } = input;
if (!['data', 'social', 'both'].includes(mode))
return json({ error: 'Unknown mode.' }, 400);
const run = { id: randomUUID(), mode, status: 'queued', requestedAt: Date.now() };
state.busy = true;
state.pendingManual = run;
const logs = {
lines: [{ at: Date.now(), level: 'info', text: `Starting ${mode} run` }],
omitted: 0,
};
state.activeRun = { mode, startedAt: Date.now(), logs };
setTimeout(
() =>
logs.lines.push({
at: Date.now(),
level: 'info',
text:
mode === 'social'
? '[Social] Checking post checkpoints…'
: '[Updater] [Schedules] Updating data…',
}),
1500,
);
setTimeout(
() =>
logs.lines.push({
at: Date.now(),
level: 'info',
text:
mode === 'social' ? '[Social] Preparing a due Bluesky post…' : '[Updater] [Schedules] Done.',
}),
3200,
);
setTimeout(
() =>
logs.lines.push({
at: Date.now(),
level: 'info',
text: '[Preview] Simulated work completed.',
}),
5000,
);
setTimeout(() => {
run.status = 'running';
run.startedAt = Date.now();
}, 700);
setTimeout(() => {
state.lastManualRun = {
...run,
logs,
ok: true,
status: 'succeeded',
finishedAt: Date.now(),
runMs: Date.now() - run.startedAt,
updaters:
mode === 'social'
? { ok: true, skipped: true }
: {
ok: true,
updaters: ['Schedules', 'Timeline', 'CoopSchedules', 'Merchandises'].map(name => ({
name,
ok: true,
})),
},
social:
mode === 'data'
? { ok: true, skipped: true }
: { ok: true, posts: [{ name: 'Schedule', ok: true, simulated: true }] },
};
state.runHistory.unshift({ ...state.lastManualRun, trigger: 'manual' });
state.runHistory = state.runHistory.slice(0, 50);
state.busy = false;
state.pendingManual = null;
state.activeRun = null;
}, 6000);
return json({ ok: true, run }, 202);
}
json({ error: 'Not found.' }, 404);
} catch {
json({ error: 'Invalid preview request.' }, 400);
}
}).listen(port, '127.0.0.1', () =>
console.log(`Admin preview: http://127.0.0.1:${port}/admin/ (simulated runs only)`),
);