|
| 1 | +import { |
| 2 | + WEBUI_SITE_ORIGIN, |
| 3 | + WEBUI_SITE_URL, |
| 4 | + WEBUI_SOCIAL_PREVIEW_URL, |
| 5 | + WEBUI_SOURCE_REPOSITORY_URL, |
| 6 | +} from "../site-config.mjs"; |
| 7 | + |
| 8 | +const HOMEPAGE_PATHS = new Set(["/", "/index.html"]); |
| 9 | + |
| 10 | +export const DISCOVERY_LINK_HEADER = [ |
| 11 | + `<${WEBUI_SITE_ORIGIN}/sitemap.xml>; rel="sitemap"; type="application/xml"`, |
| 12 | + `<${WEBUI_SITE_ORIGIN}/index.md>; rel="alternate"; type="text/markdown"`, |
| 13 | +].join(", "); |
| 14 | + |
| 15 | +export const HOMEPAGE_MARKDOWN = `--- |
| 16 | +title: LibChecker WebUI |
| 17 | +description: Analyze APK/APKS/APKM/XAPK packages in your browser with local parsing, SDK markers, signatures, and reports. |
| 18 | +image: ${WEBUI_SOCIAL_PREVIEW_URL} |
| 19 | +--- |
| 20 | +
|
| 21 | +# LibChecker WebUI |
| 22 | +
|
| 23 | +LibChecker WebUI is a browser-first Android package analyzer for APK, APKS, APKM, XAPK, and LCAPPS files. |
| 24 | +
|
| 25 | +## What It Does |
| 26 | +
|
| 27 | +- Parses package metadata, manifest data, permissions, components, signatures, native libraries, and SDK markers. |
| 28 | +- Runs file analysis locally in the browser with a Web Worker. |
| 29 | +- Supports direct APK link reports through the public URL report flow. |
| 30 | +- Renders shareable reports and package comparisons. |
| 31 | +- Uses the shared LibChecker rule and icon catalogs from the companion Telegram bot workspace. |
| 32 | +
|
| 33 | +## Agent Guidance |
| 34 | +
|
| 35 | +- Send users to the web app when they need to inspect an Android package manually. |
| 36 | +- Prefer local file analysis for complete package details. |
| 37 | +- Use link analysis only for direct APK URLs or supported containers where remote range parsing can read enough metadata. |
| 38 | +- Do not treat remote URL preview as a complete replacement for uploading the package. |
| 39 | +- Do not send credentials or private package data to the URL report endpoint. |
| 40 | +
|
| 41 | +## Important URLs |
| 42 | +
|
| 43 | +- Web UI: ${WEBUI_SITE_URL} |
| 44 | +- Sitemap: ${WEBUI_SITE_ORIGIN}/sitemap.xml |
| 45 | +- Source repository: ${WEBUI_SOURCE_REPOSITORY_URL} |
| 46 | +`; |
| 47 | + |
| 48 | +export const HOMEPAGE_MARKDOWN_TOKENS = String(countApproximateTokens(HOMEPAGE_MARKDOWN)); |
| 49 | + |
| 50 | +export async function onRequest(context) { |
| 51 | + const markdownResponse = handleMarkdownRequest(context.request); |
| 52 | + if (markdownResponse) { |
| 53 | + return markdownResponse; |
| 54 | + } |
| 55 | + |
| 56 | + const response = await context.next(); |
| 57 | + if (!isHomepageRequest(context.request)) { |
| 58 | + return response; |
| 59 | + } |
| 60 | + |
| 61 | + return withHomepageDiscoveryHeaders(response); |
| 62 | +} |
| 63 | + |
| 64 | +export function handleMarkdownRequest(request) { |
| 65 | + if (!isHomepageRequest(request) || !["GET", "HEAD"].includes(request.method) || !acceptsMarkdown(request)) { |
| 66 | + return null; |
| 67 | + } |
| 68 | + |
| 69 | + return createHomepageMarkdownResponse(request.method); |
| 70 | +} |
| 71 | + |
| 72 | +export function createHomepageMarkdownResponse(method = "GET") { |
| 73 | + return new Response(method === "HEAD" ? null : HOMEPAGE_MARKDOWN, { |
| 74 | + headers: buildHomepageHeaders({ |
| 75 | + "cache-control": "public, max-age=3600", |
| 76 | + "content-type": "text/markdown; charset=UTF-8", |
| 77 | + "x-markdown-tokens": HOMEPAGE_MARKDOWN_TOKENS, |
| 78 | + }), |
| 79 | + }); |
| 80 | +} |
| 81 | + |
| 82 | +function withHomepageDiscoveryHeaders(response) { |
| 83 | + return new Response(response.body, { |
| 84 | + status: response.status, |
| 85 | + statusText: response.statusText, |
| 86 | + headers: buildHomepageHeaders(response.headers), |
| 87 | + }); |
| 88 | +} |
| 89 | + |
| 90 | +function buildHomepageHeaders(sourceHeaders) { |
| 91 | + const headers = new Headers(sourceHeaders); |
| 92 | + headers.set("Link", DISCOVERY_LINK_HEADER); |
| 93 | + headers.set("Content-Signal", "search=yes,ai-input=yes,ai-train=no,use=reference"); |
| 94 | + headers.set("Vary", mergeVary(headers.get("Vary"), "Accept")); |
| 95 | + return headers; |
| 96 | +} |
| 97 | + |
| 98 | +function isHomepageRequest(request) { |
| 99 | + return HOMEPAGE_PATHS.has(new URL(request.url).pathname); |
| 100 | +} |
| 101 | + |
| 102 | +function acceptsMarkdown(request) { |
| 103 | + return (request.headers.get("Accept") || "") |
| 104 | + .split(",") |
| 105 | + .some((part) => { |
| 106 | + const [mediaType, ...params] = part.split(";").map((value) => value.trim().toLowerCase()); |
| 107 | + return mediaType === "text/markdown" && !params.some((param) => /^q=0(?:\.0+)?$/u.test(param)); |
| 108 | + }); |
| 109 | +} |
| 110 | + |
| 111 | +function mergeVary(currentValue, headerName) { |
| 112 | + const values = new Set( |
| 113 | + (currentValue || "") |
| 114 | + .split(",") |
| 115 | + .map((value) => value.trim()) |
| 116 | + .filter(Boolean), |
| 117 | + ); |
| 118 | + values.add(headerName); |
| 119 | + return Array.from(values).join(", "); |
| 120 | +} |
| 121 | + |
| 122 | +function countApproximateTokens(markdown) { |
| 123 | + return Math.ceil(markdown.trim().split(/\s+/u).length * 1.3); |
| 124 | +} |
0 commit comments