Closed samih7 closed 2 years ago
We are experiencing the exact same issue, with a similar setup, except we store the token in the AsyncStorage
using a wrapper like you did. Our users are also getting logged out after some time.
I'm very interested to get more infos on that issue.
Let me know if I can assist with anything to get this progressing.
Sorry you are experiencing issues! Would you be able to provide a larger code snippet or perhaps a basic sample repo that shows this behavior? I'd like to reproduce this, but I haven't seen this behavior before with a React Native app I have configured with federation.
I'm mainly interested in where/how currentSession
is used as well as how exactly Amplify is configured.
Also, have you reproduced this behavior without passing in a custom storage solution? Amplify will use AsyncStorage
by default, so there isn't a need to pass it in explicitly.
@amhinson Does the following help?
Pretty sure we were able to reproduce it without passing any storage implementation yes, then we did pass one because of the following issue. Seems sessions problems can arise from aws-amplify
still using the React Native AsyncStorage
implementation while it's now been moved to https://github.com/react-native-async-storage/async-storage.
import Auth from '@aws-amplify/auth';
import Amplify, { Hub, HubCallback } from '@aws-amplify/core';
import React from 'react';
import { Linking } from 'react-native';
import Config from 'react-native-config';
import InAppBrowser from 'react-native-inappbrowser-reborn';
import { navigationRef } from './navigation/helpers';
Amplify.configure({
aws_project_region: Config.AWS_REGION,
aws_cognito_region: Config.AWS_REGION,
aws_user_pools_id: Config.AWS_USER_POOLS_ID,
aws_user_pools_web_client_id: Config.AWS_USER_POOLS_WEB_CLIENT_ID,
// storage: SecureStorage,
oauth: {
domain: Config.OAUTH_DOMAIN,
scope: ['email', 'profile', 'openid', 'aws.cognito.signin.user.admin'],
redirectSignIn: Config.OAUTH_URI_SCHEME,
redirectSignOut: Config.OAUTH_URI_SCHEME,
responseType: Config.OAUTH_RESPONSE_TYPE,
urlOpener: async (url: string, redirectUrl: string) => {
if (url.includes('logout')) {
return;
}
if (!(await InAppBrowser.isAvailable())) {
await Linking.openURL(url);
return;
}
const result = await InAppBrowser.openAuth(url, redirectUrl, {
showTitle: false,
enableUrlBarHiding: true,
enableDefaultShare: false,
ephemeralWebSession: false,
});
if (result.type === 'success') {
await Linking.openURL(result.url);
}
},
},
});
// Used before firing API calls
export const getAuthorizationToken = async (): Promise<string | null> => {
const session = await Auth.currentSession();
const token = session?.getIdToken()?.getJwtToken();
return typeof token === 'string' ? `Bearer ${token}` : null;
};
const useAuthListener = (): void => {
const handleAuth = React.useCallback<HubCallback>(
async ({ payload: { event, data } }) => {
switch (event) {
case 'signIn':
{
const token = data?.signInUserSession?.idToken?.jwtToken;
const groups =
data?.signInUserSession?.accessToken?.payload['cognito:groups'];
if (token && groups) {
if (groups.includes('USER')) {
navigationRef.current?.navigate('Home');
} else if (groups.includes('BETA_CANDIDATE')) {
navigationRef.current?.navigate('Beta');
}
}
}
break;
case 'signOut':
navigationRef.current?.navigate('Login');
// Cleanup some internal storages
break;
default:
break;
}
},
[]
);
React.useEffect(() => {
Hub.listen('auth', handleAuth);
return (): void => {
Hub.remove('auth', handleAuth);
};
}, [handleAuth]);
};
const App: React.FC = () => {
useAuthListener();
React.useEffect(() => {
async function redirectUser(): Promise<void> {
try {
const session = await Auth.currentSession();
if (!session?.getIdToken()?.getJwtToken()) {
throw new Error("Auth.currentSession hasn't returned any session");
}
navigationRef.current?.navigate('Home');
} catch {
navigationRef.current?.navigate('Login');
}
}
redirectUser();
}, []);
// Bunch of screens triggering actions such as:
// Auth.federatedSignIn, Auth.signIn, Auth.signOut, Auth.signUp, Auth.resendSignUp, Auth.forgotPassword, Auth.confirmSignUp
return <></>;
};
export default App;
I'm having this same issue. Here's how I reproduce in an emulator:
I'm using a custom storage object as described in the Amplify docs. It turns out that the storage object isn't loading all the data it needs from memory before we try to validate the session. So the order of events is:
I'm toying around with different solutions now. The most promising so far is to add a slight delay (100ms) before the first call to Auth.currentSession() to give the sync() method time to complete.
@nerdygirl Thanks for the explanation. Just to make sure we're talking about the same issue:
You mentioned that if you hit "r" in the terminal, you get redirected to login since currentSession()
fails. What happens if you refresh again/restart the app after that? In my case, the logout is permanent: the storage has been cleared out and it doesn't matter if I try restarting the app several times, I just won't get logged in.
Yeah, the same thing happens to me in that case.
And although I swear this was working on both Android and iOS previously, it now only appears to work on Android. The difference seems to be that the keys stored in AsyncStorage are now wiped out on iOS, even if I'm just refreshing the app.
Are you noticing any differences between Android and iOS, @samih7?
@nerdygirl Both occurring on iOS and Android for me.
I just got the issue while debugging our app, I logged out the storage content and got the following:
[{"key": "amplify-redirected-from-hosted-ui", "service": "my_service", "value": "true"}, {"key": "amplify-signin-with-hostedUI", "service": "my_service", "value": "true"}]
So it really looks like user-related data has been cleared out from the storage. @amhinson
Edit: once again, Auth.currentSession
is failing with 'No current user' error
Same issue. Using own implementation of storage based on SecureStore (expo-secure-store). No issues on Amplify auth v2, started happening on v3.
I have a new theory I'm testing out. Hoping it's helpful to someone (or you can also test and we can share notes).
I followed instructions from AWS Cognito docs to write my custom storage class. Here's the code they provide for setItem()
and sync()
with a couple of comments from me:
static setItem(key, value) {
AsyncStorage.setItem(MYSTORAGE_KEY_PREFIX + key, value); // I REPLACED ASYNCSTORAGE WITH KEYCHAIN
dataMemory[key] = value;
return dataMemory[key];
}
static sync() {
if (!MyStorage.syncPromise) {
MyStorage.syncPromise = new Promise((res, rej) => {
AsyncStorage.getAllKeys((errKeys, keys) => { // BUT MY KEYS AREN'T IN ASYNC STORAGE
if (errKeys) rej(errKeys);
const memoryKeys = keys.filter((key) => key.startsWith(MYSTORAGE_KEY_PREFIX));
AsyncStorage.multiGet(memoryKeys, (err, stores) => {
if (err) rej(err);
stores.map((result, index, store) => {
const key = store[index][0];
const value = store[index][1];
const memoryKey = key.replace(MYSTORAGE_KEY_PREFIX, '');
dataMemory[memoryKey] = value;
});
res();
});
});
});
}
return MyStorage.syncPromise;
}
In my code, I replaced this line:
AsyncStorage.setItem(MYSTORAGE_KEY_PREFIX + key, value);
with a call to Keychain.setInternetCredentials()
One million tests and console.log statements later, I believe that what happens is this:
setItem()
to store 7 key/value pairssync()
sync()
looks for keys in AsyncStorage, but they're not thereSo to fix, I either need to store the keys—and just the keys, not the values—in AsyncStorage or I need to store them somewhere else and retrieve them in the sync()
method.
A quick test with storing them in AsyncStorage works locally, but I haven't tried it on a device yet. I just added this to the top of setItem()
:
AsyncStorage.setItem(MYSTORAGE_KEY_PREFIX + key, value);
Probably not what I'll go with when I ship it, but at least I don't have to log in again every time I refresh the app on my emulator for right now. Tomorrow, I'll figure out a good way to store these keys in Keychain for consistency.
UPDATE: In a major face-palm moment, I figured out why I thought I had it working previously. It's because I tested on my emulator with the default storage. Since that's AsyncStorage, all the keys did get into AsyncStorage. So then when I re-added my custom storage class, it worked! So exciting! Except it was all lies.
The fix I mentioned on April 4 is still needed because of the way I'm querying my own backend service for user data. You can see my confusion on April 8 when it suddenly was only working on one emulator. I now believe that's because I spun up a new iOS emulator so it stopped working there, but kept working on Android.
@nerdygirl @rcCaregiven @Goszu @therealemjy Are you guys also reproducing the issue on v4.x.x?
@samih7 v4 blows up with Expo:
The package at "node_modules/crypto-js/core.js" attempted to import the Node standard library module "crypto". It failed because the native React runtime does not include the Node standard library. Read more at https://docs.expo.io/workflow/using-libraries/#using-third-party-libraries
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Not stale bot, still looking into this. Thank you @Goszu, @samih7, and @nerdygirl for your patience and the details you've provided us thus far. Apologies for the delay.
Thanks everyone for all the details. I am looking into this and I don't seem to be able to reproduce this issue. I tried different auth flows (default and custom) with both default and custom storage solutions and there is no problem with maintaining the current session even after many reloads (or exiting the app and coming back). Auth.currentSession()
and Auth.currentAuthenticatedUser()
always work as expected.
Does the issue occur consistently after specific actions? if so, can anyone provide a little more details on how exactly is it reproduced?
We did end up resolving this on our end. It had to do with how we were returning promises (or how/when they resolved) after replacing AsyncStorage with react-native-keychain. It has all been refactored since our initial fix, so unfortunately I don't have better details than that.
We see this issue occasionally. It is rare but does happen. I have not been able to pin it down to a particular user action. They open their device and find themselves logged out.
We see this issue occasionally. It is rare but does happen. I have not been able to pin it down to a particular user action. They open their device and find themselves logged out.
Exactly the same than @joebernard for us, except it is not that rare. We also removed the custom auth storage layer we had implemented, so we now use plain AsyncStorage
that comes with aws-amplify
. But yes, the worst part is that we can't reproduce the issue consistently either.
@Khairo-kh In another context we tried to call currentCredentials
from @aws-amplify/auth
and we received a No Cognito Identity pool provided for unauthenticated access
error. Makes sense since we indeed didn't have an identity pool configured, but could this be related to the logout issue in any way?
@samih7 I think this is an expected response if no identity pool is configured. Identity pools are used more on the authorization side of things. This can be something like authorizing authenticated users to access certain AWS services (S3 storage for example), or allowing unauthenticated access to certain resources. The issue described here seems to be more about maintaining sessions after authenticating a user (with user pools) so I don't think this is related. Still looking into this, thanks everyone for the feedback.
Any news on that? I'm facing the same issue here
Any update from aws or a potential solution??? Using the amplify authentication wrapper in a react application, Im currently having this issue on only 2 machines out of many where the user has to login every day or every few days. However, all the other machines will remain logged in for the default of 30 days.
I have also tried using a custom storage class like stated in the docs to no avail.
We see this issue occasionally. It is rare but does happen. I have not been able to pin it down to a particular user action. They open their device and find themselves logged out.
Exactly the same than @joebernard for us, except it is not that rare. We also removed the custom auth storage layer we had implemented, so we now use plain
AsyncStorage
that comes withaws-amplify
. But yes, the worst part is that we can't reproduce the issue consistently either.
@samih7 Have you noticed the issue only stays with the computer and not the user?? Im having this issue on 2 machines out of 20. I have everyone using the same credentials to login but this issue will only happen on the same 2 machines no matter who uses them.
I think I found out what was causing it in my case. I am using expo-secure-store as a storage solution. It does not accept certain characters inside keys (only accepts ".", "-", and "_"). In my setup phone number is being used as a user name. It is a part of the key for session details. Phone number includes "+" sign. That basically throws an error and session information is not persisted.
We see this issue occasionally. It is rare but does happen. I have not been able to pin it down to a particular user action. They open their device and find themselves logged out.
Exactly the same than @joebernard for us, except it is not that rare. We also removed the custom auth storage layer we had implemented, so we now use plain
AsyncStorage
that comes withaws-amplify
. But yes, the worst part is that we can't reproduce the issue consistently either.@samih7 Have you noticed the issue only stays with the computer and not the user?? Im having this issue on 2 machines out of 20. I have everyone using the same credentials to login but this issue will only happen on the same 2 machines no matter who uses them.
Hard to say in our case. The same users tend to get logged out, but it could indeed be device-related.
I think I found out what was causing it in my case. I am using expo-secure-store as a storage solution. It does not accept certain characters inside keys (only accepts ".", "-", and "_"). In my setup phone number is being used as a user name. It is a part of the key for session details. Phone number includes "+" sign. That basically throws an error and session information is not persisted.
For us it's definitely not a matter of custom storage — we removed that layer and the issue was still occurring.
I think I found out what was causing it in my case. I am using expo-secure-store as a storage solution. It does not accept certain characters inside keys (only accepts ".", "-", and "_"). In my setup phone number is being used as a user name. It is a part of the key for session details. Phone number includes "+" sign. That basically throws an error and session information is not persisted.
@Goszu - did you find a solution to this?
@angusmccloud yes. If you are using custom storage solution (like 'expo-secure-store') and it does not accept certain chars in keys, try switching to AsyncStorage for example. Another option is to write a bit of code that will replace unsupported characters when saving, retrieving and deleting storage items.
Any updates on the issue?
We are experiencing a similar behavior with some users getting logged out from time to time (some days to some minutes after login)
No custom storage solution is being used
@Andrea-Vigano - I don't remember specifics (this was almost a year ago), but looking at my project...
On login/sign-up I store credentials in the secure store, and stringified:
const credentials = { email, password: pwd };
await SecureStore.setItemAsync("auth", JSON.stringify(credentials));
Then when looking to see if the user is logged in, I first check to see if Amplify things they're signed in:
const user = await Auth.currentAuthenticatedUser();
If they're not, I try to log them in with the secureStore:
const credentials = await SecureStore.getItemAsync('auth');
if(credentials) {
const { email, password } = JSON.parse(credentials);
const user = await Auth.signIn(email, password);
const formattedUser = await formatAuthUser(user);
return formattedUser;
} else {
return unauthedUser;
}
Note: There are try/catches and things wrapped around each of these sections of code, etc...
Describe the bug We use
amplify-js
3.3.20
in our React Native app and some of our users are getting logged out from once very couple of days to several times a day.We retrieve user's token with the
currentSession
API to then redirect the user accordingly (to login page or home page), and it seems that this function sometimes throws a 'No current user' error even if the user was previously logged in. Before that, we were usingcurrentAuthenticatedUser
instead and the same problem was happening ('The user is not authenticated' error was thrown).We provide a custom storage using
react-native-sensitive-info
to store user data securely (we created a wrapper based on https://docs.amplify.aws/lib/auth/manageusers/q/platform/js#managing-security-tokens). To isolate the problem, we also released a version withoutreact-native-sensitive-info
where we simply passed in anAsyncStorage
implementation as in https://docs.amplify.aws/lib/auth/manageusers/q/platform/js#managing-security-tokens but we still had the logout issue, which suggests it does not come from this other library. We also checked that ID & access tokens were correctly refreshed byaws-amplify
which seems to be the case.Resembles https://github.com/aws-amplify/amplify-js/issues/4351.
To Reproduce
currentSession
throws an error)Expected behavior
Stay logged in if we were previously logged in before.
What is Configured? If applicable, please provide what is configured for Amplify CLI:
Environment
``` System: OS: macOS 11.2.3 CPU: (12) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz Memory: 402.45 MB / 32.00 GB Shell: 5.8 - /bin/zsh Binaries: Node: 12.18.4 - /usr/local/bin/node npm: 6.14.11 - /usr/local/bin/npm Watchman: 4.9.0 - /usr/local/bin/watchman Browsers: Chrome: 89.0.4389.82 Safari: 14.0.3 npmPackages: @apollo/client: 3.3.7 => 3.3.7 @babel/core: 7.12.10 => 7.12.10 @babel/runtime: 7.12.5 => 7.12.5 @graphql-codegen/cli: 1.20.1 => 1.20.1 @graphql-codegen/fragment-matcher: 2.0.1 => 2.0.1 @graphql-codegen/import-types-preset: 1.18.1 => 1.18.1 @graphql-codegen/near-operation-file-preset: 1.17.13 => 1.17.13 @graphql-codegen/typescript: 1.20.2 => 1.20.2 @graphql-codegen/typescript-operations: 1.17.14 => 1.17.14 @graphql-codegen/typescript-react-apollo: 2.2.1 => 2.2.1 @react-native-async-storage/async-storage: 1.13.3 => 1.13.3 @react-native-community/masked-view: 0.1.10 => 0.1.10 @react-native-community/netinfo: 5.9.10 => 5.9.10 @react-native-community/slider: 3.0.3 => 3.0.3 @react-native-firebase/analytics: 10.1.1 => 10.1.1 @react-native-firebase/app: 10.1.0 => 10.1.0 @react-native-firebase/messaging: 10.1.1 => 10.1.1 @react-native-firebase/perf: 10.1.1 => 10.1.1 @react-native-picker/picker: 1.9.10 => 1.9.10 @react-navigation/bottom-tabs: 5.11.7 => 5.11.7 @react-navigation/material-top-tabs: 5.3.13 => 5.3.13 @react-navigation/native: 5.9.2 => 5.9.2 @react-navigation/stack: 5.14.2 => 5.14.2 @sentry/react: 5.29.2 => 5.29.2 @sentry/react-native: 2.1.1 => 2.1.1 @storybook/addon-actions: 5.3.21 => 5.3.21 @storybook/addon-knobs: 5.3.21 => 5.3.21 @storybook/addon-links: 5.3.21 => 5.3.21 @storybook/addon-ondevice-actions: 5.3.23 => 5.3.23 @storybook/addon-ondevice-knobs: 5.3.23 => 5.3.23 @storybook/addon-storyshots: 6.0.27 => 6.0.27 @storybook/addons: 6.0.27 => 6.0.27 @storybook/react-native: 5.3.23 => 5.3.23 @storybook/react-native-server: 5.3.23 => 5.3.23 @testing-library/jest-native: 4.0.1 => 4.0.1 @testing-library/react-hooks: 3.4.2 => 3.4.2 @testing-library/react-native: 7.1.0 => 7.1.0 @types/i18n-js: 3.8.0 => 3.8.0 @types/jest: 26.0.20 => 26.0.20 @types/lodash: 4.14.168 => 4.14.168 @types/react: 16.9.52 => 16.9.52 @types/react-native: 0.63.45 => 0.63.45 @types/react-native-permissions: 2.0.0 => 2.0.0 @types/react-native-share: 1.1.4 => 1.1.4 @types/react-native-vector-icons: 6.4.6 => 6.4.6 @types/react-redux: 7.1.16 => 7.1.16 @types/react-test-renderer: 16.9.3 => 16.9.3 @types/recompose: 0.30.7 => 0.30.7 @types/redux-mock-store: 1.0.2 => 1.0.2 @types/styled-components-react-native: 5.1.1 => 5.1.1 @types/uuid: 3.4.6 => 3.4.6 @types/yup: 0.29.7 => 0.29.7 @typescript-eslint/eslint-plugin: 4.16.1 => 4.16.1 @typescript-eslint/parser: 4.16.1 => 4.16.1 @welldone-software/why-did-you-render: 5.0.0 => 5.0.0 amazon-cognito-identity-js: 4.5.5 => 4.5.5 apollo-link-rest: 0.8.0-beta.0 => 0.8.0-beta.0 apollo-link-timeout: 4.0.0 => 4.0.0 aws-amplify: 3.3.20 => 3.3.20 babel-jest: 26.6.3 => 26.6.3 babel-plugin-inline-import: 3.0.0 => 3.0.0 babel-plugin-transform-react-remove-prop-types: 0.4.24 => 0.4.24 babel-plugin-transform-remove-console: 6.9.4 => 6.9.4 core-js: 3.8.3 => 3.8.3 cross-fetch: 3.0.6 => 3.0.6 date-fns: 2.16.1 => 2.16.1 eslint: 7.21.0 => 7.21.0 eslint-config-prettier: 8.1.0 => 8.1.0 eslint-plugin-import: 2.22.1 => 2.22.1 eslint-plugin-prettier: 3.3.1 => 3.3.1 eslint-plugin-react: 7.22.0 => 7.22.0 eslint-plugin-react-hooks: 4.2.0 => 4.2.0 eslint-plugin-react-native: 3.10.0 => 3.10.0 eslint-plugin-react-native-a11y: 2.0.4 => 2.0.4 eslint-plugin-simple-import-sort: 7.0.0 => 7.0.0 eslint-watch: 7.0.0 => 7.0.0 expo-av: 9.0.0 => 9.0.0 expo-video-thumbnails: 4.3.0 => 4.3.0 filesize: 6.1.0 => 6.1.0 flipper-plugin-performance: 1.2.0 => 1.2.0 formik: 2.2.6 => 2.2.6 graphql: 15.5.0 => 15.5.0 graphql-anywhere: 4.2.7 => 4.2.7 husky: 4.3.8 => 4.3.8 i18n-js: 3.5.0 => 3.5.0 jest: 26.6.3 => 26.6.3 jest-styled-components: 7.0.3 => 7.0.3 jetifier: 1.6.6 => 1.6.6 lint-staged: 10.5.3 => 10.5.3 lodash: 4.17.20 => 4.17.20 metro-react-native-babel-preset: 0.64.0 => 0.64.0 mockdate: 3.0.2 => 3.0.2 patch-package: 6.4.4 => 6.4.4 polished: 4.0.5 => 4.0.5 prettier: 2.2.1 => 2.2.1 querystring: 0.2.0 => 0.2.0 react: 16.13.1 => 16.13.1 react-devtools: 3.6.3 => 3.6.3 react-dom: 16.13.1 => 16.13.1 react-error-boundary: 2.2.3 => 2.2.3 react-native: 0.63.4 => 0.63.4 react-native-config: git://github.com/luggit/react-native-config.git#89a602bf8be3808838403a97afaf915caeec76c2 => 0.11.7 react-native-date-picker: 3.2.9 => 3.2.9 react-native-dev-menu: 4.0.2 => 4.0.2 react-native-device-info: 5.3.0 => 5.3.0 react-native-email-link: 1.10.0 => 1.10.0 react-native-fast-image: 8.3.4 => 8.3.4 react-native-flipper: 0.79.0 => 0.79.0 react-native-flipper-apollo-devtools: 0.0.2 => 0.0.2 react-native-gesture-handler: 1.9.0 => 1.9.0 react-native-get-random-values: 1.5.1 => 1.5.1 react-native-haptic-feedback: 1.11.0 => 1.11.0 react-native-image-crop-picker: 0.36.0 => 0.36.0 react-native-inappbrowser-reborn: 3.5.1 => 3.5.1 react-native-keyboard-aware-scroll-view: 0.9.3 => 0.9.3 react-native-lightbox-v2: 0.8.7 => 0.8.7 react-native-linear-gradient: 2.5.6 => 2.5.6 react-native-localize: 2.0.1 => 2.0.1 react-native-modal: 11.6.1 => 11.6.1 react-native-parsed-text: 0.0.22 => 0.0.22 react-native-performance: 1.4.0 => 1.4.0 react-native-performance-flipper-reporter: 1.2.0 => 1.2.0 react-native-permissions: 2.0.3 => 2.0.3 react-native-picker-select: 8.0.4 => 8.0.4 react-native-reanimated: 1.13.2 => 2.0.0 react-native-safe-area-context: 2.0.3 => 2.0.3 react-native-screens: 2.17.1 => 2.17.1 react-native-sensitive-info: 5.5.8 => 5.5.8 react-native-share: 2.0.0 => 2.0.0 react-native-shimmer-placeholder: 2.0.6 => 2.0.6 react-native-splash-screen: 3.2.0 => 3.2.0 react-native-svg: 12.1.0 => 12.1.0 react-native-svg-transformer: 0.14.3 => 0.14.3 react-native-tab-view: 2.15.2 => 2.15.2 react-native-unimodules: 0.12.0 => 0.12.0 react-native-url-preview: 1.1.9 => 1.1.9 react-native-vector-icons: 6.6.0 => 6.6.0 react-redux: 7.2.2 => 7.2.2 react-test-renderer: 16.14.0 => 16.14.0 redux: 4.0.5 => 4.0.5 redux-flipper: 1.4.2 => 1.4.2 redux-mock-store: 1.5.4 => 1.5.4 redux-persist: 6.0.0 => 6.0.0 redux-saga: 1.1.3 => 1.1.3 reselect: 4.0.0 => 4.0.0 styled-components: 5.2.1 => 5.2.1 typesafe-actions: 5.1.0 => 5.1.0 typescript: 4.2.2 => 4.2.2 uuid: 3.3.3 => 3.3.3 yup: 0.29.3 => 0.29.3 npmGlobalPackages: apollo: 2.31.2 expo-cli: 4.3.0 npm: 6.14.11 parcel-bundler: 1.12.4 ```Smartphone (please complete the following information):