Skip to content

Commit 80d8326

Browse files
Add hash instead of unsafe-inline for naive-ui (#33)
* Add hash instead of unsafe-inline for naive-ui * Migrate GA to node 20 * Check errors in browser's console
1 parent f587459 commit 80d8326

7 files changed

+116
-20
lines changed

.github/workflows/buildAndDeploy.yml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,11 @@ jobs:
1313
group: build-and-deploy
1414
cancel-in-progress: true
1515
runs-on: ubuntu-24.04
16-
env:
17-
# https://github.blog/changelog/2024-03-07-github-actions-all-actions-will-run-on-node20-instead-of-node16-by-default/
18-
FORCE_JAVASCRIPT_ACTIONS_TO_NODE20: true
1916
steps:
20-
- uses: actions/checkout@v3
17+
- uses: actions/checkout@v4
2118

2219
- name: Setup Node
23-
uses: actions/setup-node@v3
20+
uses: actions/setup-node@v4
2421
with:
2522
node-version: 20
2623

@@ -49,7 +46,7 @@ jobs:
4946
run: PLAYWRIGHT_USE_BUILD=1 npm run test:e2e
5047

5148
- name: Deploy
52-
uses: peaceiris/actions-gh-pages@v3
49+
uses: peaceiris/actions-gh-pages@v4
5350
if: ${{ github.ref == 'refs/heads/master' }}
5451
with:
5552
github_token: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/checkPullRequests.yml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,11 @@ jobs:
1111
group: ${{ github.head_ref }}
1212
cancel-in-progress: true
1313
runs-on: ubuntu-24.04
14-
env:
15-
# https://github.blog/changelog/2024-03-07-github-actions-all-actions-will-run-on-node20-instead-of-node16-by-default/
16-
FORCE_JAVASCRIPT_ACTIONS_TO_NODE20: true
1714
steps:
18-
- uses: actions/checkout@v3
15+
- uses: actions/checkout@v4
1916

2017
- name: Setup Node
21-
uses: actions/setup-node@v3
18+
uses: actions/setup-node@v4
2219
with:
2320
node-version: 20
2421

.github/workflows/makeArtifactWithTestScreenshots.yml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,11 @@ jobs:
66
make_artifact_with_test_screenshots:
77
name: Make artifact with Test Screenshots
88
runs-on: ubuntu-24.04
9-
env:
10-
# https://github.blog/changelog/2024-03-07-github-actions-all-actions-will-run-on-node20-instead-of-node16-by-default/
11-
FORCE_JAVASCRIPT_ACTIONS_TO_NODE20: true
129
steps:
13-
- uses: actions/checkout@v3
10+
- uses: actions/checkout@v4
1411

1512
- name: Setup Node
16-
uses: actions/setup-node@v3
13+
uses: actions/setup-node@v4
1714
with:
1815
node-version: 20
1916

e2e/vue.spec.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,53 @@
11
import { expect, test } from "@playwright/test";
22

3-
// See here how to get started:
4-
// https://playwright.dev/docs/intro
3+
// Documentation: https://playwright.dev/docs/intro
4+
5+
let errorMessagesCount = 0;
6+
7+
const ignoreErrors = [
8+
"ResizeObserver loop completed with undelivered notifications.",
9+
];
10+
11+
// Register a global error listener
12+
test.beforeEach(async ({ page }) => {
13+
errorMessagesCount = 0;
14+
15+
page.on("pageerror", (error) => {
16+
if (ignoreErrors.includes(error.message)) {
17+
return;
18+
}
19+
console.log(">> Console error: ", error);
20+
++errorMessagesCount;
21+
});
22+
});
23+
24+
test.afterEach(() => {
25+
expect(errorMessagesCount).toBe(0);
26+
});
27+
528
test("visits the app root url, sitemap.txt and robots.txt", async ({
629
page,
30+
browserName,
731
}) => {
832
await page.goto("/");
33+
await page.waitForTimeout(2000);
934
await expect(page.locator("h1")).toHaveText("Get Crypto Address");
1035

36+
// Next tests are chromium only
37+
if (browserName !== "chromium") {
38+
return;
39+
}
40+
1141
if (process.env.PLAYWRIGHT_USE_BUILD) {
1242
await page.goto("/sitemap.txt");
43+
await page.waitForTimeout(500);
1344
expect(await page.locator("pre").innerText()).toMatchSnapshot(
1445
"sitemap.txt",
1546
);
1647
}
1748

1849
await page.goto("/robots.txt");
50+
await page.waitForTimeout(500);
1951
expect(await page.locator("pre").innerText()).toMatchSnapshot("robots.txt");
2052
});
2153

@@ -52,6 +84,7 @@ test("General flow", async ({ page, context, browserName }) => {
5284

5385
// Generate new addresses
5486
await page.getByRole("button", { name: "Generate new addresses" }).click();
87+
await page.waitForTimeout(500);
5588

5689
// Check the count of generated addresses
5790
const $addresses = page.locator('[data-test-el="key-address-item"]');
@@ -88,6 +121,7 @@ test("General flow", async ({ page, context, browserName }) => {
88121
await $openModalButton.click();
89122
const $modal = page.getByRole("dialog");
90123
await $modal.waitFor({ state: "visible", timeout: 1000 });
124+
await page.waitForTimeout(100);
91125
const $modalSecret = $modal.locator(
92126
'[data-test-id="dialog-qr-code-secret"] .n-thing-main__description',
93127
);
@@ -99,6 +133,7 @@ test("General flow", async ({ page, context, browserName }) => {
99133
const $modalMask = page.locator(".n-modal-mask");
100134
await page.mouse.click(1, 1);
101135
await $modalMask.waitFor({ state: "detached", timeout: 1000 });
136+
await page.waitForTimeout(100);
102137
}
103138

104139
/// Paper wallet page
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Add hashes of inline styles to the CSP policy in the HTML.
3+
*
4+
* @description Naive-ui uses inline styles to style components.
5+
*
6+
* [tag-nonce]
7+
* @param {string} html
8+
* @param {string[]} listOfHashes
9+
* @returns {*}
10+
*/
11+
export function addInlineStylesHashesToHtml(html, listOfHashes) {
12+
const hashes = listOfHashes.map((hash) => `'${hash}'`).join(" ");
13+
14+
return html.replace(
15+
"style-src 'self'",
16+
`style-src 'self' 'unsafe-hashes' ${hashes}`,
17+
);
18+
}

node/csp/getInlineStylesHashes.mjs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import crypto from "crypto";
2+
3+
/**
4+
* Calculates SHA-256 hash of the given style content and returns it in base64 format.
5+
*
6+
* ```
7+
* # Input
8+
* max-width:250px;text-align:left;margin:0 auto;width:100%;
9+
* # output
10+
* sha256-O5IIiIzIB9wS0DmNOhwTAp7C6vPXN8QJ3R0ZS+HTUNM=
11+
* ```
12+
*
13+
* @param {string} styleContent
14+
* @returns {string}
15+
*/
16+
function calculateStyleHash(styleContent) {
17+
const hash = crypto
18+
.createHash("sha256")
19+
.update(styleContent, "utf8")
20+
.digest("base64");
21+
return `sha256-${hash}`;
22+
}
23+
24+
/**
25+
* Extracts inline styles from the given HTML content.
26+
*
27+
* @param {string} appHtml - The HTML content to extract inline styles from.
28+
* @returns {string[]} An array of inline style strings.
29+
*/
30+
function getInlineStyles(appHtml) {
31+
return (
32+
appHtml
33+
.match(/ style=".*?"/g)
34+
?.map((line) => line.replace(/^ style="/, "").replace(/"$/, "")) || []
35+
);
36+
}
37+
38+
/**
39+
* Generates an array of unique SHA-256 hashes for all inline styles found in the given HTML content.
40+
*
41+
* @param {string} appHtml - The HTML content to extract and hash inline styles from.
42+
* @returns {string[]} An array of unique SHA-256 hashes in base64 format.
43+
*/
44+
export function getInlineStylesHashes(appHtml) {
45+
return [...new Set(getInlineStyles(appHtml).map(calculateStyleHash))];
46+
}

prerender.mjs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import fs from "node:fs";
22
import path from "node:path";
33
import { createServer } from "vite";
4+
import { addInlineStylesHashesToHtml } from "./node/csp/addInlineStylesHashesToHtml.mjs";
5+
import { getInlineStylesHashes } from "./node/csp/getInlineStylesHashes.mjs";
46
import generateSitemap from "./node/sitemap/generateSitemap.mjs";
57

68
// todo refactor file, separate into functions
@@ -21,7 +23,12 @@ generateSitemap(routerPaths, "https://getcryptoaddress.github.io", "dist");
2123
for (const routerPath of routerPaths) {
2224
const { appHtml, ctx } = await render(routerPath);
2325

24-
const pageHtml = template
26+
let pageHtml = template;
27+
28+
const styleHashes = getInlineStylesHashes(appHtml);
29+
pageHtml = addInlineStylesHashesToHtml(pageHtml, styleHashes);
30+
31+
pageHtml = pageHtml
2532
.replace("<!--app-head-->", ctx?.teleports?.head || "")
2633
.replace("<!--app-html-->", appHtml)
2734
.replace(/<!--.*?-->/g, "")
@@ -32,7 +39,6 @@ for (const routerPath of routerPaths) {
3239
recursive: true,
3340
});
3441
fs.writeFileSync(path.join(pageFolder, "index.html"), pageHtml);
35-
console.log("Generated:", path.join(pageFolder, "index.html"));
3642
await new Promise((resolve) => setTimeout(resolve, 300));
3743
}
3844
await vite.close();

0 commit comments

Comments
 (0)