Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions .github/workflows/buildAndDeploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,11 @@ jobs:
group: build-and-deploy
cancel-in-progress: true
runs-on: ubuntu-24.04
env:
# https://github.blog/changelog/2024-03-07-github-actions-all-actions-will-run-on-node20-instead-of-node16-by-default/
FORCE_JAVASCRIPT_ACTIONS_TO_NODE20: true
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 20

Expand Down Expand Up @@ -49,7 +46,7 @@ jobs:
run: PLAYWRIGHT_USE_BUILD=1 npm run test:e2e

- name: Deploy
uses: peaceiris/actions-gh-pages@v3
uses: peaceiris/actions-gh-pages@v4
if: ${{ github.ref == 'refs/heads/master' }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
Expand Down
7 changes: 2 additions & 5 deletions .github/workflows/checkPullRequests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,11 @@ jobs:
group: ${{ github.head_ref }}
cancel-in-progress: true
runs-on: ubuntu-24.04
env:
# https://github.blog/changelog/2024-03-07-github-actions-all-actions-will-run-on-node20-instead-of-node16-by-default/
FORCE_JAVASCRIPT_ACTIONS_TO_NODE20: true
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 20

Expand Down
7 changes: 2 additions & 5 deletions .github/workflows/makeArtifactWithTestScreenshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,11 @@ jobs:
make_artifact_with_test_screenshots:
name: Make artifact with Test Screenshots
runs-on: ubuntu-24.04
env:
# https://github.blog/changelog/2024-03-07-github-actions-all-actions-will-run-on-node20-instead-of-node16-by-default/
FORCE_JAVASCRIPT_ACTIONS_TO_NODE20: true
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 20

Expand Down
39 changes: 37 additions & 2 deletions e2e/vue.spec.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,53 @@
import { expect, test } from "@playwright/test";

// See here how to get started:
// https://playwright.dev/docs/intro
// Documentation: https://playwright.dev/docs/intro

let errorMessagesCount = 0;

const ignoreErrors = [
"ResizeObserver loop completed with undelivered notifications.",
];

// Register a global error listener
test.beforeEach(async ({ page }) => {
errorMessagesCount = 0;

page.on("pageerror", (error) => {
if (ignoreErrors.includes(error.message)) {
return;
}
console.log(">> Console error: ", error);
++errorMessagesCount;
});
});

test.afterEach(() => {
expect(errorMessagesCount).toBe(0);
});

test("visits the app root url, sitemap.txt and robots.txt", async ({
page,
browserName,
}) => {
await page.goto("/");
await page.waitForTimeout(2000);
await expect(page.locator("h1")).toHaveText("Get Crypto Address");

// Next tests are chromium only
if (browserName !== "chromium") {
return;
}

if (process.env.PLAYWRIGHT_USE_BUILD) {
await page.goto("/sitemap.txt");
await page.waitForTimeout(500);
expect(await page.locator("pre").innerText()).toMatchSnapshot(
"sitemap.txt",
);
}

await page.goto("/robots.txt");
await page.waitForTimeout(500);
expect(await page.locator("pre").innerText()).toMatchSnapshot("robots.txt");
});

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

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

// Check the count of generated addresses
const $addresses = page.locator('[data-test-el="key-address-item"]');
Expand Down Expand Up @@ -88,6 +121,7 @@ test("General flow", async ({ page, context, browserName }) => {
await $openModalButton.click();
const $modal = page.getByRole("dialog");
await $modal.waitFor({ state: "visible", timeout: 1000 });
await page.waitForTimeout(100);
const $modalSecret = $modal.locator(
'[data-test-id="dialog-qr-code-secret"] .n-thing-main__description',
);
Expand All @@ -99,6 +133,7 @@ test("General flow", async ({ page, context, browserName }) => {
const $modalMask = page.locator(".n-modal-mask");
await page.mouse.click(1, 1);
await $modalMask.waitFor({ state: "detached", timeout: 1000 });
await page.waitForTimeout(100);
}

/// Paper wallet page
Expand Down
18 changes: 18 additions & 0 deletions node/csp/addInlineStylesHashesToHtml.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Add hashes of inline styles to the CSP policy in the HTML.
*
* @description Naive-ui uses inline styles to style components.
*
* [tag-nonce]
* @param {string} html
* @param {string[]} listOfHashes
* @returns {*}
*/
export function addInlineStylesHashesToHtml(html, listOfHashes) {
const hashes = listOfHashes.map((hash) => `'${hash}'`).join(" ");

return html.replace(
"style-src 'self'",
`style-src 'self' 'unsafe-hashes' ${hashes}`,
);
}
46 changes: 46 additions & 0 deletions node/csp/getInlineStylesHashes.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import crypto from "crypto";

/**
* Calculates SHA-256 hash of the given style content and returns it in base64 format.
*
* ```
* # Input
* max-width:250px;text-align:left;margin:0 auto;width:100%;
* # output
* sha256-O5IIiIzIB9wS0DmNOhwTAp7C6vPXN8QJ3R0ZS+HTUNM=
* ```
*
* @param {string} styleContent
* @returns {string}
*/
function calculateStyleHash(styleContent) {
const hash = crypto
.createHash("sha256")
.update(styleContent, "utf8")
.digest("base64");
return `sha256-${hash}`;
}

/**
* Extracts inline styles from the given HTML content.
*
* @param {string} appHtml - The HTML content to extract inline styles from.
* @returns {string[]} An array of inline style strings.
*/
function getInlineStyles(appHtml) {
return (
appHtml
.match(/ style=".*?"/g)
?.map((line) => line.replace(/^ style="/, "").replace(/"$/, "")) || []
);
}

/**
* Generates an array of unique SHA-256 hashes for all inline styles found in the given HTML content.
*
* @param {string} appHtml - The HTML content to extract and hash inline styles from.
* @returns {string[]} An array of unique SHA-256 hashes in base64 format.
*/
export function getInlineStylesHashes(appHtml) {
return [...new Set(getInlineStyles(appHtml).map(calculateStyleHash))];
}
10 changes: 8 additions & 2 deletions prerender.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import fs from "node:fs";
import path from "node:path";
import { createServer } from "vite";
import { addInlineStylesHashesToHtml } from "./node/csp/addInlineStylesHashesToHtml.mjs";
import { getInlineStylesHashes } from "./node/csp/getInlineStylesHashes.mjs";
import generateSitemap from "./node/sitemap/generateSitemap.mjs";

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

const pageHtml = template
let pageHtml = template;

const styleHashes = getInlineStylesHashes(appHtml);
pageHtml = addInlineStylesHashesToHtml(pageHtml, styleHashes);

pageHtml = pageHtml
.replace("<!--app-head-->", ctx?.teleports?.head || "")
.replace("<!--app-html-->", appHtml)
.replace(/<!--.*?-->/g, "")
Expand All @@ -32,7 +39,6 @@ for (const routerPath of routerPaths) {
recursive: true,
});
fs.writeFileSync(path.join(pageFolder, "index.html"), pageHtml);
console.log("Generated:", path.join(pageFolder, "index.html"));
await new Promise((resolve) => setTimeout(resolve, 300));
}
await vite.close();