Skip to content

Commit 2ef98e5

Browse files
committed
history: error boundary
1 parent 59fcdfe commit 2ef98e5

File tree

5 files changed

+145
-37
lines changed

5 files changed

+145
-37
lines changed

special-pages/pages/history/app/components/App.jsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,25 @@ export function App() {
7575
</div>
7676
);
7777
}
78+
79+
export function AppLevelErrorBoundaryFallback({ children }) {
80+
return (
81+
<div class={styles.paddedError}>
82+
<p>{children}</p>
83+
<div class={styles.paddedErrorRecovery}>
84+
You can try to{' '}
85+
<button
86+
onClick={() => {
87+
9;
88+
const current = new URL(window.location.href);
89+
current.search = '';
90+
current.pathname = '';
91+
location.href = current.toString();
92+
}}
93+
>
94+
Reload this page
95+
</button>
96+
</div>
97+
</div>
98+
);
99+
}

special-pages/pages/history/app/components/App.module.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,11 @@ body {
106106
}
107107
}
108108
}
109+
110+
.paddedError {
111+
padding: 1rem;
112+
}
113+
114+
.paddedErrorRecovery {
115+
margin-top: 1rem;
116+
}

special-pages/pages/history/app/history.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ params for a query: (note: can be an empty string!)
7373
"term": "example.com"
7474
},
7575
"offset": 0,
76-
"limit": 50
76+
"limit": 50,
77+
"source": "initial"
7778
}
7879
```
7980

@@ -85,7 +86,8 @@ params for a range, note: the values here will match what you returned from `get
8586
"range": "today"
8687
},
8788
"offset": 0,
88-
"limit": 50
89+
"limit": 50,
90+
"source": "initial"
8991
}
9092
```
9193

Lines changed: 82 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { h, render } from 'preact';
22
import { EnvironmentProvider, UpdateEnvironment } from '../../../shared/components/EnvironmentProvider.js';
33

4-
import { App } from './components/App.jsx';
4+
import { App, AppLevelErrorBoundaryFallback } from './components/App.jsx';
55
import { Components } from './components/Components.jsx';
66

77
import enStrings from '../public/locales/en/history.json';
@@ -14,6 +14,7 @@ import { HistoryServiceProvider } from './global/Providers/HistoryServiceProvide
1414
import { Settings } from './Settings.js';
1515
import { SelectionProvider } from './global/Providers/SelectionProvider.js';
1616
import { QueryProvider } from './global/Providers/QueryProvider.js';
17+
import { InlineErrorBoundary } from '../../../shared/components/InlineErrorBoundary.js';
1718

1819
/**
1920
* @param {Element} root
@@ -45,47 +46,54 @@ export async function init(root, messaging, baseEnvironment) {
4546
.withDebounce(baseEnvironment.urlParams.get('debounce'))
4647
.withUrlDebounce(baseEnvironment.urlParams.get('urlDebounce'));
4748

48-
console.log('initialSetup', init);
49-
console.log('environment', environment);
50-
console.log('settings', settings);
49+
if (!window.__playwright_01) {
50+
console.log('initialSetup', init);
51+
console.log('environment', environment);
52+
console.log('settings', settings);
53+
}
5154

52-
const strings =
53-
environment.locale === 'en'
54-
? enStrings
55-
: await fetch(`./locales/${environment.locale}/history.json`)
56-
.then((resp) => {
57-
if (!resp.ok) {
58-
throw new Error('did not give a result');
59-
}
60-
return resp.json();
61-
})
62-
.catch((e) => {
63-
console.error('Could not load locale', environment.locale, e);
64-
return enStrings;
65-
});
55+
/**
56+
* @param {string} message
57+
*/
58+
const didCatchInit = (message) => {
59+
messaging.reportInitException({ message });
60+
};
6661

62+
const strings = await getStrings(environment);
6763
const service = new HistoryService(messaging);
6864
const query = paramsToQuery(environment.urlParams, 'initial');
69-
const initial = await service.getInitial(query);
65+
const initial = await fetchInitial(query, service, didCatchInit);
7066

7167
if (environment.display === 'app') {
7268
render(
73-
<EnvironmentProvider debugState={environment.debugState} injectName={environment.injectName} willThrow={environment.willThrow}>
74-
<UpdateEnvironment search={window.location.search} />
75-
<TranslationProvider translationObject={strings} fallback={enStrings} textLength={environment.textLength}>
76-
<MessagingContext.Provider value={messaging}>
77-
<SettingsContext.Provider value={settings}>
78-
<QueryProvider query={query.query}>
79-
<HistoryServiceProvider service={service} initial={initial}>
80-
<SelectionProvider>
81-
<App />
82-
</SelectionProvider>
83-
</HistoryServiceProvider>
84-
</QueryProvider>
85-
</SettingsContext.Provider>
86-
</MessagingContext.Provider>
87-
</TranslationProvider>
88-
</EnvironmentProvider>,
69+
<InlineErrorBoundary
70+
messaging={messaging}
71+
context={'History view application'}
72+
fallback={(message) => {
73+
return <AppLevelErrorBoundaryFallback>{message}</AppLevelErrorBoundaryFallback>;
74+
}}
75+
>
76+
<EnvironmentProvider
77+
debugState={environment.debugState}
78+
injectName={environment.injectName}
79+
willThrow={environment.willThrow}
80+
>
81+
<UpdateEnvironment search={window.location.search} />
82+
<TranslationProvider translationObject={strings} fallback={enStrings} textLength={environment.textLength}>
83+
<MessagingContext.Provider value={messaging}>
84+
<SettingsContext.Provider value={settings}>
85+
<QueryProvider query={query.query}>
86+
<HistoryServiceProvider service={service} initial={initial}>
87+
<SelectionProvider>
88+
<App />
89+
</SelectionProvider>
90+
</HistoryServiceProvider>
91+
</QueryProvider>
92+
</SettingsContext.Provider>
93+
</MessagingContext.Provider>
94+
</TranslationProvider>
95+
</EnvironmentProvider>
96+
</InlineErrorBoundary>,
8997
root,
9098
);
9199
} else if (environment.display === 'components') {
@@ -99,3 +107,42 @@ export async function init(root, messaging, baseEnvironment) {
99107
);
100108
}
101109
}
110+
111+
/**
112+
* @param {import('../types/history.js').HistoryQuery} query
113+
* @param {HistoryService} service
114+
* @param {(message: string) => void} didCatch
115+
* @returns {Promise<import('./history.service.js').InitialServiceData>}
116+
*/
117+
async function fetchInitial(query, service, didCatch) {
118+
try {
119+
return await service.getInitial(query);
120+
} catch (e) {
121+
console.error(e);
122+
didCatch(e.message || String(e));
123+
return {
124+
ranges: {
125+
ranges: [{ id: 'all', count: 0 }],
126+
},
127+
query: {
128+
info: { query: { term: '' }, finished: true },
129+
results: [],
130+
lastQueryParams: null,
131+
},
132+
};
133+
}
134+
}
135+
136+
/**
137+
* @param {import("../../../shared/environment").Environment} environment
138+
*/
139+
async function getStrings(environment) {
140+
return environment.locale === 'en'
141+
? enStrings
142+
: await fetch(`./locales/${environment.locale}/new-tab.json`)
143+
.then((x) => x.json())
144+
.catch((e) => {
145+
console.error('Could not load locale', environment.locale, e);
146+
return enStrings;
147+
});
148+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { h } from 'preact';
2+
import { ErrorBoundary } from './ErrorBoundary.js';
3+
4+
export const INLINE_ERROR = 'A problem occurred with this feature. DuckDuckGo was notified';
5+
6+
/**
7+
* @param {object} props
8+
* @param {import("preact").ComponentChild} props.children
9+
* @param {(message: string) => string} [props.format] - This gives you access to the error message, so you can append relevant context
10+
* @param {string} [props.context] - Passed to `ErrorBoundary`, if you provide this, it will be prepended to messages. Favor this before
11+
* using `format`
12+
* @param {(message: string) => import("preact").ComponentChild} [props.fallback]
13+
* @param {{reportPageException: (arg: {message:string}) => void}} props.messaging
14+
*/
15+
export function InlineErrorBoundary({ children, format, context, fallback, messaging }) {
16+
/**
17+
* @param {string} message
18+
*/
19+
const didCatch = (message) => {
20+
const formatted = format?.(message) || message;
21+
messaging.reportPageException({ message: formatted });
22+
};
23+
const fallbackElement = fallback?.(INLINE_ERROR) || <p>{INLINE_ERROR}</p>;
24+
return (
25+
<ErrorBoundary context={context} didCatch={({ message }) => didCatch(message)} fallback={fallbackElement}>
26+
{children}
27+
</ErrorBoundary>
28+
);
29+
}

0 commit comments

Comments
 (0)