diff --git a/src/src/components/Background/index.ts b/src/src/components/Background/index.ts index c58192a..9b94741 100644 --- a/src/src/components/Background/index.ts +++ b/src/src/components/Background/index.ts @@ -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' }); }); @@ -38,7 +40,7 @@ const requestHandlers: { request: Extract>['request'] ) => Promise>['response']>; } = { - Authenticate: ({ interactive }) => + Authenticate: async ({ interactive }) => new Promise((resolve, reject) => chrome.identity.getAuthToken({ interactive }, (token) => typeof token === 'string' @@ -46,12 +48,10 @@ const requestHandlers: { : reject(chrome.runtime.lastError) ) ), - ReloadExtension: () => + ReloadExtension: async () => new Promise((resolve) => { chrome.tabs.reload(); chrome.runtime.reload(); resolve(undefined); }), }; - -export {}; diff --git a/src/src/components/Contexts/AuthContext.tsx b/src/src/components/Contexts/AuthContext.tsx index 0f0d663..1c57be3 100644 --- a/src/src/components/Contexts/AuthContext.tsx +++ b/src/src/components/Contexts/AuthContext.tsx @@ -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; }; -type NotAuthenticated = { - readonly authenticated: false; - readonly handleAuthenticate: () => Promise; -}; - -type Auth = Authenticated | NotAuthenticated; - let unsafeToken: string | undefined = undefined; export const unsafeGetToken = () => unsafeToken; @@ -22,16 +15,14 @@ export function AuthenticationProvider({ }: { readonly children: React.ReactNode; }): JSX.Element { + const [token, setToken] = React.useState(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), @@ -39,15 +30,18 @@ export function AuthenticationProvider({ ); React.useEffect(() => void handleAuthenticate(false), [handleAuthenticate]); - const [auth, setAuth] = React.useState({ - authenticated: false, - handleAuthenticate, - }); + const auth = React.useMemo( + () => ({ + token, + handleAuthenticate, + }), + [token, handleAuthenticate] + ); return {children}; } export const AuthContext = React.createContext({ - authenticated: false, + token: undefined, handleAuthenticate: () => { throw new Error('AuthContext is not defined'); }, diff --git a/src/src/components/Contexts/CalendarsContext.tsx b/src/src/components/Contexts/CalendarsContext.tsx index c38e90e..eb70dad 100644 --- a/src/src/components/Contexts/CalendarsContext.tsx +++ b/src/src/components/Contexts/CalendarsContext.tsx @@ -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>( 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', @@ -49,7 +50,7 @@ export function CalendarsSpy({ Array.from(calendars).sort(sortFunction(({ summary }) => summary)) ); return [...primary, ...secondary]; - }, [authenticated]), + }, [isAuthenticated]), false ); diff --git a/src/src/components/Core/App.tsx b/src/src/components/Core/App.tsx index 8ec02b0..7d01344 100644 --- a/src/src/components/Core/App.tsx +++ b/src/src/components/Core/App.tsx @@ -70,14 +70,16 @@ export function App(): JSX.Element | null { {debugOverlay} - 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')} diff --git a/src/src/components/EventsStore/index.ts b/src/src/components/EventsStore/index.ts index bac71bf..f50860f 100644 --- a/src/src/components/EventsStore/index.ts +++ b/src/src/components/EventsStore/index.ts @@ -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'); @@ -67,6 +68,7 @@ export function useEvents( }, [ignoreAllDayEvents]); const virtualCalendars = useVirtualCalendars(); + const auth = React.useContext(AuthContext); const [durations] = useAsyncState( React.useCallback(async () => { @@ -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); @@ -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;