Skip to content

Commit 1dfc4b0

Browse files
authored
Merge pull request #16 from echohello-dev/feature/helmet-plausible
chore: CSP Plausible
2 parents 9f2727f + de9dbe8 commit 1dfc4b0

9 files changed

+59
-24
lines changed

.env.example

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
# GitHub
12
GITHUB_TOKEN=
23

4+
# Backstage
5+
PLAUSIBLE_DATA_DOMAIN=backstage.localhost
6+
PLAUSIBLE_SOURCE_URL=http://plausible.localhost/js/script.js
7+
38
# Plausible
49
BASE_URL=http://plausible.localhost
5-
PLAUSIBLE_DATA_DOMAIN=http://backstage.localhost
6-
PLAUSIBLE_SOURCE_DOMAIN=http://plausible.localhost
710
SECRET_KEY_BASE=
811
TOTP_VAULT_KEY=

app-config.example.yaml

+4-3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ catalog:
2020
target: ../../examples/all.yaml
2121
- type: file
2222
target: ../../examples/acme-corp.yaml
23-
24-
plausible:
25-
enabled: false
23+
# plausible:
24+
# enabled: true
25+
# dataDomain: backstage.localhost
26+
# sourceUrl: http://plausible.localhost/js/script.js

app-config.production.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,4 @@ catalog:
3636
plausible:
3737
enabled: true
3838
dataDomain: ${PLAUSIBLE_DATA_DOMAIN}
39-
sourceDomain: ${PLAUSIBLE_SOURCE_DOMAIN}
39+
sourceUrl: ${PLAUSIBLE_SOURCE_URL}

app-config.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ backend:
1919
# host: 127.0.0.1
2020
csp:
2121
connect-src: ["'self'", 'http:', 'https:']
22+
script-src: ["'self'", 'http:', 'https:']
2223
# Content-Security-Policy directives follow the Helmet format: https://helmetjs.github.io/#reference
2324
# Default Helmet Content-Security-Policy values can be removed by setting the key to false
2425
cors:

plugins/plausible/config.d.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
11
export interface Config {
22
plausible?: {
33
/**
4+
* Whether Plausible Analytics is enabled.
5+
*
46
* @visibility frontend
57
*/
68
enabled: boolean;
79

810
/**
11+
* Plausible data domain to track.
12+
*
913
* @visibility frontend
14+
* @example example.com
1015
*/
1116
dataDomain: string;
1217

1318
/**
19+
* Plausible source URL to load the script from.
20+
*
1421
* @visibility frontend
22+
* @example https://plausible.example.com/js/script.js
1523
*/
16-
sourceDomain: string;
24+
sourceUrl: string;
1725
};
1826
}

plugins/plausible/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"@material-ui/core": "^4.9.13",
3131
"@material-ui/icons": "^4.9.1",
3232
"@material-ui/lab": "4.0.0-alpha.61",
33+
"react-helmet": "^6.1.0",
3334
"react-use": "^17.2.4"
3435
},
3536
"peerDependencies": {
@@ -43,6 +44,7 @@
4344
"@testing-library/jest-dom": "^6.0.0",
4445
"@testing-library/react": "^14.0.0",
4546
"@testing-library/user-event": "^14.0.0",
47+
"@types/react-helmet": "^6",
4648
"msw": "^1.0.0",
4749
"react": "^16.13.1 || ^17.0.0 || ^18.0.0"
4850
},
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import { useApi, configApiRef } from '@backstage/core-plugin-api';
22
import React from 'react';
3+
import { Helmet } from 'react-helmet';
34

