mirror of
https://github.com/logto-io/logto.git
synced 2025-01-06 20:40:08 -05:00
Merge pull request #3905 from logto-io/gao-add-google-tag
refactor: add google tag conversion report
This commit is contained in:
commit
f1d73af537
5 changed files with 67 additions and 2 deletions
|
@ -37,6 +37,12 @@ export default function withSecurityHeaders<InputContext extends RequestContext>
|
||||||
const coreOrigins = urlSet.origins;
|
const coreOrigins = urlSet.origins;
|
||||||
const developmentOrigins = conditionalArray(!isProduction && 'ws:');
|
const developmentOrigins = conditionalArray(!isProduction && 'ws:');
|
||||||
const appInsightsOrigins = ['https://*.applicationinsights.azure.com'];
|
const appInsightsOrigins = ['https://*.applicationinsights.azure.com'];
|
||||||
|
// Gtag will load by `<script />`
|
||||||
|
const gtagOrigins = [
|
||||||
|
'https://*.googletagmanager.com',
|
||||||
|
'https://*.doubleclick.net',
|
||||||
|
'https://*.googleadservices.com',
|
||||||
|
];
|
||||||
|
|
||||||
return async (
|
return async (
|
||||||
context: InputContext,
|
context: InputContext,
|
||||||
|
@ -63,6 +69,7 @@ export default function withSecurityHeaders<InputContext extends RequestContext>
|
||||||
|
|
||||||
const basicSecurityHeaderSettings: HelmetOptions = {
|
const basicSecurityHeaderSettings: HelmetOptions = {
|
||||||
contentSecurityPolicy: false, // Exclusively set for console app only
|
contentSecurityPolicy: false, // Exclusively set for console app only
|
||||||
|
crossOriginEmbedderPolicy: { policy: 'credentialless' },
|
||||||
expectCt: false, // Not recommended, will be deprecated by modern browsers
|
expectCt: false, // Not recommended, will be deprecated by modern browsers
|
||||||
dnsPrefetchControl: false,
|
dnsPrefetchControl: false,
|
||||||
referrerPolicy: {
|
referrerPolicy: {
|
||||||
|
@ -89,7 +96,7 @@ export default function withSecurityHeaders<InputContext extends RequestContext>
|
||||||
directives: {
|
directives: {
|
||||||
'upgrade-insecure-requests': null,
|
'upgrade-insecure-requests': null,
|
||||||
imgSrc: ["'self'", 'data:', 'https:'],
|
imgSrc: ["'self'", 'data:', 'https:'],
|
||||||
scriptSrc: ["'self'", "'unsafe-eval'", "'unsafe-inline'"],
|
scriptSrc: ["'self'", "'unsafe-eval'", "'unsafe-inline'", ...gtagOrigins],
|
||||||
connectSrc: [
|
connectSrc: [
|
||||||
"'self'",
|
"'self'",
|
||||||
...adminOrigins,
|
...adminOrigins,
|
||||||
|
@ -97,6 +104,7 @@ export default function withSecurityHeaders<InputContext extends RequestContext>
|
||||||
...coreOrigins,
|
...coreOrigins,
|
||||||
...developmentOrigins,
|
...developmentOrigins,
|
||||||
...appInsightsOrigins,
|
...appInsightsOrigins,
|
||||||
|
...gtagOrigins,
|
||||||
],
|
],
|
||||||
frameSrc: ["'self'", ...coreOrigins, ...adminOrigins],
|
frameSrc: ["'self'", ...coreOrigins, ...adminOrigins],
|
||||||
},
|
},
|
||||||
|
|
3
packages/console/src/include.d/gtag.d.ts
vendored
Normal file
3
packages/console/src/include.d/gtag.d.ts
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
declare interface Window {
|
||||||
|
dataLayer?: unknown[];
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ import { Component, ConsoleEvent } from '@logto/app-insights/custom-event';
|
||||||
import { TrackOnce } from '@logto/app-insights/react';
|
import { TrackOnce } from '@logto/app-insights/react';
|
||||||
import { Theme } from '@logto/schemas';
|
import { Theme } from '@logto/schemas';
|
||||||
import { useContext, useEffect } from 'react';
|
import { useContext, useEffect } from 'react';
|
||||||
|
import { Helmet } from 'react-helmet';
|
||||||
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
||||||
import { SWRConfig } from 'swr';
|
import { SWRConfig } from 'swr';
|
||||||
|
|
||||||
|
@ -14,6 +15,7 @@ import useSwrOptions from '@/hooks/use-swr-options';
|
||||||
import NotFound from '@/pages/NotFound';
|
import NotFound from '@/pages/NotFound';
|
||||||
|
|
||||||
import * as styles from './App.module.scss';
|
import * as styles from './App.module.scss';
|
||||||
|
import { gtagAwTrackingId, gtagSignUpConversionId, logtoProductionHostname } from './constants';
|
||||||
import AppContent from './containers/AppContent';
|
import AppContent from './containers/AppContent';
|
||||||
import useUserOnboardingData from './hooks/use-user-onboarding-data';
|
import useUserOnboardingData from './hooks/use-user-onboarding-data';
|
||||||
import About from './pages/About';
|
import About from './pages/About';
|
||||||
|
@ -21,9 +23,14 @@ import Congrats from './pages/Congrats';
|
||||||
import SignInExperience from './pages/SignInExperience';
|
import SignInExperience from './pages/SignInExperience';
|
||||||
import Welcome from './pages/Welcome';
|
import Welcome from './pages/Welcome';
|
||||||
import { OnboardingPage, OnboardingRoute } from './types';
|
import { OnboardingPage, OnboardingRoute } from './types';
|
||||||
import { getOnboardingPage } from './utils';
|
import { getOnboardingPage, gtag } from './utils';
|
||||||
|
|
||||||
const welcomePathname = getOnboardingPage(OnboardingPage.Welcome);
|
const welcomePathname = getOnboardingPage(OnboardingPage.Welcome);
|
||||||
|
/**
|
||||||
|
* Due to the special of Google Tag, it should be `true` only in the Logto Cloud production environment.
|
||||||
|
* Add the leading '.' to make it safer (ignore hostnames like "foologto.io").
|
||||||
|
*/
|
||||||
|
const shouldReportToGtag = window.location.hostname.endsWith('.' + logtoProductionHostname);
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const swrOptions = useSwrOptions();
|
const swrOptions = useSwrOptions();
|
||||||
|
@ -37,6 +44,27 @@ function App() {
|
||||||
};
|
};
|
||||||
}, [setThemeOverride]);
|
}, [setThemeOverride]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This `useEffect()` initiates Google Tag and report a sign-up conversion to it.
|
||||||
|
* It may run multiple times (e.g. a user visit multiple times to finish the onboarding process,
|
||||||
|
* which rarely happens), but it'll be okay since we've set conversion's "Count" to "One"
|
||||||
|
* which means only the first interaction is valuable.
|
||||||
|
*
|
||||||
|
* Track this conversion in the backend has been considered, but Google does not provide
|
||||||
|
* a clear guideline for it and marks the [Node library](https://developers.google.com/tag-platform/tag-manager/api/v2/libraries)
|
||||||
|
* as "alpha" which looks unreliable.
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
if (shouldReportToGtag) {
|
||||||
|
gtag('js', new Date());
|
||||||
|
gtag('config', gtagAwTrackingId);
|
||||||
|
gtag('event', 'conversion', {
|
||||||
|
send_to: gtagSignUpConversionId,
|
||||||
|
});
|
||||||
|
console.debug('Google Tag event fires');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: { questionnaire },
|
data: { questionnaire },
|
||||||
isLoaded,
|
isLoaded,
|
||||||
|
@ -52,6 +80,15 @@ function App() {
|
||||||
<div className={styles.app}>
|
<div className={styles.app}>
|
||||||
<SWRConfig value={swrOptions}>
|
<SWRConfig value={swrOptions}>
|
||||||
<AppBoundary>
|
<AppBoundary>
|
||||||
|
{shouldReportToGtag && (
|
||||||
|
<Helmet>
|
||||||
|
<script
|
||||||
|
async
|
||||||
|
crossOrigin="anonymous"
|
||||||
|
src={`https://www.googletagmanager.com/gtag/js?id=${gtagAwTrackingId}`}
|
||||||
|
/>
|
||||||
|
</Helmet>
|
||||||
|
)}
|
||||||
<Toast />
|
<Toast />
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route index element={<Navigate replace to={welcomePathname} />} />
|
<Route index element={<Navigate replace to={welcomePathname} />} />
|
||||||
|
|
|
@ -11,3 +11,8 @@ export const emailUsLink = buildUrl(contactEmailLink, {
|
||||||
|
|
||||||
export const logtoBlogLink = 'https://docs.logto.io/blog?utm_source=console';
|
export const logtoBlogLink = 'https://docs.logto.io/blog?utm_source=console';
|
||||||
export const aboutCloudPreviewLink = 'https://docs.logto.io/about/cloud-preview?utm_source=console';
|
export const aboutCloudPreviewLink = 'https://docs.logto.io/about/cloud-preview?utm_source=console';
|
||||||
|
|
||||||
|
export const gtagAwTrackingId = 'AW-11124811245';
|
||||||
|
/** This ID indicates a user has truly signed up for Logto Cloud. */
|
||||||
|
export const gtagSignUpConversionId = `${gtagAwTrackingId}/RVejCKC65qMYEO3L3Lgp`;
|
||||||
|
export const logtoProductionHostname = 'logto.io';
|
||||||
|
|
|
@ -5,3 +5,15 @@ import { OnboardingRoute } from './types';
|
||||||
|
|
||||||
export const getOnboardingPage = (page: OnboardingPage) =>
|
export const getOnboardingPage = (page: OnboardingPage) =>
|
||||||
joinPath(OnboardingRoute.Onboarding, page);
|
joinPath(OnboardingRoute.Onboarding, page);
|
||||||
|
|
||||||
|
/** This function is updated from the Google Tag official code snippet. */
|
||||||
|
export function gtag(..._: unknown[]) {
|
||||||
|
if (!window.dataLayer) {
|
||||||
|
// eslint-disable-next-line @silverhand/fp/no-mutation
|
||||||
|
window.dataLayer = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// We cannot use rest params here since gtag has some internal logic about `arguments` for data transpiling
|
||||||
|
// eslint-disable-next-line @silverhand/fp/no-mutating-methods, prefer-rest-params
|
||||||
|
window.dataLayer.push(arguments);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue