Skip to content

Commit f0210e3

Browse files
committed
migrate to google identity service library for auth
1 parent 280874b commit f0210e3

7 files changed

Lines changed: 142 additions & 109 deletions

File tree

gatsby/onInitialClientRender.js

Lines changed: 91 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,107 @@
11
import loadScript from "load-script"
22
import { store } from "./wrapRootElement"
33

4+
const addTokenListener = gapi => {
5+
let token = gapi.client.getToken()
6+
let tokenListener = _token => {}
7+
8+
Object.defineProperty(gapi.client, "token", {
9+
get: () => token,
10+
set: newToken => {
11+
token = newToken
12+
tokenListener(token)
13+
},
14+
})
15+
16+
gapi.client.setTokenListener = listener => {
17+
tokenListener = listener
18+
}
19+
}
20+
21+
const handleCredentialResponse = response => {
22+
// The response object contains the JWT ID token in the `credential` field.
23+
// We can decode it to get user information.
24+
const user = JSON.parse(atob(response.credential.split(".")[1]))
25+
console.log("user", user)
26+
store.dispatch({ type: "setUser", user })
27+
}
28+
429
export const onInitialClientRender = () => {
5-
loadScript(`https://apis.google.com/js/api.js`, err => {
6-
if (err) {
7-
console.error("Could not load gapi")
8-
return
9-
}
30+
const gapiPromise = new Promise((resolve, reject) => {
31+
loadScript("https://apis.google.com/js/api.js", err => {
32+
if (err) {
33+
reject(err)
34+
} else {
35+
window.gapi.load("client", () => {
36+
addTokenListener(window.gapi)
37+
resolve(window.gapi)
38+
})
39+
}
40+
})
41+
})
1042

11-
var SCOPES = ["https://www.googleapis.com/auth/analytics.readonly"]
43+
const gisPromise = new Promise((resolve, reject) => {
44+
loadScript("https://accounts.google.com/gsi/client", err => {
45+
if (err) {
46+
reject(err)
47+
} else resolve(window.google)
48+
})
49+
})
50+
51+
Promise.all([gapiPromise, gisPromise])
52+
.then(([gapi, google]) => {
53+
const SCOPES = "https://www.googleapis.com/auth/analytics.readonly"
54+
const clientId = process.env.GATSBY_GAPI_CLIENT_ID
1255

13-
const clientId = process.env.GAPI_CLIENT_ID
56+
if (!clientId) {
57+
console.error(
58+
"GATSBY_GAPI_CLIENT_ID is not defined. Please check your .env file."
59+
)
60+
store.dispatch({ type: "gapiStatus", status: "cannot initialize" })
61+
return
62+
}
1463

15-
// TODO - Remove :analytics and replace it with the discovery document.
16-
window.gapi.load("client:auth2:analytics", () => {
1764
Promise.all([
18-
window.gapi.client.load(
19-
"https://analyticsreporting.googleapis.com/$discovery/rest?version=v4"
20-
),
21-
window.gapi.client.load(
65+
gapi.client.load(
2266
"https://analyticsdata.googleapis.com/$discovery/rest"
2367
),
24-
window.gapi.client.load(
68+
gapi.client.load(
2569
"https://analyticsadmin.googleapis.com/$discovery/rest"
2670
),
27-
]).then(() => {
28-
window.gapi.client
29-
.init({
30-
scope: SCOPES.join(" "),
31-
clientId,
71+
])
72+
.then(() => {
73+
const tokenClient = google.accounts.oauth2.initTokenClient({
74+
client_id: clientId,
75+
scope: SCOPES,
76+
callback: tokenResponse => {
77+
gapi.client.setToken(tokenResponse)
78+
},
3279
})
33-
.then(() => {
34-
store.dispatch({ type: "setGapi", gapi: window.gapi })
35-
const user = window.gapi.auth2.getAuthInstance().currentUser.get()
36-
store.dispatch({
37-
type: "setUser",
38-
user: user.isSignedIn() ? user : undefined,
39-
})
40-
window.gapi.auth2.getAuthInstance().currentUser.listen(user => {
41-
store.dispatch({
42-
type: "setUser",
43-
user: user.isSignedIn() ? user : undefined,
44-
})
45-
})
80+
81+
google.accounts.id.initialize({
82+
client_id: clientId,
83+
callback: handleCredentialResponse,
4684
})
47-
.catch(e => {
48-
store.dispatch({ type: "setGapi", gapi: window.gapi })
49-
store.dispatch({
50-
type: "setUser",
51-
user: undefined,
52-
})
53-
store.dispatch({
54-
type: "gapiStatus",
55-
status: "cannot initialize",
56-
})
57-
console.error(e)
85+
86+
gapi.client.setTokenListener(token => {
87+
if (token === null) {
88+
store.dispatch({ type: "setUser", user: undefined })
89+
}
5890
})
59-
}, console.error)
91+
92+
store.dispatch({ type: "setGapi", gapi })
93+
store.dispatch({ type: "setGoogle", google })
94+
store.dispatch({ type: "setTokenClient", tokenClient })
95+
store.dispatch({ type: "gapiStatus", status: "initialized" })
96+
})
97+
.catch(e => {
98+
console.error("gapi client.load error", e)
99+
store.dispatch({ type: "setGapi", gapi: window.gapi })
100+
store.dispatch({ type: "gapiStatus", status: "cannot initialize" })
101+
})
60102
})
61-
})
62-
}
103+
.catch(err => {
104+
console.error("Could not load gapi or gis", err)
105+
store.dispatch({ type: "gapiStatus", status: "cannot initialize" })
106+
})
107+
}

gatsby/wrapRootElement.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,17 @@ type State =
2020
{
2121
user?: {},
2222
gapi?: PartialDeep<typeof gapi>,
23+
google?: any,
24+
tokenClient?: any,
2325
toast?: string,
24-
status?: string
26+
gapiStatus?: string
2527
}
2628

2729
type Action =
2830
| { type: 'setUser', user: {} | undefined }
2931
| { type: 'setGapi', gapi: PartialDeep<typeof gapi> | undefined }
32+
| { type: 'setGoogle', google: any }
33+
| { type: 'setTokenClient', tokenClient: any }
3034
| { type: 'setToast', toast: string | undefined }
3135
| { type: 'gapiStatus', status: string | undefined };
3236
const reducer = (state: State = {}, action: Action) => {
@@ -35,6 +39,10 @@ const reducer = (state: State = {}, action: Action) => {
3539
return { ...state, user: action.user }
3640
case "setGapi":
3741
return { ...state, gapi: action.gapi }
42+
case "setGoogle":
43+
return { ...state, google: action.google }
44+
case "setTokenClient":
45+
return { ...state, tokenClient: action.tokenClient }
3846
case "setToast":
3947
return { ...state, toast: action.toast }
4048
case "gapiStatus":

lib/scripts/check-config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const writeEnvFile = async (
2727
const gapiLine =
2828
config.gapiClientId === SKIP_QUESTION
2929
? undefined
30-
: `GAPI_CLIENT_ID=${config.gapiClientId}`
30+
: `GATSBY_GAPI_CLIENT_ID=${config.gapiClientId}`
3131
const bitlyLine =
3232
config.bitlyClientId === SKIP_QUESTION
3333
? undefined

src/components/Layout/Login.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { UserStatus } from "./useLogin"
77

88
interface LoginProps {
99
className?: string
10-
user: gapi.auth2.GoogleUser | undefined
10+
user: User | undefined
1111
userStatus: UserStatus
1212
login: () => void
1313
logout: () => void
@@ -20,7 +20,7 @@ const Login: React.FC<LoginProps> = ({
2020
login,
2121
logout,
2222
}) => {
23-
if (userStatus === UserStatus.Pending) {
23+
if (userStatus === undefined) {
2424
return null
2525
}
2626
return (

src/components/Layout/index.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ import { ErrorBoundary } from "react-error-boundary"
4949
import ErrorFallback from "../ErrorFallback"
5050

5151

52-
5352
const notMobile = (theme: Theme) => theme.breakpoints.up("md")
5453
const mobile = (theme: Theme) => theme.breakpoints.between(0, "sm")
5554

@@ -69,7 +68,7 @@ interface TemplateProps {
6968
userStatus?: UserStatus
7069
login?: () => void
7170
logout?: () => void
72-
user?: gapi.auth2.GoogleUser
71+
user?: User
7372
}
7473

7574
const PREFIX = 'Layout2';

src/components/Layout/useLogin.ts

Lines changed: 25 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,57 @@
11
import { Requestable, RequestStatus } from "@/types"
2-
import { useState, useEffect, useCallback } from "react"
3-
4-
import { useSelector } from "react-redux"
2+
import { useSelector, useDispatch } from "react-redux"
3+
import { useCallback } from "react"
54

65
export enum UserStatus {
76
SignedIn,
87
SignedOut,
9-
Pending,
108
}
119

1210
interface Successful {
1311
userStatus: UserStatus
14-
user: gapi.auth2.GoogleUser | undefined
12+
user: User | undefined
1513
logout: () => void
1614
login: () => void
1715
}
16+
1817
interface InProgress {}
1918
interface Failed {
2019
message: string
2120
}
21+
2222
const useLogin = (): Requestable<Successful, {}, InProgress, Failed> => {
23-
const [requestStatus, setRequestStatus] = useState(RequestStatus.NotStarted)
2423
const user = useSelector((state: AppState) => state.user)
25-
const gapi = useSelector((state: AppState) => state.gapi)
24+
const tokenClient = useSelector((state: AppState) => state.tokenClient)
2625
const gapiStatus = useSelector((state: AppState) => state.gapiStatus)
27-
const [userStatus, setUserStatus] = useState<UserStatus>(UserStatus.Pending)
26+
const gapi = useSelector((state: AppState) => state.gapi)
27+
const google = useSelector((state: AppState) => state.google)
28+
29+
const userStatus = user ? UserStatus.SignedIn : UserStatus.SignedOut
2830

2931
const login = useCallback(() => {
30-
if (gapi === undefined) {
31-
return
32+
if (tokenClient) {
33+
tokenClient.requestAccessToken({})
3234
}
33-
gapi.auth2.getAuthInstance().signIn()
34-
}, [gapi])
35+
}, [tokenClient])
3536

3637
const logout = useCallback(() => {
37-
if (gapi === undefined) {
38-
return
38+
const token = gapi?.client.getToken()
39+
if (token !== null && token !== undefined) {
40+
google.accounts.auth2.revoke(token.access_token, () => {
41+
gapi?.client.setToken(null)
42+
})
3943
}
40-
gapi.auth2.getAuthInstance().signOut()
4144
}, [gapi])
4245

43-
useEffect(() => {
44-
if (gapiStatus === "cannot initialize") {
45-
setRequestStatus(RequestStatus.Failed)
46-
return
47-
}
48-
if (gapi === undefined) {
49-
return
50-
}
51-
52-
if (
53-
requestStatus === RequestStatus.Successful ||
54-
requestStatus === RequestStatus.Failed ||
55-
requestStatus === RequestStatus.InProgress
56-
) {
57-
return
58-
}
59-
60-
if (requestStatus === RequestStatus.NotStarted) {
61-
setRequestStatus(RequestStatus.InProgress)
62-
}
63-
64-
gapi.auth2.getAuthInstance().isSignedIn.listen(signedIn => {
65-
setUserStatus(signedIn ? UserStatus.SignedIn : UserStatus.SignedOut)
66-
})
67-
68-
gapi.auth2.getAuthInstance().isSignedIn.get()
69-
? setUserStatus(UserStatus.SignedIn)
70-
: setUserStatus(UserStatus.SignedOut)
71-
72-
setRequestStatus(RequestStatus.Successful)
73-
}, [gapi, requestStatus, gapiStatus])
74-
75-
if (requestStatus === RequestStatus.Successful) {
76-
return { status: requestStatus, userStatus, user, login, logout }
46+
if (gapiStatus === "cannot initialize") {
47+
return { status: RequestStatus.Failed, message: "gapi failed to initialize" }
7748
}
78-
if (requestStatus === RequestStatus.Failed) {
79-
return { status: requestStatus, message: gapiStatus || "unknown" }
49+
50+
if (gapiStatus !== "initialized" || !tokenClient) {
51+
return { status: RequestStatus.InProgress }
8052
}
8153

82-
return { status: requestStatus }
54+
return { status: RequestStatus.Successful, userStatus, user, login, logout }
8355
}
8456

85-
export default useLogin
57+
export default useLogin

src/global.d.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,20 @@ declare module "*.svg" {
2020
export default value
2121
}
2222

23-
declare module "*.module.css";
23+
declare module "*.module.css"
2424

25+
interface User {
26+
name: string
27+
email: string
28+
picture: string
29+
}
30+
31+
// Define the global AppState.
2532
declare interface AppState {
26-
user?: gapi.auth2.GoogleUser
27-
gapi?: typeof gapi
28-
gapiStatus?: string
33+
gapi: typeof gapi | undefined
34+
google: typeof google | undefined
35+
gapiStatus: string | undefined
36+
user: User | undefined
2937
measurementID: string
38+
tokenClient: google.accounts.oauth2.TokenClient | undefined
3039
}

0 commit comments

Comments
 (0)