Skip to content

Commit

Permalink
Chore: add amplitude (#155)
Browse files Browse the repository at this point in the history
* chore: deploy GTM directly

* chore: add amplitude

* chore: refactor

* chore: move FpjsProvider to Providers

* chore: fix comment

* chore: fix typo
  • Loading branch information
JuroUhlar authored Oct 7, 2024
1 parent 9dd668e commit 2e16ec4
Show file tree
Hide file tree
Showing 11 changed files with 237 additions and 19 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"node": ">=18.17.0"
},
"dependencies": {
"@amplitude/analytics-browser": "^2.11.5",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0",
"@fingerprintjs/fingerprintjs-pro-react": "^2.6.2",
Expand Down
5 changes: 3 additions & 2 deletions src/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import { FunctionComponent, PropsWithChildren } from 'react';
import Footer from './client/components/common/Footer/Footer';
import Header from './client/components/common/Header/Header';
import styles from './styles/layout.module.scss';
import DeploymentUtils from './client/DeploymentUtils';
import { IS_PRODUCTION } from './envShared';
import { Analytics } from './client/analytics/Analytics';

export const Layout: FunctionComponent<PropsWithChildren<{ embed: boolean }>> = ({ children, embed }) => {
return (
<div className={styles.layout}>
{embed ? null : <Header />}
<DeploymentUtils />
{IS_PRODUCTION ? <Analytics /> : null}
<div>{children}</div>
{embed ? null : <Footer />}
</div>
Expand Down
11 changes: 9 additions & 2 deletions src/Providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { QueryClient, QueryClientProvider } from 'react-query';
import { SnackbarProvider } from 'notistack';
import { PropsWithChildren } from 'react';
import { CloseSnackbarButton, CustomSnackbar } from './client/components/common/Alert/Alert';
import { FpjsProvider } from '@fingerprintjs/fingerprintjs-pro-react';
import { FP_LOAD_OPTIONS } from './pages/_app';
import { FingerprintJSPro, FpjsProvider } from '@fingerprintjs/fingerprintjs-pro-react';
import { env } from './env';

const queryClient = new QueryClient({
defaultOptions: {
Expand All @@ -15,6 +15,13 @@ const queryClient = new QueryClient({
},
});

export const FP_LOAD_OPTIONS: FingerprintJSPro.LoadOptions = {
apiKey: env.NEXT_PUBLIC_API_KEY,
scriptUrlPattern: [env.NEXT_PUBLIC_SCRIPT_URL_PATTERN, FingerprintJSPro.defaultScriptUrlPattern],
endpoint: [env.NEXT_PUBLIC_ENDPOINT, FingerprintJSPro.defaultEndpoint],
region: env.NEXT_PUBLIC_REGION,
};

function Providers({ children }: PropsWithChildren) {
return (
<QueryClientProvider client={queryClient}>
Expand Down
13 changes: 11 additions & 2 deletions src/app/playground/hooks/usePlaygroundSignals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { EventResponse } from '@fingerprintjs/fingerprintjs-pro-server-api';
import { useState } from 'react';
import { useQuery } from 'react-query';

export function usePlaygroundSignals() {
export function usePlaygroundSignals(config?: { onServerApiSuccess?: (data: EventResponse) => void }) {
const {
data: agentResponse,
isLoading: isLoadingAgentResponse,
Expand All @@ -29,7 +29,16 @@ export function usePlaygroundSignals() {
}
return res.json();
}),
{ enabled: Boolean(agentResponse), retry: false, onSuccess: (data) => setCachedEvent(data) },
{
enabled: Boolean(agentResponse),
retry: false,
onSuccess: (data) => {
if (data) {
setCachedEvent(data);
config?.onServerApiSuccess?.(data);
}
},
},
);

return {
Expand Down
4 changes: 0 additions & 4 deletions src/client/DeploymentUtils.tsx

This file was deleted.

67 changes: 67 additions & 0 deletions src/client/analytics/Amplitude.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import * as amplitude from '@amplitude/analytics-browser';
import { usePlaygroundSignals } from '../../app/playground/hooks/usePlaygroundSignals';
import { FunctionComponent } from 'react';

const AMPLITUDE_INGRESS_PROXY = 'https://dlxhio63e79vv.cloudfront.net/ampl-api/2/httpapi';
const EVENT_TYPE = 'Demo Page Viewed';

/**
* This is an Amplitude plugin that renames the Page view event_properties according to our analytics needs
*/
const renameEventPropertiesEnrichment: amplitude.Types.EnrichmentPlugin = {
name: 'rename-event-properties-enrichment',
type: 'enrichment',
setup: async () => undefined,
execute: async (event) => {
// Only apply to Demo Page View events
if (event.event_type !== EVENT_TYPE) {
return event;
}

// Rename event properties
const originalEventProperties = event.event_properties;
event.event_properties = {
'Demo Page Domain': originalEventProperties?.['[Amplitude] Page Domain'],
'Demo Page Location': originalEventProperties?.['[Amplitude] Page Location'],
'Demo Page Path': originalEventProperties?.['[Amplitude] Page Path'],
'Demo Page Title': originalEventProperties?.['[Amplitude] Page Title'],
'Demo Page URL': originalEventProperties?.['[Amplitude] Page URL'],
};
return event;
},
};

type AmplitudeProps = {
apiKey: string;
};

export const Amplitude: FunctionComponent<AmplitudeProps> = ({ apiKey }) => {
usePlaygroundSignals({
onServerApiSuccess: (event) => {
const visitorId = event.products.identification?.data?.visitorId;
const botDetected = event?.products?.botd?.data?.bot?.result === 'bad' ? 'True' : 'False';

amplitude.add(renameEventPropertiesEnrichment);
amplitude.init(apiKey, {
defaultTracking: {
pageViews: {
eventType: EVENT_TYPE,
},
attribution: false,
sessions: false,
formInteractions: false,
fileDownloads: false,
},
deviceId: visitorId,
serverUrl: AMPLITUDE_INGRESS_PROXY,
});

// Set Amplify user's custom `botDetected` property based on Fingerprint Bot Detection result
const identifyEvent = new amplitude.Identify();
identifyEvent.set('botDetected', botDetected);
amplitude.identify(identifyEvent);
},
});

return null;
};
43 changes: 43 additions & 0 deletions src/client/analytics/Analytics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* This file and folder just contains code for tools that help us see trends in how people use demo.fingerprint.com
* It is only deployed for production builds (`next build`) and when the expected environment variables are set
* It is not part of the reference implementation for any of the use cases, feel free to ignore it
**/
'use client';

import { env } from '../../env';

import { GoogleTagManager } from './Gtm';
import { Amplitude } from './Amplitude';
import { useEffect } from 'react';

// GTM API requires dataLayer access through global window variable
declare global {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Window {
// eslint-disable-next-line @typescript-eslint/ban-types
dataLayer: object[];
}
}

const enableAnalytics = () => {
// Required for development since without SSR sendEvent will be called before Helmet has a chance to inject the script that initializes dataLayer.
window.dataLayer = window.dataLayer ?? [];
window.dataLayer.push({ event: 'event.enableAnalytics' }, { enableAnalytics: true });
};

const GTM_ID = env.NEXT_PUBLIC_GTM_ID;
const AMPLITUDE_API_KEY = env.NEXT_PUBLIC_AMPLITUDE_API_KEY;

export const Analytics = () => {
useEffect(() => {
enableAnalytics();
}, []);

return (
<>
{GTM_ID ? <GoogleTagManager gtmId={GTM_ID} /> : null}
{AMPLITUDE_API_KEY ? <Amplitude apiKey={AMPLITUDE_API_KEY} /> : null}
</>
);
};
19 changes: 19 additions & 0 deletions src/client/analytics/Gtm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Script from 'next/script';

export const GoogleTagManager = ({ gtmId }: { gtmId: string }) => {
return (
<Script
id='gtag-base'
strategy='afterInteractive'
dangerouslySetInnerHTML={{
__html: `
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer', '${gtmId}');
`,
}}
/>
);
};
7 changes: 7 additions & 0 deletions src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ export const env = createEnv({
.string()
.min(1)
.default('https://staging.fingerprinthub.com/fp-sealed/result?region=us'),
// Analytics
NEXT_PUBLIC_GTM_ID: z.string().min(1).optional(),
NEXT_PUBLIC_AMPLITUDE_API_KEY: z.string().min(1).optional(),
},
/*
* Due to how Next.js bundles environment variables on Edge and Client,
Expand Down Expand Up @@ -112,6 +115,10 @@ export const env = createEnv({
NEXT_PUBLIC_SEALED_RESULTS_PUBLIC_API_KEY: process.env.NEXT_PUBLIC_SEALED_RESULTS_PUBLIC_API_KEY,
NEXT_PUBLIC_SEALED_RESULTS_SCRIPT_URL: process.env.NEXT_PUBLIC_SEALED_RESULTS_SCRIPT_URL,
NEXT_PUBLIC_SEALED_RESULTS_ENDPOINT: process.env.NEXT_PUBLIC_SEALED_RESULTS_ENDPOINT,

// Analytics
NEXT_PUBLIC_GTM_ID: process.env.NEXT_PUBLIC_GTM_ID,
NEXT_PUBLIC_AMPLITUDE_API_KEY: process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY,
},
isServer:
// Comprehensive server check
Expand Down
9 changes: 0 additions & 9 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
import '../styles/global-styles.scss';
import Head from 'next/head';
import { FingerprintJSPro } from '@fingerprintjs/fingerprintjs-pro-react';
import { AppProps } from 'next/app';
import Providers from '../Providers';
import { Layout } from '../Layout';
import { env } from '../env';

export const FP_LOAD_OPTIONS: FingerprintJSPro.LoadOptions = {
apiKey: env.NEXT_PUBLIC_API_KEY,
scriptUrlPattern: [env.NEXT_PUBLIC_SCRIPT_URL_PATTERN, FingerprintJSPro.defaultScriptUrlPattern],
endpoint: [env.NEXT_PUBLIC_ENDPOINT, FingerprintJSPro.defaultEndpoint],
region: env.NEXT_PUBLIC_REGION,
};

export type CustomPageProps = { embed?: boolean };

Expand Down
77 changes: 77 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,76 @@
resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf"
integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==

"@amplitude/analytics-browser@^2.11.5":
version "2.11.5"
resolved "https://registry.yarnpkg.com/@amplitude/analytics-browser/-/analytics-browser-2.11.5.tgz#3d5f28ea78e14b7664c428c69c242b5c9e72a2a7"
integrity sha512-cHxYtVRwzyySyI6o7YiYFIztcoCpewL1DXFnI8NhgBGqapTaIH74lnddeT0jc6KcHRQxfsRp0bStpUGlfNTRgA==
dependencies:
"@amplitude/analytics-client-common" "^2.3.3"
"@amplitude/analytics-core" "^2.5.2"
"@amplitude/analytics-remote-config" "^0.4.0"
"@amplitude/analytics-types" "^2.8.2"
"@amplitude/plugin-autocapture-browser" "^1.0.1"
"@amplitude/plugin-page-view-tracking-browser" "^2.3.1"
tslib "^2.4.1"

"@amplitude/analytics-client-common@>=1 <3", "@amplitude/analytics-client-common@^2.3.3":
version "2.3.3"
resolved "https://registry.yarnpkg.com/@amplitude/analytics-client-common/-/analytics-client-common-2.3.3.tgz#7a23d72d8884b2723043274f13c7ece35104d62d"
integrity sha512-HOvD2A8DH0y2LATrQ0AhFSOCk6yZayebu4+UsEBT76Q7EEYtnc59WMCRRIi5Zv6OqHpOvABumF7g3HmL6ehTYA==
dependencies:
"@amplitude/analytics-connector" "^1.4.8"
"@amplitude/analytics-core" "^2.5.2"
"@amplitude/analytics-types" "^2.8.2"
tslib "^2.4.1"

"@amplitude/analytics-connector@^1.4.8":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@amplitude/analytics-connector/-/analytics-connector-1.5.0.tgz#89a78b8c6463abe4de1d621db4af6c62f0d62b0a"
integrity sha512-T8mOYzB9RRxckzhL0NTHwdge9xuFxXEOplC8B1Y3UX3NHa3BLh7DlBUZlCOwQgMc2nxDfnSweDL5S3bhC+W90g==

"@amplitude/analytics-core@>=1 <3", "@amplitude/analytics-core@^2.5.2":
version "2.5.2"
resolved "https://registry.yarnpkg.com/@amplitude/analytics-core/-/analytics-core-2.5.2.tgz#502cb294dad94ec4dd8330e19228d9101ebaa0ad"
integrity sha512-4ojYUL7LA+qrlaz1n1nxpsbpgS1k6DOrQ3fBiQOuDJE8Av0aZfylDksFPnZvD1+MMdIm/ONkVAYfEaW3x/uH3Q==
dependencies:
"@amplitude/analytics-types" "^2.8.2"
tslib "^2.4.1"

"@amplitude/analytics-remote-config@^0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@amplitude/analytics-remote-config/-/analytics-remote-config-0.4.0.tgz#e9835836ef40c6b2e72bc8c7a88803dda5559556"
integrity sha512-ilp9Dz8Z92V9Wilmz8XIbvEbtuVaN65+jM06JP8I7wL8eNOHVIi4HcI151BzIyekjbprbS1w18Ps3dj2sHlFXA==
dependencies:
"@amplitude/analytics-client-common" ">=1 <3"
"@amplitude/analytics-core" ">=1 <3"
"@amplitude/analytics-types" ">=1 <3"
tslib "^2.4.1"

"@amplitude/analytics-types@>=1 <3", "@amplitude/analytics-types@^2.8.2":
version "2.8.2"
resolved "https://registry.yarnpkg.com/@amplitude/analytics-types/-/analytics-types-2.8.2.tgz#aee2b0d42c962d8408056b45b957f18dbb2529ef"
integrity sha512-SWFXIMxhFm1/k3PUvxvYLY1iwzS28yd9A6pa5pEnrbaAZwM+E/24ucxs59VGp1N5qlIsvF0aVGSoKzN4ydh4eA==

"@amplitude/plugin-autocapture-browser@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@amplitude/plugin-autocapture-browser/-/plugin-autocapture-browser-1.0.1.tgz#02d734a96cc240e28afad63fe88d9c6449c8add4"
integrity sha512-uc+OyTrNyz5C9/T49jattgNCfU7PRcs4jKzgAM+p1rjtaSPDIdQBm09icrFPOsai5hMKFKPYSWUq0HpoK0uaHA==
dependencies:
"@amplitude/analytics-client-common" ">=1 <3"
"@amplitude/analytics-types" ">=1 <3"
rxjs "^7.8.1"
tslib "^2.4.1"

"@amplitude/plugin-page-view-tracking-browser@^2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.3.1.tgz#b953dc3bcf60a851bcccd647848a810ed30c35d7"
integrity sha512-4oWyXKA4sM7LQcdFXMpA+LDoZLRcTpfTbYictdWA40/baK5aMneJqJolDZVROMLnPm/Lpx9+3+AitLpKMiyVEQ==
dependencies:
"@amplitude/analytics-client-common" "^2.3.3"
"@amplitude/analytics-types" "^2.8.2"
tslib "^2.4.1"

"@ampproject/remapping@^2.2.0":
version "2.3.0"
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4"
Expand Down Expand Up @@ -5401,6 +5471,13 @@ run-parallel@^1.1.9:
dependencies:
queue-microtask "^1.2.2"

rxjs@^7.8.1:
version "7.8.1"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543"
integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==
dependencies:
tslib "^2.1.0"

safe-array-concat@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.0.tgz#8d0cae9cb806d6d1c06e08ab13d847293ebe0692"
Expand Down

0 comments on commit 2e16ec4

Please sign in to comment.