Skip to content

Commit b9dcaa7

Browse files
committed
edge case improvements
1 parent 8d0f7fc commit b9dcaa7

2 files changed

Lines changed: 44 additions & 6 deletions

File tree

src/components/Authentication/Authentication.tsx

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,25 @@ const Authentication = (props: {
3333
const [passwordVisibility, setPasswordVisibility] = useState(false);
3434
const [biometricsAvailable, setBiometricsAvailable] = useState(false);
3535
const [setupBiometrics, setSetupBiometrics] = useState(false);
36+
const [modalVisible, setModalVisible] = useState(true);
37+
// Guards against the success setTimeout (queued before the user tapped
38+
// cancel) firing after a cancel propagation already started, which
39+
// would otherwise call actionStatus(true) seconds after actionStatus(false).
40+
const propagated = useRef(false);
41+
42+
// Fade out the modal before the parent unmounts us. Without this, an
43+
// abrupt unmount during the same render commit (e.g. Home setting
44+
// submittingTransaction=true on approve) leaves iOS Fabric to take an
45+
// empty snapshot of the dismissed view, blanketing the screen with a
46+
// black overlay that can also block touches until the app is restarted.
47+
const propagate = (status: boolean) => {
48+
if (propagated.current) return;
49+
propagated.current = true;
50+
setModalVisible(false);
51+
setTimeout(() => {
52+
props.actionStatus(status);
53+
}, 300);
54+
};
3655

3756
useEffect(() => {
3857
if (!props.biomatricsAllowed) {
@@ -110,7 +129,7 @@ const Authentication = (props: {
110129
if (data && data.password) {
111130
setPassword('');
112131
setPasswordVisibility(false);
113-
props.actionStatus(true);
132+
propagate(true);
114133
} else {
115134
// biometrics failed, were tempered with, disable biometrics option and only allow for password authentication
116135
setPassword('');
@@ -139,7 +158,7 @@ const Authentication = (props: {
139158
console.log('Close');
140159
setPassword('');
141160
setPasswordVisibility(false);
142-
props.actionStatus(false);
161+
propagate(false);
143162
};
144163

145164
const grantAccess = async () => {
@@ -222,7 +241,7 @@ const Authentication = (props: {
222241
setPassword('');
223242
setPasswordVisibility(false);
224243
setSetupBiometrics(false);
225-
props.actionStatus(true);
244+
propagate(true);
226245
} catch (error) {
227246
console.log(error);
228247
displayMessage('error', t('home:err_auth_pw_check'));
@@ -237,7 +256,7 @@ const Authentication = (props: {
237256
<Modal
238257
animationType="fade"
239258
transparent={true}
240-
visible={true}
259+
visible={modalVisible}
241260
onRequestClose={() => close()}
242261
>
243262
<BlurOverlay />

src/contexts/SocketContext.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import React, { createContext, useState, useEffect, useCallback } from 'react';
1+
import React, {
2+
createContext,
3+
useState,
4+
useEffect,
5+
useCallback,
6+
useRef,
7+
} from 'react';
28
import io, { Socket } from 'socket.io-client';
39
import { useAppSelector } from '../hooks';
410
import { useRelayAuth } from '../hooks';
@@ -322,15 +328,28 @@ export const SocketProvider = ({ children }: { children: React.ReactNode }) => {
322328
};
323329
}, [wkIdentity]);
324330

331+
// Tracks whether we emitted 'leave' on the way to background. iOS
332+
// briefly transitions to 'inactive' (not 'background') for system
333+
// overlays like Face ID, the control center pull-down, or an incoming
334+
// call banner. The socket stays connected through those, so we must
335+
// not re-join on the way back — re-joining causes the relay to
336+
// re-deliver the pending tx request, which surfaces in the UI as a
337+
// duplicate approval prompt right after a successful broadcast.
338+
const wasInBackground = useRef(false);
339+
325340
useEffect(() => {
326341
let subscription: NativeEventSubscription | null = null;
327342

328343
if (socket && wkIdentity) {
329344
subscription = AppState.addEventListener('change', (state) => {
330345
if (state === 'active') {
331-
emitAuthenticatedJoin(socket, wkIdentity);
346+
if (wasInBackground.current) {
347+
emitAuthenticatedJoin(socket, wkIdentity);
348+
wasInBackground.current = false;
349+
}
332350
} else if (state === 'background') {
333351
socket.emit('leave', { wkIdentity });
352+
wasInBackground.current = true;
334353
}
335354
});
336355
}

0 commit comments

Comments
 (0)