-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
deeplinking in android detached app fires multiple times when app in background #2128
Comments
Any updates on this? @aniltirola |
I actually saw that it also fires multiple times in the expo client, however the URL is just the base URL without path/to/my/detailscreen (also without the parameters if there are any). I'm encountering this when working with OAuth and Google [Update] - my mistake. It doesn't fires multiple times in the expo client. |
This is still broken in production for us on Expo SDK 35, expo-web-browser 7.0.1. |
I can confirm this is broken sdk 35 |
I ended up finding a fix for this - commenting out the following lines in amplify/auth/lib/Auth.js
|
We are experiencing a similar issue. It fires 3 times but only on the android standalone apk. It works well on iOS and also on both iOS and android using the expo client |
Hi friends, https://gist.github.com/aniltirola/369e08971e18662772d8239bc101fe6f I think we can close, because it is a small bug, which is easy to workaround. |
Not sure that the app crashing is a small bug, but thank you for posting your workaround! |
Hi @schellack! |
My app restarts. Unfortunately the workaround doesn't seem to help. Perhaps because I'm using AWS Amplify, which fires off multiple requests, and thus gets back multiple deeplinks? I'm still (weeks later) trying to find some way to work around this issue. But this is still very broken for me. I cannot deploy my app on Android as a result. |
I just wrapped my handling function in lodash's Bit of a hack though. I don't know why this issue is closed. |
I worked around this by adding a simple short-circuit check in my Linking handler component:
|
@aniltirola can you please reopen this issue? This should be fixed in the expo core, not using downstream solutions like the one you posted. Those of us that are still experiencing this would be grateful if you could reopen this issue... |
Linking.removeEventListener seems to do the job |
Just found this page and it seems apropos for me. This happened to me on my deep link standalone android. I'm glad that it fires after following a lot of guides, but it now fires exactly 3 times every single time I click a link. I verified this by my own logging. I also verified using the same logging that the listener in question is definitely only being registered once (thinking on the off chance it was getting triple registered by clones of the component, for example). So this bug seems alive and well. Expo SDK 42 (42.0.0). |
Have the same issue, here is a complete code sample from #14550: export const CreditCardAdd: FC<CreditCardAdd> = ({
onAdd,
}) => {
const { client, handleError } = useClient();
const [number, setNumber] = useState<string>('');
const [expirationDate, setExpirationDate] = useState<string>('');
const [csv, setCsv] = useState<string>('');
const [ready, setReady] = useState<boolean>(false);
const [loading, setLoading] = useState<boolean>(false);
const creditCardRef = useRef<CreditCardsData>();
const registrationDataRef = useRef<string>();
const ipAddressRef = useRef<string>();
const headersRef = useRef<any>();
const handleCreditCardRegistrationError = (error: FeathersError): void => {
setLoading(false);
if (error.code === 402 || error.message === 'credit-cards.register.failure') {
Alert.alert(
"Impossible d'enregistrer la carte",
'Merci de vérifier les données saisies ainsi que la validité de la carte.'
+ '\nSi le problème persiste, contactez-nous.',
);
} else {
handleError(error);
}
};
const urlEventHandler = async (event): Promise<void> => {
const parsedUrl = queryString.parseUrl(event.url);
// Works on iOS only and cause a crash on Android will opening an another browser instance.
if (Platform.OS === 'ios') {
console.log('WebBrowser.dismissBrowser');
await WebBrowser.dismissBrowser();
}
if (
parsedUrl.url.endsWith('/headers')
) {
console.log('headers');
headersRef.current = parsedUrl.query;
// We have to set a dirty workaround to the "already opened browser" issue.
setTimeout(async () => {
await WebBrowser.openBrowserAsync(
`${Constants.manifest?.extra?.apiEndpoint
}/browser.html?redirectUrl=${
Linking.createURL('browser-info')}`,
);
}, 1000);
}
if (
parsedUrl.url.endsWith('/browser-info')
) {
console.log('browser-info');
client.service('credit-cards')
.patch(creditCardRef.current._id, {
registrationId: creditCardRef.current.registrationId,
registrationData: registrationDataRef.current,
registrationIpAddress: ipAddressRef.current,
// @ts-expect-error Unable to set the type of the parsed query.
registrationBrowserInfo: {
...parsedUrl.query,
acceptHeader: headersRef.current.accept,
},
secureModeRedirectUrl: Linking.createURL('paybox-3ds', {
queryParams: { creditCardId: creditCardRef.current._id },
}),
})
.then(ResultHelpers.toOne)
.then((result) => WebBrowser.openBrowserAsync(result.secureModeUrl, {
// @see https://github.com/expo/expo/issues/8072#issuecomment-621173298
showInRecents: true,
}))
.catch((error) => {
handleCreditCardRegistrationError(error);
});
}
if (
parsedUrl.url.endsWith('/paybox-3ds')
&& typeof parsedUrl.query.creditCardId === 'string'
&& typeof parsedUrl.query.transactionId === 'string'
) {
console.log('paybox-3ds');
client.service('credit-cards')
.patch(
parsedUrl.query.creditCardId,
{
transactionId: parsedUrl.query.transactionId,
},
)
.then(ResultHelpers.toOne)
.then((result) => onAdd(result._id))
.catch(handleCreditCardRegistrationError);
}
};
useFocusEffect(
useCallback(() => {
Linking.addEventListener('url', urlEventHandler);
return () => Linking.removeEventListener('url', urlEventHandler);
}, []),
);
const handleSubmit: ButtonProps['onPress'] = () => {
const expirationDateRaw = expirationDate.replace('/', '');
const numberRaw = number.replace(/\s/g, '');
setLoading(true);
client.service('credit-cards').create({
lastDigits: numberRaw.slice(numberRaw.length - 4),
expirationDate: expirationDateRaw,
})
.then(ResultHelpers.toOne)
.then(
(result) => {
const cardValidationData = {
accessKeyRef: result.accessKey,
data: result.preRegistrationData,
cardNumber: numberRaw,
cardExpirationDate: expirationDateRaw,
cardCvx: csv,
};
const formBody = Object.keys(cardValidationData)
.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(cardValidationData[key])}`).join('&');
creditCardRef.current = result;
return Promise
.all([
fetch(result.registrationUrl, {
method: 'POST',
body: formBody,
headers: {
'Content-type': 'application/x-www-form-urlencoded;charset=UTF-8',
},
})
.then((registrationResult) => registrationResult.text()),
fetch('https://api.ipify.org').then((ipResult) => ipResult.text()),
])
.then(async ([registrationData, ipAddress]) => {
registrationDataRef.current = registrationData;
ipAddressRef.current = ipAddress;
await WebBrowser.openBrowserAsync(
`${Constants.manifest?.extra?.apiEndpoint
}/headers?redirectUrl=${
Linking.createURL('headers')}`,
);
});
},
)
.catch(handleCreditCardRegistrationError);
};
const handleNumberChange = (newNumber: string): void => {
setNumber(newNumber.length > number.length
? newNumber.replace(/\s/g, '').split(/(.{4})/).filter((x) => x).join(' ')
: newNumber);
};
const handleExpirationDateChange = (newExpirationDate: string): void => {
setExpirationDate(newExpirationDate.length > expirationDate.length
? newExpirationDate.replace(/\//g, '').split(/(.{2})/).filter((x) => x).join('/')
: newExpirationDate);
};
useEffect(() => setReady(
Boolean(number) && number.length === 19
&& Boolean(expirationDate) && expirationDate.length === 5
&& Boolean(csv) && csv.length === 3,
), [number, expirationDate, csv]);
return (
<ScrollView>
<Spacer />
<Input
label="Numéro de carte"
value={number}
onChangeText={handleNumberChange}
keyboardType="number-pad"
autoFocus
maxLength={19}
editable={!loading}
/>
<View style={{ flexDirection: 'row' }}>
<Input
label="MM/AA"
value={expirationDate}
onChangeText={handleExpirationDateChange}
keyboardType="number-pad"
maxLength={5}
editable={!loading}
/>
<Input
label="Code de sécurité (CVV)"
value={csv}
onChangeText={setCsv}
keyboardType="number-pad"
editable={!loading}
/>
</View>
<Button
title="Valider"
onPress={handleSubmit}
loading={loading}
disabled={!ready}
/>
</ScrollView>
);
}; @aniltirola I read your gist, but I honestly don't see what to do comparing to my use case. I will try @bryjch's workaround which look simpler. May you add your feedback about this method? Also, I have to agree with @duhaime: This sneaky bug happens only on standalone android app, which is a real pain to identify, debug and fix. This expo issue should be re-opened waiting for a real solution provided by expo itself. May you reconsider its status? Regards |
@aniltirola Your workaround is indeed working, thanks for that. Here my diff using hooks: diff --git a/src/components/payment/CreditCardAdd.tsx b/src/components/payment/CreditCardAdd.tsx
index 66d1163..6f082b3 100644
--- a/src/components/payment/CreditCardAdd.tsx
+++ b/src/components/payment/CreditCardAdd.tsx
@@ -55,6 +55,14 @@ export const CreditCardAdd: FC<CreditCardAdd> = ({
};
const urlEventHandler = async (event): Promise<void> => {
+ // @see https://github.com/expo/expo/issues/2128#issuecomment-547418161
+ const now = Date.now();
+ const elapsed = now - global.timestampLastDeepLinkEvent;
+ if (elapsed < 1000) {
+ return;
+ }
+ global.timestampLastDeepLinkEvent = now;
+
const parsedUrl = queryString.parseUrl(event.url);
// Works on iOS only and cause a crash on Android will opening an another browser instance.
@@ -128,6 +136,7 @@ export const CreditCardAdd: FC<CreditCardAdd> = ({
};
useEffect(() => {
+ global.timestampLastDeepLinkEvent = -1;
Linking.addEventListener('url', urlEventHandler);
return () => Linking.removeEventListener('url', urlEventHandler); However, relying on an estimated "time to spawn" is quite dangerous and definitely not a solution to this bug. |
Environment
app-target: android
"expo": "^29.0.0",
"expokit": "1.4.0",
"react": "16.3.1",
"react-native": "https://github.com/expo/react-native/archive/sdk-29.0.0.tar.gz"
"exp": 56.0.0
create a android-standalone app with
exp detach
Steps to Reproduce
scheme
inapp.json
toexpodeeplinktest
Linking.addEventListener
from react-native and print events to the consoleexp detach
adb shell am start -a android.intent.action.VIEW -d "expodeeplinktest://path/to/my/detailscreen"
url
is fired multiple timesExpected Behavior
url
should only be fired one timeActual Behavior
url
fires multiple timesurl
fires only one time (as expected)Reproducible Demo
here is the gist to reproduce
https://gist.github.com/aniltirola/11dbe26f464bba315219d7ff5fe5e1bd
here is the snack
https://snack.expo.io/@anil_from_the_alps/expodeeplinktest
The text was updated successfully, but these errors were encountered: