Skip to content

Commit c9efdfa

Browse files
sumomomomomolinedoestrolling
authored andcommitted
Clean up PersistenceSaga + add persistence for GIS login
1 parent cbf5d1f commit c9efdfa

File tree

2 files changed

+75
-87
lines changed

2 files changed

+75
-87
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"@szhsin/react-menu": "^4.0.0",
4141
"@tanstack/react-table": "^8.9.3",
4242
"@tremor/react": "^1.8.2",
43+
"@types/google.accounts": "^0.0.14",
4344
"ace-builds": "^1.4.14",
4445
"acorn": "^8.9.0",
4546
"ag-grid-community": "^31.0.0",

src/commons/sagas/PersistenceSaga.tsx

Lines changed: 74 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,22 @@ const ROOT_ID = 'root';
3636
const MIME_SOURCE = 'text/plain';
3737
// const MIME_FOLDER = 'application/vnd.google-apps.folder';
3838

39-
// TODO: fix all calls to (window.google as any).accounts
39+
// GIS Token Client
40+
let tokenClient: google.accounts.oauth2.TokenClient;
41+
4042
export function* persistenceSaga(): SagaIterator {
41-
// Starts the function* () for every dispatched LOGOUT_GOOGLE action
42-
// Same for all takeLatest() calls below, with respective types from PersistenceTypes
4343
yield takeLatest(LOGOUT_GOOGLE, function* () {
4444
yield put(actions.playgroundUpdatePersistenceFile(undefined));
4545
yield call(ensureInitialised);
4646
yield gapi.client.setToken(null);
4747
yield handleUserChanged(null);
48+
yield localStorage.removeItem("gsi-access-token");
4849
});
4950

5051
yield takeLatest(PERSISTENCE_OPEN_PICKER, function* (): any {
5152
let toastKey: string | undefined;
5253
try {
5354
yield call(ensureInitialisedAndAuthorised);
54-
5555
const { id, name, picked } = yield call(pickFile, 'Pick a file to open');
5656
if (!picked) {
5757
return;
@@ -311,138 +311,125 @@ const initialisationPromise: Promise<void> = new Promise(res => {
311311
startInitialisation = res;
312312
}).then(initialise);
313313

314-
const getUserProfileData = async (accessToken: string) => {
315-
const headers = new Headers()
316-
headers.append('Authorization', `Bearer ${accessToken}`)
314+
async function getUserProfileData(accessToken: string) {
315+
const headers = new Headers();
316+
headers.append('Authorization', `Bearer ${accessToken}`);
317317
const response = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', {
318318
headers
319-
})
319+
});
320320
const data = await response.json();
321321
return data;
322322
}
323323

324+
async function isOAuthTokenValid(accessToken: string) {
325+
const userProfileData = await getUserProfileData(accessToken);
326+
return userProfileData.error ? false : true;
327+
}
328+
329+
// updates store and localStorage
324330
async function handleUserChanged(accessToken: string | null) {
325-
if (accessToken === null) { // TODO: check if access token is invalid instead of null
331+
// logs out if null
332+
if (accessToken === null) {
326333
store.dispatch(actions.setGoogleUser(undefined));
327-
}
328-
else {
334+
} else {
329335
const userProfileData = await getUserProfileData(accessToken)
330336
const email = userProfileData.email;
331-
console.log("handleUserChanged", email);
332337
store.dispatch(actions.setGoogleUser(email));
338+
localStorage.setItem("gsi-access-token", accessToken);
333339
}
334340
}
335341

336-
let tokenClient: any;
337-
338-
async function initialise() {
342+
async function initialise() { // only called once
339343
// load GIS script
340-
await new Promise<void>((resolve, reject) => {
344+
// adapted from https://github.com/MomenSherif/react-oauth
345+
await new Promise<void> ((resolve, reject) => {
341346
const scriptTag = document.createElement('script');
342347
scriptTag.src = 'https://accounts.google.com/gsi/client';
343348
scriptTag.async = true;
344349
scriptTag.defer = true;
345-
//scriptTag.nonce = nonce;
346-
scriptTag.onload = () => {
347-
console.log("success");
348-
resolve();
349-
//setScriptLoadedSuccessfully(true);
350-
//onScriptLoadSuccessRef.current?.();
351-
};
350+
scriptTag.onload = () => resolve();
352351
scriptTag.onerror = (ev) => {
353-
console.log("failure");
354352
reject(ev);
355-
//setScriptLoadedSuccessfully(false);
356-
//onScriptLoadErrorRef.current?.();
357353
};
358-
359354
document.body.appendChild(scriptTag);
360355
});
361356

362357
// load and initialize gapi.client
363-
await new Promise<void>((resolve, reject) =>
364-
gapi.load('client', { callback: () => {console.log("gapi.client loaded");resolve();}, onerror: reject })
358+
await new Promise<void> ((resolve, reject) =>
359+
gapi.load('client', {
360+
callback: resolve,
361+
onerror: reject
362+
})
365363
);
366364
await gapi.client.init({
367365
discoveryDocs: DISCOVERY_DOCS
368366
});
369367

370-
// juju
371-
// TODO: properly fix types here
372-
await new Promise((resolve, reject) => {
373-
//console.log("At least ur here");
374-
resolve((window.google as any).accounts.oauth2.initTokenClient({
375-
client_id: Constants.googleClientId,
368+
// initialize GIS client
369+
await new Promise<google.accounts.oauth2.TokenClient> ((resolve, reject) => {
370+
resolve(window.google.accounts.oauth2.initTokenClient({
371+
client_id: Constants.googleClientId!,
376372
scope: SCOPES,
377-
callback: ''
373+
callback: () => void 0 // will be updated in getToken()
378374
}));
379375
}).then((c) => {
380-
//console.log(c);
381376
tokenClient = c;
382-
//console.log(tokenClient.requestAccessToken);
383377
});
384378

385-
//await console.log("tokenClient", tokenClient);
386-
387-
388-
//await gapi.client.init({
389-
//apiKey: Constants.googleApiKey,
390-
//clientId: Constants.googleClientId,
391-
//discoveryDocs: DISCOVERY_DOCS,
392-
//scope: SCOPES
393-
//});
394-
//gapi.auth2.getAuthInstance().currentUser.listen(handleUserChanged);
395-
//handleUserChanged(gapi.auth2.getAuthInstance().currentUser.get());
379+
// check for stored token
380+
// if it exists and is valid, load manually
381+
// leave checking whether it is valid or not to ensureInitialisedAndAuthorised
382+
if (localStorage.getItem("gsi-access-token")) {
383+
await loadToken(localStorage.getItem("gsi-access-token")!);
384+
}
396385
}
397386

398-
// TODO: fix types
399387
// adapted from https://developers.google.com/identity/oauth2/web/guides/migration-to-gis
400-
async function getToken(err: any) {
401-
402-
//if (err.result.error.code == 401 || (err.result.error.code == 403) &&
403-
// (err.result.error.status == "PERMISSION_DENIED")) {
404-
if (err) { //TODO: fix after debugging
388+
async function getToken() {
389+
await new Promise((resolve, reject) => {
390+
try {
391+
// Settle this promise in the response callback for requestAccessToken()
392+
// as any used here cos of limitations of the type declaration library
393+
(tokenClient as any).callback = (resp: google.accounts.oauth2.TokenResponse) => {
394+
if (resp.error !== undefined) {
395+
reject(resp);
396+
}
397+
// GIS has automatically updated gapi.client with the newly issued access token.
398+
handleUserChanged(gapi.client.getToken().access_token);
399+
resolve(resp);
400+
};
401+
tokenClient.requestAccessToken();
402+
} catch (err) {
403+
reject(err);
404+
}
405+
});
406+
}
405407

406-
// The access token is missing, invalid, or expired, prompt for user consent to obtain one.
407-
await new Promise((resolve, reject) => {
408-
try {
409-
// Settle this promise in the response callback for requestAccessToken()
410-
tokenClient.callback = (resp: any) => {
411-
if (resp.error !== undefined) {
412-
reject(resp);
413-
}
414-
// GIS has automatically updated gapi.client with the newly issued access token.
415-
console.log('gapi.client access token: ' + JSON.stringify(gapi.client.getToken()));
416-
resolve(resp);
417-
};
418-
console.log(tokenClient.requestAccessToken);
419-
tokenClient.requestAccessToken();
420-
} catch (err) {
421-
console.log(err)
422-
}
423-
});
424-
} else {
425-
// Errors unrelated to authorization: server errors, exceeding quota, bad requests, and so on.
426-
throw new Error(err);
427-
}
408+
// manually load token, when token is not gotten from getToken()
409+
// but instead from localStorage
410+
async function loadToken(accessToken: string) {
411+
gapi.client.setToken({access_token: accessToken});
412+
return handleUserChanged(accessToken);
428413
}
429414

430415
function* ensureInitialised() {
431416
startInitialisation();
432417
yield initialisationPromise;
433418
}
434419

435-
function* ensureInitialisedAndAuthorised() {
420+
function* ensureInitialisedAndAuthorised() { // called multiple times
436421
yield call(ensureInitialised);
437-
// only call getToken if there is no token in gapi
438-
console.log(gapi.client.getToken());
439-
if (gapi.client.getToken() === null) {
440-
yield getToken(true);
441-
yield handleUserChanged(gapi.client.getToken().access_token);
442-
} //TODO: fix after debugging
443-
//if (!gapi.auth2.getAuthInstance().isSignedIn.get()) {
444-
// yield gapi.auth2.getAuthInstance().signIn();
445-
//}
422+
const currToken = gapi.client.getToken();
423+
424+
if (currToken === null) {
425+
yield call(getToken);
426+
} else {
427+
// check if loaded token is still valid
428+
const isValid: boolean = yield call(isOAuthTokenValid, currToken.access_token);
429+
if (!isValid) {
430+
yield call(getToken);
431+
}
432+
}
446433
}
447434

448435
type PickFileResult =

0 commit comments

Comments
 (0)