diff --git a/package.json b/package.json
index 1baac3b9ad0..13d5e7a446c 100644
--- a/package.json
+++ b/package.json
@@ -298,8 +298,6 @@
"packages/*"
],
"resolutions": {
- "@react-pdf/layout": "3.9.1",
- "@react-pdf/textkit": "4.3.0",
"@types/react": "18.2.14",
"asn1.js": ">=5.4.1",
"axios": "^1.13.4",
diff --git a/packages/functional-tests/package.json b/packages/functional-tests/package.json
index 652acb42180..93ca51a4b7f 100644
--- a/packages/functional-tests/package.json
+++ b/packages/functional-tests/package.json
@@ -15,7 +15,6 @@
"devDependencies": {
"@playwright/test": "1.61.1",
"@types/eslint": "^8",
- "@types/pdf-parse": "^1.1.4",
"@types/upng-js": "^2",
"eslint": "^8.56.0",
"eslint-plugin-playwright": "^0.22.2",
@@ -23,7 +22,6 @@
"fxa-content-server": "workspace:*",
"fxa-settings": "workspace:*",
"jsqr": "^1.4.0",
- "pdf-parse": "^1.1.1",
"typescript": "5.5.3",
"upng-js": "^2.1.0"
},
diff --git a/packages/functional-tests/tests/resetPassword/oauthResetPasswordRecoveryKey.spec.ts b/packages/functional-tests/tests/resetPassword/oauthResetPasswordRecoveryKey.spec.ts
index c97b330a423..dc7b1b37589 100644
--- a/packages/functional-tests/tests/resetPassword/oauthResetPasswordRecoveryKey.spec.ts
+++ b/packages/functional-tests/tests/resetPassword/oauthResetPasswordRecoveryKey.spec.ts
@@ -55,7 +55,7 @@ test.describe('severity-1 #smoke', () => {
const keyDownload = await resetPassword.downloadRecoveryKey();
const filename = keyDownload.suggestedFilename();
expect(filename).toMatch(
- new RegExp(`Mozilla-Recovery-Key_[0-9-]{10}_${credentials.email}.pdf`)
+ new RegExp(`Mozilla-Recovery-Key_[0-9-]{10}_${credentials.email}.txt`)
);
expect(filename.length).toBeGreaterThan(0);
diff --git a/packages/functional-tests/tests/resetPassword/resetPasswordRecoveryKey.spec.ts b/packages/functional-tests/tests/resetPassword/resetPasswordRecoveryKey.spec.ts
index 651bc32adbb..170aac8410b 100644
--- a/packages/functional-tests/tests/resetPassword/resetPasswordRecoveryKey.spec.ts
+++ b/packages/functional-tests/tests/resetPassword/resetPasswordRecoveryKey.spec.ts
@@ -75,7 +75,7 @@ test.describe('severity-1 #smoke', () => {
const keyDownload = await resetPassword.downloadRecoveryKey();
const filename = keyDownload.suggestedFilename();
expect(filename).toMatch(
- new RegExp(`Mozilla-Recovery-Key_[0-9-]{10}_${credentials.email}.pdf`)
+ new RegExp(`Mozilla-Recovery-Key_[0-9-]{10}_${credentials.email}.txt`)
);
expect(filename.length).toBeGreaterThan(0);
diff --git a/packages/functional-tests/tests/settings/recoveryKey.spec.ts b/packages/functional-tests/tests/settings/recoveryKey.spec.ts
index 3272ca85ff0..ca6aa1ed6f5 100644
--- a/packages/functional-tests/tests/settings/recoveryKey.spec.ts
+++ b/packages/functional-tests/tests/settings/recoveryKey.spec.ts
@@ -3,7 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import fs from 'fs';
-import pdfParse from 'pdf-parse';
import { Page, expect, test } from '../../lib/fixtures/standard';
import { BaseTarget, Credentials } from '../../lib/targets/base';
import { TestAccountTracker } from '../../lib/testAccountTracker';
@@ -45,7 +44,7 @@ test.describe('severity-1 #smoke', () => {
expect(clipboard).toEqual(newKey);
});
- test('can download recovery key as PDF', async ({
+ test('can download recovery key as a text file', async ({
target,
pages: { page, recoveryKey, settings, signin },
testAccountTracker,
@@ -80,7 +79,7 @@ test.describe('severity-1 #smoke', () => {
const filename = dl.suggestedFilename();
expect(filename.length).toBeLessThanOrEqual(75);
expect(filename).toBe(
- `Mozilla-Recovery-Key_${date}_${credentials.email}.pdf`
+ `Mozilla-Recovery-Key_${date}_${credentials.email}.txt`
);
// Test uses try/finally to ensure the downloaded file is deleted after tests
@@ -90,15 +89,8 @@ test.describe('severity-1 #smoke', () => {
await dl.saveAs(filename);
expect(fs.existsSync(filename)).toBeTruthy();
- const getPDF = async (file: fs.PathOrFileDescriptor) => {
- const readFileSync = fs.readFileSync(file);
- const pdfExtract = await pdfParse(readFileSync);
- // Verify downloaded file contains key
- expect(pdfExtract.text).toContain(newKey);
- // Verify the PDF file contains only one page
- expect(pdfExtract.numpages).toEqual(1);
- };
- getPDF(filename);
+ const contents = await fs.promises.readFile(filename, 'utf8');
+ expect(contents).toBe(newKey);
} finally {
// Delete the downloaded file
await fs.promises.unlink(filename);
diff --git a/packages/fxa-settings/package.json b/packages/fxa-settings/package.json
index a09924cd576..b45b3ae8847 100644
--- a/packages/fxa-settings/package.json
+++ b/packages/fxa-settings/package.json
@@ -131,7 +131,6 @@
"@emotion/styled": "^11.13.0",
"@material-ui/core": "v5.0.0-alpha.24",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.3",
- "@react-pdf/renderer": "3.2.1",
"@svgr/webpack": "^8.1.0",
"@types/material-ui": "^0.21.8",
"babel-jest": "^29.7.0",
@@ -153,7 +152,6 @@
"eslint": "^8.3.0",
"eslint-webpack-plugin": "^4.2.0",
"file-loader": "^6.2.0",
- "file-saver": "^2.0.5",
"fs-extra": "^11.2.0",
"fxa-auth-client": "workspace:*",
"fxa-pairing-channel": "1.0.2",
@@ -201,7 +199,6 @@
"@testing-library/jest-dom": "^6.1.3",
"@testing-library/react-hooks": "^8.0.0",
"@testing-library/user-event": "^14.6.0",
- "@types/file-saver": "^2.0.5",
"@types/jest": "26.0.23",
"@types/node": "^22.13.5",
"@types/prop-types": "^15",
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKey/en.ftl b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKey/en.ftl
new file mode 100644
index 00000000000..bbf68e42a60
--- /dev/null
+++ b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKey/en.ftl
@@ -0,0 +1,11 @@
+## ButtonDownloadRecoveryKey
+## Clicking on this button downloads a plain text file that contains the user's account recovery key
+## The account recovery key can be used to recover data when users forget their account password
+
+# Button to download the account recovery key as a plain text file and navigate to the next step
+# The next (and final) step is an optional prompt to save a storage hint
+recovery-key-download-button-v4 = Download and continue
+
+# Error message shown in a banner if the account recovery key download failed.
+# The id keeps "pdf" from when this was a PDF, to preserve existing translations.
+recovery-key-pdf-download-error = Sorry, there was a problem downloading your account recovery key.
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/index.stories.tsx b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKey/index.stories.tsx
similarity index 75%
rename from packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/index.stories.tsx
rename to packages/fxa-settings/src/components/ButtonDownloadRecoveryKey/index.stories.tsx
index 6057d478cfe..63595ce2d45 100644
--- a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/index.stories.tsx
+++ b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKey/index.stories.tsx
@@ -4,25 +4,22 @@
import { Meta } from '@storybook/react';
import AppLayout from '../AppLayout';
-import ButtonDownloadRecoveryKeyPDF from '.';
+import ButtonDownloadRecoveryKey from '.';
import { withLocalization } from 'fxa-react/lib/storybooks';
import { MOCK_EMAIL } from '../../pages/mocks';
export default {
- title: 'Components/ButtonDownloadRecoveryKeyPDF',
- component: ButtonDownloadRecoveryKeyPDF,
+ title: 'Components/ButtonDownloadRecoveryKey',
+ component: ButtonDownloadRecoveryKey,
decorators: [withLocalization],
} as Meta;
const recoveryKeyValue = 'ABCD 1234 ABCD 1234 ABCD 1234 ABCD O0O0';
-const viewName = 'settings.recovery-key';
const storyWithAccount = (email = MOCK_EMAIL) => {
const story = () => (
-
+
);
return story;
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKey/index.test.tsx b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKey/index.test.tsx
new file mode 100644
index 00000000000..c94b486e303
--- /dev/null
+++ b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKey/index.test.tsx
@@ -0,0 +1,166 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { renderWithLocalizationProvider } from 'fxa-react/lib/test-utils/localizationProvider';
+import { ButtonDownloadRecoveryKey, getFilename } from '.';
+import { downloadTextFile } from '../../lib/download';
+import * as Sentry from '@sentry/browser';
+import { MOCK_EMAIL } from '../../pages/mocks';
+
+jest.mock('../../lib/download', () => ({
+ downloadTextFile: jest.fn(),
+}));
+
+jest.mock('@sentry/browser', () => ({
+ captureException: jest.fn(),
+}));
+
+const MOCK_RECOVERY_KEY = 'WXYZ WXYZ WXYZ WXYZ WXYZ WXYZ WXYZ WXYZ';
+
+const renderButton = (navigateForward?: () => void) =>
+ renderWithLocalizationProvider(
+
+ );
+
+const getDownloadButton = () =>
+ screen.getByRole('button', { name: 'Download and continue' });
+
+const renderAndClickDownload = async (navigateForward?: () => void) => {
+ const user = userEvent.setup();
+ renderButton(navigateForward);
+ await user.click(getDownloadButton());
+};
+
+const mockDownloadFailure = () =>
+ jest.mocked(downloadTextFile).mockImplementationOnce(() => {
+ throw new Error('download failed');
+ });
+
+beforeEach(() => {
+ jest.clearAllMocks();
+ // clearAllMocks leaves implementations queued; reset so an unconsumed
+ // failure cannot leak into the next test.
+ jest.mocked(downloadTextFile).mockReset();
+});
+
+describe('ButtonDownloadRecoveryKey', () => {
+ it('renders the download button', () => {
+ renderButton();
+
+ expect(getDownloadButton()).toBeInTheDocument();
+ });
+
+ it('tags the button for Glean click metrics', () => {
+ renderButton();
+
+ expect(getDownloadButton()).toHaveAttribute(
+ 'data-glean-id',
+ 'account_pref_recovery_key_download'
+ );
+ });
+
+ it('downloads the recovery key as a text file when clicked', async () => {
+ await renderAndClickDownload();
+
+ const today = new Date().toISOString().split('T')[0];
+ expect(downloadTextFile).toHaveBeenCalledWith(
+ MOCK_RECOVERY_KEY,
+ `Mozilla-Recovery-Key_${today}_${MOCK_EMAIL}.txt`
+ );
+ });
+
+ it('shows an inline error banner when the download throws', async () => {
+ mockDownloadFailure();
+
+ await renderAndClickDownload();
+
+ expect(screen.getByRole('alert')).toHaveTextContent(
+ 'Sorry, there was a problem downloading your account recovery key.'
+ );
+ });
+
+ it('reports the failure to Sentry when the download throws', async () => {
+ mockDownloadFailure();
+
+ await renderAndClickDownload();
+
+ expect(Sentry.captureException).toHaveBeenCalledWith(expect.any(Error));
+ });
+
+ it('does not navigate forward when the download throws', async () => {
+ mockDownloadFailure();
+ const navigateForward = jest.fn();
+
+ await renderAndClickDownload(navigateForward);
+
+ expect(navigateForward).not.toHaveBeenCalled();
+ });
+
+ it('navigates forward when the download succeeds', async () => {
+ const navigateForward = jest.fn();
+
+ await renderAndClickDownload(navigateForward);
+
+ expect(navigateForward).toHaveBeenCalledTimes(1);
+ });
+
+ it('clears a previous error once a retry succeeds', async () => {
+ const user = userEvent.setup();
+ mockDownloadFailure();
+ renderButton();
+
+ await user.click(getDownloadButton());
+ expect(screen.getByRole('alert')).toBeInTheDocument();
+
+ await user.click(getDownloadButton());
+
+ expect(screen.queryByRole('alert')).not.toBeInTheDocument();
+ });
+
+ it('renders no error banner before any download is attempted', () => {
+ renderButton();
+
+ expect(screen.queryByRole('alert')).not.toBeInTheDocument();
+ });
+});
+
+describe('getFilename', () => {
+ const MOCK_DATE = '2026-08-10';
+
+ beforeEach(() => {
+ jest.useFakeTimers().setSystemTime(new Date(`${MOCK_DATE}T12:00:00.000Z`));
+ });
+
+ afterEach(() => {
+ jest.useRealTimers();
+ });
+
+ it('builds the filename from the prefix, current date and email', () => {
+ expect(getFilename(MOCK_EMAIL)).toBe(
+ `Mozilla-Recovery-Key_${MOCK_DATE}_${MOCK_EMAIL}.txt`
+ );
+ });
+
+ it('replaces characters that are unsafe in a filename', () => {
+ expect(getFilename('a/b@example.com')).toBe(
+ `Mozilla-Recovery-Key_${MOCK_DATE}_a_b@example.com.txt`
+ );
+ });
+
+ it('truncates a very long email but keeps the prefix, date and extension', () => {
+ const longEmail =
+ 'supercalifragilisticexpialidocious@marypoppins.superfan.conference.com';
+ const filename = getFilename(longEmail);
+
+ expect(filename).toBe(
+ `Mozilla-Recovery-Key_${MOCK_DATE}_supercalifragilisticexpialidocious@mary.txt`
+ );
+ });
+});
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKey/index.tsx b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKey/index.tsx
new file mode 100644
index 00000000000..7d8468df936
--- /dev/null
+++ b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKey/index.tsx
@@ -0,0 +1,90 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { useState } from 'react';
+import * as Sentry from '@sentry/browser';
+import { RelierCmsInfo, useFtlMsgResolver } from '../../models';
+import { FtlMsg } from 'fxa-react/lib/utils';
+import { downloadTextFile } from '../../lib/download';
+import Banner from '../Banner';
+import CmsButtonWithFallback from '../CmsButtonWithFallback';
+
+interface ButtonDownloadRecoveryKeyProps {
+ navigateForward?: () => void;
+ recoveryKeyValue: string;
+ email: string;
+ cmsInfo?: RelierCmsInfo;
+}
+
+const FILENAME_EXTENSION = '.txt';
+// Arbitrary, but well under the 260 character Windows path limit.
+const MAX_FILENAME_LENGTH = 75;
+
+export const getFilename = (email: string) => {
+ const date = new Date().toISOString().split('T')[0];
+ // FxA permits "/" in an email local part, so don't rely on the browser
+ // alone to reduce the download hint to a safe leaf name.
+ const safeEmail = email.replace(/[^\w.@+-]/g, '_');
+ const stem = `Mozilla-Recovery-Key_${date}_${safeEmail}`.slice(
+ 0,
+ MAX_FILENAME_LENGTH - FILENAME_EXTENSION.length
+ );
+
+ return stem + FILENAME_EXTENSION;
+};
+
+export const ButtonDownloadRecoveryKey = ({
+ navigateForward,
+ recoveryKeyValue,
+ email,
+ cmsInfo,
+}: ButtonDownloadRecoveryKeyProps) => {
+ const ftlMsgResolver = useFtlMsgResolver();
+ const [localizedError, setLocalizedError] = useState('');
+
+ const handleDownloadClick = () => {
+ try {
+ downloadTextFile(recoveryKeyValue, getFilename(email));
+ } catch (error) {
+ // The key is the only way back into encrypted Sync data, so a systemic
+ // failure here needs to be visible in aggregate, not just to the user.
+ Sentry.captureException(error);
+ // Not useAlertBar: is only mounted by SettingsLayout, and
+ // two of the three screens using this button render under AppLayout.
+ setLocalizedError(
+ ftlMsgResolver.getMsg(
+ 'recovery-key-pdf-download-error',
+ 'Sorry, there was a problem downloading your account recovery key.'
+ )
+ );
+ return;
+ }
+
+ setLocalizedError('');
+ navigateForward?.();
+ };
+
+ return (
+ <>
+ {localizedError && (
+
+ )}
+
+
+ Download and continue
+
+
+ >
+ );
+};
+
+export default ButtonDownloadRecoveryKey;
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/RecoveryKeyPDF.tsx b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/RecoveryKeyPDF.tsx
deleted file mode 100644
index c27b88e285a..00000000000
--- a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/RecoveryKeyPDF.tsx
+++ /dev/null
@@ -1,262 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import {
- Document,
- Font,
- Link,
- Page,
- StyleSheet,
- Text,
- View,
-} from '@react-pdf/renderer';
-import { LocalizedRecoveryKeyPdfContent } from '.';
-import {
- FolderSvg,
- GlobeSvg,
- KeySvg,
- LockSvg,
- PrinterSvg,
- RecoveryKeySvg,
- MozillaLogoSvg,
-} from './VectorImagesForPdf';
-import notoSansMonoSemiBold from './fonts/NotoSansMono-SemiBold.ttf';
-import { FontData } from './requiredFont';
-
-interface RecoveryKeyPDFProps {
- recoveryKeyValue: string;
- localizedText: LocalizedRecoveryKeyPdfContent;
- requiredFont: FontData;
- email: string;
-}
-
-export const RecoveryKeyPDF = ({
- recoveryKeyValue,
- requiredFont,
- email,
- localizedText,
-}: RecoveryKeyPDFProps) => {
- const { family, sources, direction, breakwords } = requiredFont;
-
- Font.register({
- family,
- fonts: sources,
- });
-
- Font.register({
- family: 'Noto Sans Mono',
- fonts: [
- {
- src: notoSansMonoSemiBold,
- fontStyle: 'normal',
- fontWeight: 'semibold',
- },
- ],
- });
-
- const splitText = (text: string) => {
- return text.split(' ');
- };
-
- // By default, react-pdf inserts hyphens wherever a word overflows its containers,
- // without regard to language rules.
- // For languages such as Chinese and Japanese that do not contain spaces between words,
- // text should break at the end of the line without adding a hyphen.
- // Code snippet here was taken from:
- // https://github.com/diegomura/react-pdf/issues/692#issuecomment-631819389
- breakwords
- ? Font.registerHyphenationCallback((word: string) => {
- if (word.length === 1) {
- return [word];
- } else {
- return Array.from(word).reduce((acc: (string | '')[], char) => {
- acc = acc.concat([char, '']);
- return acc;
- }, []);
- }
- })
- : // For other languages that contain spaces between words, text that would overflow its container
- // should break at the preceeding space between words, without hyphenation.
- // See documentation about disabling default hyphenation:
- // https://react-pdf.org/fonts#registerhyphenationcallback
- Font.registerHyphenationCallback((word: string) => [word]);
-
- const styles = StyleSheet.create({
- page: {
- flexDirection: 'column',
- backgroundColor: '#ffffff',
- paddingVertical: '2cm',
- paddingHorizontal: '2cm',
- fontFamily: family,
- textAlign: direction === 'ltr' ? 'left' : 'right',
- },
- purpleSection: {
- flexDirection: 'column',
- marginTop: '56px',
- paddingHorizontal: '28px',
- paddingBottom: '28px',
- borderRadius: 10,
- backgroundColor: '#e3dff5',
- textAlign: 'center',
- },
- whiteSection: {
- padding: '16px',
- borderRadius: 5,
- backgroundColor: '#ffffff',
- fontSize: '12px',
- gap: '8px',
- alignItems: 'flex-start',
- },
- heading: {
- fontSize: '18px',
- fontWeight: 'bold',
- marginTop: '10px',
- marginBottom: '4px',
- },
- subheading: {
- fontSize: '12px',
- fontWeight: 'semibold',
- marginTop: '28px',
- marginBottom: '12px',
- },
- listItem: {
- flexDirection: direction === 'ltr' ? 'row' : 'row-reverse',
- gap: '4px',
- alignItems: 'center',
- flexBasis: '50%',
- paddingVertical: '4px',
- },
- listItemText: {
- fontSize: '10px',
- fontWeight: 'normal',
- },
- });
-
- return (
-
-
-
-
-
-
-
- {localizedText.heading}
- {/* TODO in FXA-8313: Verify formatting of RTL dates - bidirectionality markers are not respected
- and numeric values are incorrectly reversed (e.g., 3202 instead of 2023) */}
-
- {localizedText.dateGenerated}
-
-
- {email}
-
-
-
-
-
- {localizedText.keyLegend}
-
-
-
- {recoveryKeyValue}
-
-
-
-
-
- {direction === 'ltr' ? (
-
- {localizedText.instructions}
-
- ) : (
- // this is a hack to support RTL text that flows over more than one line
- // react-pdf does not (yet) support RTL out-of-the-box, and long paragraphs of RTL
- // text were flowing from the bottom up. This splits the text into an array of words
- // that can be reflowed from top-right to bottom-left.
- // TODO in FXA-8313: Verify punctuation incl. parentheses. They may be inverted and misplaced.
- splitText(localizedText.instructions).map((word, i) => {
- return (
-
- {word}
-
- );
- })
- )}
-
- {localizedText.storageHeading}
-
-
-
-
- {localizedText.storageIdeaFolder}
-
-
-
-
-
- {localizedText.storageIdeaCloud}
-
-
-
-
-
- {localizedText.storageIdeaPrint}
-
-
-
-
-
- {localizedText.storageIdeaPwdManager}
-
-
-
-
- {localizedText.findOutMoreHeading}
-
-
- https://mzl.la/3bNrM1I
-
-
-
-
- );
-};
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/VectorImagesForPdf.tsx b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/VectorImagesForPdf.tsx
deleted file mode 100644
index fc2c2cee9bc..00000000000
--- a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/VectorImagesForPdf.tsx
+++ /dev/null
@@ -1,214 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { Svg, Path, Rect, Circle } from '@react-pdf/renderer';
-
-/*
- To include SVG in PDFs with react-pdf/renderer, SVGs must to be added directly in the format explained in docs.
- This requires modifying the source code of SVGs to match the expected format.
-
- Note that support for advanced SVG features (e.g., gradients and shadows) is limited/null,
- and it's generally preferable to use simplifed versions of graphics without these complexities.
-
- If we need to we can explore turning the SVG into base64 PNG programmatically,
- with a last resort to just use a PNG version of the image (discouraged).
-
- See docs: https://react-pdf.org/svg
-*/
-
-export const RecoveryKeySvg = () => {
- return (
-
- );
-};
-
-export const FolderSvg = () => {
- return (
-
- );
-};
-
-export const GlobeSvg = () => {
- return (
-
- );
-};
-
-export const LockSvg = () => {
- return (
-
- );
-};
-
-export const PrinterSvg = () => {
- return (
-
- );
-};
-
-export const KeySvg = (props: { direction: 'rtl' | 'ltr' }) => {
- const { direction } = props;
- return (
-
- );
-};
-
-export const MozillaLogoSvg = () => {
- return (
-
- );
-};
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/__snapshots__/index.test.tsx.snap b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/__snapshots__/index.test.tsx.snap
deleted file mode 100644
index 044683fe856..00000000000
--- a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/__snapshots__/index.test.tsx.snap
+++ /dev/null
@@ -1,16 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`ButtonDownloadRecoveryKeyPDF renders button as expected: without CMS 1`] = `
-
-
-
-
-
-`;
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/en.ftl b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/en.ftl
deleted file mode 100644
index 3bb9c477cbf..00000000000
--- a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/en.ftl
+++ /dev/null
@@ -1,33 +0,0 @@
-## ButtonDownloadRecoveryKeyPDF
-## Clicking on this button downloads a PDF file that contains the user's account recovery key
-## The account recovery key can be used to recover data when users forget their account password
-
-# Button to download the account recovery key as a PDF file and navigate to the next step
-# The next (and final) step is an optional prompt to save a storage hint
-# .title will displayed as a tooltip on the button
-recovery-key-download-button-v3 = Download and continue
- .title = Download and continue
-
-recovery-key-pdf-heading = Account Recovery Key
-
-# Date when the account recovery key was created and this file was downloaded
-# { $date }: formatted date with 'medium' dateStyle format (e.g., for 'en': Jul 31, 2023)
-recovery-key-pdf-download-date = Generated: { $date }
-
-# Shown directly above recovery key value and preceeded by a key icon
-recovery-key-pdf-key-legend = Account Recovery Key
-
-# Instructions in the text file to prompt the user to keep this information in a secure, easy to remember location.
-# Password resets without this account recovery key can result in data loss.
-# "key" here refers to "account recovery key"
-recovery-key-pdf-instructions = This key allows you to recover your encrypted browser data (including passwords, bookmarks, and history) if you forget your password. Store it in a place you’ll remember.
-
-# This heading is shown above a list of options for storing the account recovery key
-# "key" here refers to "account recovery key"
-recovery-key-pdf-storage-ideas-heading = Places to store your key
-
-# Followed by a link (https://mzl.la/3bNrM1I) to get more information and support
-recovery-key-pdf-support = Learn more about your account recovery key
-
-# Error message displayed in an alert bar if the PDF download failed.
-recovery-key-pdf-download-error = Sorry, there was a problem downloading your account recovery key.
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSans-Bold.ttf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSans-Bold.ttf
deleted file mode 100644
index 0d19068399c..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSans-Bold.ttf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSans-Regular.ttf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSans-Regular.ttf
deleted file mode 100644
index 7552fbe806d..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSans-Regular.ttf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSans-SemiBold.ttf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSans-SemiBold.ttf
deleted file mode 100644
index b460754c54b..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSans-SemiBold.ttf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansGeorgian-Bold.ttf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansGeorgian-Bold.ttf
deleted file mode 100644
index cc088702dbc..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansGeorgian-Bold.ttf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansGeorgian-Regular.ttf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansGeorgian-Regular.ttf
deleted file mode 100644
index 49163cc37e2..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansGeorgian-Regular.ttf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansGeorgian-SemiBold.ttf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansGeorgian-SemiBold.ttf
deleted file mode 100644
index c81606b8be9..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansGeorgian-SemiBold.ttf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansHebrew-Bold.ttf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansHebrew-Bold.ttf
deleted file mode 100644
index 04e5eb0f56a..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansHebrew-Bold.ttf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansHebrew-Regular.ttf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansHebrew-Regular.ttf
deleted file mode 100644
index ca7a0fdb7f9..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansHebrew-Regular.ttf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansHebrew-SemiBold.ttf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansHebrew-SemiBold.ttf
deleted file mode 100644
index 92c9637e367..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansHebrew-SemiBold.ttf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansJP-Bold.ttf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansJP-Bold.ttf
deleted file mode 100644
index 384f8ebb89a..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansJP-Bold.ttf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansJP-Regular.ttf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansJP-Regular.ttf
deleted file mode 100644
index 1583096a2d7..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansJP-Regular.ttf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansJP-SemiBold.ttf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansJP-SemiBold.ttf
deleted file mode 100644
index cc72ab304d9..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansJP-SemiBold.ttf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansKR-Bold.otf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansKR-Bold.otf
deleted file mode 100644
index be388bf5f96..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansKR-Bold.otf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansKR-Medium.otf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansKR-Medium.otf
deleted file mode 100644
index 5ddbbc03803..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansKR-Medium.otf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansKR-Regular.otf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansKR-Regular.otf
deleted file mode 100644
index 7c5c2fae3f7..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansKR-Regular.otf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansMono-SemiBold.ttf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansMono-SemiBold.ttf
deleted file mode 100644
index 5b899b45f4e..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansMono-SemiBold.ttf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansSC-Bold.otf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansSC-Bold.otf
deleted file mode 100644
index 172eb674127..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansSC-Bold.otf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansSC-Medium.otf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansSC-Medium.otf
deleted file mode 100644
index 0a5bd9e5fe6..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansSC-Medium.otf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansSC-Regular.otf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansSC-Regular.otf
deleted file mode 100644
index d350ffa79e3..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansSC-Regular.otf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansTC-Bold.otf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansTC-Bold.otf
deleted file mode 100644
index 6f0c8be4bb9..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansTC-Bold.otf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansTC-Medium.otf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansTC-Medium.otf
deleted file mode 100644
index a0811eb27d9..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansTC-Medium.otf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansTC-Regular.otf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansTC-Regular.otf
deleted file mode 100644
index 29724773df3..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansTC-Regular.otf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansThai-Bold.ttf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansThai-Bold.ttf
deleted file mode 100644
index d081be4b28e..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansThai-Bold.ttf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansThai-Regular.ttf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansThai-Regular.ttf
deleted file mode 100644
index a6c4d1c9438..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansThai-Regular.ttf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansThai-SemiBold.ttf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansThai-SemiBold.ttf
deleted file mode 100644
index 26f6a6fa251..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSansThai-SemiBold.ttf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSerifGurmukhi-Bold.ttf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSerifGurmukhi-Bold.ttf
deleted file mode 100644
index 354bd731ccb..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSerifGurmukhi-Bold.ttf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSerifGurmukhi-Regular.ttf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSerifGurmukhi-Regular.ttf
deleted file mode 100644
index 37e0ed1424c..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSerifGurmukhi-Regular.ttf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSerifGurmukhi-SemiBold.ttf b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSerifGurmukhi-SemiBold.ttf
deleted file mode 100644
index 37ac49c1a83..00000000000
Binary files a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/NotoSerifGurmukhi-SemiBold.ttf and /dev/null differ
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/README.md b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/README.md
deleted file mode 100644
index 6535a4fd9a2..00000000000
--- a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/README.md
+++ /dev/null
@@ -1,29 +0,0 @@
-## Why these fonts?
-
-These fonts are used to generate recovery key PDFs client-side with @react-pdf in fxa-settings.
-
-## Why Noto?
-
-Noto is a collection of fonts with multiple weights and widths in sans, serif, mono, and other styles. This family of fonts supports more than 1,000 languages and over 150 writing systems.
-
-Read more: https://fonts.google.com/noto
-These fonts are licensed under the [Open Font License](https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL).
-
-For most of the locales supported by Mozilla accounts (see `supportedLanguages.json`), we can default to the standard Noto Sans, with a few exceptions that require specific fonts:
-
-- Chinese: Noto Sans SC
-- Georgian: Noto Sans Georgian
-- Hebrew: Noto Sans Hebrew
-- Japanese: Noto Sans JP
-- Korean: Noto Sans KR
-- Punjabi: Noto Sans Gurmukhi
-- Thai: Noto Sans Thai
-
-## Adding new fonts/locales for PDF localization
-
-New fonts (e.g., for additional languages or font weights/styles) must be registered for use by react-pdf.
-
-1. Add required font files to `assets/fonts`
-2. Verify if new type declarations need to be added for typescript to recognize the font files as valid import modules. Currently, `*.ttf` and `*.otf` are included - if the fonts have another extension, add it to the `fonts.d.ts`.
-3. Import the font(s) in `ButtonDownloadRecoveryPDF/index.tsx`
-4. Add a new case to the switch in `ButtonDownloadRecoveryPDF/GetRequiredFont.tsx` for the new locale (see exisiting examples in the file and consult react-pdf docs: https://react-pdf.org/fonts#register).
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/fonts.d.ts b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/fonts.d.ts
deleted file mode 100644
index d04fb10a72f..00000000000
--- a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/fonts/fonts.d.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-declare module '*.ttf';
-declare module '*.otf';
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/index.test.tsx b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/index.test.tsx
deleted file mode 100644
index 42cb85e4a5d..00000000000
--- a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/index.test.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { fireEvent, screen } from '@testing-library/react';
-import { renderWithLocalizationProvider } from 'fxa-react/lib/test-utils/localizationProvider';
-import { ButtonDownloadRecoveryKeyPDF, getFilename } from '.';
-import { logViewEvent } from '../../lib/metrics';
-import { TextEncoder } from 'util';
-import { MOCK_EMAIL } from '../../pages/mocks';
-import { MemoryRouter } from 'react-router';
-
-Object.assign(global, { TextEncoder });
-
-const recoveryKeyValue = 'WXYZ WXYZ WXYZ WXYZ WXYZ WXYZ WXYZ WXYZ';
-const viewName = 'settings.account-recovery';
-
-jest.mock('../../lib/metrics', () => ({
- logViewEvent: jest.fn(),
-}));
-
-jest.mock('@react-pdf/renderer', () => {
- return {
- pdf: jest.fn().mockResolvedValue({
- toBlob: jest.fn().mockResolvedValue(new globalThis.Blob()),
- updateContainer: jest.fn(),
- }),
- };
-});
-
-beforeAll(() => {
- window.URL.createObjectURL = jest.fn();
-});
-
-describe('ButtonDownloadRecoveryKeyPDF', () => {
- it('renders button as expected', () => {
- const { container } = renderWithLocalizationProvider(
-
-
-
- );
- screen.getByText('Download and continue');
- expect(container).toMatchSnapshot('without CMS');
- });
-
- // Content of downloaded file is tested in Playwright tests
- // including validating that the expected key is included and matches the key in the DataBlock
- it('emits a metrics event when the link is clicked', () => {
- renderWithLocalizationProvider(
-
-
-
- );
- const downloadButton = screen.getByText('Download and continue');
- fireEvent.click(downloadButton);
-
- expect(logViewEvent).toHaveBeenCalledWith(
- `flow.${viewName}`,
- 'recovery-key.download-option'
- );
- });
-});
-
-describe('getFilename function', () => {
- it('sets the filename as expected with a reasonably-sized email', () => {
- const filename = getFilename(MOCK_EMAIL);
-
- // Test the date formatting
- const mockDateObject = '2023-05-10T17:00:40.722Z';
- const mockDateFunctionOutput = new Date(mockDateObject)
- .toISOString()
- .split('T')[0];
- const mockExpectedDate = '2023-05-10';
-
- expect(mockDateFunctionOutput).toEqual(mockExpectedDate);
-
- // Filename should be created from current date
- const date = new Date().toISOString().split('T')[0];
-
- expect(filename).toContain(
- `Mozilla-Recovery-Key_${date}_${MOCK_EMAIL}.pdf`
- );
- expect(filename.length).toBeLessThanOrEqual(75);
- });
-
- it('sets the filename with a truncated email as expected when the email is very long', () => {
- const longEmail =
- 'supercalifragilisticexpialidocious@marypoppins.superfan.conference.com';
- const filename = getFilename(longEmail);
-
- // Filename should be created from current date
- const date = new Date().toISOString().split('T')[0];
-
- const fullFilename = `Mozilla-Recovery-Key_${date}_${longEmail}.pdf`;
- // Full filename would be longer than 75 char if not truncated
- expect(fullFilename.length).toBeGreaterThan(75);
-
- // Actual filename is truncated
- expect(filename.length).toBeLessThanOrEqual(75);
- // filename still contains full prefix and date
- expect(filename).toContain('Mozilla-Recovery-Key');
- expect(filename).toContain(date);
- });
-});
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/index.tsx b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/index.tsx
deleted file mode 100644
index e20c629925c..00000000000
--- a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/index.tsx
+++ /dev/null
@@ -1,165 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { RelierCmsInfo, useAlertBar, useFtlMsgResolver } from '../../models';
-import { pdf } from '@react-pdf/renderer';
-import { saveAs } from 'file-saver';
-import { RecoveryKeyPDF } from '../ButtonDownloadRecoveryKeyPDF/RecoveryKeyPDF';
-import {
- FtlMsg,
- LocalizedDateOptions,
- getLocalizedDate,
-} from 'fxa-react/lib/utils';
-import { logViewEvent } from '../../lib/metrics';
-import { FontData, getRequiredFont } from './requiredFont';
-import { determineLocale } from '@fxa/shared/l10n';
-import CmsButtonWithFallback from '../CmsButtonWithFallback';
-
-export interface LocalizedRecoveryKeyPdfContent {
- heading: string;
- dateGenerated: string;
- keyLegend: string;
- instructions: string;
- storageHeading: string;
- storageIdeaFolder: string;
- storageIdeaCloud: string;
- storageIdeaPrint: string;
- storageIdeaPwdManager: string;
- findOutMoreHeading: string;
-}
-
-interface ButtonDownloadRecoveryKeyPDFProps {
- navigateForward?: () => void;
- recoveryKeyValue: string;
- viewName: string;
- email: string;
- cmsInfo?: RelierCmsInfo;
-}
-
-export const getFilename = (email: string) => {
- const date = new Date().toISOString().split('T')[0];
- const prefix = 'Mozilla-Recovery-Key';
- // Windows has a max directory length of 260 characters (including path)
- // filename should be kept much shorter (maxLength is arbitrary).
- let filename = `${prefix}_${date}_${email}`.substring(0, 71) + `.pdf`;
-
- return filename;
-};
-
-export const ButtonDownloadRecoveryKeyPDF = ({
- navigateForward,
- recoveryKeyValue,
- viewName,
- email,
- cmsInfo,
-}: ButtonDownloadRecoveryKeyPDFProps) => {
- const keyCreated = Date.now();
- const currentLanguage = determineLocale(
- window.navigator.languages.join(', ')
- );
- const ftlMsgResolver = useFtlMsgResolver();
-
- const alertBar = useAlertBar();
-
- const keyDateFallback = Intl.DateTimeFormat('default', {
- dateStyle: 'medium',
- }).format(new Date(keyCreated));
-
- const keyDateFluent = getLocalizedDate(
- keyCreated,
- LocalizedDateOptions.MediumDateStyle
- );
-
- const localizedText: LocalizedRecoveryKeyPdfContent = {
- heading: ftlMsgResolver.getMsg(
- 'recovery-key-pdf-heading',
- 'Account Recovery Key'
- ),
- dateGenerated: ftlMsgResolver.getMsg(
- 'recovery-key-pdf-download-date',
- `Generated: ${keyDateFallback}`,
- { date: keyDateFluent }
- ),
- keyLegend: ftlMsgResolver.getMsg(
- 'recovery-key-pdf-key-legend',
- 'Account Recovery Key'
- ),
- instructions: ftlMsgResolver.getMsg(
- 'recovery-key-pdf-instructions',
- 'This key allows you to recover your encrypted browser data (including passwords, bookmarks, and history) if you forget your password. Store it in a place you’ll remember. '
- ),
- storageHeading: ftlMsgResolver.getMsg(
- 'recovery-key-pdf-storage-ideas-heading',
- 'Places to store your key'
- ),
- storageIdeaFolder: ftlMsgResolver.getMsg(
- 'flow-recovery-key-download-storage-ideas-folder-v2',
- 'Folder on secure device'
- ),
- storageIdeaCloud: ftlMsgResolver.getMsg(
- 'flow-recovery-key-download-storage-ideas-cloud',
- 'Trusted cloud storage'
- ),
- storageIdeaPrint: ftlMsgResolver.getMsg(
- 'flow-recovery-key-download-storage-ideas-print-v2',
- 'Printed physical copy'
- ),
- storageIdeaPwdManager: ftlMsgResolver.getMsg(
- 'flow-recovery-key-download-storage-ideas-pwd-manager',
- 'Password manager'
- ),
- findOutMoreHeading: ftlMsgResolver.getMsg(
- 'recovery-key-pdf-support',
- 'Learn more about your account recovery key'
- ),
- };
-
- const requiredFont: FontData = getRequiredFont(currentLanguage);
-
- // File download test coverage is provided by Playwright functional test
- const downloadFile = async () => {
- const doc = (
-
- );
-
- try {
- const asPdf = pdf();
- asPdf.updateContainer(doc);
- const blob = await asPdf.toBlob();
- const filename = getFilename(email);
- saveAs(blob, filename);
- logViewEvent(`flow.${viewName}`, `recovery-key.download-success`);
- } catch (e) {
- logViewEvent(`flow.${viewName}`, `recovery-key.download-failed`);
- alertBar.error(
- ftlMsgResolver.getMsg(
- 'recovery-key-pdf-download-error',
- 'Sorry, there was a problem downloading your account recovery key.'
- )
- );
- }
- };
-
- return (
- <>
-
- {
- logViewEvent(`flow.${viewName}`, `recovery-key.download-option`);
- await downloadFile();
- navigateForward && navigateForward();
- }}
- buttonColor={cmsInfo?.shared.buttonColor}
- >
- Download and continue
-
-
- >
- );
-};
-
-export default ButtonDownloadRecoveryKeyPDF;
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/requiredFont.test.tsx b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/requiredFont.test.tsx
deleted file mode 100644
index e463205fe77..00000000000
--- a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/requiredFont.test.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { getRequiredFont } from './requiredFont';
-
-describe('getRequiredFont', () => {
- it('returns the default font when the languages is unknown or not recognized', async () => {
- const requiredFont = getRequiredFont('unknown');
- expect(requiredFont.family).toEqual('Noto Sans');
- expect(requiredFont.direction).toEqual('ltr');
- });
-
- it('returns the expected font data when the language is Chinese (mainland)', async () => {
- const requiredFont = getRequiredFont('zh-cn');
- expect(requiredFont.family).toEqual('Noto Sans SC');
- expect(requiredFont.direction).toEqual('ltr');
- });
-
- it('returns the expected font data when the language is Chinese (Taiwan)', async () => {
- const requiredFont = getRequiredFont('zh-tw');
- expect(requiredFont.family).toEqual('Noto Sans TC');
- expect(requiredFont.direction).toEqual('ltr');
- });
-
- it('returns the expected font data when the language is Georgian', async () => {
- const requiredFont = getRequiredFont('zh-tw');
- expect(requiredFont.family).toEqual('Noto Sans TC');
- expect(requiredFont.direction).toEqual('ltr');
- });
-
- it('returns the expected font data when the language is Hebrew', async () => {
- let requiredFont = getRequiredFont('he');
- expect(requiredFont.family).toEqual('Noto Sans Hebrew');
- expect(requiredFont.direction).toEqual('rtl');
- });
-
- it('returns the expected font data when the language is Japanese', async () => {
- let requiredFont = getRequiredFont('ja');
- expect(requiredFont.family).toEqual('Noto Sans JP');
- expect(requiredFont.direction).toEqual('ltr');
-
- // can match if lowercase or uppercase
- requiredFont = getRequiredFont('ja-jp');
- expect(requiredFont.family).toEqual('Noto Sans JP');
-
- requiredFont = getRequiredFont('ja-JP');
- expect(requiredFont.family).toEqual('Noto Sans JP');
- });
-
- it('returns the expected font data when the language is Korean', async () => {
- const requiredFont = getRequiredFont('ko');
- expect(requiredFont.family).toEqual('Noto Sans KR');
- expect(requiredFont.direction).toEqual('ltr');
- });
-
- // Punjabi (India) uses Gurmukhi script and is written left-to-right
- // Punjabi (Pakistan) - pa-PK - is currently unsupported and uses Shahmukhi script and is written right-to-left
- it('returns the expected font data when the language is Punjabi', async () => {
- const requiredFont = getRequiredFont('pa');
- expect(requiredFont.family).toEqual('Noto Serif Gurmukhi');
- expect(requiredFont.direction).toEqual('ltr');
- });
-
- it('returns the expected font data when the language is Thai', async () => {
- const requiredFont = getRequiredFont('th');
- expect(requiredFont.family).toEqual('Noto Sans Thai');
- expect(requiredFont.direction).toEqual('ltr');
- });
-});
diff --git a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/requiredFont.tsx b/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/requiredFont.tsx
deleted file mode 100644
index 497114a8a7c..00000000000
--- a/packages/fxa-settings/src/components/ButtonDownloadRecoveryKeyPDF/requiredFont.tsx
+++ /dev/null
@@ -1,300 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import notoSansRegular from './fonts/NotoSans-Regular.ttf';
-import notoSansSemibold from './fonts/NotoSans-SemiBold.ttf';
-import notoSansBold from './fonts/NotoSans-Bold.ttf';
-import notoSansGeorgianRegular from './fonts/NotoSansGeorgian-Regular.ttf';
-import notoSansGeorgianSemibold from './fonts/NotoSansGeorgian-SemiBold.ttf';
-import notoSansGeorgianBold from './fonts/NotoSansGeorgian-Bold.ttf';
-import notoSerifGurmukhiRegular from './fonts/NotoSerifGurmukhi-Regular.ttf';
-import notoSerifGurmukhiSemibold from './fonts/NotoSerifGurmukhi-SemiBold.ttf';
-import notoSerifGurmukhiBold from './fonts/NotoSerifGurmukhi-Bold.ttf';
-import notoSansHebrewRegular from './fonts/NotoSansHebrew-Regular.ttf';
-import notoSansHebrewSemibold from './fonts/NotoSansHebrew-SemiBold.ttf';
-import notoSansHebrewBold from './fonts/NotoSansHebrew-Bold.ttf';
-import notoSansJPRegular from './fonts/NotoSansJP-Regular.ttf';
-import notoSansJPSemibold from './fonts/NotoSansJP-SemiBold.ttf';
-import notoSansJPBold from './fonts/NotoSansJP-Bold.ttf';
-import notoSansKRRegular from './fonts/NotoSansKR-Regular.otf';
-import notoSansKRSemibold from './fonts/NotoSansKR-Medium.otf';
-import notoSansKRBold from './fonts/NotoSansKR-Bold.otf';
-import notoSansSCRegular from './fonts/NotoSansSC-Regular.otf';
-import notoSansSCMedium from './fonts/NotoSansSC-Medium.otf';
-import notoSansSCBold from './fonts/NotoSansSC-Bold.otf';
-import notoSansThaiRegular from './fonts/NotoSansThai-Regular.ttf';
-import notoSansThaiSemibold from './fonts/NotoSansThai-SemiBold.ttf';
-import notoSansThaiBold from './fonts/NotoSansThai-Bold.ttf';
-import notoSansTCRegular from './fonts/NotoSansTC-Regular.otf';
-import notoSansTCMedium from './fonts/NotoSansTC-Medium.otf';
-import notoSansTCBold from './fonts/NotoSansTC-Bold.otf';
-
-export interface FontSource {
- src: string;
- style: 'normal' | 'italic' | 'oblique';
- weight:
- | 'thin'
- | 'ultralight'
- | 'light'
- | 'normal'
- | 'medium'
- | 'semibold'
- | 'bold'
- | 'ultrabold'
- | 'heavy'
- | number;
-}
-export interface FontData {
- family: string;
- sources: FontSource[];
- direction: 'ltr' | 'rtl';
- breakwords: boolean;
-}
-
-export const getRequiredFont = (language: string) => {
- // Languages supported below based on 'supportedLanguages.json'
- // New cases should only be added if additional languages are not supported by the default Noto Sans font
- // Specifying the directionality of the language will impact the layout of the PDF
- // Fonts should be saved to fxa-settings/src/assets/fonts - see instructions in fxa-settings/src/assets/fonts/README.md
-
- let requiredFont: FontData;
- switch (language.toLowerCase()) {
- // Japanese
- case 'ja':
- case 'ja-jp':
- requiredFont = {
- family: 'Noto Sans JP',
- sources: [
- {
- src: notoSansJPRegular,
- style: 'normal',
- weight: 'normal',
- },
- {
- src: notoSansJPSemibold,
- style: 'normal',
- weight: 'semibold',
- },
- {
- src: notoSansJPBold,
- style: 'normal',
- weight: 'bold',
- },
- ],
- direction: 'ltr',
- breakwords: true,
- };
- break;
- // Georgian
- case 'ka':
- requiredFont = {
- family: 'Noto Sans Georgian',
- sources: [
- {
- src: notoSansGeorgianRegular,
- style: 'normal',
- weight: 'normal',
- },
- {
- src: notoSansGeorgianSemibold,
- style: 'normal',
- weight: 'semibold',
- },
- {
- src: notoSansGeorgianBold,
- style: 'normal',
- weight: 'bold',
- },
- ],
- direction: 'ltr',
- breakwords: false,
- };
- break;
- // Hebrew
- case 'he':
- case 'he-il':
- requiredFont = {
- family: 'Noto Sans Hebrew',
- sources: [
- {
- src: notoSansHebrewRegular,
- style: 'normal',
- weight: 'normal',
- },
- {
- src: notoSansHebrewSemibold,
- style: 'normal',
- weight: 'semibold',
- },
- {
- src: notoSansHebrewBold,
- style: 'normal',
- weight: 'bold',
- },
- ],
- direction: 'rtl',
- breakwords: false,
- };
- break;
- // Korean
- case 'ko':
- case 'ko-kp':
- case 'ko-kr':
- requiredFont = {
- family: 'Noto Sans KR',
- sources: [
- {
- src: notoSansKRRegular,
- style: 'normal',
- weight: 'normal',
- },
- {
- src: notoSansKRSemibold,
- style: 'normal',
- weight: 'medium',
- },
- {
- src: notoSansKRBold,
- style: 'normal',
- weight: 'bold',
- },
- ],
- direction: 'ltr',
- breakwords: false,
- };
- break;
- // Punjabi (India)
- // Noto Sans variant of this font was not working as expected and was preventing PDF generation
- // Replaced with Noto Serif version
- // NOTE: Punjabi (Pakistan) - pa-PK - is not currently supported and falls back to Punjabi (India)
- // If Punjabi (Pakistan) becomes supported, and will need its own script (Shamukhi) and direction (RTL)
- case 'pa':
- case 'pa-in':
- requiredFont = {
- family: 'Noto Serif Gurmukhi',
- sources: [
- {
- src: notoSerifGurmukhiRegular,
- style: 'normal',
- weight: 'normal',
- },
- {
- src: notoSerifGurmukhiSemibold,
- style: 'normal',
- weight: 'semibold',
- },
- {
- src: notoSerifGurmukhiBold,
- style: 'normal',
- weight: 'bold',
- },
- ],
- direction: 'ltr',
- breakwords: false,
- };
- break;
- // Thai
- case 'th':
- requiredFont = {
- family: 'Noto Sans Thai',
- sources: [
- {
- src: notoSansThaiRegular,
- style: 'normal',
- weight: 'normal',
- },
- {
- src: notoSansThaiSemibold,
- style: 'normal',
- weight: 'semibold',
- },
- {
- src: notoSansThaiBold,
- style: 'normal',
- weight: 'bold',
- },
- ],
- direction: 'ltr',
- breakwords: false,
- };
- break;
- // Chinese (Mainland) - Simplified Chinese
- case 'zh':
- case 'zh-cn':
- requiredFont = {
- family: 'Noto Sans SC',
- sources: [
- {
- src: notoSansSCRegular,
- style: 'normal',
- weight: 'normal',
- },
- {
- src: notoSansSCMedium,
- style: 'normal',
- weight: 'medium',
- },
- {
- src: notoSansSCBold,
- style: 'normal',
- weight: 'bold',
- },
- ],
- direction: 'ltr',
- breakwords: true,
- };
- break;
- // Chinese (Taiwan) - Traditional Chinese
- case 'zh-tw':
- requiredFont = {
- family: 'Noto Sans TC',
- sources: [
- {
- src: notoSansTCRegular,
- style: 'normal',
- weight: 'normal',
- },
- {
- src: notoSansTCMedium,
- style: 'normal',
- weight: 'medium',
- },
- {
- src: notoSansTCBold,
- style: 'normal',
- weight: 'bold',
- },
- ],
- direction: 'ltr',
- breakwords: true,
- };
- break;
- default:
- requiredFont = {
- family: 'Noto Sans',
- sources: [
- {
- src: notoSansRegular,
- style: 'normal',
- weight: 'normal',
- },
- {
- src: notoSansSemibold,
- style: 'normal',
- weight: 'semibold',
- },
- {
- src: notoSansBold,
- style: 'normal',
- weight: 'bold',
- },
- ],
- direction: 'ltr',
- breakwords: false,
- };
- break;
- }
-
- return requiredFont;
-};
diff --git a/packages/fxa-settings/src/components/DataBlock/index.test.tsx b/packages/fxa-settings/src/components/DataBlock/index.test.tsx
index 0ccb0353c59..8b54b9f07b2 100644
--- a/packages/fxa-settings/src/components/DataBlock/index.test.tsx
+++ b/packages/fxa-settings/src/components/DataBlock/index.test.tsx
@@ -22,7 +22,6 @@ const multiValue = [
Object.defineProperty(window.navigator, 'clipboard', {
value: { writeText: jest.fn() },
});
-window.URL.createObjectURL = jest.fn();
it('can render single values', () => {
renderWithLocalizationProvider();
diff --git a/packages/fxa-settings/src/components/DataBlockInline/index.test.tsx b/packages/fxa-settings/src/components/DataBlockInline/index.test.tsx
index 1fdea6a4cf8..deba654dba8 100644
--- a/packages/fxa-settings/src/components/DataBlockInline/index.test.tsx
+++ b/packages/fxa-settings/src/components/DataBlockInline/index.test.tsx
@@ -11,7 +11,6 @@ const singleValue = 'ANMD 1S09 7Y2Y 4EES 02CW BJ6Z PYKP H69F';
Object.defineProperty(window.navigator, 'clipboard', {
value: { writeText: jest.fn() },
});
-window.URL.createObjectURL = jest.fn();
it('can render single values', () => {
renderWithLocalizationProvider();
diff --git a/packages/fxa-settings/src/components/GetDataTrio/index.test.tsx b/packages/fxa-settings/src/components/GetDataTrio/index.test.tsx
index 2dc837affb4..c4e8ae9aab8 100644
--- a/packages/fxa-settings/src/components/GetDataTrio/index.test.tsx
+++ b/packages/fxa-settings/src/components/GetDataTrio/index.test.tsx
@@ -14,7 +14,6 @@ const url = 'https://mozilla.org';
const setTooltipVisible = jest.fn();
it('renders Trio as expected', () => {
- window.URL.createObjectURL = jest.fn();
renderWithLocalizationProvider(
{
});
it('renders single Copy button as expected', () => {
- window.URL.createObjectURL = jest.fn();
renderWithLocalizationProvider(
Download and continue
diff --git a/packages/fxa-settings/src/components/RecoveryKeySetupDownload/index.test.tsx b/packages/fxa-settings/src/components/RecoveryKeySetupDownload/index.test.tsx
index 54c86c31953..2794a8bf83f 100644
--- a/packages/fxa-settings/src/components/RecoveryKeySetupDownload/index.test.tsx
+++ b/packages/fxa-settings/src/components/RecoveryKeySetupDownload/index.test.tsx
@@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-import { screen, waitFor, within } from '@testing-library/react';
+import { screen, within } from '@testing-library/react';
import { renderWithLocalizationProvider } from 'fxa-react/lib/test-utils/localizationProvider';
import { Subject } from './mocks';
import {
@@ -27,13 +27,7 @@ describe('RecoveryKeySetupDownload', () => {
const listItems = within(list).getAllByRole('listitem');
expect(listItems.length).toBe(4);
- await waitFor(
- () => {
- const b = screen.getByRole('button', { name: 'Download and continue' });
- expect(b).toBeInTheDocument();
- },
- { timeout: 5000 }
- );
+ screen.getByRole('button', { name: 'Download and continue' });
screen.getByRole('button', {
name: 'Continue without downloading',
diff --git a/packages/fxa-settings/src/components/RecoveryKeySetupDownload/index.tsx b/packages/fxa-settings/src/components/RecoveryKeySetupDownload/index.tsx
index a405ea26840..48ea48e75bd 100644
--- a/packages/fxa-settings/src/components/RecoveryKeySetupDownload/index.tsx
+++ b/packages/fxa-settings/src/components/RecoveryKeySetupDownload/index.tsx
@@ -2,7 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-import React, { Suspense, lazy } from 'react';
import {
FolderIconListItem,
GlobeIconListItem,
@@ -11,8 +10,8 @@ import {
} from '../IconListItem';
import { FtlMsg } from 'fxa-react/lib/utils';
import { logViewEvent } from '../../lib/metrics';
-import LoadingSpinner from 'fxa-react/components/LoadingSpinner';
import DataBlockInline from '../DataBlockInline';
+import ButtonDownloadRecoveryKey from '../ButtonDownloadRecoveryKey';
import { RelierCmsInfo } from '../../models';
export interface RecoveryKeySetupDownloadProps {
@@ -23,14 +22,6 @@ export interface RecoveryKeySetupDownloadProps {
cmsInfo?: RelierCmsInfo;
}
-const spinner = (
-
-);
-
-const ButtonDownloadRecoveryKeyPDF = lazy(
- () => import('../ButtonDownloadRecoveryKeyPDF')
-);
-
export const InlineRecoveryKeySetupDownload = ({
recoveryKeyValue,
email,
@@ -81,11 +72,9 @@ export const InlineRecoveryKeySetupDownload = ({
-
-
-
+