recents
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { spawn } from 'node:child_process';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import fs from 'node:fs/promises';
|
||||
import { createServer } from 'node:http';
|
||||
import path from 'node:path';
|
||||
import { Readable } from 'node:stream';
|
||||
@@ -17,6 +18,8 @@ const wss = new WebSocketServer({ noServer: true });
|
||||
|
||||
const PORT = Number(process.env.PORT ?? 3000);
|
||||
const FFMPEG_PATH = process.env.FFMPEG_PATH ?? 'ffmpeg';
|
||||
const RECENT_URLS_PATH = process.env.RECENT_URLS_PATH ?? path.join(__dirname, '..', 'data', 'recent-urls.json');
|
||||
const RECENT_URL_LIMIT = clampInteger(process.env.RECENT_URL_LIMIT, 12, 1, 50);
|
||||
const SESSION_TTL_MS = 60 * 60 * 1000;
|
||||
const MAX_WS_BUFFER_BYTES = 12 * 1024 * 1024;
|
||||
const JPEG_SOI = Buffer.from([0xff, 0xd8]);
|
||||
@@ -31,6 +34,8 @@ const defaults = {
|
||||
|
||||
const sessions = new Map();
|
||||
const sourceTokens = new Map();
|
||||
let recentUrls = [];
|
||||
let recentWrite = Promise.resolve();
|
||||
|
||||
app.disable('x-powered-by');
|
||||
app.use(express.json({ limit: '32kb' }));
|
||||
@@ -40,7 +45,17 @@ app.get('/api/health', (_request, response) => {
|
||||
response.json({ ok: true, ffmpeg: FFMPEG_PATH });
|
||||
});
|
||||
|
||||
app.post('/api/session', (request, response) => {
|
||||
app.get('/api/recent-urls', (_request, response) => {
|
||||
response.json({
|
||||
urls: recentUrls.map((item) => ({
|
||||
url: item.url,
|
||||
displayUrl: redactSecrets(item.url),
|
||||
lastPlayedAt: item.lastPlayedAt,
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/api/session', async (request, response) => {
|
||||
let url;
|
||||
|
||||
try {
|
||||
@@ -61,6 +76,12 @@ app.post('/api/session', (request, response) => {
|
||||
lastUsedAt: Date.now(),
|
||||
});
|
||||
|
||||
try {
|
||||
await addRecentUrl(url);
|
||||
} catch (error) {
|
||||
console.warn(`Failed to store recent URL: ${error.message}`);
|
||||
}
|
||||
|
||||
response.status(201).json({ id, options });
|
||||
});
|
||||
|
||||
@@ -313,6 +334,8 @@ setInterval(() => {
|
||||
}
|
||||
}, 60 * 1000).unref();
|
||||
|
||||
await loadRecentUrls();
|
||||
|
||||
server.listen(PORT, () => {
|
||||
console.log(`Frame stream app listening at http://localhost:${PORT}`);
|
||||
});
|
||||
@@ -362,6 +385,45 @@ function parseAudioBitrate(value) {
|
||||
return /^\d{2,3}k$/i.test(value) ? value.toLowerCase() : defaults.audioBitrate;
|
||||
}
|
||||
|
||||
async function loadRecentUrls() {
|
||||
try {
|
||||
const raw = await fs.readFile(RECENT_URLS_PATH, 'utf8');
|
||||
const parsed = JSON.parse(raw);
|
||||
const items = Array.isArray(parsed?.urls) ? parsed.urls : [];
|
||||
|
||||
recentUrls = items
|
||||
.filter((item) => typeof item?.url === 'string' && typeof item?.lastPlayedAt === 'string')
|
||||
.slice(0, RECENT_URL_LIMIT);
|
||||
} catch (error) {
|
||||
if (error.code !== 'ENOENT') {
|
||||
console.warn(`Failed to read recent URLs: ${error.message}`);
|
||||
}
|
||||
|
||||
recentUrls = [];
|
||||
}
|
||||
}
|
||||
|
||||
async function addRecentUrl(url) {
|
||||
const lastPlayedAt = new Date().toISOString();
|
||||
recentUrls = [
|
||||
{ url, lastPlayedAt },
|
||||
...recentUrls.filter((item) => item.url !== url),
|
||||
].slice(0, RECENT_URL_LIMIT);
|
||||
|
||||
recentWrite = recentWrite.catch(() => {}).then(() => saveRecentUrls());
|
||||
await recentWrite;
|
||||
}
|
||||
|
||||
async function saveRecentUrls() {
|
||||
const directory = path.dirname(RECENT_URLS_PATH);
|
||||
const temporaryPath = `${RECENT_URLS_PATH}.${process.pid}.tmp`;
|
||||
const payload = `${JSON.stringify({ urls: recentUrls }, null, 2)}\n`;
|
||||
|
||||
await fs.mkdir(directory, { recursive: true });
|
||||
await fs.writeFile(temporaryPath, payload, 'utf8');
|
||||
await fs.rename(temporaryPath, RECENT_URLS_PATH);
|
||||
}
|
||||
|
||||
function getSession(sessionId) {
|
||||
const session = sessions.get(sessionId);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user