diff --git a/app/layout.tsx b/app/layout.tsx
index 5f9006f..89eaafe 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -36,6 +36,7 @@ export default function RootLayout({ children }: LayoutProps<"/">) {
diff --git a/components/public/google-adsense.tsx b/components/public/google-adsense.tsx
index d78b88c..6bbad9c 100644
--- a/components/public/google-adsense.tsx
+++ b/components/public/google-adsense.tsx
@@ -1,5 +1,3 @@
-import Script from "next/script";
-
const clientId = process.env.NEXT_PUBLIC_GOOGLE_ADSENSE_CLIENT_ID?.trim();
export function GoogleAdsense() {
@@ -8,10 +6,9 @@ export function GoogleAdsense() {
}
return (
-
);
diff --git a/lib/catalog/sync.ts b/lib/catalog/sync.ts
index 5dfd080..003d109 100644
--- a/lib/catalog/sync.ts
+++ b/lib/catalog/sync.ts
@@ -770,7 +770,12 @@ async function executeCatalogSyncJob(initial: CatalogSyncJob, actor: AuditActor)
}
export async function getCurrentCatalogVersion(): Promise {
- return (await getCurrentCatalogState())?.version ?? null;
+ try {
+ return (await getCurrentCatalogState())?.version ?? null;
+ } catch (error) {
+ console.error("Unable to read the current catalog version", error);
+ return null;
+ }
}
async function getCurrentCatalogState() {
diff --git a/lib/guides/queries.ts b/lib/guides/queries.ts
index 4c0c6f0..81ceaaf 100644
--- a/lib/guides/queries.ts
+++ b/lib/guides/queries.ts
@@ -121,10 +121,15 @@ export async function getActiveCatalog() {
if (!characters.length) return null;
const groupedAliases = aliasesByItem(aliases);
const materialByKey = new Map(materials.map((item) => [item.key, item]));
- const storage = await getMediaStorage();
- const { version } = catalogVersionSchema.parse(
- await storage.file("catalog/current.json").json(),
- );
+ let version = "unknown";
+ try {
+ const storage = await getMediaStorage();
+ version = catalogVersionSchema.parse(
+ await storage.file("catalog/current.json").json(),
+ ).version;
+ } catch (error) {
+ console.error("Unable to read the active catalog version", error);
+ }
return {
version,
characters: characters.map((item) => ({ ...catalogCharacterWithUrls(item, materialByKey), aliases: groupedAliases.character.get(item.key) ?? [] })),
diff --git a/lib/security/headers.ts b/lib/security/headers.ts
index f9534a6..7f170ff 100644
--- a/lib/security/headers.ts
+++ b/lib/security/headers.ts
@@ -1,12 +1,14 @@
export function securityHeaders(production: boolean) {
const ads = "https://*.googlesyndication.com https://*.doubleclick.net https://*.googleadservices.com https://*.googletagservices.com https://www.google.com";
+ const cloudflareAnalytics = "https://static.cloudflareinsights.com";
+ const adConnections = "https://*.adtrafficquality.google";
const policy = [
"default-src 'self'",
- `script-src 'self' 'unsafe-inline' ${production ? "" : "'unsafe-eval'"} https://challenges.cloudflare.com ${ads}`,
+ `script-src 'self' 'unsafe-inline' ${production ? "" : "'unsafe-eval'"} https://challenges.cloudflare.com ${cloudflareAnalytics} ${ads}`,
"style-src 'self' 'unsafe-inline'",
`img-src 'self' data: blob: https://buzz-cdn.astrxl.dev ${ads}`,
"font-src 'self' data:",
- `connect-src 'self' https://buzz-cdn.astrxl.dev https://challenges.cloudflare.com ${ads}${production ? "" : " ws: wss:"}`,
+ `connect-src 'self' https://buzz-cdn.astrxl.dev https://challenges.cloudflare.com ${adConnections} ${ads}${production ? "" : " ws: wss:"}`,
`frame-src https://challenges.cloudflare.com https://www.youtube-nocookie.com ${ads}`,
"media-src 'self' blob:",
"worker-src 'self' blob:",
diff --git a/tests/public-performance.test.ts b/tests/public-performance.test.ts
index 7b6e3d5..e04a611 100644
--- a/tests/public-performance.test.ts
+++ b/tests/public-performance.test.ts
@@ -72,6 +72,8 @@ describe("public rendering performance", () => {
expect(adsense).not.toContain('from "@/lib/auth/client"');
expect(adsense).toContain('process.env.NODE_ENV !== "production"');
expect(adsense).toContain("pagead2.googlesyndication.com");
+ expect(adsense).toContain("");
expect(guide).toContain("");
expect(guideSection).toContain("");
@@ -79,4 +81,17 @@ describe("public rendering performance", () => {
"google.com, pub-9687404323559597, DIRECT, f08c47fec0942fa0",
);
});
+
+ it("allows the production analytics and advertising endpoints", async () => {
+ const headers = await repositoryFile("lib/security/headers.ts");
+
+ expect(headers).toContain("https://static.cloudflareinsights.com");
+ expect(headers).toContain("https://*.adtrafficquality.google");
+ });
+
+ it("accepts Next.js deployment metadata on the document root", async () => {
+ const layout = await repositoryFile("app/layout.tsx");
+
+ expect(layout).toContain("suppressHydrationWarning");
+ });
});