fix(POSTV1-02): 修复便携版网页入口门禁
Dada P0-A isolated Windows CI / validate-and-package (push) Waiting to run

This commit is contained in:
suyx
2026-08-05 12:58:28 +08:00
parent fd0003a804
commit 99fd3b1802
5 changed files with 47 additions and 12 deletions
+12 -3
View File
@@ -238,6 +238,7 @@ export interface CreateAppOptions {
assetReleases?: AssetReleaseReader; assetReleases?: AssetReleaseReader;
bootstrap?: () => BootstrapResponse | Promise<BootstrapResponse>; bootstrap?: () => BootstrapResponse | Promise<BootstrapResponse>;
browserGate?: boolean; browserGate?: boolean;
productIndexHtml?: string;
browserSupportRelease?: BrowserSupportRelease; browserSupportRelease?: BrowserSupportRelease;
browserSupportSecret?: Buffer; browserSupportSecret?: Buffer;
credits?: CreditService; credits?: CreditService;
@@ -273,7 +274,7 @@ const supportGateHtml = readFileSync(resolve(supportGateDirectory, "index.html")
const supportGateCss = readFileSync(resolve(supportGateDirectory, "support-gate.css"), "utf8"); const supportGateCss = readFileSync(resolve(supportGateDirectory, "support-gate.css"), "utf8");
const supportGateJavaScript = readFileSync(resolve(supportGateDirectory, "support-gate.js"), "utf8"); const supportGateJavaScript = readFileSync(resolve(supportGateDirectory, "support-gate.js"), "utf8");
const productWebRoot = resolve(process.env.DADA_WEB_ROOT ?? "apps/web/dist"); 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") ? readFileSync(resolve(productWebRoot, "index.html"), "utf8")
: undefined; : undefined;
const clientHints = "Sec-CH-UA, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Platform"; 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); : undefined);
const browserGate = options.browserGate ?? true; const browserGate = options.browserGate ?? true;
const productIndexHtml = options.productIndexHtml ?? packagedProductIndexHtml;
const browserSupportSecret = options.browserSupportSecret ?? randomBytes(32); const browserSupportSecret = options.browserSupportSecret ?? randomBytes(32);
const browserSupportRelease = options.browserSupportRelease; const browserSupportRelease = options.browserSupportRelease;
const app = Fastify({ const app = Fastify({
@@ -905,9 +907,16 @@ export async function createApp(options: CreateAppOptions = {}) {
}); });
for (const route of ["/", "/app", "/app/*", "/admin", "/admin/*"]) { 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"); 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) => { app.get("/assets/*", { schema: { hide: true } }, async (request, reply) => {
+1
View File
@@ -69,6 +69,7 @@ async function checkSupport() {
browserValue.textContent = `${result.browser.brand} ${result.browser.major}`; browserValue.textContent = `${result.browser.brand} ${result.browser.major}`;
supportedValue.textContent = supportedLabel(result.supported_browsers); supportedValue.textContent = supportedLabel(result.supported_browsers);
window.dispatchEvent(new CustomEvent("dada:support-ready")); window.dispatchEvent(new CustomEvent("dada:support-ready"));
window.location.replace(window.location.pathname.startsWith("/admin") ? "/admin" : "/app");
return; return;
} }
showBlocked( showBlocked(
+11 -1
View File
@@ -1,10 +1,20 @@
import { execFileSync } from "node:child_process";
import { readFileSync } from "node:fs";
import { resolve } from "node:path"; import { resolve } from "node:path";
import { buildAndValidatePortablePackage } from "./lib/portable-package.mjs"; import { buildAndValidatePortablePackage } from "./lib/portable-package.mjs";
import { validateFinalReleaseRecord } from "./lib/wp7-07-final-release.mjs";
const outputIndex = process.argv.indexOf("--output"); const outputIndex = process.argv.indexOf("--output");
const outputRoot = outputIndex >= 0 ? resolve(process.argv[outputIndex + 1]) : resolve(".build", "portable-release"); 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({ console.log(JSON.stringify({
package: result.packageManifest.package_name, package: result.packageManifest.package_name,
sha256: result.packageManifest.zip_sha256, sha256: result.packageManifest.zip_sha256,
+14 -1
View File
@@ -125,7 +125,12 @@ afterAll(async () => {
describe("TDD-WP0-BRW-001 supported browser contract", () => { describe("TDD-WP0-BRW-001 supported browser contract", () => {
it("cross-checks UA-CH and issues only a short-lived signed support cookie", async () => { 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: "<!doctype html><title>Dada product test</title><div id=\"root\"></div>",
} 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({ const checked = await app.inject({
headers: supportedEdge.headers, headers: supportedEdge.headers,
method: "POST", method: "POST",
@@ -150,6 +155,14 @@ describe("TDD-WP0-BRW-001 supported browser contract", () => {
const cookie = supportCookie(checked); const cookie = supportCookie(checked);
expect(cookie).toBeDefined(); 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({ const product = await app.inject({
headers: { headers: {
cookie, cookie,
+6 -4
View File
@@ -125,18 +125,20 @@ test("portable package serves the product and keeps SQLite data across API resta
let api = startApi(configPath, dataRoot); let api = startApi(configPath, dataRoot);
try { try {
await waitForHealth(api); 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`, { const support = await fetch(`http://127.0.0.1:${port}/api/v1/support/check`, {
method: "POST", 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"' }, 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" }), 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)); assert.equal(support.status, 200);
if (support.status === 200) {
const cookie = support.headers.get("set-cookie")?.split(";", 1)[0]; 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.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.equal(page.status, 200);
assert.match(await page.text(), /<div id="root"><\/div>/); assert.match(await page.text(), /<div id="root"><\/div>/);
}
assert.ok(existsSync(join(dataRoot, "db", "dada.sqlite3"))); assert.ok(existsSync(join(dataRoot, "db", "dada.sqlite3")));
await verifyWorkerStartup(configPath); await verifyWorkerStartup(configPath);
} finally { } finally {