11import Cookies from 'js-cookie' ;
22import { useEffect } from 'react' ;
33import { v4 as uuidv4 } from 'uuid' ;
4- import { useAppContext } from './contexts/appContext' ;
54
5+ import { useAppContext } from './contexts/appContext' ;
66const GA_TRACKING_ID = 'G-72XG3F8LNJ' ; // This is Hashnode's GA tracking ID
77const isProd = process . env . NEXT_PUBLIC_MODE === 'production' ;
88const BASE_PATH = process . env . NEXT_PUBLIC_BASE_URL || '' ;
99
1010export const Analytics = ( ) => {
11- const { publication, post } = useAppContext ( ) ;
12-
13- useEffect ( ( ) => {
14- if ( ! isProd ) return ;
15-
16- _sendPageViewsToHashnodeGoogleAnalytics ( ) ;
17- _sendViewsToHashnodeInternalAnalytics ( ) ;
18- _sendViewsToHashnodeAnalyticsDashboard ( ) ;
19- } , [ ] ) ;
20-
21- if ( ! isProd ) return null ;
11+ const { publication, post, series, page } = useAppContext ( ) ;
2212
2313 const _sendPageViewsToHashnodeGoogleAnalytics = ( ) => {
2414 // @ts -ignore
@@ -27,7 +17,6 @@ export const Analytics = () => {
2717 first_party_collection : true ,
2818 } ) ;
2919 } ;
30-
3120 const _sendViewsToHashnodeInternalAnalytics = async ( ) => {
3221 // Send to Hashnode's own internal analytics
3322 const event : Record < string , string | number | object > = {
@@ -42,17 +31,14 @@ export const Analytics = () => {
4231 referrer : window . document . referrer ,
4332 } ,
4433 } ;
45-
4634 let deviceId = Cookies . get ( '__amplitudeDeviceID' ) ;
4735 if ( ! deviceId ) {
4836 deviceId = uuidv4 ( ) ;
4937 Cookies . set ( '__amplitudeDeviceID' , deviceId , {
5038 expires : 365 * 2 ,
5139 } ) ; // expire after two years
5240 }
53-
5441 event [ 'device_id' ] = deviceId ;
55-
5642 await fetch ( `${ BASE_PATH } /ping/data-event` , {
5743 method : 'POST' ,
5844 headers : {
@@ -61,7 +47,6 @@ export const Analytics = () => {
6147 body : JSON . stringify ( { events : [ event ] } ) ,
6248 } ) ;
6349 } ;
64-
6550 const _sendViewsToHashnodeAnalyticsDashboard = async ( ) => {
6651 const LOCATION = window . location ;
6752 const NAVIGATOR = window . navigator ;
@@ -72,21 +57,17 @@ export const Analytics = () => {
7257 LOCATION . pathname +
7358 LOCATION . search +
7459 LOCATION . hash ;
75-
7660 const query = new URL ( currentFullURL ) . searchParams ;
77-
7861 const utm_id = query . get ( 'utm_id' ) ;
7962 const utm_campaign = query . get ( 'utm_campaign' ) ;
8063 const utm_source = query . get ( 'utm_source' ) ;
8164 const utm_medium = query . get ( 'utm_medium' ) ;
8265 const utm_term = query . get ( 'utm_term' ) ;
8366 const utm_content = query . get ( 'utm_content' ) ;
84-
8567 let referrer = document . referrer || '' ;
8668 if ( referrer . indexOf ( window . location . hostname ) !== - 1 ) {
8769 referrer = '' ;
8870 }
89-
9071 const data = {
9172 publicationId : publication . id ,
9273 postId : post && post . id ,
@@ -107,27 +88,6 @@ export const Analytics = () => {
10788 utm_content,
10889 } ;
10990
110- // send to Umami powered advanced Hashnode analytics
111- if ( publication . integrations ?. umamiWebsiteUUID ) {
112- await fetch ( `${ BASE_PATH } /api/collect` , {
113- method : 'POST' ,
114- headers : {
115- 'Content-Type' : 'application/json' ,
116- } ,
117- body : JSON . stringify ( {
118- payload : {
119- website : publication . integrations . umamiWebsiteUUID ,
120- url : window . location . pathname ,
121- referrer : referrer ,
122- hostname : window . location . hostname ,
123- language : NAVIGATOR . language ,
124- screen : `${ window . screen . width } x${ window . screen . height } ` ,
125- } ,
126- type : 'pageview' ,
127- } ) ,
128- } ) ;
129- }
130-
13191 // For Hashnode Blog Dashboard Analytics
13292 fetch ( `${ BASE_PATH } /ping/view` , {
13393 method : 'POST' ,
@@ -138,5 +98,91 @@ export const Analytics = () => {
13898 } ) ;
13999 } ;
140100
101+ function _sendViewsToAdvancedAnalyticsDashboard ( ) {
102+ const publicationId = publication . id ;
103+ const postId = post && post . id ;
104+ const seriesId = series ?. id || post ?. series ?. id ;
105+ const staticPageId = page && page . id ;
106+
107+ const data = {
108+ publicationId,
109+ postId,
110+ seriesId,
111+ staticPageId,
112+ } ;
113+
114+ if ( ! publicationId ) {
115+ console . warn ( 'Publication ID is missing; could not send analytics.' ) ;
116+ return ;
117+ }
118+
119+ const isBrowser = typeof window !== 'undefined' ;
120+ if ( ! isBrowser ) {
121+ return ;
122+ }
123+
124+ const isLocalhost = window . location . hostname === 'localhost' ;
125+ if ( isLocalhost ) {
126+ console . warn (
127+ 'Analytics API call is skipped because you are running on localhost; data:' ,
128+ data ,
129+ ) ;
130+ return ;
131+ }
132+
133+ const event = {
134+ // timestamp will be added in API
135+ payload : {
136+ publicationId,
137+ postId : postId || null ,
138+ seriesId : seriesId || null ,
139+ pageId : staticPageId || null ,
140+ url : window . location . href ,
141+ referrer : document . referrer || null ,
142+ language : navigator . language || null ,
143+ screen : `${ window . screen . width } x${ window . screen . height } ` ,
144+ } ,
145+ type : 'pageview' ,
146+ } ;
147+
148+ const blob = new Blob (
149+ [
150+ JSON . stringify ( {
151+ events : [ event ] ,
152+ } ) ,
153+ ] ,
154+ {
155+ type : 'application/json; charset=UTF-8' ,
156+ } ,
157+ ) ;
158+
159+ let hasSentBeacon = false ;
160+ try {
161+ if ( navigator . sendBeacon ) {
162+ hasSentBeacon = navigator . sendBeacon ( `/api/analytics` , blob ) ;
163+ }
164+ } catch ( error ) {
165+ // do nothing; in case there is an error we fall back to fetch
166+ }
167+
168+ if ( ! hasSentBeacon ) {
169+ fetch ( `/api/analytics` , {
170+ method : 'POST' ,
171+ body : blob ,
172+ credentials : 'omit' ,
173+ keepalive : true ,
174+ } ) ;
175+ }
176+ }
177+
178+ useEffect ( ( ) => {
179+ if ( ! isProd ) return ;
180+
181+ _sendPageViewsToHashnodeGoogleAnalytics ( ) ;
182+ _sendViewsToHashnodeInternalAnalytics ( ) ;
183+ _sendViewsToHashnodeAnalyticsDashboard ( ) ;
184+ _sendViewsToAdvancedAnalyticsDashboard ( ) ;
185+ } , [ ] ) ;
186+
141187 return null ;
142- } ;
188+ } ;
0 commit comments