fix: Updates gapi to use new google identity service.

fix: Updates gapi to use new google identity service.
This commit is contained in:
damencho 2023-03-03 12:51:38 -06:00
parent 29b6ce7721
commit bbb339d86f
4 changed files with 132 additions and 53 deletions

View File

@ -52,6 +52,7 @@ export function loadGoogleAPI() {
return Promise.resolve(); return Promise.resolve();
}) })
.then(() => dispatch(setGoogleAPIState(GOOGLE_API_STATES.LOADED))) .then(() => dispatch(setGoogleAPIState(GOOGLE_API_STATES.LOADED)))
.then(() => googleApi.signInIfNotSignedIn())
.then(() => googleApi.isSignedIn()) .then(() => googleApi.isSignedIn())
.then(isSignedIn => { .then(isSignedIn => {
if (isSignedIn) { if (isSignedIn) {
@ -150,8 +151,7 @@ export function setGoogleAPIState(
* selectedBoundStreamID: *} | never>)} * selectedBoundStreamID: *} | never>)}
*/ */
export function showAccountSelection() { export function showAccountSelection() {
return () => return () => googleApi.showAccountSelection(true);
googleApi.showAccountSelection();
} }
/** /**
@ -161,7 +161,7 @@ export function showAccountSelection() {
*/ */
export function signIn() { export function signIn() {
return (dispatch: Dispatch<any>) => googleApi.get() return (dispatch: Dispatch<any>) => googleApi.get()
.then(() => googleApi.signInIfNotSignedIn()) .then(() => googleApi.signInIfNotSignedIn(true))
.then(() => dispatch({ .then(() => dispatch({
type: SET_GOOGLE_API_STATE, type: SET_GOOGLE_API_STATE,
googleAPIState: GOOGLE_API_STATES.SIGNED_IN googleAPIState: GOOGLE_API_STATES.SIGNED_IN
@ -205,10 +205,10 @@ export function updateProfile() {
.then(profile => { .then(profile => {
dispatch({ dispatch({
type: SET_GOOGLE_API_PROFILE, type: SET_GOOGLE_API_PROFILE,
profileEmail: profile.getEmail() profileEmail: profile.email
}); });
return profile.getEmail(); return profile.email;
}); });
} }

View File

@ -20,10 +20,9 @@ export const API_URL_LIVE_BROADCASTS = 'https://content.googleapis.com/youtube/v
/** /**
* Array of API discovery doc URLs for APIs used by the googleApi. * Array of API discovery doc URLs for APIs used by the googleApi.
* *
* @type {string[]} * @type {string}
*/ */
export const DISCOVERY_DOCS export const DISCOVERY_DOCS = 'https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest';
= [ 'https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest' ];
/** /**
* An enumeration of the different states the Google API can be in. * An enumeration of the different states the Google API can be in.
@ -61,6 +60,13 @@ export const GOOGLE_API_STATES = {
*/ */
export const GOOGLE_SCOPE_CALENDAR = 'https://www.googleapis.com/auth/calendar'; export const GOOGLE_SCOPE_CALENDAR = 'https://www.googleapis.com/auth/calendar';
/**
* Google API auth scope to access user email.
*
* @type {string}
*/
export const GOOGLE_SCOPE_USERINFO = 'https://www.googleapis.com/auth/userinfo.email';
/** /**
* Google API auth scope to access YouTube streams. * Google API auth scope to access YouTube streams.
* *

View File

@ -3,10 +3,13 @@ import {
API_URL_LIVE_BROADCASTS, API_URL_LIVE_BROADCASTS,
DISCOVERY_DOCS, DISCOVERY_DOCS,
GOOGLE_SCOPE_CALENDAR, GOOGLE_SCOPE_CALENDAR,
GOOGLE_SCOPE_USERINFO,
GOOGLE_SCOPE_YOUTUBE GOOGLE_SCOPE_YOUTUBE
} from './constants'; } from './constants';
import logger from './logger';
const GOOGLE_API_CLIENT_LIBRARY_URL = 'https://apis.google.com/js/api.js'; const GOOGLE_API_CLIENT_LIBRARY_URL = 'https://apis.google.com/js/api.js';
const GOOGLE_GIS_LIBRARY_URL = 'https://accounts.google.com/gsi/client';
/** /**
* A promise for dynamically loading the Google API Client Library. * A promise for dynamically loading the Google API Client Library.
@ -50,9 +53,9 @@ const googleApi = {
} }
return this._getGoogleApiClient() return this._getGoogleApiClient()
.auth2.getAuthInstance() .client.oauth2
.currentUser.get() .userinfo.get().getPromise()
.getBasicProfile(); .then(r => r.result);
}); });
}, },
@ -68,23 +71,40 @@ const googleApi = {
initializeClient(clientId, enableYoutube, enableCalendar) { initializeClient(clientId, enableYoutube, enableCalendar) {
return this.get() return this.get()
.then(api => new Promise((resolve, reject) => { .then(api => new Promise((resolve, reject) => {
const scope
= `${enableYoutube ? GOOGLE_SCOPE_YOUTUBE : ''} ${enableCalendar ? GOOGLE_SCOPE_CALENDAR : ''}`
.trim();
// setTimeout is used as a workaround for api.client.init not // setTimeout is used as a workaround for api.client.init not
// resolving consistently when the Google API Client Library is // resolving consistently when the Google API Client Library is
// loaded asynchronously. See: // loaded asynchronously. See:
// github.com/google/google-api-javascript-client/issues/399 // github.com/google/google-api-javascript-client/issues/399
setTimeout(() => { setTimeout(() => {
api.client.init({ api.client.init({})
clientId, .then(() => {
discoveryDocs: DISCOVERY_DOCS, if (enableCalendar) {
scope api.client.load(DISCOVERY_DOCS);
}
})
.then(() => {
api.client.load('https://www.googleapis.com/discovery/v1/apis/oauth2/v1/rest');
}) })
.then(resolve) .then(resolve)
.catch(reject); .catch(reject);
}, 500); }, 500);
}))
.then(() => new Promise((resolve, reject) => {
try {
const scope
= `${enableYoutube ? GOOGLE_SCOPE_YOUTUBE : ''} ${enableCalendar ? GOOGLE_SCOPE_CALENDAR : ''}`
.trim();
this.tokenClient = this._getGoogleGISApiClient().accounts.oauth2.initTokenClient({
// eslint-disable-next-line camelcase
client_id: clientId,
scope: `${scope} ${GOOGLE_SCOPE_USERINFO}`,
callback: '' // defined at request time in await/promise scope.
});
resolve();
} catch (err) {
reject(err);
}
})); }));
}, },
@ -95,13 +115,38 @@ const googleApi = {
* @returns {Promise} * @returns {Promise}
*/ */
isSignedIn() { isSignedIn() {
return this.get() return new Promise((resolve, _) => {
.then(api => Boolean(api const te = parseInt(this.tokenExpires, 10);
&& api.auth2 const isExpired = isNaN(this.tokenExpires) ? true : new Date().getTime() > te;
&& api.auth2.getAuthInstance
&& api.auth2.getAuthInstance() resolve(Boolean(!isExpired));
&& api.auth2.getAuthInstance().isSignedIn });
&& api.auth2.getAuthInstance().isSignedIn.get())); },
/**
* Generates a script tag.
*
* @param {string} src - The source for the script tag.
* @returns {Promise<unknown>}
* @private
*/
_loadScriptTag(src) {
return new Promise((resolve, reject) => {
const scriptTag = document.createElement('script');
scriptTag.async = true;
scriptTag.addEventListener('error', () => {
scriptTag.remove();
reject();
});
scriptTag.addEventListener('load', resolve);
scriptTag.type = 'text/javascript';
scriptTag.src = src;
document.head.appendChild(scriptTag);
});
}, },
/** /**
@ -114,29 +159,19 @@ const googleApi = {
return googleClientLoadPromise; return googleClientLoadPromise;
} }
googleClientLoadPromise = new Promise((resolve, reject) => { googleClientLoadPromise = this._loadScriptTag(GOOGLE_API_CLIENT_LIBRARY_URL)
const scriptTag = document.createElement('script'); .catch(() => {
scriptTag.async = true;
scriptTag.addEventListener('error', () => {
scriptTag.remove();
googleClientLoadPromise = null; googleClientLoadPromise = null;
})
reject();
});
scriptTag.addEventListener('load', resolve);
scriptTag.type = 'text/javascript';
scriptTag.src = GOOGLE_API_CLIENT_LIBRARY_URL;
document.head.appendChild(scriptTag);
})
.then(() => new Promise((resolve, reject) => .then(() => new Promise((resolve, reject) =>
this._getGoogleApiClient().load('client:auth2', { this._getGoogleApiClient().load('client', {
callback: resolve, callback: resolve,
onerror: reject onerror: reject
}))) })))
.then(this._loadScriptTag(GOOGLE_GIS_LIBRARY_URL))
.catch(() => {
googleClientLoadPromise = null;
})
.then(() => this._getGoogleApiClient()); .then(() => this._getGoogleApiClient());
return googleClientLoadPromise; return googleClientLoadPromise;
@ -171,25 +206,50 @@ const googleApi = {
* Prompts the participant to sign in to the Google API Client Library, even * Prompts the participant to sign in to the Google API Client Library, even
* if already signed in. * if already signed in.
* *
* @param {boolean} consent - Whether to show account selection dialog.
* @returns {Promise} * @returns {Promise}
*/ */
showAccountSelection() { showAccountSelection(consent: boolean) {
return this.get() return this.get()
.then(api => api.auth2.getAuthInstance().signIn()); .then(api => new Promise((resolve, reject) => {
try {
// Settle this promise in the response callback for requestAccessToken()
this.tokenClient.callback = resp => {
if (resp.error !== undefined) {
reject(resp);
}
// Get the number of seconds the token is valid for, subtract 5 minutes
// to account for differences in clock settings and convert to ms.
const expiresIn = (parseInt(api.client.getToken().expires_in, 10) - 300) * 1000;
const now = new Date();
const expireDate = new Date(now.getTime() + expiresIn);
this.tokenExpires = expireDate.getTime().toString();
resolve(resp);
};
this.tokenClient.requestAccessToken({ prompt: consent ? 'consent' : '' });
} catch (err) {
logger.error('Error requesting token', err);
}
}));
}, },
/** /**
* Prompts the participant to sign in to the Google API Client Library, if * Prompts the participant to sign in to the Google API Client Library, if
* not already signed in. * not already signed in.
* *
* @param {boolean} consent - Whether to show account selection dialog.
* @returns {Promise} * @returns {Promise}
*/ */
signInIfNotSignedIn() { signInIfNotSignedIn(consent: boolean) {
return this.get() return this.get()
.then(() => this.isSignedIn()) .then(() => this.isSignedIn())
.then(isSignedIn => { .then(isSignedIn => {
if (!isSignedIn) { if (!isSignedIn) {
return this.showAccountSelection(); return this.showAccountSelection(consent);
} }
}); });
}, },
@ -201,11 +261,10 @@ const googleApi = {
*/ */
signOut() { signOut() {
return this.get() return this.get()
.then(api => .then(() => {
api.auth2 this.tokenClient = undefined;
&& api.auth2.getAuthInstance this.tokenExpires = undefined;
&& api.auth2.getAuthInstance() });
&& api.auth2.getAuthInstance().signOut());
}, },
/** /**
@ -387,6 +446,17 @@ const googleApi = {
*/ */
_getGoogleApiClient() { _getGoogleApiClient() {
return window.gapi; return window.gapi;
},
/**
* Returns the global Google Identity Services Library object.
*
* @private
* @returns {Object|undefined}
*/
_getGoogleGISApiClient() {
return window.google;
} }
}; };

View File

@ -0,0 +1,3 @@
import { getLogger } from '../base/logging/functions';
export default getLogger('features/base/redux');