Skip to content

Commit a049e43

Browse files
committedAug 3, 2024
Add GoatCounter for analytics
1 parent 8ab9161 commit a049e43

File tree

6 files changed

+93
-7
lines changed

6 files changed

+93
-7
lines changed
 

‎.env.test

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ GOTRUE_SECURITY_CAPTCHA_ENABLED="true"
44
GOTRUE_SECURITY_CAPTCHA_PROVIDER="turnstile"
55
GOTRUE_SECURITY_CAPTCHA_SECRET="1x0000000000000000000000000000000AA"
66
NEXT_PUBLIC_GOTRUE_SECURITY_CAPTCHA_SITEKEY="1x00000000000000000000BB"
7+
NEXT_PUBLIC_ANALYTICS_SITE_ID='faithdashboard-test'

‎app/privacy-policy/page.tsx

+11-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ async function PrivacyPolicy() {
1010
Dashboard collects and handles your personal information.
1111
</p>
1212

13-
<p>This privacy policy was last updated on June 27th, 2022.</p>
13+
<p>This privacy policy was last updated on August 3rd, 2024.</p>
1414

1515
<h2>Secure Connection</h2>
1616

@@ -58,8 +58,16 @@ async function PrivacyPolicy() {
5858
<h2>Analytics</h2>
5959

6060
<p>
61-
As of Februrary 9th, 2024, Faith Dashboard no longer collects any
62-
analytics.
61+
Faith Dashboard uses <a href="https://goatcounter.com/">GoatCounter</a>,
62+
a privacy-focused analytics platform. We only use this information to
63+
examine traffic trends and aggregated visitor statistics (i.e. the
64+
percentage of Chrome users, or the total number of people who viewed a
65+
particular page). However, you would need to read the{' '}
66+
<a href="https://www.goatcounter.com/help/privacy">
67+
GoatCounter privacy policy
68+
</a>
69+
to understand what information GoatCounter collects. We do not otherwise
70+
share this information.
6371
</p>
6472

6573
<h2>Email Privacy</h2>

‎components/app/App.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import ThemeMetadata from './ThemeMetadata';
2424
import UpdateNotification from './UpdateNotification';
2525
import { getDefaultAppState } from './appUtils';
2626
import getAppNotificationMessage from './getAppNotificationMessage';
27+
import useAnalytics from './useAnalytics';
2728
import useAppSync from './useAppSync';
2829
import useThemeForEntirePage from './useThemeForEntirePage';
2930

@@ -104,6 +105,7 @@ function App({
104105
}, [setIsTutorialStarted]);
105106

106107
useTouchDeviceDetection();
108+
useAnalytics();
107109

108110
const isMounted = useMountListener();
109111
const isSignedIn = Boolean(user) && isSessionActive(session);

‎components/app/useAnalytics.ts

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { useEffect } from 'react';
2+
3+
// Define a basic type for the goatcounter global
4+
declare global {
5+
interface Window {
6+
goatcounter?: {
7+
count: (options: object) => void;
8+
};
9+
}
10+
}
11+
12+
// Resolve a promise when GoatCounter is fully loaded and ready to use on the
13+
// page
14+
export async function getGoatcounter(): Promise<
15+
NonNullable<typeof window.goatcounter>
16+
> {
17+
return new Promise((resolve) => {
18+
if (window.goatcounter) {
19+
resolve(window.goatcounter);
20+
} else {
21+
const script = document.createElement('script');
22+
script.addEventListener('load', () => {
23+
if (window.goatcounter) {
24+
resolve(window.goatcounter);
25+
} else {
26+
console.log('goatcounter script loaded but global not available');
27+
}
28+
});
29+
script.async = true;
30+
script.dataset.goatcounter = `https://${process.env.NEXT_PUBLIC_ANALYTICS_SITE_ID}.goatcounter.com/count`;
31+
script.dataset.goatcounterSettings = JSON.stringify({ no_onload: true });
32+
script.src = 'https://gc.zgo.at/count.v4.js';
33+
script.crossOrigin = 'anonymous';
34+
script.integrity =
35+
'sha384-nRw6qfbWyJha9LhsOtSb2YJDyZdKvvCFh0fJYlkquSFjUxp9FVNugbfy8q1jdxI+';
36+
document.head.appendChild(script);
37+
}
38+
});
39+
}
40+
41+
// Count a single pageview with GoatCounter
42+
export async function countPageview() {
43+
const goatcounter = await getGoatcounter();
44+
goatcounter.count({
45+
path: location.pathname + location.search + location.hash
46+
});
47+
}
48+
49+
// Use the
50+
function useAnalytics() {
51+
useEffect(() => {
52+
if (process.env.NEXT_PUBLIC_ANALYTICS_SITE_ID) {
53+
countPageview();
54+
}
55+
}, []);
56+
}
57+
export default useAnalytics;

‎middleware.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,18 @@ import type { NextRequest } from 'next/server';
1111
// <https://stackoverflow.com/questions/76270173/can-a-nonce-be-used-for-multiple-scripts-or-not>)
1212
function generateCSP() {
1313
const nonce = crypto.randomUUID();
14-
return `default-src 'none'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://hcaptcha.com https://*.hcaptcha.com; font-src 'self' https://fonts.gstatic.com data:; img-src * data:; script-src 'self' 'nonce-${nonce}' https://storage.googleapis.com https://challenges.cloudflare.com; frame-src 'self' https://challenges.cloudflare.com; child-src 'self' https://challenges.cloudflare.com; connect-src *; manifest-src 'self'; media-src *;`;
14+
return [
15+
`default-src 'none';`,
16+
` style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://hcaptcha.com https://*.hcaptcha.com;`,
17+
` font-src 'self' https://fonts.gstatic.com data:;`,
18+
` img-src * data:;`,
19+
` script-src 'self' 'nonce-${nonce}' https://storage.googleapis.com ${process.env.NEXT_PUBLIC_ANALYTICS_SITE_ID ? 'https://gc.zgo.at' : ''} https://challenges.cloudflare.com;`,
20+
` frame-src 'self' https://challenges.cloudflare.com;`,
21+
` child-src 'self' https://challenges.cloudflare.com;`,
22+
` connect-src *;`,
23+
` manifest-src 'self';`,
24+
` media-src *;`
25+
].join(' ');
1526
}
1627

1728
// Source:

‎next.config.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,16 @@ const withPWA = require('next-pwa')({
2121
// 'SKIP_WAITING'}." (source:
2222
// https://developers.google.com/web/tools/workbox/reference-docs/latest/module-workbox-webpack-plugin.GenerateSW#GenerateSW)
2323
skipWaiting: false,
24-
// Fix bad-precaching-response errors from service worker due to use of
25-
// middleware (source: https://github.com/shadowwalker/next-pwa/issues/291)
26-
runtimeCaching,
24+
runtimeCaching: [
25+
// Fix bad-precaching-response errors from service worker due to use of
26+
// middleware (source: https://github.com/shadowwalker/next-pwa/issues/291)
27+
...runtimeCaching,
28+
// Fix no-response errors for GoatCounter analytics scripts
29+
{
30+
urlPattern: /^https:\/\/gc\.zgo\.at\//i,
31+
handler: 'NetworkOnly'
32+
}
33+
],
2734
buildExcludes: [
2835
// This is necessary to prevent service worker errors; see
2936
// <https://github.com/shadowwalker/next-pwa/issues/424#issuecomment-1399683017>

0 commit comments

Comments
 (0)