mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
fix(pwa): avoid CSP-blocked stylesheet deferral
This commit is contained in:
1 parent
697eab6e73
commit
bb4e386c6c
6 files changed
+113
-5
No files matched your search
@@ -2,8 +2,19 @@ import { defineConfig, devices } from '@playwright/test';
|
||||
import { nxE2EPreset } from '@nx/playwright/preset';
|
||||
import { workspaceRoot } from '@nx/devkit';
|
||||
|
||||
const isStaticPwaE2E = process.env['IPTVNATOR_E2E_STATIC_PWA'] === '1';
|
||||
const staticPwaPort = process.env['IPTVNATOR_E2E_STATIC_PORT'] ?? '4300';
|
||||
// For CI, you may want to set BASE_URL to the deployed application.
|
||||
const baseURL = process.env['BASE_URL'] || 'http://localhost:4200';
|
||||
const baseURL =
|
||||
process.env['BASE_URL'] ||
|
||||
(isStaticPwaE2E
|
||||
? `http://localhost:${staticPwaPort}`
|
||||
: 'http://localhost:4200');
|
||||
const webServerCommand =
|
||||
isStaticPwaE2E
|
||||
? `pnpm nx run web:serve-static --port=${staticPwaPort}`
|
||||
: 'pnpm nx run web:serve';
|
||||
const reuseExistingWebServer = isStaticPwaE2E ? false : !process.env['CI'];
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
@@ -29,9 +40,9 @@ export default defineConfig({
|
||||
*/
|
||||
webServer: [
|
||||
{
|
||||
command: 'pnpm nx run web:serve',
|
||||
url: 'http://localhost:4200',
|
||||
reuseExistingServer: !process.env['CI'],
|
||||
command: webServerCommand,
|
||||
url: baseURL,
|
||||
reuseExistingServer: reuseExistingWebServer,
|
||||
cwd: workspaceRoot,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
test('@pwa-static PWA build applies the full stylesheet under CSP', async ({
|
||||
page,
|
||||
}) => {
|
||||
const cspConsoleErrors: string[] = [];
|
||||
page.on('console', (message) => {
|
||||
if (
|
||||
message.type() === 'error' &&
|
||||
/content security policy|inline event handler|onload/i.test(
|
||||
message.text()
|
||||
)
|
||||
) {
|
||||
cspConsoleErrors.push(message.text());
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
const stylesheetLinks = page.locator('link[rel="stylesheet"]');
|
||||
await expect(stylesheetLinks.first()).toBeAttached();
|
||||
|
||||
const stylesheetMedia = await stylesheetLinks.evaluateAll((links) =>
|
||||
links.map((link) => ({
|
||||
href: link.getAttribute('href'),
|
||||
media: link.getAttribute('media'),
|
||||
}))
|
||||
);
|
||||
|
||||
const appStylesheet = stylesheetMedia.find((link) =>
|
||||
/^styles-.*\.css$/.test(link.href ?? '')
|
||||
);
|
||||
expect(appStylesheet).toBeDefined();
|
||||
expect(appStylesheet?.media).not.toBe('print');
|
||||
|
||||
const materialIcon = page.locator('mat-icon.material-icons').first();
|
||||
await expect(materialIcon).toBeVisible();
|
||||
|
||||
await expect
|
||||
.poll(() =>
|
||||
materialIcon.evaluate((icon) =>
|
||||
getComputedStyle(icon).fontFamily.toLowerCase()
|
||||
)
|
||||
)
|
||||
.toContain('material icons');
|
||||
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.evaluate(() =>
|
||||
document.fonts.check('24px "Material Icons"')
|
||||
)
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
const ngswResponse = await page.request.get('/ngsw.json');
|
||||
await expect(ngswResponse).toBeOK();
|
||||
|
||||
const ngswManifest = (await ngswResponse.json()) as {
|
||||
assetGroups?: Array<{ urls?: string[] }>;
|
||||
};
|
||||
const cachedUrls = ngswManifest.assetGroups?.flatMap(
|
||||
(assetGroup) => assetGroup.urls ?? []
|
||||
);
|
||||
|
||||
expect(cachedUrls).toEqual(
|
||||
expect.arrayContaining([expect.stringMatching(/^(?:\.\/|\/)media\//)])
|
||||
);
|
||||
expect(cspConsoleErrors).toEqual([]);
|
||||
});
|
||||
+18
-1
@@ -66,6 +66,15 @@
|
||||
"maximumError": "15kb"
|
||||
}
|
||||
],
|
||||
"optimization": {
|
||||
"scripts": true,
|
||||
"styles": {
|
||||
"minify": true,
|
||||
"inlineCritical": false,
|
||||
"removeSpecialComments": true
|
||||
},
|
||||
"fonts": true
|
||||
},
|
||||
"outputHashing": "all",
|
||||
"fileReplacements": [
|
||||
{
|
||||
@@ -89,7 +98,15 @@
|
||||
}
|
||||
],
|
||||
"outputHashing": "all",
|
||||
"optimization": true,
|
||||
"optimization": {
|
||||
"scripts": true,
|
||||
"styles": {
|
||||
"minify": true,
|
||||
"inlineCritical": false,
|
||||
"removeSpecialComments": true
|
||||
},
|
||||
"fonts": true
|
||||
},
|
||||
"extractLicenses": true,
|
||||
"sourceMap": false,
|
||||
"fileReplacements": [
|
||||
|
||||
@@ -47,6 +47,12 @@ sources through `media-src` and `connect-src` for `http:`, `https:`, `blob:`,
|
||||
and `data:`. The policy keeps `script-src` self-hosted and currently keeps
|
||||
`unsafe-inline` for existing inline styles.
|
||||
|
||||
Angular production builds must not rely on inline event handlers for stylesheet
|
||||
activation. Keep `web:build:production` and `web:build:pwa` configured without
|
||||
critical CSS stylesheet deferral (`optimization.styles.inlineCritical: false`)
|
||||
unless the CSP is intentionally changed and runtime-validated in both Electron
|
||||
and the self-hosted PWA.
|
||||
|
||||
Before tightening either value, validate both Electron development startup and
|
||||
the PWA/electron build configurations. Playback-heavy changes should also check
|
||||
that HLS, MPEG-TS, thumbnails, and local file playback are still allowed by the
|
||||
|
||||
@@ -40,6 +40,10 @@ The build must emit these files in `dist/apps/web`:
|
||||
- `safety-worker.js`
|
||||
- `worker-basic.min.js`
|
||||
|
||||
Angular also emits hashed font and media assets under `dist/apps/web/media/`.
|
||||
Keep `/media/**` in `ngsw-config.json` so the PWA service worker can cache
|
||||
bundled fonts, including Material Icons.
|
||||
|
||||
`web:serve-static` serves `dist/apps/web` and builds with `web:build:pwa`, so it
|
||||
exercises the same output layout as Docker. If Nx daemon state returns stale
|
||||
service worker outputs while changing build options, run:
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"files": [
|
||||
"/assets/**",
|
||||
"!/assets/app-config.js",
|
||||
"/media/**",
|
||||
"/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user