Skip to content

Commit 43ca5c9

Browse files
committed
add fallback text to webauthn error mapping
1 parent 30933ba commit 43ca5c9

4 files changed

Lines changed: 123 additions & 77 deletions

File tree

packages/fxa-settings/src/components/Settings/PagePasskeyAdd/index.test.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,9 @@ describe('PagePasskeyAdd', () => {
179179
mockHandleWebAuthnError.mockReturnValue({
180180
category: WebAuthnErrorCategory.UserAction,
181181
errorType: WebAuthnErrorType.NotAllowed,
182-
userMessageKey: 'passkey-registration-error-not-allowed',
182+
ftlId: 'passkey-registration-error-not-allowed',
183+
fallbackText:
184+
'Passkey setup failed or is unavailable. Try again or choose another method.',
183185
logToSentry: false,
184186
});
185187

@@ -198,7 +200,8 @@ describe('PagePasskeyAdd', () => {
198200
mockHandleWebAuthnError.mockReturnValue({
199201
category: WebAuthnErrorCategory.UserAction,
200202
errorType: WebAuthnErrorType.Timeout,
201-
userMessageKey: 'passkey-registration-error-timeout',
203+
ftlId: 'passkey-registration-error-timeout',
204+
fallbackText: 'Passkey setup was canceled. Try again.',
202205
logToSentry: false,
203206
});
204207

packages/fxa-settings/src/components/Settings/PagePasskeyAdd/index.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,7 @@ export const PagePasskeyAdd = () => {
101101
Sentry.captureException
102102
);
103103
alertBar.error(
104-
ftlMsgResolver.getMsg(
105-
categorized.userMessageKey,
106-
ftlMsgResolver.getMsg(
107-
'page-passkey-add-error-system',
108-
'System not available. Try again later.'
109-
)
110-
)
104+
ftlMsgResolver.getMsg(categorized.ftlId, categorized.fallbackText)
111105
);
112106
navigateToSettings();
113107
return;

packages/fxa-settings/src/lib/passkeys/webauthn-errors.test.ts

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22
* License, v. 2.0. If a copy of the MPL was not distributed with this
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44

5+
import fs from 'fs';
6+
import path from 'path';
57
import {
68
categorizeWebAuthnError,
9+
ERROR_MAP,
710
handleWebAuthnError,
811
WebAuthnErrorCategory,
912
WebAuthnErrorType,
@@ -30,8 +33,9 @@ describe('categorizeWebAuthnError — user-action errors (no Sentry)', () => {
3033
expect(result.category).toBe(WebAuthnErrorCategory.UserAction);
3134
expect(result.errorType).toBe(errorType);
3235
expect(result.logToSentry).toBe(false);
33-
expect(result.userMessageKey).toContain(operation);
34-
expect(result.userMessageKey).toMatch(/passkey-.+-error-.+/);
36+
expect(result.ftlId).toContain(operation);
37+
expect(result.ftlId).toMatch(/passkey-.+-error-.+/);
38+
expect(result.fallbackText).toMatch(/passkey/i);
3539
}
3640
);
3741
});
@@ -42,9 +46,10 @@ describe('categorizeWebAuthnError — user-action errors (no Sentry)', () => {
4246
dom('NotAllowedError'),
4347
'authentication'
4448
);
45-
expect(reg.userMessageKey).not.toBe(auth.userMessageKey);
46-
expect(reg.userMessageKey).toContain('registration');
47-
expect(auth.userMessageKey).toContain('authentication');
49+
expect(reg.ftlId).not.toBe(auth.ftlId);
50+
expect(reg.ftlId).toContain('registration');
51+
expect(auth.ftlId).toContain('authentication');
52+
expect(reg.fallbackText).not.toBe(auth.fallbackText);
4853
});
4954
});
5055

@@ -64,6 +69,10 @@ describe('categorizeWebAuthnError — device/platform errors (no Sentry)', () =>
6469
expect(result.category).toBe(WebAuthnErrorCategory.DevicePlatform);
6570
expect(result.errorType).toBe(errorType);
6671
expect(result.logToSentry).toBe(false);
72+
expect(result.fallbackText.length).toBeGreaterThan(0);
73+
expect(result.fallbackText).toMatch(
74+
/passkey|authenticator|secure site/i
75+
);
6776
}
6877
);
6978
});
@@ -77,8 +86,8 @@ describe('categorizeWebAuthnError — device/platform errors (no Sentry)', () =>
7786
dom('InvalidStateError'),
7887
'authentication'
7988
);
80-
expect(reg.userMessageKey).toContain('registration');
81-
expect(auth.userMessageKey).toContain('authentication');
89+
expect(reg.ftlId).toContain('registration');
90+
expect(auth.ftlId).toContain('authentication');
8291
});
8392
});
8493

@@ -123,7 +132,8 @@ describe('categorizeWebAuthnError — unexpected errors (Sentry enabled)', () =>
123132
expect(result.category).toBe(WebAuthnErrorCategory.Unexpected);
124133
expect(result.errorType).toBe(WebAuthnErrorType.Unknown);
125134
expect(result.logToSentry).toBe(true);
126-
expect(result.userMessageKey).toContain(operation);
135+
expect(result.ftlId).toContain(operation);
136+
expect(result.fallbackText).toMatch(/try|went wrong/i);
127137
}
128138
);
129139

@@ -133,9 +143,9 @@ describe('categorizeWebAuthnError — unexpected errors (Sentry enabled)', () =>
133143
dom('ConstraintError'),
134144
'authentication'
135145
);
136-
expect(reg.userMessageKey).toContain('registration');
137-
expect(reg.userMessageKey).toContain('constraint');
138-
expect(auth.userMessageKey).toContain('unexpected');
146+
expect(reg.ftlId).toContain('registration');
147+
expect(reg.ftlId).toContain('constraint');
148+
expect(auth.ftlId).toContain('unexpected');
139149
});
140150
});
141151

@@ -163,6 +173,31 @@ describe('categorizeWebAuthnError — non-DOMException inputs default to unexpec
163173
);
164174
});
165175

176+
describe('fallbackText matches FTL strings', () => {
177+
const ftlPath = path.resolve(__dirname, 'en.ftl');
178+
const ftlContent = fs.readFileSync(ftlPath, 'utf-8');
179+
180+
const ftlMessages = new Map<string, string>();
181+
for (const line of ftlContent.split('\n')) {
182+
const match = line.match(
183+
/^(passkey-(?:registration|authentication)-error-\S+)\s*=\s*(.+)$/
184+
);
185+
if (match) {
186+
ftlMessages.set(match[1], match[2].trim());
187+
}
188+
}
189+
190+
const entries = Object.entries(ERROR_MAP);
191+
192+
describe.each(OPERATIONS)('operation: %s', (operation) => {
193+
test.each(entries)('%s fallbackText matches FTL', (_, entry) => {
194+
const ftlValue = ftlMessages.get(entry.ftlId[operation]);
195+
expect(ftlValue).toBeDefined();
196+
expect(entry.fallbackText[operation]).toBe(ftlValue);
197+
});
198+
});
199+
});
200+
166201
describe('handleWebAuthnError', () => {
167202
it('calls captureException for unexpected errors', () => {
168203
const captureException = jest.fn();

0 commit comments

Comments
 (0)