expo: Expo Crypto's randomUUID function sometimes returns promises
Minimal reproducible example
N/A
Summary
We’ve had an illusive issue in our production mobile app for the past few months: the problem was hard to describe as there were a multitude of things not working in bizarrely unexpected ways.
After a pretty extensive campaign of debugging efforts, we discovered that calls to uuid’s v4() function would return a Promise Object, which would resolve to a UUID, rather than the underlying string. Part of what made this incredibly hard to discover was that Sentry uses uuids internally; so it was effectively broken.
At a guess, this issue only seems to occur 1/50 times after a user updates the native binary of their app. Once they enter this “corrupt boot” situation, a simple restart of the app would resolve the issue. We’ve not been able to reproduce the issue deterministically despite a ton of efforts.
I was wondering if anybody has any insight on how this could be possible? Shouldn’t the Expo Modules API always resolve synchronously? Maybe there could be something wrong with our project configuration? Any intuition as to what we should look at?
Screenshot of what our logging was able to capture:
- Looks like this: https://stackoverflow.com/questions/57410321/async-await-function-returning-40-0-65-0-55-null-72-null
- Note: some of our log dumps were failing because
JSON.stringifythrows when you give it an unresolved promise (but works once resolved).
We polyfill the global object like this:
Other bits:
- React Native’s polyfillGlobal: https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Utilities/PolyfillFunctions.js
- We didn’t use expo-standard-web-crypto as it doesn’t seem to provide
randomUUID- why is this?
Relevant package versions:
expo-crypto:~12.4.1expo:~49.0.5uuid:^9.0.0expo-updates:~0.18.11
Environment
expo-env-info 1.0.5 environment info: System: OS: macOS 13.5 Shell: 5.9 - /bin/zsh Binaries: Node: 16.15.1 - ~/.nvm/versions/node/v16.15.1/bin/node Yarn: 3.4.1 - ~/.nvm/versions/node/v16.15.1/bin/yarn npm: 8.11.0 - ~/.nvm/versions/node/v16.15.1/bin/npm Watchman: 2023.05.15.00 - /opt/homebrew/bin/watchman Managers: CocoaPods: 1.12.1 - /opt/homebrew/bin/pod SDKs: iOS SDK: Platforms: DriverKit 22.2, iOS 16.2, macOS 13.1, tvOS 16.1, watchOS 9.1 Android SDK: API Levels: 28, 30, 31, 33 Build Tools: 29.0.2, 30.0.2, 30.0.3, 31.0.0, 33.0.0 System Images: android-31 | Google APIs ARM 64 v8a, android-31 | Google Play ARM 64 v8a, android-33 | Google APIs ARM 64 v8a, android-33 | Google Play ARM 64 v8a IDEs: Android Studio: 2021.2 AI-212.5712.43.2112.8815526 Xcode: 14.2/14C18 - /usr/bin/xcodebuild npmGlobalPackages: eas-cli: 3.15.1 expo-cli: 6.3.10 Expo Workflow: bare
About this issue
- Original URL
- State: open
- Created 10 months ago
- Reactions: 5
- Comments: 17 (4 by maintainers)
@tsapeta Hi, we’re on SDK 50 and it’s definitely still occurring. We’ve also found two other bugs in other libraries that exhibit similar issues, which is the expected data isn’t available when it should be on startup (in secure-store and localization).
I might be making a huge presumption, but that’s three expo native modules that exhibit very similar issues. Is it possible there’s an underlying issue in Expo’s native module implementation somewhere? It’s very random to trigger it, which suggests a race condition somewhere to me, but I’m not an expert in the underlying Expo code 😃
While I’ve not tried to figure out the conditions for triggering this bug (or the localization one we’ve also run afoul of), the only way we’re able to trigger the bug in secure-store is when the app is closed, and is opened from cold-start by pressing on a notification alert. And even then, it only happens randomly. Hence it’s really difficult to prepare a minimal reproduction of it!
I am absolutely willing to help track down this issue as it’s become the cause of much hair tearing here. I can submit crash logs wherever possible and test out anything you need me to.
This problem just occurred for me and I’ve been able to reproduce it and trace it using the debugger. The call always returns a promise. I can’t tell what’s going on in the proxy code I’m tracing into, but it ends up here:
[NativeModulesProxy.native.js] NativeProxy[exportedMethodsKey][moduleName].forEach((methodInfo) => { NativeModulesProxy[moduleName][methodInfo.name] = (…args) => { // Use the new proxy to call methods on legacy modules, if possible. if (ExpoNativeProxy?.callMethod) { return ExpoNativeProxy.callMethod(moduleName, methodInfo.name, args); } // Otherwise fall back to the legacy proxy. // This is deprecated and might be removed in SDK47 or later. const { key, argumentsCount } = methodInfo; if (argumentsCount !== args.length) { return Promise.reject(new Error(
Native method ${moduleName}.${methodInfo.name} expects ${argumentsCount} ${argumentsCount === 1 ? 'argument' : 'arguments'} but received ${args.length})); } return LegacyNativeProxy.callMethod(moduleName, key, args); }; });[NativeModules.js] function genMethod(moduleID: number, methodID: number, type: MethodType) { let fn = null; if (type === ‘promise’) { fn = function promiseMethodWrapper(…args: Array<mixed>) { // In case we reject, capture a useful stack trace here. /* $FlowFixMe[class-object-subtyping] added when improving typing for * this parameters */ const enqueueingFrameError: ExtendedError = new Error(); return new Promise((resolve, reject) => { BatchedBridge.enqueueNativeCall( moduleID, methodID, args, data => resolve(data), errorData => reject( updateErrorWithErrorData( (errorData: $FlowFixMe), enqueueingFrameError, ), ), );
Thanks for all the details @Braden1996 – we’ve been seeing this mentioned several times but without any details that helped to narrow it down (till now!).
I’ll also try to reproduce this – it’s the only closure in the module, so perhaps this has something to do with it?
If you’re looking for a relatively easy fix in the meantime, try migrate away from
expo-cryptotoreact-native-get-random-values. It worked for us at the time! Although we’re now on SDK 50 and are usingexpo-cryptoperfectly fine.We’re planning to upgrade to SDK 50 in the coming weeks, I’ll keep you posted. But as mentioned by @skdrew, the issue occurs very randomly.
Btw thanks for re-opening the issue. 😃
@martinezleoml Could you try on SDK 50? My PR that autoclosed this issue landed in SDK 50.
Thought I’d add an extra comment here - this issue was causing our production app to have a serious error that has resulted in a large number of negative reviews on the app store. In order to work around it we’ve replaced expo-crypto with react-native-get-random-values and uuid.
This issue is not resolved. It should be re-opened until it’s been confirmed to be fixed.
Had the same issue while upgrading to Expo 48.
expo: 48.0.20expo-crypto: 12.2.1It is very difficult to reproduce, and when it happens, just restarting the application to get it working again.