diff --git a/deploy/nginx.conf b/deploy/nginx.conf
index 6da594e..9978615 100644
--- a/deploy/nginx.conf
+++ b/deploy/nginx.conf
@@ -33,12 +33,16 @@ map $host $parenthese_app_csp {
# script-src 'sha256-...' script en ligne du
(animation du logo, dans index.html et en/index.html).
# S'il change, recalculer l'empreinte : base64(sha256(contenu exact entre
# )), sinon l'animation ne se joue plus (sans autre effet).
+# 2e 'sha256-...' règles de préchargement des pages (
+
Parenthèse : l'arbre généalogique vivant de votre famille
@@ -72,6 +72,10 @@
]
}
+
+
diff --git a/landing/public/aide/index.html b/landing/public/aide/index.html
index 6d04e81..416b631 100644
--- a/landing/public/aide/index.html
+++ b/landing/public/aide/index.html
@@ -51,6 +51,10 @@
]
}
+
+
diff --git a/landing/public/application-arbre-genealogique/index.html b/landing/public/application-arbre-genealogique/index.html
index 9d0ec80..3c84127 100644
--- a/landing/public/application-arbre-genealogique/index.html
+++ b/landing/public/application-arbre-genealogique/index.html
@@ -49,6 +49,10 @@
]
}
+
+
diff --git a/landing/public/arbre-genealogique-a-remplir/index.html b/landing/public/arbre-genealogique-a-remplir/index.html
index 68f077f..ecfeb97 100644
--- a/landing/public/arbre-genealogique-a-remplir/index.html
+++ b/landing/public/arbre-genealogique-a-remplir/index.html
@@ -63,6 +63,10 @@
]
}
+
+
diff --git a/landing/public/arbre-genealogique-avec-photos/index.html b/landing/public/arbre-genealogique-avec-photos/index.html
index deece9a..3fdb9c0 100644
--- a/landing/public/arbre-genealogique-avec-photos/index.html
+++ b/landing/public/arbre-genealogique-avec-photos/index.html
@@ -48,6 +48,10 @@
]
}
+
+
diff --git a/landing/public/comment-faire-un-arbre-genealogique/index.html b/landing/public/comment-faire-un-arbre-genealogique/index.html
index 31e6472..6ae11a6 100644
--- a/landing/public/comment-faire-un-arbre-genealogique/index.html
+++ b/landing/public/comment-faire-un-arbre-genealogique/index.html
@@ -65,6 +65,10 @@
]
}
+
+
diff --git a/landing/public/cousinade/index.html b/landing/public/cousinade/index.html
index 146e4ef..bd8e652 100644
--- a/landing/public/cousinade/index.html
+++ b/landing/public/cousinade/index.html
@@ -54,6 +54,10 @@
]
}
+
+
diff --git a/landing/public/donnees-et-vie-privee/index.html b/landing/public/donnees-et-vie-privee/index.html
index e4b0ae9..cd33d07 100644
--- a/landing/public/donnees-et-vie-privee/index.html
+++ b/landing/public/donnees-et-vie-privee/index.html
@@ -35,6 +35,10 @@
"publisher": { "@type": "Person", "name": "Ilies Allali", "email": "pro.allali.ilies@gmail.com" }
}
+
+
diff --git a/landing/public/en/family-reunion/index.html b/landing/public/en/family-reunion/index.html
index 8a3e418..be55ccf 100644
--- a/landing/public/en/family-reunion/index.html
+++ b/landing/public/en/family-reunion/index.html
@@ -54,6 +54,10 @@
]
}
+
+
diff --git a/landing/public/en/family-tree-app/index.html b/landing/public/en/family-tree-app/index.html
index e8c8851..cf98c6c 100644
--- a/landing/public/en/family-tree-app/index.html
+++ b/landing/public/en/family-tree-app/index.html
@@ -49,6 +49,10 @@
]
}
+
+
diff --git a/landing/public/en/family-tree-with-photos/index.html b/landing/public/en/family-tree-with-photos/index.html
index 728ddf6..d99ee5a 100644
--- a/landing/public/en/family-tree-with-photos/index.html
+++ b/landing/public/en/family-tree-with-photos/index.html
@@ -48,6 +48,10 @@
]
}
+
+
diff --git a/landing/public/en/help/index.html b/landing/public/en/help/index.html
index ff5c217..ce53f28 100644
--- a/landing/public/en/help/index.html
+++ b/landing/public/en/help/index.html
@@ -51,6 +51,10 @@
]
}
+
+
diff --git a/landing/public/en/how-to-make-a-family-tree/index.html b/landing/public/en/how-to-make-a-family-tree/index.html
index 4d0ad88..8acd568 100644
--- a/landing/public/en/how-to-make-a-family-tree/index.html
+++ b/landing/public/en/how-to-make-a-family-tree/index.html
@@ -65,6 +65,10 @@
]
}
+
+
diff --git a/landing/public/en/printable-family-tree/index.html b/landing/public/en/printable-family-tree/index.html
index 2b24bb1..65d4dd8 100644
--- a/landing/public/en/printable-family-tree/index.html
+++ b/landing/public/en/printable-family-tree/index.html
@@ -63,6 +63,10 @@
]
}
+
+
diff --git a/landing/public/en/privacy/index.html b/landing/public/en/privacy/index.html
index eedce00..374d31e 100644
--- a/landing/public/en/privacy/index.html
+++ b/landing/public/en/privacy/index.html
@@ -35,6 +35,10 @@
"publisher": { "@type": "Person", "name": "Ilies Allali", "email": "pro.allali.ilies@gmail.com" }
}
+
+
diff --git a/landing/public/en/tell-your-family-story/index.html b/landing/public/en/tell-your-family-story/index.html
index 46cfaee..601e663 100644
--- a/landing/public/en/tell-your-family-story/index.html
+++ b/landing/public/en/tell-your-family-story/index.html
@@ -47,6 +47,10 @@
]
}
+
+
diff --git a/landing/public/raconter-histoire-de-famille/index.html b/landing/public/raconter-histoire-de-famille/index.html
index f10b24c..38f1a79 100644
--- a/landing/public/raconter-histoire-de-famille/index.html
+++ b/landing/public/raconter-histoire-de-famille/index.html
@@ -47,6 +47,10 @@
]
}
+
+
diff --git a/landing/src/main.jsx b/landing/src/main.jsx
index c804f71..ade7f07 100644
--- a/landing/src/main.jsx
+++ b/landing/src/main.jsx
@@ -5,10 +5,15 @@ import App from './App.jsx'
import { analytics } from './analytics.js'
import { localeFromPath } from './i18n.js'
-// PostHog après le premier rendu : les événements émis avant sont mis en file et envoyés à l'init
+// PostHog après le premier rendu : les événements émis avant sont mis en file et envoyés à l'init.
+// Page préchargée (survol d'un lien, speculationrules) : rien ne part avant qu'on l'ouvre vraiment.
const deferInit = () => analytics.init()
-if ('requestIdleCallback' in window) window.requestIdleCallback(deferInit, { timeout: 3000 })
-else window.setTimeout(deferInit, 1500)
+const scheduleInit = () => {
+ if ('requestIdleCallback' in window) window.requestIdleCallback(deferInit, { timeout: 3000 })
+ else window.setTimeout(deferInit, 1500)
+}
+if (document.prerendering) document.addEventListener('prerenderingchange', scheduleInit, { once: true })
+else scheduleInit()
const container = document.getElementById('root')
const app = (
diff --git a/landing/test/csp-empreintes.test.mjs b/landing/test/csp-empreintes.test.mjs
new file mode 100644
index 0000000..00f1ff4
--- /dev/null
+++ b/landing/test/csp-empreintes.test.mjs
@@ -0,0 +1,39 @@
+// Les scripts en ligne de la landing (animation du logo, règles de préchargement des pages) ne passent la CSP de
+// nginx que par leur empreinte exacte (deploy/nginx.conf). Un script modifié sans recalculer son empreinte ne
+// casse rien de visible : l'animation ou le préchargement s'arrêtent sans bruit. Ce test le dit tout de suite.
+import { test } from 'node:test'
+import assert from 'node:assert/strict'
+import { createHash } from 'node:crypto'
+import { readFileSync, readdirSync, statSync } from 'node:fs'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+
+const landing = fileURLToPath(new URL('..', import.meta.url))
+const csp = readFileSync(path.join(landing, '../deploy/nginx.conf'), 'utf8').match(/map \$host \$parenthese_landing_csp \{\s*default "([^"]+)"/)[1]
+const autorisees = new Set([...csp.matchAll(/'sha256-([^']+)'/g)].map((m) => m[1]))
+const empreinte = (texte) => createHash('sha256').update(texte, 'utf8').digest('base64')
+
+function pages(dossier) {
+ return readdirSync(dossier).flatMap((nom) => {
+ const chemin = path.join(dossier, nom)
+ if (statSync(chemin).isDirectory()) return pages(chemin)
+ return nom === 'index.html' ? [chemin] : []
+ })
+}
+const toutes = [path.join(landing, 'index.html'), ...pages(path.join(landing, 'public'))]
+
+test('chaque script en ligne des pages a son empreinte dans la CSP de la landing', () => {
+ for (const fichier of toutes) {
+ const html = readFileSync(fichier, 'utf8')
+ for (const [, attributs, contenu] of html.matchAll(/