45
export const PlausibleAnalytics = () => {
56
const config = useApi(configApiRef);
67
const enabled = config.getOptionalBoolean('plausible.enabled') ?? false;
78
const dataDomain = config.getOptionalString('plausible.dataDomain');
8-
const sourceDomain = config.getOptionalString('plausible.sourceDomain');
9-
const source = `https://${sourceDomain}/js/script.js`;
9+
const sourceUrl = config.getOptionalString('plausible.sourceUrl');
1010

11-
if (!enabled || !dataDomain || !sourceDomain) {
11+
if (!enabled || !dataDomain || !sourceUrl) {
1212
return null;
1313
}
1414

15-
return <script defer data-domain={dataDomain} src={source} />;
15+
return (
16+
<Helmet>
17+
<script defer data-domain={dataDomain} src={sourceUrl} />
18+
</Helmet>
19+
);
1620
};

plugins/plausible/src/plugin.test.tsx

+17-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { render } from '@testing-library/react';
2+
import { render, waitFor } from '@testing-library/react';
33
import { ConfigApi, configApiRef } from '@backstage/core-plugin-api';
44
import { TestApiProvider } from '@backstage/test-utils';
55
import { PlausibleAnalytics } from './components/PlausibleAnalytics';
@@ -34,26 +34,31 @@ describe('PlausibleAnalytics', () => {
3434
expect(container.firstChild).toBeNull();
3535
});
3636

37-
it('renders script tag when plausible is enabled and domain is provided', () => {
37+
it('renders script tag when plausible is configured', async () => {
3838
const config = mockConfigApi({
3939
plausible: {
4040
enabled: true,
4141
dataDomain: 'example.com',
42-
sourceDomain: 'plausible.example.com',
42+
sourceUrl: 'https://plausible.example.com/js/script.js',
4343
},
4444
});
45-
const { container } = render(
45+
render(
4646
<TestApiProvider apis={[[configApiRef, config]]}>
4747
<PlausibleAnalytics />
4848
</TestApiProvider>,
4949
);
50-
const scriptTag = container.querySelector('script');
51-
expect(scriptTag).toBeInTheDocument();
52-
expect(scriptTag).toHaveAttribute('data-domain', 'example.com');
53-
expect(scriptTag).toHaveAttribute(
54-
'src',
55-
'https://plausible.example.com/js/script.js',
56-
);
57-
expect(scriptTag).toHaveAttribute('defer');
50+
51+
await waitFor(() => {
52+
const scriptTag = document.querySelector(
53+
'script[data-domain="example.com"]',
54+
);
55+
expect(scriptTag).toBeInTheDocument();
56+
expect(scriptTag).toHaveAttribute('data-domain', 'example.com');
57+
expect(scriptTag).toHaveAttribute(
58+
'src',
59+
'https://plausible.example.com/js/script.js',
60+
);
61+
expect(scriptTag).toHaveAttribute('defer');
62+
});
5863
});
5964
});

yarn.lock

+12-1
Original file line numberDiff line numberDiff line change
@@ -6152,8 +6152,10 @@ __metadata:
61526152
"@testing-library/jest-dom": "npm:^6.0.0"
61536153
"@testing-library/react": "npm:^14.0.0"
61546154
"@testing-library/user-event": "npm:^14.0.0"
6155+
"@types/react-helmet": "npm:^6"
61556156
msw: "npm:^1.0.0"
61566157
react: "npm:^16.13.1 || ^17.0.0 || ^18.0.0"
6158+
react-helmet: "npm:^6.1.0"
61576159
react-use: "npm:^17.2.4"
61586160
peerDependencies:
61596161
react: ^16.13.1 || ^17.0.0 || ^18.0.0
@@ -12100,6 +12102,15 @@ __metadata:
1210012102
languageName: node
1210112103
linkType: hard
1210212104

12105+
"@types/react-helmet@npm:^6":
12106+
version: 6.1.11
12107+
resolution: "@types/react-helmet@npm:6.1.11"
12108+
dependencies:
12109+
"@types/react": "npm:*"
12110+
checksum: 10c0/f7b3bb2151d992a108ae46fed876fb9c8119108397d9a01d150c5642782997542c8b3c52e742b56e8689b7dbfa62ca9cfc76aa7e05dec4e60c652f7ef53fa783
12111+
languageName: node
12112+
linkType: hard
12113+
1210312114
"@types/react-redux@npm:^7.1.20":
1210412115
version: 7.1.33
1210512116
resolution: "@types/react-redux@npm:7.1.33"
@@ -27523,7 +27534,7 @@ __metadata:
2752327534
languageName: node
2752427535
linkType: hard
2752527536

27526-
"react-helmet@npm:6.1.0":
27537+
"react-helmet@npm:6.1.0, react-helmet@npm:^6.1.0":
2752727538
version: 6.1.0
2752827539
resolution: "react-helmet@npm:6.1.0"
2752927540
dependencies:

0 commit comments

Comments
 (0)