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
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import {reportRecoverableError} from '../errorUtils';

describe('reportRecoverableError', () => {
const originalReportError = globalThis.reportError;

afterEach(() => {
globalThis.reportError = originalReportError;
vi.restoreAllMocks();
});

it('forwards the error to globalThis.reportError when available', () => {
const reportErrorMock = vi.fn();
globalThis.reportError = reportErrorMock;
const consoleErrorMock = vi
.spyOn(console, 'error')
.mockImplementation(() => {});

const error = new Error('boom');
reportRecoverableError(error);

expect(reportErrorMock).toHaveBeenCalledTimes(1);
expect(reportErrorMock).toHaveBeenCalledWith(error);
expect(consoleErrorMock).not.toHaveBeenCalled();
});

it('falls back to console.error when globalThis.reportError is missing', () => {
// @ts-expect-error: simulating an environment without reportError
delete globalThis.reportError;
const consoleErrorMock = vi
.spyOn(console, 'error')
.mockImplementation(() => {});

const error = new Error('boom');
reportRecoverableError(error);

expect(consoleErrorMock).toHaveBeenCalledTimes(1);
expect(consoleErrorMock).toHaveBeenCalledWith(error);
});
});
24 changes: 24 additions & 0 deletions packages/docusaurus-theme-common/src/utils/errorUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* Reports a recoverable client-side error.
*
* Uses the standard `reportError()` API when available so host-page error
* listeners (e.g. `window.onerror`, Sentry's global handler) can capture it.
* Falls back to `console.error` in environments where `reportError` does not
* exist (older browsers, some test runners).
*
* See https://github.com/facebook/docusaurus/issues/6747
*/
export function reportRecoverableError(error: unknown): void {
if (typeof globalThis.reportError === 'function') {
globalThis.reportError(error);
} else {
console.error(error);
}
}
28 changes: 20 additions & 8 deletions packages/docusaurus-theme-common/src/utils/storageUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import {useCallback, useState, useSyncExternalStore} from 'react';
import SiteStorage from '@generated/site-storage';
import {reportRecoverableError} from './errorUtils';

export type StorageType = (typeof SiteStorage)['type'] | 'none';

Expand Down Expand Up @@ -153,7 +154,11 @@ export function createStorageSlot(
try {
return storage.getItem(key);
} catch (err) {
console.error(`Docusaurus storage error, can't get key=${key}`, err);
reportRecoverableError(
new Error(`Docusaurus storage error, can't get key=${key}`, {
cause: err,
}),
);
return null;
}
},
Expand All @@ -168,9 +173,10 @@ export function createStorageSlot(
storage,
});
} catch (err) {
console.error(
`Docusaurus storage error, can't set ${key}=${newValue}`,
err,
reportRecoverableError(
new Error(`Docusaurus storage error, can't set ${key}=${newValue}`, {
cause: err,
}),
);
}
},
Expand All @@ -180,7 +186,11 @@ export function createStorageSlot(
storage.removeItem(key);
dispatchChangeEvent({key, oldValue, newValue: null, storage});
} catch (err) {
console.error(`Docusaurus storage error, can't delete key=${key}`, err);
reportRecoverableError(
new Error(`Docusaurus storage error, can't delete key=${key}`, {
cause: err,
}),
);
}
},
listen: (onChange) => {
Expand All @@ -193,9 +203,11 @@ export function createStorageSlot(
window.addEventListener('storage', listener);
return () => window.removeEventListener('storage', listener);
} catch (err) {
console.error(
`Docusaurus storage error, can't listen for changes of key=${key}`,
err,
reportRecoverableError(
new Error(
`Docusaurus storage error, can't listen for changes of key=${key}`,
{cause: err},
),
);
return () => {};
}
Expand Down