diff --git a/lib/components/secondary-device-qr-auth-context-provider.react.js b/lib/components/secondary-device-qr-auth-context-provider.react.js --- a/lib/components/secondary-device-qr-auth-context-provider.react.js +++ b/lib/components/secondary-device-qr-auth-context-provider.react.js @@ -34,9 +34,18 @@ import { fullBackupSupport } from '../utils/services-utils.js'; import { waitUntilDatabaseDeleted } from '../utils/wait-until-db-deleted.js'; +type QRAuthErrorCallback = ( + error: mixed, + isUserDataRestoreError?: boolean, +) => void; + +type ErrorSubscription = { + +remove: () => void, +}; + type Props = { +children: React.Node, - +onLogInError: (error: mixed, isUserDataRestoreError?: boolean) => void, + +onLogInError?: QRAuthErrorCallback, +generateAESKey: () => Promise, +composeTunnelbrokerQRAuthMessage: ( encryptionKey: string, @@ -56,6 +65,7 @@ +closeSecondaryQRAuth: () => void, +canGenerateQRs: boolean, +qrAuthInProgress: boolean, + +registerErrorListener: (callback: QRAuthErrorCallback) => ErrorSubscription, }; const SecondaryDeviceQRAuthContext: React.Context = @@ -65,6 +75,7 @@ closeSecondaryQRAuth: () => {}, canGenerateQRs: true, qrAuthInProgress: false, + registerErrorListener: () => ({ remove: () => {} }), }); function SecondaryDeviceQRAuthContextProvider(props: Props): React.Node { @@ -76,6 +87,32 @@ parseTunnelbrokerQRAuthMessage, } = props; + const errorListners = React.useRef>([]); + const handleError: QRAuthErrorCallback = React.useCallback( + (error: mixed, isUserDataRestoreError?: boolean) => { + onLogInError?.(error, isUserDataRestoreError); + errorListners.current.forEach(listener => + listener(error, isUserDataRestoreError), + ); + }, + [onLogInError], + ); + const registerErrorListener = React.useCallback( + (listener: QRAuthErrorCallback) => { + errorListners.current.push(listener); + + return { + remove: () => { + const idx = errorListners.current.indexOf(listener); + if (idx !== -1) { + errorListners.current.splice(idx, 1); + } + }, + }; + }, + [], + ); + const [primaryDeviceID, setPrimaryDeviceID] = React.useState(); const [qrData, setQRData] = React.useState(); @@ -146,12 +183,12 @@ try { return await logInSecondaryDevice(userID); } catch (err) { - onLogInError(err); + handleError(err); void openSecondaryQRAuth(); return null; } }, - [logInSecondaryDevice, onLogInError, openSecondaryQRAuth], + [logInSecondaryDevice, handleError, openSecondaryQRAuth], ); React.useEffect(() => { @@ -225,10 +262,10 @@ getMessageForException(e) ?? 'unknown error', new Set([logTypes.BACKUP, logTypes.ERROR]), ); - onLogInError(e, true); + handleError(e, true); } }, - [addLog, onLogInError, userDataRestore], + [addLog, handleError, userDataRestore], ); const tunnelbrokerMessageListener = React.useCallback( @@ -332,6 +369,7 @@ closeSecondaryQRAuth, canGenerateQRs: !socketState.isAuthorized, qrAuthInProgress: qrAuthState === 'started', + registerErrorListener, }), [ qrData, @@ -339,6 +377,7 @@ closeSecondaryQRAuth, socketState.isAuthorized, qrAuthState, + registerErrorListener, ], );