Compare commits

...

1 Commits

Author SHA1 Message Date
damencho bbb339d86f fix: Updates gapi to use new google identity service.
fix: Updates gapi to use new google identity service.
2023-03-03 12:56:26 -06:00
4 changed files with 132 additions and 53 deletions

View File

@ -52,6 +52,7 @@ export function loadGoogleAPI() {
return Promise.resolve();
})
.then(() => dispatch(setGoogleAPIState(GOOGLE_API_STATES.LOADED)))
.then(() => googleApi.signInIfNotSignedIn())
.then(() => googleApi.isSignedIn())
.then(isSignedIn => {
if (isSignedIn) {
@ -150,8 +151,7 @@ export function setGoogleAPIState(
* selectedBoundStreamID: *} | never>)}
*/
export function showAccountSelection() {
return () =>
googleApi.showAccountSelection();
return () => googleApi.showAccountSelection(true);
}
/**
@ -161,7 +161,7 @@ export function showAccountSelection() {
*/
export function signIn() {
return (dispatch: Dispatch<any>) => googleApi.get()
.then(() => googleApi.signInIfNotSignedIn())
.then(() => googleApi.signInIfNotSignedIn(true))
.then(() => dispatch({
type: SET_GOOGLE_API_STATE,
googleAPIState: GOOGLE_API_STATES.SIGNED_IN
@ -205,10 +205,10 @@ export function updateProfile() {
.then(profile => {
dispatch({
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.
*
* @type {string[]}
* @type {string}
*/
export const DISCOVERY_DOCS
= [ 'https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest' ];
export const DISCOVERY_DOCS = 'https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest';
/**
* 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';
/**
* 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.
*

View File

@ -3,10 +3,13 @@ import {
API_URL_LIVE_BROADCASTS,
DISCOVERY_DOCS,
GOOGLE_SCOPE_CALENDAR,
GOOGLE_SCOPE_USERINFO,
GOOGLE_SCOPE_YOUTUBE
} from './constants';
import logger from './logger';
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.
@ -50,9 +53,9 @@ const googleApi = {
}
return this._getGoogleApiClient()
.auth2.getAuthInstance()
.currentUser.get()
.getBasicProfile();
.client.oauth2
.userinfo.get().getPromise()
.then(r => r.result);
});
},
@ -68,23 +71,40 @@ const googleApi = {
initializeClient(clientId, enableYoutube, enableCalendar) {
return this.get()
.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
// resolving consistently when the Google API Client Library is
// loaded asynchronously. See:
// github.com/google/google-api-javascript-client/issues/399
setTimeout(() => {
api.client.init({
clientId,
discoveryDocs: DISCOVERY_DOCS,
scope
api.client.init({})
.then(() => {
if (enableCalendar) {
api.client.load(DISCOVERY_DOCS);
}
})
.then(() => {
api.client.load('https://www.googleapis.com/discovery/v1/apis/oauth2/v1/rest');
})
.then(resolve)
.catch(reject);
}, 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}
*/
isSignedIn() {
return this.get()
.then(api => Boolean(api
&& api.auth2
&& api.auth2.getAuthInstance
&& api.auth2.getAuthInstance()
&& api.auth2.getAuthInstance().isSignedIn
&& api.auth2.getAuthInstance().isSignedIn.get()));
return new Promise((resolve, _) => {
const te = parseInt(this.tokenExpires, 10);
const isExpired = isNaN(this.tokenExpires) ? true : new Date().getTime() > te;
resolve(Boolean(!isExpired));
});
},
/**
* 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;
}
googleClientLoadPromise = new Promise((resolve, reject) => {
const scriptTag = document.createElement('script');
scriptTag.async = true;
scriptTag.addEventListener('error', () => {
scriptTag.remove();
googleClientLoadPromise = this._loadScriptTag(GOOGLE_API_CLIENT_LIBRARY_URL)
.catch(() => {
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) =>
this._getGoogleApiClient().load('client:auth2', {
this._getGoogleApiClient().load('client', {
callback: resolve,
onerror: reject
})))
.then(this._loadScriptTag(GOOGLE_GIS_LIBRARY_URL))
.catch(() => {
googleClientLoadPromise = null;
})
.then(() => this._getGoogleApiClient());
return googleClientLoadPromise;
@ -171,25 +206,50 @@ const googleApi = {
* Prompts the participant to sign in to the Google API Client Library, even
* if already signed in.
*
* @param {boolean} consent - Whether to show account selection dialog.
* @returns {Promise}
*/
showAccountSelection() {
showAccountSelection(consent: boolean) {
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
* not already signed in.
*
* @param {boolean} consent - Whether to show account selection dialog.
* @returns {Promise}
*/
signInIfNotSignedIn() {
signInIfNotSignedIn(consent: boolean) {
return this.get()
.then(() => this.isSignedIn())
.then(isSignedIn => {
if (!isSignedIn) {
return this.showAccountSelection();
return this.showAccountSelection(consent);
}
});
},
@ -201,11 +261,10 @@ const googleApi = {
*/
signOut() {
return this.get()
.then(api =>
api.auth2
&& api.auth2.getAuthInstance
&& api.auth2.getAuthInstance()
&& api.auth2.getAuthInstance().signOut());
.then(() => {
this.tokenClient = undefined;
this.tokenExpires = undefined;
});
},
/**
@ -387,6 +446,17 @@ const googleApi = {
*/
_getGoogleApiClient() {
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');