1- import { loadScript , log } from '@guardian/libs' ;
1+ import { log } from '@guardian/libs' ;
22import { useEffect } from 'react' ;
33import { useIsSignedIn } from '../lib/useAuthStatus' ;
4-
5- const getGSIConfiguration = ( ) : { clientId : string ; loginUri : string } => {
6- switch ( window . guardian . config . stage ) {
7- case 'PROD' :
8- return {
9- clientId : 'PROD CLIENT ID' ,
10- loginUri : 'https://profile.thegulocal.com/signin/google' ,
11- } ;
12- case 'CODE' :
13- return {
14- clientId : 'CODE CLIENT ID' ,
15- loginUri : 'https://profile.thegulocal.com/signin/google' ,
16- } ;
4+ import type { StageType } from '../types/config' ;
5+
6+ const getFedCMProviders = ( stage : StageType ) : IdentityProviderConfig [ ] => {
7+ switch ( stage ) {
8+ // case 'PROD':
9+ // return [
10+ // {
11+ // configURL: 'https://accounts.google.com/gsi/fedcm.json',
12+ // clientId: '774465807556.apps.googleusercontent.com',
13+ // },
14+ // ];
15+ // case 'CODE':
16+ // return [
17+ // {
18+ // configURL: 'https://accounts.google.com/gsi/fedcm.json',
19+ // clientId: '774465807556-pkevncqpfs9486ms0bo5q1f2g9vhpior.apps.googleusercontent.com',
20+ // },
21+ // ];
1722 default :
18- return {
19- clientId :
20- '774465807556-pkevncqpfs9486ms0bo5q1f2g9vhpior.apps.googleusercontent.com' ,
21- loginUri : 'https://profile.thegulocal.com/signin/google' ,
22- } ;
23+ return [
24+ {
25+ configURL : 'https://accounts.google.com/gsi/fedcm.json' ,
26+ clientId :
27+ '774465807556-pkevncqpfs9486ms0bo5q1f2g9vhpior.apps.googleusercontent.com' ,
28+ } ,
29+ ] ;
2330 }
2431} ;
2532
26- const initializeGoogleOneTap = ( ) => ( response : { credential : string } ) => {
27- const { credential } = response ;
28-
29- const queryParams = new URLSearchParams ( ) ;
30- queryParams . append ( 'got' , credential ) ;
31- queryParams . append ( 'returnUrl' , window . location . href ) ;
32-
33- window . location . replace (
34- `https://profile.thegulocal.com/signin/google?${ queryParams . toString ( ) } ` ,
35- ) ;
33+ type IdentityCredentials = {
34+ token : string ;
3635} ;
3736
38- type PromptMomentNotification = {
39- isSkippedMoment : ( ) => boolean ;
40- isDismissedMoment : ( ) => boolean ;
41- getDismissedReason : ( ) =>
42- | 'credential_returned'
43- | 'cancel_called'
44- | 'flow_restarted' ;
45- getMomentType : ( ) => 'display' | 'skipped' | 'dismissed' ;
37+ type IdentityProviderConfig = {
38+ configURL : string ;
39+ clientId : string ;
4640} ;
4741
48- export type GoogleIdentityService = {
49- initialize : ( config : {
50- client_id : string ;
51- login_uri : string ;
52- callback : ( response : { credential : string } ) => void ;
53- auto_select ?: boolean ;
54- cancel_on_tap_outside ?: boolean ;
55- use_fedcm_for_prompt ?: boolean ;
56- } ) => void ;
57- prompt : (
58- callback : ( momentNotification : PromptMomentNotification ) => void ,
59- ) => void ;
60- } ;
61-
62- const loadGSI = async ( ) : Promise < GoogleIdentityService > => {
63- log ( 'identity' , 'Loading Google Sign-in Services (GSI)' ) ;
64- // TODO: Can we invoke the built-in FedCM API instead of using GSI?
65- // This would reduce our dpenedency on a third-party library and all of the privacy
66- // implications that come with it. It would also save loading ~80KB of JS.
67- await loadScript ( 'https://accounts.google.com/gsi/client' ) . catch ( ( e ) => {
68- throw new Error (
69- `Failed to initialize Google One Tap: failed to load GSI` ,
70- { cause : e } ,
71- ) ;
72- } ) ;
73-
74- if ( ! window . google ?. accounts ?. id ) {
75- throw new Error ( 'Failed to initialize Google One Tap: GSI not found' ) ;
76- }
77-
78- log ( 'identity' , 'Loaded Google Sign-in Services (GSI)' ) ;
79- return window . google . accounts . id ;
42+ type CredentialsProvider = {
43+ get : ( options : {
44+ identity : {
45+ context : 'signin' ;
46+ providers : IdentityProviderConfig [ ] ;
47+ } ;
48+ } ) => Promise < IdentityCredentials > ;
8049} ;
8150
8251export const GoogleOneTap = ( ) => {
@@ -98,34 +67,44 @@ export const GoogleOneTap = () => {
9867 return ;
9968 }
10069
101- const { clientId, loginUri } = getGSIConfiguration ( ) ;
102-
103- void loadGSI ( ) . then ( ( gsi ) => {
104- log ( 'identity' , 'Initializing Google One Tap' , {
105- clientId,
106- loginUri,
107- } ) ;
108-
109- gsi . initialize ( {
110- client_id : clientId ,
111- login_uri : loginUri ,
112- callback : initializeGoogleOneTap ,
113- auto_select : true ,
114- cancel_on_tap_outside : false ,
115- use_fedcm_for_prompt : true ,
116- } ) ;
117-
118- log ( 'identity' , 'Requesting Google One Tap prompt' ) ;
119- gsi . prompt ( ( notifcation ) => {
120- // TODO: Handle tracking of the prompt moment notification. Ophan?
121- log ( 'identity' , 'Google One Tap prompt notification received' , {
122- isSkippedMoment : notifcation . isSkippedMoment ( ) ,
123- isDismissedMoment : notifcation . isDismissedMoment ( ) ,
124- dismissedReason : notifcation . getDismissedReason ( ) ,
125- momentType : notifcation . getMomentType ( ) ,
126- } ) ;
70+ const credentialsProvider = window . navigator
71+ . credentials as unknown as CredentialsProvider ;
72+
73+ void credentialsProvider
74+ . get ( {
75+ identity : {
76+ context : 'signin' ,
77+ providers : getFedCMProviders ( window . guardian . config . stage ) ,
78+ } ,
79+ } )
80+ . catch ( ( error ) => {
81+ /**
82+ * The fedcm API hides issues with the user's federated login state
83+ * behind a generic NetworkError. This error is thrown up to 60
84+ * seconds after the prompt is triggered to avoid timing attacks.
85+ *
86+ * This allows the browser to avoid leaking sensitive information
87+ * about the user's login state to the website.
88+ *
89+ * Unfortunately for us it means we can't differentiate between
90+ * a genuine network error and a user declining the FedCM prompt.
91+ */
92+ if ( error instanceof Error && error . name === 'NetworkError' ) {
93+ log (
94+ 'identity' ,
95+ 'FedCM prompt failed, potentially due to user declining' ,
96+ ) ;
97+ }
98+ } )
99+ . then ( ( credentials ) => {
100+ if ( credentials ) {
101+ log ( 'identity' , 'FedCM credentials received' , {
102+ credentials,
103+ } ) ;
104+ } else {
105+ log ( 'identity' , 'No FedCM credentials received' ) ;
106+ }
127107 } ) ;
128- } ) ;
129108 } , [ isSignedIn ] ) ;
130109
131110 return < > </ > ;
0 commit comments