Compare commits

...
Author SHA1 Message Date
suyx 42f993378f fix(POSTV1-browser): 同时支持 Chrome 150 与 151
Dada P0-A isolated Windows CI / validate-and-package (push) Waiting to run
2026-08-06 00:42:19 +08:00
suyx e9fd15e7b6 fix(POSTV1-runtime): 改善后台启动与状态窗口可见性
Dada P0-A isolated Windows CI / validate-and-package (push) Waiting to run
2026-08-05 23:12:31 +08:00
13 changed files with 149 additions and 26 deletions
+2 -1
View File
@@ -3,7 +3,8 @@
"browsers": [ "browsers": [
{ {
"brand": "Google Chrome", "brand": "Google Chrome",
"fullVersion": "150.0.7871.187" "fullVersion": "150.0.7871.187",
"supportedMajorVersions": [150, 151]
}, },
{ {
"brand": "Microsoft Edge", "brand": "Microsoft Edge",
+7 -4
View File
@@ -151,10 +151,13 @@ export function createAdminDiagnosticsProvider(input: {
const system: AdminDiagnosticsResponse["system"] = { const system: AdminDiagnosticsResponse["system"] = {
api_status: "ready", api_status: "ready",
app_version: input.appVersion ?? input.browserSupportRelease?.appVersion ?? "0.0.0", app_version: input.appVersion ?? input.browserSupportRelease?.appVersion ?? "0.0.0",
browser_support: (input.browserSupportRelease?.browsers ?? []).map((browser) => ({ browser_support: (input.browserSupportRelease?.browsers ?? []).flatMap((browser) => {
brand: browser.brand, const majors = browser.supportedMajorVersions
major: Number.parseInt(browser.fullVersion.split(".")[0] ?? "0", 10), ?? [Number.parseInt(browser.fullVersion.split(".")[0] ?? "0", 10)];
})).filter((browser) => Number.isSafeInteger(browser.major) && browser.major > 0), return majors
.map((major) => ({ brand: browser.brand, major }))
.filter((entry) => Number.isSafeInteger(entry.major) && entry.major > 0);
}),
worker_status: services.services.find((service) => service.service_id === "worker")?.status === "active" worker_status: services.services.find((service) => service.service_id === "worker")?.status === "active"
? "ready" ? "ready"
: services.services.find((service) => service.service_id === "worker")?.status === "unavailable" : services.services.find((service) => service.service_id === "worker")?.status === "unavailable"
+28 -7
View File
@@ -43,7 +43,7 @@ export const BrowserSupportSuccessSchema = Type.Object(
app_version: Type.String({ maxLength: 80 }), app_version: Type.String({ maxLength: 80 }),
browser: SupportedBrowserSummarySchema, browser: SupportedBrowserSummarySchema,
status: Type.Literal("supported"), status: Type.Literal("supported"),
supported_browsers: Type.Array(SupportedBrowserSummarySchema, { maxItems: 2 }), supported_browsers: Type.Array(SupportedBrowserSummarySchema, { maxItems: 8 }),
}, },
{ additionalProperties: false, $id: "BrowserSupportSuccess" }, { additionalProperties: false, $id: "BrowserSupportSuccess" },
); );
@@ -57,6 +57,7 @@ export interface BrowserSupportRelease {
browsers: ReadonlyArray<{ browsers: ReadonlyArray<{
brand: SupportedBrand; brand: SupportedBrand;
fullVersion: string; fullVersion: string;
supportedMajorVersions?: ReadonlyArray<number>;
}>; }>;
} }
@@ -115,7 +116,14 @@ function supportedIdentity(entries: Array<{ brand: string; version: string }>) {
export function supportedBrowserSummary(release: BrowserSupportRelease | undefined) { export function supportedBrowserSummary(release: BrowserSupportRelease | undefined) {
if (!release) return []; if (!release) return [];
return release.browsers.map(({ brand, fullVersion }) => ({ brand, major: major(fullVersion)! })); return release.browsers.flatMap(({ brand, fullVersion, supportedMajorVersions }) => {
const majors = supportedMajorVersions ?? [major(fullVersion)!];
return majors.map((supportedMajor) => ({ brand, major: supportedMajor }));
});
}
function acceptedMajorVersions(browser: BrowserSupportRelease["browsers"][number]) {
return browser.supportedMajorVersions ?? [major(browser.fullVersion)!];
} }
export function validateBrowserSupportRelease(value: unknown): value is BrowserSupportRelease { export function validateBrowserSupportRelease(value: unknown): value is BrowserSupportRelease {
@@ -126,13 +134,26 @@ export function validateBrowserSupportRelease(value: unknown): value is BrowserS
} }
if (!Array.isArray(release.browsers) || release.browsers.length !== 2) return false; if (!Array.isArray(release.browsers) || release.browsers.length !== 2) return false;
const brands = new Set(release.browsers.map(({ brand }) => brand)); const brands = new Set(release.browsers.map(({ brand }) => brand));
const supportedMajorCount = release.browsers.reduce(
(count, browser) => count + (browser.supportedMajorVersions?.length ?? 1),
0,
);
return ( return (
brands.size === 2 && brands.size === 2 &&
brands.has("Google Chrome") && brands.has("Google Chrome") &&
brands.has("Microsoft Edge") && brands.has("Microsoft Edge") &&
release.browsers.every( supportedMajorCount <= 8 &&
({ brand, fullVersion }) => supportedBrands.has(brand) && fullVersionPattern.test(fullVersion), release.browsers.every(({ brand, fullVersion, supportedMajorVersions }) => {
) if (!supportedBrands.has(brand) || !fullVersionPattern.test(fullVersion)) return false;
const baselineMajor = major(fullVersion);
if (!baselineMajor) return false;
if (supportedMajorVersions === undefined) return true;
return supportedMajorVersions.length > 0
&& supportedMajorVersions.length <= 8
&& supportedMajorVersions.every((value: number) => Number.isSafeInteger(value) && value >= 1)
&& new Set(supportedMajorVersions).size === supportedMajorVersions.length
&& supportedMajorVersions.includes(baselineMajor);
})
); );
} }
@@ -188,7 +209,7 @@ export function checkBrowserSupport(
} }
const supported = release?.browsers.find(({ brand }) => brand === fullIdentity.brand); const supported = release?.browsers.find(({ brand }) => brand === fullIdentity.brand);
if (!supported || major(supported.fullVersion) !== fullIdentity.major) { if (!supported || !acceptedMajorVersions(supported).includes(fullIdentity.major)) {
return { reason: "version_unsupported", supported: false }; return { reason: "version_unsupported", supported: false };
} }
return { identity: fullIdentity, supported: true }; return { identity: fullIdentity, supported: true };
@@ -268,7 +289,7 @@ export function verifyBrowserSupportCookie(input: {
return { reason: "identity_unavailable" as const, supported: false as const }; return { reason: "identity_unavailable" as const, supported: false as const };
} }
const supported = input.release.browsers.find(({ brand }) => brand === currentIdentity.brand); const supported = input.release.browsers.find(({ brand }) => brand === currentIdentity.brand);
if (currentIdentity.major !== payload.major || major(supported?.fullVersion ?? "") !== currentIdentity.major) { if (!supported || currentIdentity.major !== payload.major || !acceptedMajorVersions(supported).includes(currentIdentity.major)) {
return { reason: "version_unsupported" as const, supported: false as const }; return { reason: "version_unsupported" as const, supported: false as const };
} }
return { identity: currentIdentity, supported: true as const }; return { identity: currentIdentity, supported: true as const };
+2
View File
@@ -107,5 +107,7 @@ if (!workerPort && process.argv.includes("--dada-ai-probe")) {
} catch { } catch {
storageStatus = "unavailable"; storageStatus = "unavailable";
control.reportStatus("storage_unavailable"); control.reportStatus("storage_unavailable");
clearInterval(keepAlive);
setTimeout(() => process.exit(1), 50);
} }
} }
+1 -1
View File
@@ -281,7 +281,7 @@ export const AdminDiagnosticsResponseSchema = Type.Object({
browser_support: Type.Array(Type.Object({ browser_support: Type.Array(Type.Object({
brand: Type.Union([Type.Literal("Google Chrome"), Type.Literal("Microsoft Edge")]), brand: Type.Union([Type.Literal("Google Chrome"), Type.Literal("Microsoft Edge")]),
major: Type.Integer({ minimum: 1 }), major: Type.Integer({ minimum: 1 }),
}, { additionalProperties: false }), { maxItems: 2 }), }, { additionalProperties: false }), { maxItems: 8 }),
worker_status: Type.Union([Type.Literal("ready"), Type.Literal("degraded"), Type.Literal("unavailable")]), worker_status: Type.Union([Type.Literal("ready"), Type.Literal("degraded"), Type.Literal("unavailable")]),
}, { additionalProperties: false }), }, { additionalProperties: false }),
}, { additionalProperties: false, $id: "AdminDiagnosticsResponse" }); }, { additionalProperties: false, $id: "AdminDiagnosticsResponse" });
+1 -1
View File
@@ -83,7 +83,7 @@ export const ErrorDetailsSchema = Type.Object(
}, },
{ additionalProperties: false }, { additionalProperties: false },
), ),
{ maxItems: 2 }, { maxItems: 8 },
), ),
), ),
capacity_status: Type.Optional( capacity_status: Type.Optional(
+22 -1
View File
@@ -14,7 +14,11 @@ export const DEFERRED_EXTERNAL_TASKS = Object.freeze(["TASK-WP7-03", "TASK-WP7-0
export function buildFinalReleaseRecord({ appVersion, browsers, buildCommit, frozenFromCommit, recordedAt, windows }) { export function buildFinalReleaseRecord({ appVersion, browsers, buildCommit, frozenFromCommit, recordedAt, windows }) {
const record = { const record = {
appVersion, appVersion,
browsers: browsers.map(({ brand, fullVersion }) => ({ brand, fullVersion })), browsers: browsers.map(({ brand, fullVersion, supportedMajorVersions }) => ({
brand,
fullVersion,
...(supportedMajorVersions ? { supportedMajorVersions: [...supportedMajorVersions] } : {}),
})),
buildCommit: buildCommit.toLowerCase(), buildCommit: buildCommit.toLowerCase(),
deferredExternalTasks: [...DEFERRED_EXTERNAL_TASKS], deferredExternalTasks: [...DEFERRED_EXTERNAL_TASKS],
finalRelease: true, finalRelease: true,
@@ -44,8 +48,25 @@ export function validateFinalReleaseRecord(record) {
} else { } else {
const brands = record.browsers.map(({ brand }) => brand).sort(); const brands = record.browsers.map(({ brand }) => brand).sort();
if (brands.join("|") !== "Google Chrome|Microsoft Edge") errors.push("browserBrands"); if (brands.join("|") !== "Google Chrome|Microsoft Edge") errors.push("browserBrands");
const supportedMajorCount = record.browsers.reduce(
(count, browser) => count + (Array.isArray(browser.supportedMajorVersions)
? browser.supportedMajorVersions.length
: 1),
0,
);
if (supportedMajorCount > 8) errors.push("supportedMajorVersions.total");
for (const browser of record.browsers) { for (const browser of record.browsers) {
if (!VERSION.test(browser.fullVersion ?? "")) errors.push(`${browser.brand}.fullVersion`); if (!VERSION.test(browser.fullVersion ?? "")) errors.push(`${browser.brand}.fullVersion`);
if (browser.supportedMajorVersions !== undefined) {
const values = browser.supportedMajorVersions;
const baselineMajor = Number.parseInt(browser.fullVersion.split(".")[0] ?? "0", 10);
if (!Array.isArray(values) || values.length === 0 || values.length > 8
|| values.some((value) => !Number.isSafeInteger(value) || value < 1)
|| new Set(values).size !== values.length
|| !values.includes(baselineMajor)) {
errors.push(`${browser.brand}.supportedMajorVersions`);
}
}
if ("path" in browser || "executablePath" in browser || "executableSha256" in browser) errors.push(`${browser.brand}.privateMetadata`); if ("path" in browser || "executablePath" in browser || "executableSha256" in browser) errors.push(`${browser.brand}.privateMetadata`);
} }
} }
+4 -1
View File
@@ -30,7 +30,10 @@ internal static class Program
}; };
var state = await runtime.StartAsync(); var state = await runtime.StartAsync();
if (!form.IsDisposed) form.SetState(state); if (!form.IsDisposed) form.SetState(state);
if (state == SupervisorState.Ready) SupervisorForm.OpenProductInSupportedBrowser(); if (state == SupervisorState.Ready && !SupervisorForm.OpenProductInSupportedBrowser())
{
form.SetBrowserLaunchFailure();
}
} }
form.Shown += async (_, _) => await StartRuntimeAsync(); form.Shown += async (_, _) => await StartRuntimeAsync();
form.RestartRequested += async () => await StartRuntimeAsync(); form.RestartRequested += async () => await StartRuntimeAsync();
+9 -4
View File
@@ -26,6 +26,7 @@ internal sealed class SupervisorForm : Form
Font = new Font("Segoe UI", 9F); Font = new Font("Segoe UI", 9F);
FormBorderStyle = FormBorderStyle.FixedDialog; FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false; MaximizeBox = false;
ShowInTaskbar = true;
StartPosition = FormStartPosition.CenterScreen; StartPosition = FormStartPosition.CenterScreen;
Text = "Dada"; Text = "Dada";
@@ -90,10 +91,6 @@ internal sealed class SupervisorForm : Form
trayIcon.DoubleClick += (_, _) => RestoreWindow(); trayIcon.DoubleClick += (_, _) => RestoreWindow();
FormClosing += (_, _) => trayIcon.Visible = false; FormClosing += (_, _) => trayIcon.Visible = false;
Resize += (_, _) =>
{
if (WindowState == FormWindowState.Minimized) Hide();
};
SetState(initialState); SetState(initialState);
} }
@@ -139,6 +136,14 @@ internal sealed class SupervisorForm : Form
Activate(); Activate();
} }
internal void SetBrowserLaunchFailure()
{
if (state == SupervisorState.Ready)
{
statusDetail.Text = "本机服务运行正常,但未能自动打开浏览器;请点击“打开 Dada”或选择浏览器。";
}
}
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
if (disposing) trayIcon.Dispose(); if (disposing) trayIcon.Dispose();
@@ -1,3 +1,4 @@
using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using Microsoft.Win32; using Microsoft.Win32;
@@ -13,10 +14,20 @@ internal static class SupportedBrowserLauncher
{ {
var executable = FindExecutable(executableName); var executable = FindExecutable(executableName);
if (executable is null) return false; if (executable is null) return false;
var startInfo = new ProcessStartInfo(executable) { UseShellExecute = false }; try
startInfo.ArgumentList.Add(uri.AbsoluteUri); {
Process.Start(startInfo); var startInfo = new ProcessStartInfo(executable) { UseShellExecute = false };
return true; startInfo.ArgumentList.Add(uri.AbsoluteUri);
return Process.Start(startInfo) is not null;
}
catch (Win32Exception)
{
return false;
}
catch (InvalidOperationException)
{
return false;
}
} }
private static string? FindExecutable(string executableName) private static string? FindExecutable(string executableName)
+30
View File
@@ -13,6 +13,14 @@ const supportedEdge = browserSupportFixture({
brand: "Microsoft Edge", brand: "Microsoft Edge",
fullVersion: "150.0.4078.99", fullVersion: "150.0.4078.99",
}); });
const supportedChrome150 = browserSupportFixture({
brand: "Google Chrome",
fullVersion: "150.0.7871.187",
});
const supportedChrome151 = browserSupportFixture({
brand: "Google Chrome",
fullVersion: "151.0.0.0",
});
const rejectedIdentityCases = [ const rejectedIdentityCases = [
{ {
expectedReason: "platform_unsupported", expectedReason: "platform_unsupported",
@@ -145,6 +153,7 @@ describe("TDD-WP0-BRW-001 supported browser contract", () => {
status: "supported", status: "supported",
supported_browsers: [ supported_browsers: [
{ brand: "Google Chrome", major: 150 }, { brand: "Google Chrome", major: 150 },
{ brand: "Google Chrome", major: 151 },
{ brand: "Microsoft Edge", major: 150 }, { brand: "Microsoft Edge", major: 150 },
], ],
}); });
@@ -188,6 +197,26 @@ describe("TDD-WP0-BRW-001 supported browser contract", () => {
expect(staleCookie.statusCode).toBe(426); expect(staleCookie.statusCode).toBe(426);
await restarted.close(); await restarted.close();
}); });
it.each([
{ expectedMajor: 150, fixture: supportedChrome150 },
{ expectedMajor: 151, fixture: supportedChrome151 },
])("accepts explicitly declared Chrome $expectedMajor", async ({ expectedMajor, fixture }) => {
const app = await createApp({ browserSupportRelease: testBrowserSupportRelease } as never);
const checked = await app.inject({
headers: fixture.headers,
method: "POST",
payload: fixture.body,
url: "/api/v1/support/check",
});
expect(checked.statusCode).toBe(200);
expect(checked.json()).toMatchObject({
browser: { brand: "Google Chrome", major: expectedMajor },
status: "supported",
});
await app.close();
});
}); });
describe("TDD-WP0-BRW-002 hard block", () => { describe("TDD-WP0-BRW-002 hard block", () => {
@@ -208,6 +237,7 @@ describe("TDD-WP0-BRW-002 hard block", () => {
reason: expectedReason, reason: expectedReason,
supported_browsers: [ supported_browsers: [
{ brand: "Google Chrome", major: 150 }, { brand: "Google Chrome", major: 150 },
{ brand: "Google Chrome", major: 151 },
{ brand: "Microsoft Edge", major: 150 }, { brand: "Microsoft Edge", major: 150 },
], ],
}, },
+1 -1
View File
@@ -1,7 +1,7 @@
export const testBrowserSupportRelease = { export const testBrowserSupportRelease = {
appVersion: "1.2.3-test", appVersion: "1.2.3-test",
browsers: [ browsers: [
{ brand: "Google Chrome", fullVersion: "150.0.7339.1" }, { brand: "Google Chrome", fullVersion: "150.0.7339.1", supportedMajorVersions: [150, 151] },
{ brand: "Microsoft Edge", fullVersion: "150.0.4078.99" }, { brand: "Microsoft Edge", fullVersion: "150.0.4078.99" },
], ],
} as const; } as const;
+27 -1
View File
@@ -10,7 +10,7 @@ function release() {
return buildFinalReleaseRecord({ return buildFinalReleaseRecord({
appVersion: "0.0.0", appVersion: "0.0.0",
browsers: [ browsers: [
{ brand: "Google Chrome", fullVersion: "150.0.7871.187" }, { brand: "Google Chrome", fullVersion: "150.0.7871.187", supportedMajorVersions: [150, 151] },
{ brand: "Microsoft Edge", fullVersion: "151.0.4129.59" }, { brand: "Microsoft Edge", fullVersion: "151.0.4129.59" },
], ],
buildCommit: "a".repeat(40), buildCommit: "a".repeat(40),
@@ -26,6 +26,32 @@ test("TDD-WP7-REL-001 creates a browser-gate compatible first-version record", (
assert.equal(record.fixedPort, 43121); assert.equal(record.fixedPort, 43121);
assert.deepEqual(record.deferredExternalTasks, ["TASK-WP7-03", "TASK-WP7-04"]); assert.deepEqual(record.deferredExternalTasks, ["TASK-WP7-03", "TASK-WP7-04"]);
assert.deepEqual(record.browsers.map(({ brand }) => brand).sort(), ["Google Chrome", "Microsoft Edge"]); assert.deepEqual(record.browsers.map(({ brand }) => brand).sort(), ["Google Chrome", "Microsoft Edge"]);
assert.deepEqual(record.browsers[0].supportedMajorVersions, [150, 151]);
});
test("TDD-WP7-REL-001 rejects unsafe or duplicate browser major lists", () => {
assert.throws(() => buildFinalReleaseRecord({
appVersion: "0.0.0",
browsers: [
{ brand: "Google Chrome", fullVersion: "150.0.7871.187", supportedMajorVersions: [150, 150] },
{ brand: "Microsoft Edge", fullVersion: "151.0.4129.59" },
],
buildCommit: "a".repeat(40),
frozenFromCommit: "b".repeat(40),
recordedAt: "2026-08-04T06:00:00.000Z",
windows: { arch: "x64", build: "26200.8875", displayVersion: "25H2" },
}), /Google Chrome\.supportedMajorVersions/);
assert.throws(() => buildFinalReleaseRecord({
appVersion: "0.0.0",
browsers: [
{ brand: "Google Chrome", fullVersion: "150.0.7871.187", supportedMajorVersions: [150, 151, 152, 153, 154, 155, 156, 157] },
{ brand: "Microsoft Edge", fullVersion: "151.0.4129.59" },
],
buildCommit: "a".repeat(40),
frozenFromCommit: "b".repeat(40),
recordedAt: "2026-08-04T06:00:00.000Z",
windows: { arch: "x64", build: "26200.8875", displayVersion: "25H2" },
}), /supportedMajorVersions\.total/);
}); });
test("TDD-WP7-SEC-001 rejects credential shapes and absolute user paths", () => { test("TDD-WP7-SEC-001 rejects credential shapes and absolute user paths", () => {