From 681b3fedbcd9dce4d0fa51fe98227c16c2e8f09d Mon Sep 17 00:00:00 2001 From: Vadorequest Date: Wed, 9 Jun 2021 15:24:54 +0200 Subject: [PATCH] Send network speed to Amplitude analytics and Sentry (#358) --- src/app/components/BrowserPageBootstrap.tsx | 12 +++ src/modules/core/amplitude/amplitude.ts | 23 +++++- .../networkInformation/networkInformation.ts | 77 +++++++++++++++++++ .../demo/built-in-utilities/hooks.tsx | 12 +++ 4 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 src/modules/core/networkInformation/networkInformation.ts diff --git a/src/app/components/BrowserPageBootstrap.tsx b/src/app/components/BrowserPageBootstrap.tsx index c1f05f56..4710c68c 100644 --- a/src/app/components/BrowserPageBootstrap.tsx +++ b/src/app/components/BrowserPageBootstrap.tsx @@ -8,6 +8,12 @@ import useDataset from '@/modules/core/data/hooks/useDataset'; import { Customer } from '@/modules/core/data/types/Customer'; import { detectLightHouse } from '@/modules/core/lightHouse/lighthouse'; import { createLogger } from '@/modules/core/logging/logger'; +import { + ClientNetworkConnectionType, + ClientNetworkInformationSpeed, + getClientNetworkConnectionType, + getClientNetworkInformationSpeed, +} from '@/modules/core/networkInformation/networkInformation'; import { configureSentryUser } from '@/modules/core/sentry/sentry'; import { cypressContext } from '@/modules/core/testing/contexts/cypressContext'; import { @@ -81,6 +87,8 @@ const BrowserPageBootstrap = (props: BrowserPageBootstrapProps): JSX.Element => const theme = useTheme(); const isCypressRunning = detectCypress(); const isLightHouseRunning = detectLightHouse(); + const networkSpeed: ClientNetworkInformationSpeed = getClientNetworkInformationSpeed(); + const networkConnectionType: ClientNetworkConnectionType = getClientNetworkConnectionType(); // Configure Sentry user and track navigation through breadcrumb configureSentryUser(userSession); @@ -103,6 +111,8 @@ const BrowserPageBootstrap = (props: BrowserPageBootstrapProps): JSX.Element => locale, userId, userConsent, + networkSpeed, + networkConnectionType, }); // Init the Cookie Consent popup, which will open on the browser @@ -175,6 +185,8 @@ const BrowserPageBootstrap = (props: BrowserPageBootstrapProps): JSX.Element => hasUserGivenAnyCookieConsent: hasUserGivenAnyCookieConsent, isCypressRunning, isLightHouseRunning, + networkSpeed, + networkConnectionType, }} // XXX Do not use "userProperties" here, add default user-related properties in getAmplitudeInstance instead // Because "event" had priority over "user event" and will be executed before diff --git a/src/modules/core/amplitude/amplitude.ts b/src/modules/core/amplitude/amplitude.ts index ed6e2d8b..70d338b0 100644 --- a/src/modules/core/amplitude/amplitude.ts +++ b/src/modules/core/amplitude/amplitude.ts @@ -1,4 +1,10 @@ import { createLogger } from '@/modules/core/logging/logger'; +import { + getClientNetworkInformationSpeed, + ClientNetworkInformationSpeed, + ClientNetworkConnectionType, + getClientNetworkConnectionType, +} from '@/modules/core/networkInformation/networkInformation'; import * as Sentry from '@sentry/node'; import { isBrowser } from '@unly/utils'; import { @@ -54,6 +60,8 @@ type GetAmplitudeInstanceProps = { locale: string; userId: string; userConsent: UserConsent; + networkSpeed: ClientNetworkInformationSpeed; + networkConnectionType: ClientNetworkConnectionType; } export const getAmplitudeInstance = (props: GetAmplitudeInstanceProps): AmplitudeClient | null => { @@ -68,6 +76,8 @@ export const getAmplitudeInstance = (props: GetAmplitudeInstanceProps): Amplitud locale, userId, userConsent, + networkSpeed, + networkConnectionType, } = props; const { isUserOptedOutOfAnalytics, @@ -75,6 +85,8 @@ export const getAmplitudeInstance = (props: GetAmplitudeInstanceProps): Amplitud } = userConsent; Sentry.configureScope((scope) => { // See https://www.npmjs.com/package/@sentry/node + scope.setTag('networkSpeed', networkSpeed); + scope.setTag('networkConnectionType', networkConnectionType); scope.setTag('iframe', `${isInIframe}`); scope.setExtra('iframe', isInIframe); scope.setExtra('iframeReferrer', iframeReferrer); @@ -123,6 +135,8 @@ export const getAmplitudeInstance = (props: GetAmplitudeInstanceProps): Amplitud // XXX Learn more about "setOnce" at https://github.com/amplitude/Amplitude-JavaScript/issues/223 visitor.setOnce('initial_lang', lang); // DA Helps figuring out if the initial language (auto-detected) is changed afterwards visitor.setOnce('initial_locale', locale); + visitor.setOnce('initial_networkSpeed', networkSpeed); + visitor.setOnce('initial_networkConnectionType', networkConnectionType); // DA This will help track down the users who discovered our platform because of an iframe visitor.setOnce('initial_iframe', isInIframe); visitor.setOnce('initial_iframeReferrer', iframeReferrer); @@ -131,6 +145,8 @@ export const getAmplitudeInstance = (props: GetAmplitudeInstanceProps): Amplitud visitor.setOnce('customer.ref', customerRef); visitor.setOnce('lang', lang); visitor.setOnce('locale', locale); + visitor.setOnce('networkSpeed', networkSpeed); + visitor.setOnce('networkConnectionType', networkConnectionType); visitor.setOnce('iframe', isInIframe); visitor.setOnce('iframeReferrer', iframeReferrer); @@ -160,11 +176,14 @@ export const sendWebVitals = (report: NextWebVitalsMetricsReport): void => { const amplitudeInstance: AmplitudeClient = amplitude.getInstance(); const universalCookiesManager = new UniversalCookiesManager(); const userData: UserSemiPersistentSession = universalCookiesManager.getUserData(); + const networkSpeed: ClientNetworkInformationSpeed = getClientNetworkInformationSpeed(); + const networkConnectionType: ClientNetworkConnectionType = getClientNetworkConnectionType(); + console.log('networkConnectionType', networkConnectionType) // https://help.amplitude.com/hc/en-us/articles/115001361248#settings-configuration-options amplitudeInstance.init(process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY, null, { // userId: null, - userId: userData.id, + userId: userData?.id, logLevel: process.env.NEXT_PUBLIC_APP_STAGE === 'production' ? 'DISABLE' : 'WARN', includeGclid: false, // GDPR Enabling this is not GDPR compliant and must not be enabled without explicit user consent - See https://croud.com/blog/news/10-point-gdpr-checklist-digital-advertising/ includeReferrer: true, // https://help.amplitude.com/hc/en-us/articles/215131888#track-referrers @@ -197,6 +216,8 @@ export const sendWebVitals = (report: NextWebVitalsMetricsReport): void => { ref: process.env.NEXT_PUBLIC_CUSTOMER_REF, }, report, + networkSpeed, + networkConnectionType, }); // eslint-disable-next-line no-console console.debug('report-web-vitals report sent to Amplitude'); diff --git a/src/modules/core/networkInformation/networkInformation.ts b/src/modules/core/networkInformation/networkInformation.ts new file mode 100644 index 00000000..c6a13dfb --- /dev/null +++ b/src/modules/core/networkInformation/networkInformation.ts @@ -0,0 +1,77 @@ +import { isBrowser } from '@unly/utils'; + +/** + * Connection speed used by the client (browser). + * + * Universal, will return "not-applicable" if executed on the server. + * Not available on all browsers, only a few of them provide such API. + * + * Experimental feature. + * + * @see https://developer.mozilla.org/fr/docs/Web/API/Navigator/connection + * @see https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/effectiveType + */ +export type ClientNetworkInformationSpeed = + '2g' | '3g' | '4g' | 'slow-2g' // Native types + | 'unknown' // When the browser doesn't provide a "Connection" feature + | 'not-applicable'; // Connection speed isn't applicable on the server + +/** + * Connection type used by the client (browser). + * + * Universal, will return "not-applicable" if executed on the server. + * Not available on all browsers, only a few of them provide such API. + * + * Experimental feature. Only supported by Chrome OS as of June 2021. + * + * @see https://developer.mozilla.org/fr/docs/Web/API/Navigator/connection + * @seehttps://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/type + */ +export type ClientNetworkConnectionType = + 'bluetooth' | 'cellular' | 'ethernet' | 'mixed' | 'none' | 'other' | 'unknown' | 'wifi' | 'wimax' + | 'not-applicable'; // Connection type isn't applicable on the server + +/** + * Returns the device's network connection speed. + * + * Meant to be used outside of React components. + * + * XXX If you want to use this in a React component and react to network changes, you should rather use the "useNetworkInformation" hook from "web-api-hooks" library. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/effectiveType + */ +export const getClientNetworkInformationSpeed = (): ClientNetworkInformationSpeed => { + let networkInformation; + + if (isBrowser()) { + // @ts-ignore Experimental feature, not described in TypeScript at the moment + networkInformation = navigator?.connection || navigator?.mozConnection || navigator?.webkitConnection; + } else { + return 'not-applicable'; + } + + return networkInformation?.effectiveType || 'unknown'; +}; + +/** + * Returns the device's network connection type. + * + * Meant to be used outside of React components. + * + * XXX If you want to use this in a React component and react to network changes, you should rather use the "useNetworkInformation" hook from "web-api-hooks" library. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/type + */ +export const getClientNetworkConnectionType = (): ClientNetworkConnectionType => { + let networkInformation; + + if (isBrowser()) { + // @ts-ignore Experimental feature, not described in TypeScript at the moment + networkInformation = navigator?.connection || navigator?.mozConnection || navigator?.webkitConnection; + } else { + return 'not-applicable'; + } + console.log('networkInformation', networkInformation); + + return networkInformation?.type; +}; diff --git a/src/pages/[locale]/demo/built-in-utilities/hooks.tsx b/src/pages/[locale]/demo/built-in-utilities/hooks.tsx index 7fe1a65d..fae4d376 100644 --- a/src/pages/[locale]/demo/built-in-utilities/hooks.tsx +++ b/src/pages/[locale]/demo/built-in-utilities/hooks.tsx @@ -189,6 +189,18 @@ const HooksPage: NextPage = (props): JSX.Element => {
+ + If you want to add more utility hooks, here are some famous open-source projects you might want to check out: +
    +
  • + state-hooks +
  • +
  • + web-api-hooks +
  • +
+
+ );