From ca149f142f2976cc2f81de6aec969c7edef777f0 Mon Sep 17 00:00:00 2001 From: Austin Turner Date: Mon, 5 Aug 2024 21:05:16 -0600 Subject: [PATCH] Add global error handling page and imrpove SFDC classic styling --- .../src/components/Button.tsx | 12 ++- .../src/core/AppInitializer.tsx | 94 +++---------------- .../src/core/AppWrapper.tsx | 2 - .../src/core/GlobalExtensionError.tsx | 29 ++++++ .../src/serviceWorker.ts | 8 +- .../src/lib/extension.types.ts | 7 ++ .../src/lib/web-extension-utils.ts | 52 +--------- 7 files changed, 67 insertions(+), 137 deletions(-) create mode 100644 apps/jetstream-web-extension/src/core/GlobalExtensionError.tsx diff --git a/apps/jetstream-web-extension/src/components/Button.tsx b/apps/jetstream-web-extension/src/components/Button.tsx index 9416597c..2f433bf3 100644 --- a/apps/jetstream-web-extension/src/components/Button.tsx +++ b/apps/jetstream-web-extension/src/components/Button.tsx @@ -1,5 +1,6 @@ /* eslint-disable no-restricted-globals */ import { css } from '@emotion/react'; +import { logger } from '@jetstream/shared/client-logger'; import { isValidSalesforceRecordId, useInterval } from '@jetstream/shared/ui-utils'; import { Maybe } from '@jetstream/types'; import { Grid, GridCol, OutsideClickHandler } from '@jetstream/ui'; @@ -158,8 +159,7 @@ export function Button() { } } } catch (ex) { - console.error(ex); - // FIXME: we need to tell the user there was a problem - most likely they are not logged in + logger.error('Error initializing org', ex); } })(); }) @@ -294,14 +294,16 @@ export function Button() {
(); -const orgConnectionError$ = orgConnectionError.asObservable(); - -registerMiddleware('Error', (response: AxiosResponse, org?: SalesforceOrgUi) => { - const connectionError = - response?.headers?.[HTTP.HEADERS.X_SFDC_ORG_CONNECTION_ERROR.toLowerCase()] || - response?.headers?.[HTTP.HEADERS.X_SFDC_ORG_CONNECTION_ERROR]; - if (org && connectionError) { - orgConnectionError.next({ uniqueId: org.uniqueId, connectionError }); - } -}); - // Configure IndexedDB database localforage.config({ name: 'jetstream-web-extension', @@ -37,17 +24,13 @@ export interface AppInitializerProps { export const AppInitializer: FunctionComponent = ({ onUserProfile, children }) => { const userProfile = useRecoilValue(fromAppState.userProfileState); - const skipFrontDoorAuth = useRecoilValue(selectSkipFrontdoorAuth); - - // const { version } = useRecoilValue(fromAppState.appVersionState); - // const appCookie = useRecoilValue(fromAppState.applicationCookieState); - // const [orgs, setOrgs] = useRecoilState(fromAppState.salesforceOrgsState); - // const invalidOrg = useObservable(orgConnectionError$); const setSelectedOrgId = useSetRecoilState(fromAppState.selectedOrgIdState); const setSalesforceOrgs = useSetRecoilState(fromAppState.salesforceOrgsState); const selectedOrg = useRecoilValue(fromAppState.selectedOrgState); + const [fatalError, setFatalError] = useState(); + useEffect(() => { (async () => { try { @@ -67,72 +50,21 @@ export const AppInitializer: FunctionComponent = ({ onUserP } } } catch (ex) { - console.error(ex); - // FIXME: we need to tell the user there was a problem - most likely they are not logged in + logger.error('Error initializing org', ex); + setFatalError(getErrorMessage(ex)); } })(); }, [setSalesforceOrgs, setSelectedOrgId]); - // useEffect(() => { - // console.log('APP VERSION', version); - // }, [version]); - - // useRollbar({ - // accessToken: environment.rollbarClientAccessToken, - // environment: appCookie.environment, - // userProfile: userProfile, - // version, - // }); - // useAmplitude(); - // usePageViews(); - - // useEffect(() => { - // if (invalidOrg) { - // const { uniqueId, connectionError } = invalidOrg; - // const clonedOrgs = orgs.map((org) => { - // if (org.uniqueId === uniqueId) { - // return { ...org, connectionError }; - // } else { - // return org; - // } - // }); - // logger.log('[invalidOrg]', invalidOrg, { orgs: clonedOrgs }); - // setOrgs(clonedOrgs); - // } - // // eslint-disable-next-line react-hooks/exhaustive-deps - // }, [invalidOrg]); - useEffect(() => { if (userProfile) { onUserProfile(userProfile); } }, [onUserProfile, userProfile]); - /** - * TODO: - * When a tab/browser window becomes visible check with the server - * 1. ensure user is still authenticated - * 2. make sure the app version has not changed, if it has then refresh the page - */ - // const handleWindowFocus = useCallback(async (event: FocusEvent) => { - // try { - // if (document.visibilityState === 'visible') { - // const { version: serverVersion } = await checkHeartbeat(); - // // TODO: inform user that there is a new version and that they should refresh their browser. - // // We could force refresh, but don't want to get into some weird infinite refresh state - // if (version !== serverVersion) { - // console.log('VERSION MISMATCH', { serverVersion, version }); - // } - // } - // } catch (ex) { - // // ignore error, but user should have been logged out if this failed - // } - // }, []); - - // useEffect(() => { - // document.addEventListener('visibilitychange', handleWindowFocus); - // return () => document.removeEventListener('visibilitychange', handleWindowFocus); - // }, [handleWindowFocus]); + if (fatalError) { + return ; + } if (!salesforceHost || !selectedOrg?.uniqueId) { return ; diff --git a/apps/jetstream-web-extension/src/core/AppWrapper.tsx b/apps/jetstream-web-extension/src/core/AppWrapper.tsx index eac6982d..822c2957 100644 --- a/apps/jetstream-web-extension/src/core/AppWrapper.tsx +++ b/apps/jetstream-web-extension/src/core/AppWrapper.tsx @@ -16,8 +16,6 @@ import AppInitializer from './AppInitializer'; enableLogger(true); -const featureFlags = new Set(); - export function AppWrapper({ children }: { children: ReactNode }) { return ( diff --git a/apps/jetstream-web-extension/src/core/GlobalExtensionError.tsx b/apps/jetstream-web-extension/src/core/GlobalExtensionError.tsx new file mode 100644 index 00000000..6e762064 --- /dev/null +++ b/apps/jetstream-web-extension/src/core/GlobalExtensionError.tsx @@ -0,0 +1,29 @@ +import { FeedbackLink, Grid } from '@jetstream/ui'; + +export const GlobalExtensionError = ({ message }: { message: string }) => { + return ( +
+

There was a problem connecting with Salesforce. Make sure you are logged in to Salesforce and try again.

+ {message &&

{message}

} +
+ +
+
    +
  1. + Bug reports and feature requests - +
  2. +
  3. + +
  4. +
  5. + Ask a question in the #vendors-jetstream Discord channel +
  6. +
  7. + You can always email us at +
  8. +
+
+
+
+ ); +}; diff --git a/apps/jetstream-web-extension/src/serviceWorker.ts b/apps/jetstream-web-extension/src/serviceWorker.ts index 3b4378f3..5de1db19 100644 --- a/apps/jetstream-web-extension/src/serviceWorker.ts +++ b/apps/jetstream-web-extension/src/serviceWorker.ts @@ -150,7 +150,13 @@ const handleResponse = (data: Message['response'], sendResponse: (response: Mess const handleError = (sendResponse: (response: MessageResponse) => void) => (err: unknown) => { console.log('ERROR', err); - sendResponse({ data: null }); + sendResponse({ + data: null, + error: { + error: true, + message: err instanceof Error ? err.message : 'An unknown error occurred', + }, + }); }; /** diff --git a/libs/web-extension-utils/src/lib/extension.types.ts b/libs/web-extension-utils/src/lib/extension.types.ts index 53b809af..dd3cf827 100644 --- a/libs/web-extension-utils/src/lib/extension.types.ts +++ b/libs/web-extension-utils/src/lib/extension.types.ts @@ -3,8 +3,15 @@ import { Maybe, SalesforceOrgUi } from '@jetstream/types'; export type Message = ToggleExtension | GetSfHost | GetSession | GetPageUrl | InitOrg; export type MessageRequest = Message['request']; + +export interface ResponseError { + error: true; + message: string; +} + export interface MessageResponse { data: T; + error?: ResponseError; } export interface SessionInfo { diff --git a/libs/web-extension-utils/src/lib/web-extension-utils.ts b/libs/web-extension-utils/src/lib/web-extension-utils.ts index 52d16f45..31bac156 100644 --- a/libs/web-extension-utils/src/lib/web-extension-utils.ts +++ b/libs/web-extension-utils/src/lib/web-extension-utils.ts @@ -15,6 +15,9 @@ type ResponseForRequest = R extends { message: infer M } function handleResponse(response: MessageResponse>) { console.log('RESPONSE', response); + if (response.error) { + throw new Error(response.error.message); + } return response.data; } @@ -43,54 +46,7 @@ export async function sendMessage(message: T): Promise try { return await chrome.runtime.sendMessage>>(message).then(handleResponse); } catch (error) { - console.error('Error getting salesforce host', error); + console.error('Error sending message', error); throw error; } } - -// export async function getHost(url: string) { -// try { -// return await chrome.runtime -// .sendMessage>({ -// message: 'GET_SF_HOST', -// url, -// }) -// .then(handleResponse); -// } catch (error) { -// console.error('Error getting salesforce host', error); -// throw error; -// } -// } - -// export async function getSession(salesforceHost: string) { -// try { -// return await chrome.runtime -// .sendMessage>({ message: 'GET_SESSION', salesforceHost }) -// .then(handleResponse); -// } catch (error) { -// console.error('Error getting session', error); -// throw error; -// } -// } - -// export async function getPageUrl(page: string) { -// try { -// return await chrome.runtime -// .sendMessage>({ message: 'GET_PAGE_URL', page }) -// .then(handleResponse); -// } catch (error) { -// console.error('Error getting session', error); -// throw error; -// } -// } - -// export async function initOrg(sessionInfo: SessionInfo) { -// try { -// return await chrome.runtime -// .sendMessage>({ message: 'INIT_ORG', sessionInfo }) -// .then(handleResponse); -// } catch (error) { -// console.error('Error getting session', error); -// throw error; -// } -// }