From 99fd3b1802e573f05c453221a478c618400801a0 Mon Sep 17 00:00:00 2001 From: suyx Date: Wed, 5 Aug 2026 12:58:28 +0800 Subject: [PATCH] =?UTF-8?q?fix(POSTV1-02):=20=E4=BF=AE=E5=A4=8D=E4=BE=BF?= =?UTF-8?q?=E6=90=BA=E7=89=88=E7=BD=91=E9=A1=B5=E5=85=A5=E5=8F=A3=E9=97=A8?= =?UTF-8?q?=E7=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/src/app.ts | 15 ++++++++++++--- apps/web/support-gate/support-gate.js | 1 + scripts/build-portable.mjs | 12 +++++++++++- tests/api/wp0-03-browser-gate.test.ts | 15 ++++++++++++++- tests/package/postv1-runtime.test.mjs | 16 +++++++++------- 5 files changed, 47 insertions(+), 12 deletions(-) diff --git a/apps/api/src/app.ts b/apps/api/src/app.ts index 2f2fb95..d0b7102 100644 --- a/apps/api/src/app.ts +++ b/apps/api/src/app.ts @@ -238,6 +238,7 @@ export interface CreateAppOptions { assetReleases?: AssetReleaseReader; bootstrap?: () => BootstrapResponse | Promise; browserGate?: boolean; + productIndexHtml?: string; browserSupportRelease?: BrowserSupportRelease; browserSupportSecret?: Buffer; credits?: CreditService; @@ -273,7 +274,7 @@ const supportGateHtml = readFileSync(resolve(supportGateDirectory, "index.html") const supportGateCss = readFileSync(resolve(supportGateDirectory, "support-gate.css"), "utf8"); const supportGateJavaScript = readFileSync(resolve(supportGateDirectory, "support-gate.js"), "utf8"); const productWebRoot = resolve(process.env.DADA_WEB_ROOT ?? "apps/web/dist"); -const productIndexHtml = existsSync(resolve(productWebRoot, "index.html")) +const packagedProductIndexHtml = existsSync(resolve(productWebRoot, "index.html")) ? readFileSync(resolve(productWebRoot, "index.html"), "utf8") : undefined; const clientHints = "Sec-CH-UA, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Platform"; @@ -714,6 +715,7 @@ export async function createApp(options: CreateAppOptions = {}) { ) : undefined); const browserGate = options.browserGate ?? true; + const productIndexHtml = options.productIndexHtml ?? packagedProductIndexHtml; const browserSupportSecret = options.browserSupportSecret ?? randomBytes(32); const browserSupportRelease = options.browserSupportRelease; const app = Fastify({ @@ -905,9 +907,16 @@ export async function createApp(options: CreateAppOptions = {}) { }); for (const route of ["/", "/app", "/app/*", "/admin", "/admin/*"]) { - app.get(route, { schema: { hide: true } }, async (_request, reply) => { + app.get(route, { schema: { hide: true } }, async (request, reply) => { reply.type("text/html; charset=utf-8"); - return productIndexHtml ?? supportGateHtml; + if (!browserGate) return productIndexHtml ?? supportGateHtml; + const verified = verifyBrowserSupportCookie({ + cookieHeader: headerValue(request.headers.cookie), + release: browserSupportRelease, + secChUa: headerValue(request.headers["sec-ch-ua"]), + secret: browserSupportSecret, + }); + return verified.supported && productIndexHtml ? productIndexHtml : supportGateHtml; }); } app.get("/assets/*", { schema: { hide: true } }, async (request, reply) => { diff --git a/apps/web/support-gate/support-gate.js b/apps/web/support-gate/support-gate.js index 259b716..17b3016 100644 --- a/apps/web/support-gate/support-gate.js +++ b/apps/web/support-gate/support-gate.js @@ -69,6 +69,7 @@ async function checkSupport() { browserValue.textContent = `${result.browser.brand} ${result.browser.major}`; supportedValue.textContent = supportedLabel(result.supported_browsers); window.dispatchEvent(new CustomEvent("dada:support-ready")); + window.location.replace(window.location.pathname.startsWith("/admin") ? "/admin" : "/app"); return; } showBlocked( diff --git a/scripts/build-portable.mjs b/scripts/build-portable.mjs index 9f56550..d5e4124 100644 --- a/scripts/build-portable.mjs +++ b/scripts/build-portable.mjs @@ -1,10 +1,20 @@ +import { execFileSync } from "node:child_process"; +import { readFileSync } from "node:fs"; import { resolve } from "node:path"; import { buildAndValidatePortablePackage } from "./lib/portable-package.mjs"; +import { validateFinalReleaseRecord } from "./lib/wp7-07-final-release.mjs"; const outputIndex = process.argv.indexOf("--output"); const outputRoot = outputIndex >= 0 ? resolve(process.argv[outputIndex + 1]) : resolve(".build", "portable-release"); -const result = await buildAndValidatePortablePackage({ outputRoot }); +const previousRelease = JSON.parse(readFileSync(resolve("RELEASE.json"), "utf8")); +const releaseRecord = validateFinalReleaseRecord({ + ...previousRelease, + buildCommit: execFileSync("git", ["rev-parse", "HEAD"], { encoding: "utf8" }).trim(), + maintenanceFromCommit: previousRelease.buildCommit, + recordedAt: new Date().toISOString(), +}); +const result = await buildAndValidatePortablePackage({ outputRoot, releaseRecord }); console.log(JSON.stringify({ package: result.packageManifest.package_name, sha256: result.packageManifest.zip_sha256, diff --git a/tests/api/wp0-03-browser-gate.test.ts b/tests/api/wp0-03-browser-gate.test.ts index 259258e..a472533 100644 --- a/tests/api/wp0-03-browser-gate.test.ts +++ b/tests/api/wp0-03-browser-gate.test.ts @@ -125,7 +125,12 @@ afterAll(async () => { describe("TDD-WP0-BRW-001 supported browser contract", () => { it("cross-checks UA-CH and issues only a short-lived signed support cookie", async () => { - const app = await createApp({ browserSupportRelease: testBrowserSupportRelease } as never); + const app = await createApp({ + browserSupportRelease: testBrowserSupportRelease, + productIndexHtml: "Dada product test
", + } as never); + const gate = await app.inject({ headers: { host: "127.0.0.1:43121" }, method: "GET", url: "/" }); + expect(gate.body).toContain("当前浏览器无法使用 Dada"); const checked = await app.inject({ headers: supportedEdge.headers, method: "POST", @@ -150,6 +155,14 @@ describe("TDD-WP0-BRW-001 supported browser contract", () => { const cookie = supportCookie(checked); expect(cookie).toBeDefined(); + const productHtml = await app.inject({ + headers: { cookie, host: "127.0.0.1:43121", "sec-ch-ua": supportedEdge.headers["sec-ch-ua"] }, + method: "GET", + url: "/app", + }); + expect(productHtml.statusCode).toBe(200); + expect(productHtml.body).toContain("Dada product test"); + expect(productHtml.body).not.toContain("当前浏览器无法使用 Dada"); const product = await app.inject({ headers: { cookie, diff --git a/tests/package/postv1-runtime.test.mjs b/tests/package/postv1-runtime.test.mjs index 99892c2..51556dc 100644 --- a/tests/package/postv1-runtime.test.mjs +++ b/tests/package/postv1-runtime.test.mjs @@ -125,18 +125,20 @@ test("portable package serves the product and keeps SQLite data across API resta let api = startApi(configPath, dataRoot); try { await waitForHealth(api); + const initialPage = await fetch(`http://127.0.0.1:${port}/`, { headers: { host: `127.0.0.1:${port}` } }); + assert.equal(initialPage.status, 200); + assert.match(await initialPage.text(), /当前浏览器无法使用 Dada/); const support = await fetch(`http://127.0.0.1:${port}/api/v1/support/check`, { method: "POST", headers: { host: `127.0.0.1:${port}`, origin: `http://127.0.0.1:${port}`, "content-type": "application/json", "sec-ch-ua": '"Google Chrome";v="150"', "sec-ch-ua-full-version-list": '"Google Chrome";v="150.0.0.0"', "sec-ch-ua-platform": '"Windows"' }, body: JSON.stringify({ brands: [{ brand: "Google Chrome", version: "150" }], full_version_list: [{ brand: "Google Chrome", version: "150.0.0.0" }], platform: "Windows" }), }); - assert.ok([200, 426].includes(support.status)); - if (support.status === 200) { - const cookie = support.headers.get("set-cookie")?.split(";", 1)[0]; - const page = await fetch(`http://127.0.0.1:${port}/app`, { headers: { host: `127.0.0.1:${port}`, cookie } }); - assert.equal(page.status, 200); - assert.match(await page.text(), /
<\/div>/); - } + assert.equal(support.status, 200); + const cookie = support.headers.get("set-cookie")?.split(";", 1)[0]; + assert.ok(cookie); + const page = await fetch(`http://127.0.0.1:${port}/app`, { headers: { host: `127.0.0.1:${port}`, cookie, "sec-ch-ua": '"Google Chrome";v="150"' } }); + assert.equal(page.status, 200); + assert.match(await page.text(), /
<\/div>/); assert.ok(existsSync(join(dataRoot, "db", "dada.sqlite3"))); await verifyWorkerStartup(configPath); } finally {