Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 83 additions & 46 deletions gatsby/onInitialClientRender.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,98 @@ import loadScript from "load-script"
import { store } from "./wrapRootElement"

export const onInitialClientRender = () => {
loadScript(`https://apis.google.com/js/api.js`, err => {
if (err) {
console.error("Could not load gapi")
return
}
const gapiPromise = new Promise((resolve, reject) => {
loadScript("https://apis.google.com/js/api.js", err => {
if (err) {
reject(err)
} else {
window.gapi.load("client", () => {
resolve(window.gapi)
})
}
})
})

const gisPromise = new Promise((resolve, reject) => {
loadScript("https://accounts.google.com/gsi/client", err => {
if (err) {
reject(err)
} else resolve(window.google)
})
})

Promise.all([gapiPromise, gisPromise])
.then(([gapi, google]) => {
const SCOPES = ["https://www.googleapis.com/auth/analytics.readonly"]
const clientId = process.env.GAPI_CLIENT_ID

var SCOPES = ["https://www.googleapis.com/auth/analytics.readonly"]
if (!clientId) {
console.error(
"GAPI_CLIENT_ID is not defined. Please check your .env file."
)
store.dispatch({ type: "gapiStatus", status: "cannot initialize" })
return
}

const clientId = process.env.GAPI_CLIENT_ID
// Google Sign-In previously helped manage user signed-in status
// With GIS we are responsible for managing sign-in state
try {
const storedTokenString = localStorage.getItem("google_token")
if (storedTokenString) {
const storedToken = JSON.parse(storedTokenString)
if (storedToken.expires_at > Date.now()) {
gapi.client.setToken(storedToken)
store.dispatch({ type: "setToken", token: storedToken })
} else {
localStorage.removeItem("google_token")
}
}
} catch (e) {
console.error("Unable to restore token from localStorage:", e)
localStorage.removeItem("google_token")
}

// TODO - Remove :analytics and replace it with the discovery document.
window.gapi.load("client:auth2:analytics", () => {
Promise.all([
window.gapi.client.load(
"https://analyticsreporting.googleapis.com/$discovery/rest?version=v4"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we can now remove the loading of the Analytics Reporting API v4 discovery document since it looks like it was for Universal Analytics accounts only and is no longer used in the project (searched for gapi.client.analyticsreporting)

),
window.gapi.client.load(
gapi.client.load(
"https://analyticsdata.googleapis.com/$discovery/rest"
),
window.gapi.client.load(
gapi.client.load(
"https://analyticsadmin.googleapis.com/$discovery/rest"
),
]).then(() => {
window.gapi.client
.init({
])
.then(() => {
// Replace gapi.auth2.init() with google.accounts.oauth2.initTokenClient()
const tokenClient = google.accounts.oauth2.initTokenClient({
client_id: clientId,
scope: SCOPES.join(" "),
clientId,
})
.then(() => {
store.dispatch({ type: "setGapi", gapi: window.gapi })
const user = window.gapi.auth2.getAuthInstance().currentUser.get()
store.dispatch({
type: "setUser",
user: user.isSignedIn() ? user : undefined,
})
window.gapi.auth2.getAuthInstance().currentUser.listen(user => {
store.dispatch({
type: "setUser",
user: user.isSignedIn() ? user : undefined,
})
})
})
.catch(e => {
store.dispatch({ type: "setGapi", gapi: window.gapi })
store.dispatch({
type: "setUser",
user: undefined,
})
store.dispatch({
type: "gapiStatus",
status: "cannot initialize",
})
console.error(e)
callback: tokenResponse => {
if (tokenResponse && tokenResponse.access_token) {
const tokenWithExpiry = {
...tokenResponse,
expires_at: Date.now() + tokenResponse.expires_in * 1000,
}
localStorage.setItem("google_token", JSON.stringify(tokenWithExpiry))
gapi.client.setToken(tokenResponse)
store.dispatch({ type: "setToken", token: tokenResponse })
} else {
store.dispatch({ type: "setToken", token: undefined })
}
}
})
}, console.error)

store.dispatch({ type: "setGapi", gapi })
store.dispatch({ type: "gapiStatus", status: "initialized" })
store.dispatch({ type: "setGoogle", google })
store.dispatch({ type: "setTokenClient", tokenClient })
})
.catch(e => {
store.dispatch({ type: "setGapi", gapi })
store.dispatch({ type: "setToken", token: undefined })
store.dispatch({ type: "gapiStatus", status: "cannot initialize" })
console.error(e)
})
})
.catch(e => {
console.error(e)
})
})
}
18 changes: 13 additions & 5 deletions gatsby/wrapRootElement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,31 @@ import {PartialDeep} from 'type-fest';

type State =
{
user?: {},
token?: {},
gapi?: PartialDeep<typeof gapi>,
google?: any,
tokenClient?: any,
toast?: string,
status?: string
gapiStatus?: string
}

type Action =
| { type: 'setUser', user: {} | undefined }
| { type: 'setToken', token: {} | undefined }
| { type: 'setGapi', gapi: PartialDeep<typeof gapi> | undefined }
| { type: 'setGoogle', google: any }
| { type: 'setTokenClient', tokenClient: any }
| { type: 'setToast', toast: string | undefined }
| { type: 'gapiStatus', status: string | undefined };
const reducer = (state: State = {}, action: Action) => {
switch (action.type) {
case "setUser":
return { ...state, user: action.user }
case "setToken":
return { ...state, token: action.token }
case "setGapi":
return { ...state, gapi: action.gapi }
case "setGoogle":
return { ...state, google: action.google }
case "setTokenClient":
return { ...state, tokenClient: action.tokenClient }
case "setToast":
return { ...state, toast: action.toast }
case "gapiStatus":
Expand Down
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,9 @@
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^13.1.8",
"@types/gapi": "^0.0.39",
"@types/gapi.auth2": "^0.0.54",
"@types/gapi.client.analytics": "^3.0.7",
"@types/gapi.client.analyticsadmin": "^1.0.0",
"@types/gapi.client.analyticsdata": "^1.0.2",
"@types/gapi.client.analyticsreporting": "^4.0.3",
"@types/gtag.js": "^0.0.5",
"@types/jest": "^26.0.23",
"@types/node": "^18.16.5",
Expand Down
2 changes: 1 addition & 1 deletion src/components/Layout/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { UserStatus } from "./useLogin"

interface LoginProps {
className?: string
user: gapi.auth2.GoogleUser | undefined
user: any
userStatus: UserStatus
login: () => void
logout: () => void
Expand Down
2 changes: 1 addition & 1 deletion src/components/Layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
userStatus?: UserStatus
login?: () => void
logout?: () => void
user?: gapi.auth2.GoogleUser
user?: any
}

const PREFIX = 'Layout2';
Expand Down Expand Up @@ -117,7 +117,7 @@
user,
}) => {
usePageView(title)
const { gaVersion, setGAVersion } = useGAVersion(pathname)

Check warning on line 120 in src/components/Layout/index.tsx

View workflow job for this annotation

GitHub Actions / build (18.x)

'setGAVersion' is assigned a value but never used

const [open, setOpen] = React.useState(false)

Expand Down
40 changes: 19 additions & 21 deletions src/components/Layout/useLogin.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Requestable, RequestStatus } from "@/types"
import { useState, useEffect, useCallback } from "react"

import { useSelector } from "react-redux"
import { useSelector, useDispatch } from "react-redux"

export enum UserStatus {
SignedIn,
Expand All @@ -11,7 +11,7 @@ export enum UserStatus {

interface Successful {
userStatus: UserStatus
user: gapi.auth2.GoogleUser | undefined
user: any
logout: () => void
login: () => void
}
Expand All @@ -21,24 +21,30 @@ interface Failed {
}
const useLogin = (): Requestable<Successful, {}, InProgress, Failed> => {
const [requestStatus, setRequestStatus] = useState(RequestStatus.NotStarted)
const user = useSelector((state: AppState) => state.user)
const token = useSelector((state: AppState) => state.token)
const gapi = useSelector((state: AppState) => state.gapi)
const gapiStatus = useSelector((state: AppState) => state.gapiStatus)
const [userStatus, setUserStatus] = useState<UserStatus>(UserStatus.Pending)
const tokenClient = useSelector((state: AppState) => state.tokenClient)
const google = useSelector((state: AppState) => state.google)
const dispatch = useDispatch()
const userStatus = token ? UserStatus.SignedIn : UserStatus.SignedOut

const login = useCallback(() => {
if (gapi === undefined) {
return
if (tokenClient) {
tokenClient.requestAccessToken()
}
gapi.auth2.getAuthInstance().signIn()
}, [gapi])
}, [tokenClient])

const logout = useCallback(() => {
if (gapi === undefined) {
return
const token = gapi?.client.getToken()
if (token && google) {
google.accounts.oauth2.revoke(token.access_token, () => {
gapi?.client.setToken(null)
dispatch({ type: "setToken", token: undefined })
localStorage.removeItem("google_token")
})
}
gapi.auth2.getAuthInstance().signOut()
}, [gapi])
}, [gapi, google, dispatch])

useEffect(() => {
if (gapiStatus === "cannot initialize") {
Expand All @@ -61,19 +67,11 @@ const useLogin = (): Requestable<Successful, {}, InProgress, Failed> => {
setRequestStatus(RequestStatus.InProgress)
}

gapi.auth2.getAuthInstance().isSignedIn.listen(signedIn => {
setUserStatus(signedIn ? UserStatus.SignedIn : UserStatus.SignedOut)
})

gapi.auth2.getAuthInstance().isSignedIn.get()
? setUserStatus(UserStatus.SignedIn)
: setUserStatus(UserStatus.SignedOut)

setRequestStatus(RequestStatus.Successful)
}, [gapi, requestStatus, gapiStatus])

if (requestStatus === RequestStatus.Successful) {
return { status: requestStatus, userStatus, user, login, logout }
return { status: requestStatus, userStatus, user: token, login, logout }
}
if (requestStatus === RequestStatus.Failed) {
return { status: requestStatus, message: gapiStatus || "unknown" }
Expand Down
4 changes: 3 additions & 1 deletion src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ declare module "*.svg" {
declare module "*.module.css";

declare interface AppState {
user?: gapi.auth2.GoogleUser
token?: Token
gapi?: typeof gapi
gapiStatus?: string
measurementID: string
tokenClient?: any
google?: typeof google
}
8 changes: 4 additions & 4 deletions src/test-utils/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ export const wrapperFor = ({
const store = makeStore()

if (isLoggedIn) {
store.dispatch({ type: "setUser", user: {} })
store.dispatch({ type: "setToken", token: {} })
} else {
store.dispatch({ type: "setUser", user: undefined })
store.dispatch({ type: "setToken", token: undefined })
}

const gapi = testGapi(gapiMocks)
Expand Down Expand Up @@ -91,9 +91,9 @@ export const withProviders = (
const store = makeStore()

if (isLoggedIn) {
store.dispatch({ type: "setUser", user: {} })
store.dispatch({ type: "setToken", token: {} })
} else {
store.dispatch({ type: "setUser", user: undefined })
store.dispatch({ type: "setToken", token: undefined })
}

const gapi = testGapi(gapiMocks)
Expand Down
2 changes: 0 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,9 @@
"jest",
"@testing-library/jest-dom",
"@types/gapi",
"@types/gapi.auth2",
"@types/gapi.client.analytics",
"@types/gapi.client.analyticsadmin",
"@types/gapi.client.analyticsdata",
"@types/gapi.client.analyticsreporting",
"@types/gtag.js"
],
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
Expand Down
Loading