diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index ab43728ae84..e93cb0da7c1 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -788,7 +788,9 @@ "undecryptableAppBoundMixed": "Orca cannot decrypt {{value0}} of this browser's cookies because they use app-bound encryption; {{value1}} more could not be decrypted for another reason. You can import cookies from a file using “From File…”.", "undecryptableKeyring": "{{value0}} cookies could not be decrypted because the system keyring was unavailable. Unlock your login keyring (or install a Secret Service provider such as gnome-keyring) and import again.", "undecryptableKeyringMixed": "{{value0}} cookies could not be decrypted because the system keyring was unavailable; {{value1}} more could not be decrypted for another reason. Unlock your login keyring (or install a Secret Service provider such as gnome-keyring) and import again.", - "undecryptableUnknown": "{{value0}} cookies could not be decrypted and were skipped. Close the source browser completely and try the import again." + "undecryptableUnknown": "{{value0}} cookies could not be decrypted and were skipped. Close the source browser completely and try the import again.", + "unrecognizedWarning": "The cookie import finished with a warning this version of Orca does not recognize. Update Orca to see the details, then check this profile before relying on its cookies.", + "undecryptableUnrecognizedReason": "{{value0}} cookies could not be decrypted and were skipped for a reason this version of Orca does not recognize. Update Orca to see the details, then try the import again." } } } diff --git a/src/renderer/src/lib/browser-cookie-import-toast.test.ts b/src/renderer/src/lib/browser-cookie-import-toast.test.ts index 23e04c53034..05858a32fd8 100644 --- a/src/renderer/src/lib/browser-cookie-import-toast.test.ts +++ b/src/renderer/src/lib/browser-cookie-import-toast.test.ts @@ -101,6 +101,46 @@ describe('emitBrowserCookieImportToast', () => { expect(warningToastMock).not.toHaveBeenCalled() }) + // Why: the summary crosses the runtime RPC wire and is cast, not decoded, so a newer host can + // publish a reason/code this client build has never heard of (#14683 follow-up). + it('still warns when a newer host sends an undeclared undecryptable reason', () => { + emitBrowserCookieImportToast( + { + ...summary, + warning: { + code: 'cookies-undecryptable', + failedCookies: 3, + reason: 'hardware-token-required' + } as unknown as BrowserCookieImportSummary['warning'] + }, + 'Imported 0 cookies.', + 'Remote Linux' + ) + + const message = warningToastMock.mock.calls[0]?.[0] + expect(typeof message).toBe('string') + expect(message).not.toBe('') + expect(message).toContain('3') + }) + + it('still warns when a newer host sends an undeclared warning code', () => { + emitBrowserCookieImportToast( + { + ...summary, + warning: { + code: 'profile-locked', + failedCookies: 3 + } as unknown as BrowserCookieImportSummary['warning'] + }, + 'Imported 0 cookies.', + 'Remote Linux' + ) + + const message = warningToastMock.mock.calls[0]?.[0] + expect(typeof message).toBe('string') + expect(message).not.toBe('') + }) + it('keeps both applicable warnings when restart fallback is unavailable', () => { emitBrowserCookieImportToast( { diff --git a/src/renderer/src/lib/browser-cookie-import-toast.ts b/src/renderer/src/lib/browser-cookie-import-toast.ts index a8fa4aa6bae..8918aa8a125 100644 --- a/src/renderer/src/lib/browser-cookie-import-toast.ts +++ b/src/renderer/src/lib/browser-cookie-import-toast.ts @@ -3,8 +3,39 @@ import type { BrowserCookieImportSummary } from '../../../shared/browser-workspa import { translate } from '@/i18n/i18n' type CookieImportWarning = NonNullable +type CookieImportWarningCode = CookieImportWarning['code'] +type UndecryptableReason = Extract['reason'] + +// Why: the summary is cast, not decoded, on the way off the runtime RPC wire, so a newer host can +// send a code/reason this build has never heard of. The Record keys are the union itself, so a new +// member fails typecheck here instead of silently falling out of the switch (#14683 follow-up). +const HANDLED_WARNING_CODES: Record = { + 'restart-fallback-unavailable': true, + 'cookies-undecryptable': true +} + +const HANDLED_UNDECRYPTABLE_REASONS: Record = { + 'app-bound-encryption': true, + 'linux-keyring-unavailable': true, + unknown: true +} + +function isHandledWarningCode(code: string): code is CookieImportWarningCode { + return Object.hasOwn(HANDLED_WARNING_CODES, code) +} + +function isHandledUndecryptableReason(reason: string): reason is UndecryptableReason { + return Object.hasOwn(HANDLED_UNDECRYPTABLE_REASONS, reason) +} function formatCookieImportWarning(warning: CookieImportWarning): string { + const code: string = warning.code + if (!isHandledWarningCode(code)) { + return translate( + 'auto.lib.browser.cookie.import.toast.unrecognizedWarning', + 'The cookie import finished with a warning this version of Orca does not recognize. Update Orca to see the details, then check this profile before relying on its cookies.' + ) + } switch (warning.code) { case 'restart-fallback-unavailable': return warning.loadedCookies === 0 @@ -21,7 +52,15 @@ function formatCookieImportWarning(warning: CookieImportWarning): string { value1: warning.loadedCookies + warning.failedCookies } ) - case 'cookies-undecryptable': + case 'cookies-undecryptable': { + const reason: string = warning.reason + if (!isHandledUndecryptableReason(reason)) { + return translate( + 'auto.lib.browser.cookie.import.toast.undecryptableUnrecognizedReason', + '{{value0}} cookies could not be decrypted and were skipped for a reason this version of Orca does not recognize. Update Orca to see the details, then try the import again.', + { value0: warning.failedCookies } + ) + } switch (warning.reason) { case 'app-bound-encryption': return warning.otherFailedCookies @@ -54,6 +93,7 @@ function formatCookieImportWarning(warning: CookieImportWarning): string { { value0: warning.failedCookies } ) } + } } }