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:
+151
-47
@@ -164,8 +164,8 @@ if (viewCount === 0) {
|
||||
"Demo View",
|
||||
JSON.stringify([
|
||||
{ url: "https://www.google.com", durationSec: 30, forceDarkMode: false, bakeStyles: false },
|
||||
{ url: "https://duck.ai", durationSec: 30, forceDarkMode: false, bakeStyles: false },
|
||||
{ url: "https://www.msn.com", durationSec: 30, forceDarkMode: false, bakeStyles: false },
|
||||
{ url: "https://duck.ai", durationSec: 30, forceDarkMode: false, bakeStyles: false },
|
||||
{ url: "https://www.msn.com", durationSec: 30, forceDarkMode: false, bakeStyles: false },
|
||||
]),
|
||||
"ssr",
|
||||
0,
|
||||
@@ -229,7 +229,7 @@ function dbDelete(id: string): boolean {
|
||||
function dbBanIP(ip: string, reason?: string): void {
|
||||
db.run(
|
||||
"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;
|
||||
}
|
||||
|
||||
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<{
|
||||
ip: string;
|
||||
banned_at: number;
|
||||
@@ -292,11 +297,11 @@ function deviceTrack(ip: string, viewId: string, viewName: string, userAgent: st
|
||||
existing.requestCount++;
|
||||
} else {
|
||||
devices.set(ip, { ip, viewId, viewName, userAgent, lastSeen: Date.now(), requestCount: 1 });
|
||||
|
||||
|
||||
// First user connecting to this view - trigger warmup
|
||||
if (!warmedUpViews.has(viewId)) {
|
||||
warmedUpViews.add(viewId);
|
||||
warmupView(viewId).catch(err => {
|
||||
warmupView(viewId).catch((err) => {
|
||||
console.error(`[warmup] Failed to warm up view ${viewId}:`, err);
|
||||
});
|
||||
}
|
||||
@@ -321,7 +326,7 @@ function devicesSnapshot(): DeviceEntry[] {
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
function getDebugData() {
|
||||
const viewsDebug = viewCacheAll().map(v => {
|
||||
const viewsDebug = viewCacheAll().map((v) => {
|
||||
const idx = getCurrentURLIndex(v);
|
||||
return {
|
||||
id: v.id,
|
||||
@@ -336,7 +341,7 @@ function getDebugData() {
|
||||
currentUrl: v.urls[idx]?.url ?? "",
|
||||
remainingSec: getRemainingSec(v),
|
||||
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;
|
||||
} catch (err) {
|
||||
console.warn(`[page-pool] resetPage failed, discarding page: ${err}`);
|
||||
try { await page.close(); } catch {}
|
||||
try {
|
||||
await page.close();
|
||||
} catch {}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -513,10 +520,7 @@ let pagePool: PagePool;
|
||||
// WEBSOCKET DEBUG CLIENTS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
const debugClients = new Map<
|
||||
any,
|
||||
{ interval: number; timer: ReturnType<typeof setInterval> }
|
||||
>();
|
||||
const debugClients = new Map<any, { interval: number; timer: ReturnType<typeof setInterval> }>();
|
||||
const DEBUG_DEFAULT_INTERVAL_MS = 1000;
|
||||
const DEBUG_MIN_INTERVAL_MS = 100;
|
||||
|
||||
@@ -668,12 +672,19 @@ async function renderViewToCache(view: View, urlIndex: number, key: string): Pro
|
||||
let body: string;
|
||||
if (view.method === "mjpeg") {
|
||||
const jpeg = await renderScreenshotFrame(
|
||||
urlItem.url, view.viewportWidth, view.viewportHeight, forceDarkMode,
|
||||
urlItem.url,
|
||||
view.viewportWidth,
|
||||
view.viewportHeight,
|
||||
forceDarkMode,
|
||||
);
|
||||
body = jpeg.toString("base64");
|
||||
} else {
|
||||
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) ──
|
||||
async function resolveCSSImports(cssText: string, baseUrl: string, depth = 0): Promise<string> {
|
||||
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 }> = [];
|
||||
|
||||
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");
|
||||
let imported = await res.text();
|
||||
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 {
|
||||
replacements.push({ match: m[0], resolved: "/* unresolved import: " + importUrl + " */" });
|
||||
}
|
||||
@@ -865,40 +880,117 @@ async function bakeComputedStyles(page: Page): Promise<void> {
|
||||
await page.evaluate((): void => {
|
||||
const computedProperties = [
|
||||
// Layout
|
||||
"display", "position", "top", "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",
|
||||
"display",
|
||||
"position",
|
||||
"top",
|
||||
"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", "flex-direction", "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",
|
||||
"flex",
|
||||
"flex-direction",
|
||||
"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-sizing", "border-top-width", "border-right-width", "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",
|
||||
"box-sizing",
|
||||
"border-top-width",
|
||||
"border-right-width",
|
||||
"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
|
||||
"color", "background-color", "background-image", "background-size", "background-position",
|
||||
"background-repeat", "background-attachment", "background-clip", "background-origin",
|
||||
"color",
|
||||
"background-color",
|
||||
"background-image",
|
||||
"background-size",
|
||||
"background-position",
|
||||
"background-repeat",
|
||||
"background-attachment",
|
||||
"background-clip",
|
||||
"background-origin",
|
||||
// Typography
|
||||
"font-family", "font-size", "font-weight", "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",
|
||||
"font-family",
|
||||
"font-size",
|
||||
"font-weight",
|
||||
"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
|
||||
"opacity", "visibility", "z-index", "overflow-x", "overflow-y",
|
||||
"box-shadow", "text-shadow", "transform", "transform-origin",
|
||||
"opacity",
|
||||
"visibility",
|
||||
"z-index",
|
||||
"overflow-x",
|
||||
"overflow-y",
|
||||
"box-shadow",
|
||||
"text-shadow",
|
||||
"transform",
|
||||
"transform-origin",
|
||||
// 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-events",
|
||||
"cursor",
|
||||
"pointer-events",
|
||||
];
|
||||
|
||||
// Cache of default computed values keyed by tagName
|
||||
const defaultCache = new Map<string, Map<string, string>>();
|
||||
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);
|
||||
|
||||
function getTagDefaults(tag: string): Map<string, string> {
|
||||
@@ -913,7 +1005,9 @@ async function bakeComputedStyles(page: Page): Promise<void> {
|
||||
cache.set(prop, cs.getPropertyValue(prop));
|
||||
}
|
||||
el.remove();
|
||||
} catch { /* unknown tag, cache will be empty */ }
|
||||
} catch {
|
||||
/* unknown tag, cache will be empty */
|
||||
}
|
||||
defaultCache.set(tag, cache);
|
||||
return cache;
|
||||
}
|
||||
@@ -921,7 +1015,8 @@ async function bakeComputedStyles(page: Page): Promise<void> {
|
||||
function shouldBake(value: string, defaultValue: string): boolean {
|
||||
if (!value) return false;
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -931,7 +1026,8 @@ async function bakeComputedStyles(page: Page): Promise<void> {
|
||||
if (el === hiddenContainer) continue;
|
||||
// Skip non-visual tags
|
||||
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 defaults = getTagDefaults(tag);
|
||||
@@ -1081,7 +1177,7 @@ async function inlineAllResources(page: Page, html: string): Promise<string> {
|
||||
|
||||
html = html.replace(
|
||||
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) {
|
||||
@@ -1498,7 +1594,9 @@ const server = Bun.serve<{ interval: number }>({
|
||||
}, interval);
|
||||
debugClients.set(ws, { interval, timer });
|
||||
// Send initial payload immediately
|
||||
try { ws.send(JSON.stringify(getDebugData())); } catch {}
|
||||
try {
|
||||
ws.send(JSON.stringify(getDebugData()));
|
||||
} catch {}
|
||||
},
|
||||
message(ws, msg) {
|
||||
try {
|
||||
@@ -1510,15 +1608,21 @@ const server = Bun.serve<{ interval: number }>({
|
||||
clearInterval(client.timer);
|
||||
client.interval = interval;
|
||||
client.timer = setInterval(() => {
|
||||
try { ws.send(JSON.stringify(getDebugData())); } catch {
|
||||
try {
|
||||
ws.send(JSON.stringify(getDebugData()));
|
||||
} catch {
|
||||
clearInterval(client.timer);
|
||||
debugClients.delete(ws);
|
||||
}
|
||||
}, interval);
|
||||
// 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) {
|
||||
const client = debugClients.get(ws);
|
||||
|
||||
Reference in New Issue
Block a user