From 3097f6b91ee94749aa1e2772fe3e761c09d2ae0e Mon Sep 17 00:00:00 2001 From: baixiangcpp Date: Sat, 27 Jun 2026 02:27:13 +0800 Subject: [PATCH] Sync external request privacy disclosures --- src/app/[lang]/privacy/page.tsx | 34 +++++++++++++++---- src/app/[lang]/trust-center/page.tsx | 26 ++++++-------- src/core/registry/index.ts | 2 +- src/core/registry/privacy.ts | 23 +++++++++++++ src/lib/sitemap-lastmod.json | 28 +++++++-------- tests/component/privacy-page.test.tsx | 8 +++++ tests/component/trust-center-page.test.tsx | 7 ++-- .../tool-network-access-metadata.test.ts | 8 +++++ 8 files changed, 97 insertions(+), 39 deletions(-) diff --git a/src/app/[lang]/privacy/page.tsx b/src/app/[lang]/privacy/page.tsx index 4ff2c141..801f4034 100644 --- a/src/app/[lang]/privacy/page.tsx +++ b/src/app/[lang]/privacy/page.tsx @@ -3,6 +3,7 @@ import Link from "next/link" import { useLang } from "@/core/i18n/lang-provider" import { TOOL_REGISTRY } from "@/core/registry" +import { getExternalRequestToolDisclosures } from "@/core/registry/privacy" import { LocalDataControls } from "@/features/privacy/local-data-controls" export default function PrivacyPage() { @@ -17,7 +18,14 @@ export default function PrivacyPage() { { title: p.privacy_contact_title, desc: p.privacy_contact_desc }, ] const toolTranslations = t.tools as Record - const externalRequestTools = TOOL_REGISTRY.filter((tool) => tool.privacy.externalRequest.required) + const externalRequestTools = getExternalRequestToolDisclosures(TOOL_REGISTRY) + .map((item) => ({ + ...item, + title: toolTranslations[item.tool.key]?.title ?? item.tool.slug, + purpose: t.common.external_network_notice.purposes?.[item.purposeKey as keyof typeof t.common.external_network_notice.purposes], + dataSentLabel: t.common.external_network_notice.external_data?.[item.dataSent as keyof typeof t.common.external_network_notice.external_data], + })) + .sort((a, b) => a.title.localeCompare(b.title, lang)) return (
@@ -43,19 +51,33 @@ export default function PrivacyPage() {

{p.privacy_external_request_tools_title}

{p.privacy_external_request_tools_desc}

- {externalRequestTools.map((tool) => ( + {externalRequestTools.map(({ tool, title, hosts, purpose, dataSentLabel, disclosure }) => (
-

{toolTranslations[tool.key]?.title ?? tool.slug}

+

{title}

{t.common.capability_external_request}

- {tool.privacy.externalRequest.disclosure} + {disclosure}

-

- {(tool.privacy.externalRequest.domains ?? []).join(", ")} +

+
+
{t.common.external_network_notice.hosts_label}
+
{hosts.join(", ")}
+
+
+
{t.common.external_network_notice.purpose_label}
+
{purpose}
+
+
+
{t.common.external_network_notice.data_sent_label}
+
{dataSentLabel}
+
+
+

+ {t.common.external_network_notice.consent_required_message}

))} diff --git a/src/app/[lang]/trust-center/page.tsx b/src/app/[lang]/trust-center/page.tsx index 6247d974..b510c38b 100644 --- a/src/app/[lang]/trust-center/page.tsx +++ b/src/app/[lang]/trust-center/page.tsx @@ -4,6 +4,7 @@ import { CheckCircle2, ExternalLink, FileText, Github, LockKeyhole, Network, Shi import { isValidLocale, requireTranslationValue } from "@/core/i18n/i18n" import { getTranslation } from "@/core/i18n/translations/catalog" import { TOOL_REGISTRY } from "@/core/registry" +import { getExternalRequestToolDisclosures } from "@/core/registry/privacy" import { JsonLdScript } from "@/core/seo/components/json-ld-script" import { SITE_URL, buildCanonicalUrl } from "@/core/seo/urls" @@ -50,17 +51,12 @@ export default async function TrustCenterPage({ const p = t.pages const common = t.common const toolCopy = t.tools as Record - const externalTools = TOOL_REGISTRY - .filter((tool) => tool.privacy.externalRequest.required) - .map((tool) => ({ - tool, - title: requireTranslationValue(toolCopy[tool.key]?.title, `tools.${tool.key}.title`), - purpose: tool.privacy.externalRequest.purposeKey - ? common.external_network_notice.purposes?.[tool.privacy.externalRequest.purposeKey as keyof typeof common.external_network_notice.purposes] - : undefined, - dataSent: tool.privacy.externalRequest.userDataSent - ? common.external_network_notice.external_data?.[tool.privacy.externalRequest.userDataSent as keyof typeof common.external_network_notice.external_data] - : undefined, + const externalTools = getExternalRequestToolDisclosures(TOOL_REGISTRY) + .map((item) => ({ + ...item, + title: requireTranslationValue(toolCopy[item.tool.key]?.title, `tools.${item.tool.key}.title`), + purpose: common.external_network_notice.purposes?.[item.purposeKey as keyof typeof common.external_network_notice.purposes], + dataSentLabel: common.external_network_notice.external_data?.[item.dataSent as keyof typeof common.external_network_notice.external_data], })) .sort((a, b) => a.title.localeCompare(b.title, locale)) @@ -279,7 +275,7 @@ export default async function TrustCenterPage({ - {externalTools.map(({ tool, title, purpose, dataSent }) => ( + {externalTools.map(({ tool, title, hosts, purpose, dataSentLabel, disclosure }) => ( @@ -287,10 +283,10 @@ export default async function TrustCenterPage({ - {(tool.privacy.externalRequest.domains ?? []).join(", ")} + {hosts.join(", ")} - {purpose ?? tool.privacy.externalRequest.disclosure} - {dataSent} + {purpose ?? disclosure} + {dataSentLabel} ))} diff --git a/src/core/registry/index.ts b/src/core/registry/index.ts index 45725244..b717768d 100644 --- a/src/core/registry/index.ts +++ b/src/core/registry/index.ts @@ -1,6 +1,6 @@ export { CATEGORIES, type ToolCategory } from "./categories" export { TOOL_MANIFESTS } from "./manifests" -export { getToolPrivacyNetworkMetadata } from "./privacy" +export { getExternalRequestToolDisclosures, getToolPrivacyNetworkMetadata } from "./privacy" export { getRelatedTools } from "./related-tools" export { TOOL_REGISTRY, TOOL_REGISTRY_ORDER, TOOLS_BY_KEY, getToolByKey, getToolBySlug, getToolsByCategory } from "./registry" export { formatToolRegistryStatsTemplate, getToolRegistryStats, type ToolRegistryStats } from "./stats" diff --git a/src/core/registry/privacy.ts b/src/core/registry/privacy.ts index fe3dcee9..184f27cc 100644 --- a/src/core/registry/privacy.ts +++ b/src/core/registry/privacy.ts @@ -30,3 +30,26 @@ export function getToolPrivacyNetworkMetadata(privacy: ToolPrivacyManifest): Too externalDataSent: privacy.externalRequest.userDataSent as ToolExternalDataSent | undefined, } } + +export type ExternalRequestToolDisclosure = { + tool: ToolMeta + hosts: readonly string[] + purposeKey: string + dataSent: ToolExternalDataSent + disclosure: string +} + +export function getExternalRequestToolDisclosures(tools: readonly ToolMeta[]): ExternalRequestToolDisclosure[] { + return tools + .filter((tool) => tool.privacy.externalRequest.required) + .map((tool) => { + const externalRequest = tool.privacy.externalRequest + return { + tool, + hosts: externalRequest.domains ?? [], + purposeKey: externalRequest.purposeKey ?? "", + dataSent: externalRequest.userDataSent ?? "none", + disclosure: externalRequest.disclosure ?? "", + } + }) +} diff --git a/src/lib/sitemap-lastmod.json b/src/lib/sitemap-lastmod.json index 218488ec..920de947 100644 --- a/src/lib/sitemap-lastmod.json +++ b/src/lib/sitemap-lastmod.json @@ -220,22 +220,22 @@ "fr": "2026-06-25T00:00:00.000Z" }, "privacy": { - "en": "2026-06-20T00:00:00.000Z", - "zh-CN": "2026-06-20T00:00:00.000Z", - "zh-TW": "2026-06-20T00:00:00.000Z", - "ja": "2026-06-20T00:00:00.000Z", - "ko": "2026-06-20T00:00:00.000Z", - "de": "2026-06-20T00:00:00.000Z", - "fr": "2026-06-20T00:00:00.000Z" + "en": "2026-06-26T00:00:00.000Z", + "zh-CN": "2026-06-26T00:00:00.000Z", + "zh-TW": "2026-06-26T00:00:00.000Z", + "ja": "2026-06-26T00:00:00.000Z", + "ko": "2026-06-26T00:00:00.000Z", + "de": "2026-06-26T00:00:00.000Z", + "fr": "2026-06-26T00:00:00.000Z" }, "trust-center": { - "en": "2026-06-24T00:00:00.000Z", - "zh-CN": "2026-06-24T00:00:00.000Z", - "zh-TW": "2026-06-24T00:00:00.000Z", - "ja": "2026-06-24T00:00:00.000Z", - "ko": "2026-06-24T00:00:00.000Z", - "de": "2026-06-24T00:00:00.000Z", - "fr": "2026-06-24T00:00:00.000Z" + "en": "2026-06-26T00:00:00.000Z", + "zh-CN": "2026-06-26T00:00:00.000Z", + "zh-TW": "2026-06-26T00:00:00.000Z", + "ja": "2026-06-26T00:00:00.000Z", + "ko": "2026-06-26T00:00:00.000Z", + "de": "2026-06-26T00:00:00.000Z", + "fr": "2026-06-26T00:00:00.000Z" }, "roadmap": { "en": "2026-06-25T00:00:00.000Z", diff --git a/tests/component/privacy-page.test.tsx b/tests/component/privacy-page.test.tsx index 986eb39d..6aad997a 100644 --- a/tests/component/privacy-page.test.tsx +++ b/tests/component/privacy-page.test.tsx @@ -27,5 +27,13 @@ describe("PrivacyPage", () => { expect(screen.getByText("Instagram Photo Downloader")).toBeInTheDocument() expect(screen.getByText("youtube.com, youtube-nocookie.com, youtu.be, i.ytimg.com")).toBeInTheDocument() expect(screen.getByText("instagram.com")).toBeInTheDocument() + expect(screen.getAllByText("Hosts").length).toBeGreaterThan(0) + expect(screen.getAllByText("Purpose").length).toBeGreaterThan(0) + expect(screen.getAllByText("Data sent").length).toBeGreaterThan(0) + expect(screen.getAllByText("Generate and preview public thumbnail image URLs derived from the video link you enter.").length).toBeGreaterThan(0) + expect(screen.getByText("Download media from a URL you provide after you confirm you are allowed to use it.")).toBeInTheDocument() + expect(screen.getAllByText("A derived public asset URL may be requested by your browser.").length).toBeGreaterThan(0) + expect(screen.getByText("The URL you provide may be requested by your browser.")).toBeInTheDocument() + expect(screen.getAllByText("Network access starts only after you choose the external-request action.").length).toBeGreaterThan(0) }) }) diff --git a/tests/component/trust-center-page.test.tsx b/tests/component/trust-center-page.test.tsx index 343faa1c..dad01b68 100644 --- a/tests/component/trust-center-page.test.tsx +++ b/tests/component/trust-center-page.test.tsx @@ -3,6 +3,7 @@ import { describe, expect, it, vi } from "vitest" import TrustCenterPage from "@/app/[lang]/trust-center/page" import { getTranslation } from "@/core/i18n/translations/catalog" import { TOOL_REGISTRY } from "@/core/registry" +import { getExternalRequestToolDisclosures } from "@/core/registry/privacy" vi.mock("next/link", () => ({ default: ({ href, children, ...props }: { href: string; children: React.ReactNode }) => ( @@ -29,14 +30,14 @@ describe("TrustCenterPage", () => { expect(screen.getByRole("rowheader", { name: "Pipeline Builder" })).toBeInTheDocument() expect(screen.getByRole("rowheader", { name: "External-request tools" })).toBeInTheDocument() - const externalTools = TOOL_REGISTRY.filter((tool) => tool.privacy.externalRequest.required) + const externalTools = getExternalRequestToolDisclosures(TOOL_REGISTRY) const toolCopy = getTranslation("en").tools as Record const table = screen.getByRole("table", { name: "External request tools" }) - for (const tool of externalTools) { + for (const { tool, hosts } of externalTools) { const title = toolCopy[tool.key]?.title ?? tool.slug expect(within(table).getByRole("link", { name: title })).toHaveAttribute("href", `/en/${tool.slug}`) - for (const domain of tool.privacy.externalRequest.domains ?? []) { + for (const domain of hosts) { expect(within(table).getByText(new RegExp(domain.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")))).toBeInTheDocument() } } diff --git a/tests/guards/tool-network-access-metadata.test.ts b/tests/guards/tool-network-access-metadata.test.ts index 45fc4825..932b39dc 100644 --- a/tests/guards/tool-network-access-metadata.test.ts +++ b/tests/guards/tool-network-access-metadata.test.ts @@ -198,4 +198,12 @@ describe("tool network access metadata", () => { expect(offenders).toEqual([]) }) + + it("keeps Privacy Policy and Trust Center external request lists on the shared disclosure source", () => { + const trustCenterPage = fs.readFileSync(path.join(ROOT, "src/app/[lang]/trust-center/page.tsx"), "utf8") + const privacyPage = fs.readFileSync(path.join(ROOT, "src/app/[lang]/privacy/page.tsx"), "utf8") + + expect(trustCenterPage).toContain("getExternalRequestToolDisclosures(TOOL_REGISTRY)") + expect(privacyPage).toContain("getExternalRequestToolDisclosures(TOOL_REGISTRY)") + }) })