Skip to content

Commit

Permalink
Add GoatCounter for analytics
Browse files Browse the repository at this point in the history
  • Loading branch information
caleb531 committed Aug 3, 2024
1 parent 8ab9161 commit a049e43
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 7 deletions.
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ GOTRUE_SECURITY_CAPTCHA_ENABLED="true"
GOTRUE_SECURITY_CAPTCHA_PROVIDER="turnstile"
GOTRUE_SECURITY_CAPTCHA_SECRET="1x0000000000000000000000000000000AA"
NEXT_PUBLIC_GOTRUE_SECURITY_CAPTCHA_SITEKEY="1x00000000000000000000BB"
NEXT_PUBLIC_ANALYTICS_SITE_ID='faithdashboard-test'
14 changes: 11 additions & 3 deletions app/privacy-policy/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ async function PrivacyPolicy() {
Dashboard collects and handles your personal information.
</p>

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

<h2>Secure Connection</h2>

Expand Down Expand Up @@ -58,8 +58,16 @@ async function PrivacyPolicy() {
<h2>Analytics</h2>

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

<h2>Email Privacy</h2>
Expand Down
2 changes: 2 additions & 0 deletions components/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import ThemeMetadata from './ThemeMetadata';
import UpdateNotification from './UpdateNotification';
import { getDefaultAppState } from './appUtils';
import getAppNotificationMessage from './getAppNotificationMessage';
import useAnalytics from './useAnalytics';
import useAppSync from './useAppSync';
import useThemeForEntirePage from './useThemeForEntirePage';

Expand Down Expand Up @@ -104,6 +105,7 @@ function App({
}, [setIsTutorialStarted]);

useTouchDeviceDetection();
useAnalytics();

const isMounted = useMountListener();
const isSignedIn = Boolean(user) && isSessionActive(session);
Expand Down
57 changes: 57 additions & 0 deletions components/app/useAnalytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useEffect } from 'react';

// Define a basic type for the goatcounter global
declare global {
interface Window {
goatcounter?: {
count: (options: object) => void;
};
}
}

// Resolve a promise when GoatCounter is fully loaded and ready to use on the
// page
export async function getGoatcounter(): Promise<
NonNullable<typeof window.goatcounter>
> {
return new Promise((resolve) => {
if (window.goatcounter) {
resolve(window.goatcounter);
} else {
const script = document.createElement('script');
script.addEventListener('load', () => {
if (window.goatcounter) {
resolve(window.goatcounter);
} else {
console.log('goatcounter script loaded but global not available');
}
});
script.async = true;
script.dataset.goatcounter = `https://${process.env.NEXT_PUBLIC_ANALYTICS_SITE_ID}.goatcounter.com/count`;
script.dataset.goatcounterSettings = JSON.stringify({ no_onload: true });
script.src = 'https://gc.zgo.at/count.v4.js';
script.crossOrigin = 'anonymous';
script.integrity =
'sha384-nRw6qfbWyJha9LhsOtSb2YJDyZdKvvCFh0fJYlkquSFjUxp9FVNugbfy8q1jdxI+';
document.head.appendChild(script);
}
});
}

// Count a single pageview with GoatCounter
export async function countPageview() {
const goatcounter = await getGoatcounter();
goatcounter.count({
path: location.pathname + location.search + location.hash
});
}

// Use the
function useAnalytics() {
useEffect(() => {
if (process.env.NEXT_PUBLIC_ANALYTICS_SITE_ID) {
countPageview();
}
}, []);
}
export default useAnalytics;
13 changes: 12 additions & 1 deletion middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,18 @@ import type { NextRequest } from 'next/server';
// <https://stackoverflow.com/questions/76270173/can-a-nonce-be-used-for-multiple-scripts-or-not>)
function generateCSP() {
const nonce = crypto.randomUUID();
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 *;`;
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 ${process.env.NEXT_PUBLIC_ANALYTICS_SITE_ID ? 'https://gc.zgo.at' : ''} 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 *;`
].join(' ');
}

// Source:
Expand Down
13 changes: 10 additions & 3 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,16 @@ const withPWA = require('next-pwa')({
// 'SKIP_WAITING'}." (source:
// https://developers.google.com/web/tools/workbox/reference-docs/latest/module-workbox-webpack-plugin.GenerateSW#GenerateSW)
skipWaiting: false,
// Fix bad-precaching-response errors from service worker due to use of
// middleware (source: https://github.com/shadowwalker/next-pwa/issues/291)
runtimeCaching,
runtimeCaching: [
// Fix bad-precaching-response errors from service worker due to use of
// middleware (source: https://github.com/shadowwalker/next-pwa/issues/291)
...runtimeCaching,
// Fix no-response errors for GoatCounter analytics scripts
{
urlPattern: /^https:\/\/gc\.zgo\.at\//i,
handler: 'NetworkOnly'
}
],
buildExcludes: [
// This is necessary to prevent service worker errors; see
// <https://github.com/shadowwalker/next-pwa/issues/424#issuecomment-1399683017>
Expand Down

0 comments on commit a049e43

Please sign in to comment.