Skip to content

Commit

Permalink
Request new token if expired
Browse files Browse the repository at this point in the history
  • Loading branch information
maxpatiiuk committed Feb 19, 2023
1 parent b9838a5 commit f3e4fde
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 41 deletions.
18 changes: 9 additions & 9 deletions src/src/components/Background/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { emitEvent, Requests } from './messages';
import { State } from 'typesafe-reducer';
import type { State } from 'typesafe-reducer';

// Based on https://stackoverflow.com/a/50548409/8584605
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo) {
import type { Requests } from './messages';
import { emitEvent } from './messages';

/** Based on https://stackoverflow.com/a/50548409/8584605 */
chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
if (changeInfo?.status === 'complete')
emitEvent(tabId, { type: 'TabUpdate' }).catch(console.trace);
});

chrome.action.onClicked.addListener(function () {
chrome.action.onClicked.addListener(() => {
chrome.tabs.create({ url: 'https://calendar.google.com' });
});

Expand Down Expand Up @@ -38,20 +40,18 @@ const requestHandlers: {
request: Extract<Requests, State<TYPE>>['request']
) => Promise<Extract<Requests, State<TYPE>>['response']>;
} = {
Authenticate: ({ interactive }) =>
Authenticate: async ({ interactive }) =>
new Promise((resolve, reject) =>
chrome.identity.getAuthToken({ interactive }, (token) =>
typeof token === 'string'
? resolve({ token })
: reject(chrome.runtime.lastError)
)
),
ReloadExtension: () =>
ReloadExtension: async () =>
new Promise((resolve) => {
chrome.tabs.reload();
chrome.runtime.reload();
resolve(undefined);
}),
};

export {};
34 changes: 14 additions & 20 deletions src/src/components/Contexts/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,11 @@ import React from 'react';

import { sendRequest } from '../Background/messages';

type Authenticated = {
readonly authenticated: true;
readonly token: string;
type Auth = {
readonly token: string | undefined;
readonly handleAuthenticate: (interactive: boolean) => Promise<void>;
};

type NotAuthenticated = {
readonly authenticated: false;
readonly handleAuthenticate: () => Promise<void>;
};

type Auth = Authenticated | NotAuthenticated;

let unsafeToken: string | undefined = undefined;
export const unsafeGetToken = () => unsafeToken;

Expand All @@ -22,32 +15,33 @@ export function AuthenticationProvider({
}: {
readonly children: React.ReactNode;
}): JSX.Element {
const [token, setToken] = React.useState<string | undefined>(undefined);
const handleAuthenticate = React.useCallback(
async (interactive = true) =>
async (interactive: boolean) =>
sendRequest('Authenticate', { interactive })
.then(({ token }) => {
if (typeof token === 'string') {
unsafeToken = token;
setAuth({
authenticated: true,
token,
});
setToken(token);
} else console.warn('Authentication canceled');
})
.catch(console.error),
[]
);
React.useEffect(() => void handleAuthenticate(false), [handleAuthenticate]);

const [auth, setAuth] = React.useState<Auth>({
authenticated: false,
handleAuthenticate,
});
const auth = React.useMemo(
() => ({
token,
handleAuthenticate,
}),
[token, handleAuthenticate]
);
return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>;
}

export const AuthContext = React.createContext<Auth>({
authenticated: false,
token: undefined,
handleAuthenticate: () => {
throw new Error('AuthContext is not defined');
},
Expand Down
7 changes: 4 additions & 3 deletions src/src/components/Contexts/CalendarsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ export function CalendarsSpy({
}: {
readonly children: React.ReactNode;
}): JSX.Element {
const { authenticated } = React.useContext(AuthContext);
const { token } = React.useContext(AuthContext);
const isAuthenticated = typeof token === 'string';
const [calendars] = useAsyncState<RA<CalendarListEntry>>(
React.useCallback(async () => {
if (!authenticated) return undefined;
if (!isAuthenticated) return undefined;
const response = await ajax(
formatUrl(
'https://www.googleapis.com/calendar/v3/users/me/calendarList',
Expand All @@ -49,7 +50,7 @@ export function CalendarsSpy({
Array.from(calendars).sort(sortFunction(({ summary }) => summary))
);
return [...primary, ...secondary];
}, [authenticated]),
}, [isAuthenticated]),
false
);

Expand Down
18 changes: 10 additions & 8 deletions src/src/components/Core/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,16 @@ export function App(): JSX.Element | null {
{debugOverlay}
<Button.White
aria-pressed={isOpen ? true : undefined}
onClick={
auth.authenticated
? handleToggle
: (): void =>
void auth
.handleAuthenticate()
.then(handleToggle)
.catch(console.error)
/*
* Making the auth request even if already authenticated because
* the token may have expired. This is not a performance problem
* because token is cached by Google Chrome
*/
onClick={(): void =>
void auth
.handleAuthenticate(true)
.then(handleToggle)
.catch(console.error)
}
>
{commonText('calendarPlus')}
Expand Down
12 changes: 11 additions & 1 deletion src/src/components/EventsStore/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { CalendarsContext } from '../Contexts/CalendarsContext';
import { ruleMatchers, useVirtualCalendars } from '../PowerTools/AutoComplete';
import { usePref } from '../Preferences/usePref';
import { AuthContext } from '../Contexts/AuthContext';

export const summedDurations: unique symbol = Symbol('calendarTotal');

Expand Down Expand Up @@ -67,6 +68,7 @@ export function useEvents(
}, [ignoreAllDayEvents]);

const virtualCalendars = useVirtualCalendars();
const auth = React.useContext(AuthContext);

const [durations] = useAsyncState(
React.useCallback(async () => {
Expand All @@ -77,6 +79,7 @@ export function useEvents(
endDate === undefined
)
return undefined;
await auth.handleAuthenticate(true);
await Promise.all(
calendars.map(async ({ id }) => {
const daysBetween = getDatesBetween(startDate, endDate);
Expand Down Expand Up @@ -145,7 +148,14 @@ export function useEvents(
})
);
return extractData(eventsStore.current, calendars, startDate, endDate);
}, [calendars, startDate, endDate, ignoreAllDayEvents, virtualCalendars]),
}, [
auth,
calendars,
startDate,
endDate,
ignoreAllDayEvents,
virtualCalendars,
]),
false
);
return durations;
Expand Down

0 comments on commit f3e4fde

Please sign in to comment.