Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@
"@types/carbon-components-react": "^7.24.0",
"@types/carbon__icons-react": "^10.21.0",
"@types/gapi": "^0.0.39",
"@types/gapi.auth2": "^0.0.52",
"@types/gapi.client.drive": "^3.0.13",
"@types/google.accounts": "^0.0.6",
"@types/google.picker": "^0.0.34",
"@types/natural-compare-lite": "^1.4.0",
"@types/node": "^14.14.10",
Expand Down
1 change: 1 addition & 0 deletions packages/website/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script src="https://apis.google.com/js/api.js"></script>
<script src="https://accounts.google.com/gsi/client"></script>
<div id="root"></div>
</body>
</html>
21 changes: 3 additions & 18 deletions packages/website/src/components/GapiErrorDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,14 @@ import { InlineIcon } from '@iconify/react';
import { InlineNotification, NotificationActionButton } from 'carbon-components-react';
import { Stack } from 'office-ui-fabric-react';
import React, { useMemo } from 'react';
import { handleGapiError, signIn } from '../utils';
import { handleGapiError } from '../utils';
import { isUserSignedIn, signIn, userProfile } from '../utils/google-auth';

function GapiErrorDisplay_({ error, subtitle }: { error: any; subtitle?: string }) {
const e = useMemo(() => handleGapiError(error), [error]);
const isSignedIn = gapi.auth2.getAuthInstance().isSignedIn.get();
const isSignedIn = isUserSignedIn();

if (false) {
return (
<InlineNotification
kind="error"
subtitle={
<Stack tokens={{ childrenGap: 8 }} style={{ marginTop: 8 }}>
<div>
You are signed in with{' '}
{gapi.auth2.getAuthInstance().currentUser.get().getBasicProfile().getEmail()}.
</div>
<div>Are you using the incorrect Google account, or typing a wrong URL?</div>
</Stack>
}
title={`Failed to load content: ${e.message}`}
hideCloseButton
/>
);
} else {
return (
<InlineNotification
Expand Down
79 changes: 44 additions & 35 deletions packages/website/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { InlineLoading } from 'carbon-components-react';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import { get } from 'lodash';
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
Expand All @@ -10,6 +11,7 @@ import './global.carbon.scss';
import './global.index.scss';
import { registerIcons, store } from './utils';
import { extConn } from './utils/extensionApi';
import { isUserSignedIn, setupGoogleAuth } from './utils/google-auth';

async function reportManifestUrl() {
// TODO: Maybe infer manifest url from config is better.
Expand All @@ -20,53 +22,60 @@ async function reportManifestUrl() {
console.log('Manifest url stored');
}

export async function setupGapi() {
if (!gapi) {
throw new Error('gapi is not defined');
}

await new Promise((resolve, reject) =>
gapi.load('client', { callback: resolve, onerror: reject })
);

const initConfig = {
apiKey: getConfig().REACT_APP_GAPI_KEY,
discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/drive/v3/rest'],
hosted_domain: getConfig().REACT_APP_GAPI_HOSTED_DOMAIN,
cookie_policy: getConfig().REACT_APP_GAPI_COOKIE_POLICY,
};
await gapi.client.load('drive', 'v3');
await gapi.client.init(initConfig);
}

async function main() {
if (!gapi) {
ReactDOM.render(
<InlineLoading description="Error. Please reload" />,
document.getElementById('root')
);
}
gapi.load('client:auth2', () => {
gapi.client.load('drive', 'v3', async () => {
try {
await loadConfig();

const initConfig = {
apiKey: getConfig().REACT_APP_GAPI_KEY,
clientId: getConfig().REACT_APP_GAPI_CLIENT_ID,
discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/drive/v3/rest'],
scope:
'https://www.googleapis.com/auth/drive.metadata https://www.googleapis.com/auth/drive.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents.readonly',
hosted_domain: getConfig().REACT_APP_GAPI_HOSTED_DOMAIN,
cookie_policy: getConfig().REACT_APP_GAPI_COOKIE_POLICY,
};

await gapi.client.init(initConfig);
const isSignedIn = gapi.auth2.getAuthInstance().isSignedIn.get();

ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App isSignedIn={isSignedIn} />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
} catch (ex) {
console.error(ex);
ReactDOM.render(
<InlineLoading description="Error. Please reload" />,
document.getElementById('root')
);
}
});
});

ReactDOM.render(
<InlineLoading description="Loading Google API..." />,
document.getElementById('root')
);

try {
await loadConfig();
await setupGapi();
await setupGoogleAuth();

const isSignedIn = isUserSignedIn();

ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App isSignedIn={isSignedIn} />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
} catch (ex) {
console.error(ex);
ReactDOM.render(
<InlineLoading description="Error. Please reload" />,
document.getElementById('root')
);
}

reportManifestUrl();
dayjs.extend(relativeTime);
Expand Down
7 changes: 4 additions & 3 deletions packages/website/src/layout/HeaderUserAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import { UserAvatar20 } from '@carbon/icons-react';
import { HeaderGlobalAction } from 'carbon-components-react';
import React, { useMemo } from 'react';
import Avatar from 'react-avatar';
import { isUserSignedIn, userProfile } from '../utils/google-auth';

function HeaderUserAction_() {
// TODO: Respond to sign in state change
const isSignedIn = gapi.auth2.getAuthInstance().isSignedIn.get();
const isSignedIn = isUserSignedIn();
const profile = useMemo(() => {
if (isSignedIn) {
return gapi.auth2.getAuthInstance().currentUser.get().getBasicProfile();
return userProfile;
} else {
return null;
}
Expand All @@ -23,7 +24,7 @@ function HeaderUserAction_() {
} else {
return (
<HeaderGlobalAction aria-label="Sign Out">
<Avatar name={profile!.getName()} src={profile!.getImageUrl()} size="30" round />
<Avatar name={profile?.name} src={profile?.picture} size="30" round />
</HeaderGlobalAction>
);
}
Expand Down
12 changes: 6 additions & 6 deletions packages/website/src/layout/HeaderUserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { Stack } from 'office-ui-fabric-react';
import React, { useMemo } from 'react';
import Avatar from 'react-avatar';
import { NavMenu } from '../components';
import { signIn, signOut } from '../utils';
import { isUserSignedIn, signIn, signOut, userProfile } from '../utils/google-auth';

export function HeaderUserMenu() {
const isSignedIn = gapi.auth2.getAuthInstance().isSignedIn.get();
const isSignedIn = isUserSignedIn();
const profile = useMemo(() => {
if (isSignedIn) {
return gapi.auth2.getAuthInstance().currentUser.get().getBasicProfile();
return userProfile;
} else {
return null;
}
Expand All @@ -32,10 +32,10 @@ export function HeaderUserMenu() {
<>
<NavMenu.Link href="https://myaccount.google.com/profile">
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 8 }}>
<Avatar name={profile!.getName()} src={profile!.getImageUrl()} size="30" round />
<Avatar name={profile?.name} src={profile?.picture} size="30" round />
<Stack tokens={{ childrenGap: 8 }}>
<div>{profile!.getEmail()}</div>
<div>{profile!.getName()}</div>
<div>{profile?.email}</div>
<div>{profile?.name}</div>
</Stack>
</Stack>
</NavMenu.Link>
Expand Down
5 changes: 3 additions & 2 deletions packages/website/src/pages/FileAction.moveFile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
showModal,
store,
} from '../utils';
import { googleAccessToken } from '../utils/google-auth';
import { promptError } from './FileAction.utils';

interface IMoveFileModalBodyProps {
Expand Down Expand Up @@ -80,7 +81,7 @@ function MoveFileModalBody({ files, targetFolderId, closeFn }: IMoveFileModalBod
succeeded++;
dispatch(updateFile(fileResponse.result));
} catch (e) {
errors.push(e);
errors.push(e as Error);
} finally {
setFinished((f) => f + 1);
}
Expand Down Expand Up @@ -160,7 +161,7 @@ export function showMoveFile(parentFolder: DriveFile) {
.enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
.enableFeature(google.picker.Feature.SUPPORT_TEAM_DRIVES)
.setOAuthToken(
gapi.auth2.getAuthInstance().currentUser.get().getAuthResponse().access_token
googleAccessToken?.access_token ?? ''
)
.setDeveloperKey(getConfig().REACT_APP_GAPI_KEY)
.setCallback(async (data) => {
Expand Down
11 changes: 0 additions & 11 deletions packages/website/src/utils/auth.ts

This file was deleted.

91 changes: 91 additions & 0 deletions packages/website/src/utils/google-auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { getConfig } from '../config';

interface UserProfile {
name: string;
email: string;
picture: string;
}

export let googleIdToken: google.accounts.id.CredentialResponse | null = null;
export let googleAccessToken: google.accounts.oauth2.TokenResponse | null = null;
export let userProfile: UserProfile | null = null;
export const isUserSignedIn = () => !!googleAccessToken?.access_token;

const parseJwt = (token: string) => {
var base64Url = token.split('.')[1];
var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
var jsonPayload = decodeURIComponent(
window
.atob(base64)
.split('')
.map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
})
.join('')
);

return JSON.parse(jsonPayload);
};

const getGoogleIdToken = () =>
new Promise<google.accounts.id.CredentialResponse>((resolve) => {
google.accounts.id.initialize({
client_id: getConfig().REACT_APP_GAPI_CLIENT_ID,
auto_select: true,
callback: (response) => {
google.accounts.id.cancel();
googleIdToken = response;
userProfile = parseJwt(response.credential);
resolve(response);
},
});
google.accounts.id.prompt();
});

const getAccessToken = () =>
new Promise<google.accounts.oauth2.TokenResponse>((resolve) => {
const callback = (response: google.accounts.oauth2.TokenResponse) => {
googleAccessToken = response;
resolve(response);
};
const tokenClient = google.accounts.oauth2.initTokenClient({
client_id: getConfig().REACT_APP_GAPI_CLIENT_ID,
hint: userProfile?.email,
scope:
'https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/drive.metadata https://www.googleapis.com/auth/drive.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents.readonly',
callback,
});
tokenClient.requestAccessToken();
});

let refreshTimeoutHandle: number | undefined;

export async function setupGoogleAuth() {
clearTimeout(refreshTimeoutHandle);
if (!google) {
throw new Error('google(gsi) is not defined');
}
try {
await getGoogleIdToken();
const tokenResponse = await getAccessToken();
gapi.client.setToken(tokenResponse);
// refresh accessToken after 55 minutes before it expires
refreshTimeoutHandle = (setTimeout(setupGoogleAuth, 55 * 60 * 1000) as unknown) as number;
} catch (error) {
// error happened, probably we are not signed in anymore
googleIdToken = null;
googleAccessToken = null;
userProfile = null;
throw error;
}
}

export const signOut = async () => {
google.accounts.id.disableAutoSelect();
googleIdToken = null;
await setupGoogleAuth();
};

export const signIn = async (ev: any) => {
await setupGoogleAuth();
};
2 changes: 1 addition & 1 deletion packages/website/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from './auth';
export * from './google-auth';
export * from './extensionApi';
export * from './gapi';
export * from './history';
Expand Down
Loading