style: format code for better readability
- Format arrays and objects on multiple lines for clarity - Add trailing commas to arrays and objects - Format function parameters across multiple lines when needed - Format string concatenation and template literals consistently - Format try-catch blocks with proper spacing
This commit is contained in:
+148
-44
@@ -229,7 +229,7 @@ function dbDelete(id: string): boolean {
|
|||||||
function dbBanIP(ip: string, reason?: string): void {
|
function dbBanIP(ip: string, reason?: string): void {
|
||||||
db.run(
|
db.run(
|
||||||
"INSERT OR REPLACE INTO banned_ips (ip, banned_at, reason, banned_by) VALUES (?, ?, ?, ?)",
|
"INSERT OR REPLACE INTO banned_ips (ip, banned_at, reason, banned_by) VALUES (?, ?, ?, ?)",
|
||||||
[ip, Math.floor(Date.now() / 1000), reason ?? "Manual ban", "admin"]
|
[ip, Math.floor(Date.now() / 1000), reason ?? "Manual ban", "admin"],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +241,12 @@ function dbIsBanned(ip: string): boolean {
|
|||||||
return db.query("SELECT ip FROM banned_ips WHERE ip = ?").get(ip) != null;
|
return db.query("SELECT ip FROM banned_ips WHERE ip = ?").get(ip) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function dbGetBannedIPs(): Array<{ ip: string; banned_at: number; reason: string; banned_by: string }> {
|
function dbGetBannedIPs(): Array<{
|
||||||
|
ip: string;
|
||||||
|
banned_at: number;
|
||||||
|
reason: string;
|
||||||
|
banned_by: string;
|
||||||
|
}> {
|
||||||
return db.query("SELECT * FROM banned_ips ORDER BY banned_at DESC").all() as Array<{
|
return db.query("SELECT * FROM banned_ips ORDER BY banned_at DESC").all() as Array<{
|
||||||
ip: string;
|
ip: string;
|
||||||
banned_at: number;
|
banned_at: number;
|
||||||
@@ -296,7 +301,7 @@ function deviceTrack(ip: string, viewId: string, viewName: string, userAgent: st
|
|||||||
// First user connecting to this view - trigger warmup
|
// First user connecting to this view - trigger warmup
|
||||||
if (!warmedUpViews.has(viewId)) {
|
if (!warmedUpViews.has(viewId)) {
|
||||||
warmedUpViews.add(viewId);
|
warmedUpViews.add(viewId);
|
||||||
warmupView(viewId).catch(err => {
|
warmupView(viewId).catch((err) => {
|
||||||
console.error(`[warmup] Failed to warm up view ${viewId}:`, err);
|
console.error(`[warmup] Failed to warm up view ${viewId}:`, err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -321,7 +326,7 @@ function devicesSnapshot(): DeviceEntry[] {
|
|||||||
// ═══════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
function getDebugData() {
|
function getDebugData() {
|
||||||
const viewsDebug = viewCacheAll().map(v => {
|
const viewsDebug = viewCacheAll().map((v) => {
|
||||||
const idx = getCurrentURLIndex(v);
|
const idx = getCurrentURLIndex(v);
|
||||||
return {
|
return {
|
||||||
id: v.id,
|
id: v.id,
|
||||||
@@ -336,7 +341,7 @@ function getDebugData() {
|
|||||||
currentUrl: v.urls[idx]?.url ?? "",
|
currentUrl: v.urls[idx]?.url ?? "",
|
||||||
remainingSec: getRemainingSec(v),
|
remainingSec: getRemainingSec(v),
|
||||||
totalCycleSec: v.urls.reduce((s, u) => s + u.durationSec, 0),
|
totalCycleSec: v.urls.reduce((s, u) => s + u.durationSec, 0),
|
||||||
cacheEntries: cacheStats().filter(c => c.viewId === v.id),
|
cacheEntries: cacheStats().filter((c) => c.viewId === v.id),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -438,7 +443,9 @@ class PagePool {
|
|||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(`[page-pool] resetPage failed, discarding page: ${err}`);
|
console.warn(`[page-pool] resetPage failed, discarding page: ${err}`);
|
||||||
try { await page.close(); } catch {}
|
try {
|
||||||
|
await page.close();
|
||||||
|
} catch {}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -513,10 +520,7 @@ let pagePool: PagePool;
|
|||||||
// WEBSOCKET DEBUG CLIENTS
|
// WEBSOCKET DEBUG CLIENTS
|
||||||
// ═══════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
const debugClients = new Map<
|
const debugClients = new Map<any, { interval: number; timer: ReturnType<typeof setInterval> }>();
|
||||||
any,
|
|
||||||
{ interval: number; timer: ReturnType<typeof setInterval> }
|
|
||||||
>();
|
|
||||||
const DEBUG_DEFAULT_INTERVAL_MS = 1000;
|
const DEBUG_DEFAULT_INTERVAL_MS = 1000;
|
||||||
const DEBUG_MIN_INTERVAL_MS = 100;
|
const DEBUG_MIN_INTERVAL_MS = 100;
|
||||||
|
|
||||||
@@ -668,12 +672,19 @@ async function renderViewToCache(view: View, urlIndex: number, key: string): Pro
|
|||||||
let body: string;
|
let body: string;
|
||||||
if (view.method === "mjpeg") {
|
if (view.method === "mjpeg") {
|
||||||
const jpeg = await renderScreenshotFrame(
|
const jpeg = await renderScreenshotFrame(
|
||||||
urlItem.url, view.viewportWidth, view.viewportHeight, forceDarkMode,
|
urlItem.url,
|
||||||
|
view.viewportWidth,
|
||||||
|
view.viewportHeight,
|
||||||
|
forceDarkMode,
|
||||||
);
|
);
|
||||||
body = jpeg.toString("base64");
|
body = jpeg.toString("base64");
|
||||||
} else {
|
} else {
|
||||||
body = await renderSSR(
|
body = await renderSSR(
|
||||||
urlItem.url, view.viewportWidth, view.viewportHeight, forceDarkMode, bakeStyles,
|
urlItem.url,
|
||||||
|
view.viewportWidth,
|
||||||
|
view.viewportHeight,
|
||||||
|
forceDarkMode,
|
||||||
|
bakeStyles,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -823,7 +834,8 @@ function stripScriptTags(html: string): string {
|
|||||||
// ── Recursive @import resolver (max 3 levels deep) ──
|
// ── Recursive @import resolver (max 3 levels deep) ──
|
||||||
async function resolveCSSImports(cssText: string, baseUrl: string, depth = 0): Promise<string> {
|
async function resolveCSSImports(cssText: string, baseUrl: string, depth = 0): Promise<string> {
|
||||||
if (depth > 3) return cssText;
|
if (depth > 3) return cssText;
|
||||||
const importRe = /@import\s+(?:url\s*\(\s*["']?([^)"']+)["']?\s*\)|["']([^"']+)["'])\s*([^;]*);/gi;
|
const importRe =
|
||||||
|
/@import\s+(?:url\s*\(\s*["']?([^)"']+)["']?\s*\)|["']([^"']+)["'])\s*([^;]*);/gi;
|
||||||
const replacements: Array<{ match: string; resolved: string }> = [];
|
const replacements: Array<{ match: string; resolved: string }> = [];
|
||||||
|
|
||||||
let m: RegExpExecArray | null;
|
let m: RegExpExecArray | null;
|
||||||
@@ -839,7 +851,10 @@ async function resolveCSSImports(cssText: string, baseUrl: string, depth = 0): P
|
|||||||
if (!res.ok) throw new Error("failed");
|
if (!res.ok) throw new Error("failed");
|
||||||
let imported = await res.text();
|
let imported = await res.text();
|
||||||
imported = await resolveCSSImports(imported, importUrl, depth + 1);
|
imported = await resolveCSSImports(imported, importUrl, depth + 1);
|
||||||
replacements.push({ match: m[0], resolved: "/* begin " + importUrl + " */\n" + imported + "\n/* end " + importUrl + " */" });
|
replacements.push({
|
||||||
|
match: m[0],
|
||||||
|
resolved: "/* begin " + importUrl + " */\n" + imported + "\n/* end " + importUrl + " */",
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
replacements.push({ match: m[0], resolved: "/* unresolved import: " + importUrl + " */" });
|
replacements.push({ match: m[0], resolved: "/* unresolved import: " + importUrl + " */" });
|
||||||
}
|
}
|
||||||
@@ -865,40 +880,117 @@ async function bakeComputedStyles(page: Page): Promise<void> {
|
|||||||
await page.evaluate((): void => {
|
await page.evaluate((): void => {
|
||||||
const computedProperties = [
|
const computedProperties = [
|
||||||
// Layout
|
// Layout
|
||||||
"display", "position", "top", "right", "bottom", "left",
|
"display",
|
||||||
"width", "height", "min-width", "min-height", "max-width", "max-height",
|
"position",
|
||||||
"margin", "margin-top", "margin-right", "margin-bottom", "margin-left",
|
"top",
|
||||||
"padding", "padding-top", "padding-right", "padding-bottom", "padding-left",
|
"right",
|
||||||
|
"bottom",
|
||||||
|
"left",
|
||||||
|
"width",
|
||||||
|
"height",
|
||||||
|
"min-width",
|
||||||
|
"min-height",
|
||||||
|
"max-width",
|
||||||
|
"max-height",
|
||||||
|
"margin",
|
||||||
|
"margin-top",
|
||||||
|
"margin-right",
|
||||||
|
"margin-bottom",
|
||||||
|
"margin-left",
|
||||||
|
"padding",
|
||||||
|
"padding-top",
|
||||||
|
"padding-right",
|
||||||
|
"padding-bottom",
|
||||||
|
"padding-left",
|
||||||
// Flex/Grid
|
// Flex/Grid
|
||||||
"flex", "flex-direction", "flex-wrap", "flex-flow", "flex-grow", "flex-shrink", "flex-basis",
|
"flex",
|
||||||
"justify-content", "align-items", "align-content", "align-self", "gap", "row-gap", "column-gap",
|
"flex-direction",
|
||||||
"grid", "grid-template", "grid-template-columns", "grid-template-rows", "grid-area",
|
"flex-wrap",
|
||||||
|
"flex-flow",
|
||||||
|
"flex-grow",
|
||||||
|
"flex-shrink",
|
||||||
|
"flex-basis",
|
||||||
|
"justify-content",
|
||||||
|
"align-items",
|
||||||
|
"align-content",
|
||||||
|
"align-self",
|
||||||
|
"gap",
|
||||||
|
"row-gap",
|
||||||
|
"column-gap",
|
||||||
|
"grid",
|
||||||
|
"grid-template",
|
||||||
|
"grid-template-columns",
|
||||||
|
"grid-template-rows",
|
||||||
|
"grid-area",
|
||||||
// Box model
|
// Box model
|
||||||
"box-sizing", "border-top-width", "border-right-width", "border-bottom-width", "border-left-width",
|
"box-sizing",
|
||||||
"border-top-style", "border-right-style", "border-bottom-style", "border-left-style",
|
"border-top-width",
|
||||||
"border-top-color", "border-right-color", "border-bottom-color", "border-left-color",
|
"border-right-width",
|
||||||
"border-top-left-radius", "border-top-right-radius", "border-bottom-right-radius", "border-bottom-left-radius",
|
"border-bottom-width",
|
||||||
|
"border-left-width",
|
||||||
|
"border-top-style",
|
||||||
|
"border-right-style",
|
||||||
|
"border-bottom-style",
|
||||||
|
"border-left-style",
|
||||||
|
"border-top-color",
|
||||||
|
"border-right-color",
|
||||||
|
"border-bottom-color",
|
||||||
|
"border-left-color",
|
||||||
|
"border-top-left-radius",
|
||||||
|
"border-top-right-radius",
|
||||||
|
"border-bottom-right-radius",
|
||||||
|
"border-bottom-left-radius",
|
||||||
// Colors & Background
|
// Colors & Background
|
||||||
"color", "background-color", "background-image", "background-size", "background-position",
|
"color",
|
||||||
"background-repeat", "background-attachment", "background-clip", "background-origin",
|
"background-color",
|
||||||
|
"background-image",
|
||||||
|
"background-size",
|
||||||
|
"background-position",
|
||||||
|
"background-repeat",
|
||||||
|
"background-attachment",
|
||||||
|
"background-clip",
|
||||||
|
"background-origin",
|
||||||
// Typography
|
// Typography
|
||||||
"font-family", "font-size", "font-weight", "font-style", "font-variant",
|
"font-family",
|
||||||
"line-height", "text-align", "text-decoration-line", "text-decoration-color",
|
"font-size",
|
||||||
"text-transform", "letter-spacing", "word-spacing",
|
"font-weight",
|
||||||
"white-space", "overflow-wrap", "word-break",
|
"font-style",
|
||||||
|
"font-variant",
|
||||||
|
"line-height",
|
||||||
|
"text-align",
|
||||||
|
"text-decoration-line",
|
||||||
|
"text-decoration-color",
|
||||||
|
"text-transform",
|
||||||
|
"letter-spacing",
|
||||||
|
"word-spacing",
|
||||||
|
"white-space",
|
||||||
|
"overflow-wrap",
|
||||||
|
"word-break",
|
||||||
// Visual
|
// Visual
|
||||||
"opacity", "visibility", "z-index", "overflow-x", "overflow-y",
|
"opacity",
|
||||||
"box-shadow", "text-shadow", "transform", "transform-origin",
|
"visibility",
|
||||||
|
"z-index",
|
||||||
|
"overflow-x",
|
||||||
|
"overflow-y",
|
||||||
|
"box-shadow",
|
||||||
|
"text-shadow",
|
||||||
|
"transform",
|
||||||
|
"transform-origin",
|
||||||
// List/Table
|
// List/Table
|
||||||
"list-style-type", "list-style-position", "border-collapse", "border-spacing",
|
"list-style-type",
|
||||||
|
"list-style-position",
|
||||||
|
"border-collapse",
|
||||||
|
"border-spacing",
|
||||||
// Cursor/pointer
|
// Cursor/pointer
|
||||||
"cursor", "pointer-events",
|
"cursor",
|
||||||
|
"pointer-events",
|
||||||
];
|
];
|
||||||
|
|
||||||
// Cache of default computed values keyed by tagName
|
// Cache of default computed values keyed by tagName
|
||||||
const defaultCache = new Map<string, Map<string, string>>();
|
const defaultCache = new Map<string, Map<string, string>>();
|
||||||
const hiddenContainer = document.createElement("div");
|
const hiddenContainer = document.createElement("div");
|
||||||
hiddenContainer.style.cssText = "position:absolute;visibility:hidden;pointer-events:none;top:-9999px;";
|
hiddenContainer.style.cssText =
|
||||||
|
"position:absolute;visibility:hidden;pointer-events:none;top:-9999px;";
|
||||||
document.body.appendChild(hiddenContainer);
|
document.body.appendChild(hiddenContainer);
|
||||||
|
|
||||||
function getTagDefaults(tag: string): Map<string, string> {
|
function getTagDefaults(tag: string): Map<string, string> {
|
||||||
@@ -913,7 +1005,9 @@ async function bakeComputedStyles(page: Page): Promise<void> {
|
|||||||
cache.set(prop, cs.getPropertyValue(prop));
|
cache.set(prop, cs.getPropertyValue(prop));
|
||||||
}
|
}
|
||||||
el.remove();
|
el.remove();
|
||||||
} catch { /* unknown tag, cache will be empty */ }
|
} catch {
|
||||||
|
/* unknown tag, cache will be empty */
|
||||||
|
}
|
||||||
defaultCache.set(tag, cache);
|
defaultCache.set(tag, cache);
|
||||||
return cache;
|
return cache;
|
||||||
}
|
}
|
||||||
@@ -921,7 +1015,8 @@ async function bakeComputedStyles(page: Page): Promise<void> {
|
|||||||
function shouldBake(value: string, defaultValue: string): boolean {
|
function shouldBake(value: string, defaultValue: string): boolean {
|
||||||
if (!value) return false;
|
if (!value) return false;
|
||||||
// Skip browser keywords that cannot be serialized as inline style values
|
// Skip browser keywords that cannot be serialized as inline style values
|
||||||
if (value === "initial" || value === "inherit" || value === "unset" || value === "revert") return false;
|
if (value === "initial" || value === "inherit" || value === "unset" || value === "revert")
|
||||||
|
return false;
|
||||||
return value !== defaultValue;
|
return value !== defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -931,7 +1026,8 @@ async function bakeComputedStyles(page: Page): Promise<void> {
|
|||||||
if (el === hiddenContainer) continue;
|
if (el === hiddenContainer) continue;
|
||||||
// Skip non-visual tags
|
// Skip non-visual tags
|
||||||
const tag = el.tagName.toLowerCase();
|
const tag = el.tagName.toLowerCase();
|
||||||
if (["script", "style", "meta", "link", "noscript", "template", "head", "html"].includes(tag)) continue;
|
if (["script", "style", "meta", "link", "noscript", "template", "head", "html"].includes(tag))
|
||||||
|
continue;
|
||||||
|
|
||||||
const computed = window.getComputedStyle(el);
|
const computed = window.getComputedStyle(el);
|
||||||
const defaults = getTagDefaults(tag);
|
const defaults = getTagDefaults(tag);
|
||||||
@@ -1081,7 +1177,7 @@ async function inlineAllResources(page: Page, html: string): Promise<string> {
|
|||||||
|
|
||||||
html = html.replace(
|
html = html.replace(
|
||||||
linkRegex,
|
linkRegex,
|
||||||
`<style${mediaAttr}>/* inlined: ${css.url} */\n${resolvedCSS}\n</style>`
|
`<style${mediaAttr}>/* inlined: ${css.url} */\n${resolvedCSS}\n</style>`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for (const img of payload.images) {
|
for (const img of payload.images) {
|
||||||
@@ -1498,7 +1594,9 @@ const server = Bun.serve<{ interval: number }>({
|
|||||||
}, interval);
|
}, interval);
|
||||||
debugClients.set(ws, { interval, timer });
|
debugClients.set(ws, { interval, timer });
|
||||||
// Send initial payload immediately
|
// Send initial payload immediately
|
||||||
try { ws.send(JSON.stringify(getDebugData())); } catch {}
|
try {
|
||||||
|
ws.send(JSON.stringify(getDebugData()));
|
||||||
|
} catch {}
|
||||||
},
|
},
|
||||||
message(ws, msg) {
|
message(ws, msg) {
|
||||||
try {
|
try {
|
||||||
@@ -1510,15 +1608,21 @@ const server = Bun.serve<{ interval: number }>({
|
|||||||
clearInterval(client.timer);
|
clearInterval(client.timer);
|
||||||
client.interval = interval;
|
client.interval = interval;
|
||||||
client.timer = setInterval(() => {
|
client.timer = setInterval(() => {
|
||||||
try { ws.send(JSON.stringify(getDebugData())); } catch {
|
try {
|
||||||
|
ws.send(JSON.stringify(getDebugData()));
|
||||||
|
} catch {
|
||||||
clearInterval(client.timer);
|
clearInterval(client.timer);
|
||||||
debugClients.delete(ws);
|
debugClients.delete(ws);
|
||||||
}
|
}
|
||||||
}, interval);
|
}, interval);
|
||||||
// Push immediately after rate change
|
// Push immediately after rate change
|
||||||
try { ws.send(JSON.stringify(getDebugData())); } catch {}
|
try {
|
||||||
|
ws.send(JSON.stringify(getDebugData()));
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
/* ignore malformed messages */
|
||||||
}
|
}
|
||||||
} catch { /* ignore malformed messages */ }
|
|
||||||
},
|
},
|
||||||
close(ws) {
|
close(ws) {
|
||||||
const client = debugClients.get(ws);
|
const client = debugClients.get(ws);
|
||||||
|
|||||||
Reference in New Issue
Block a user