clerk/javascript

Expo/JavaScript client fails in strange ways with poor or no network connectivity

Closed this issue ยท 15 comments

Preliminary Checks

Reproduction

https://github.com/statico/repro-clerk-connectivity

Publishable key

pk_test_Y2xlcmsuaW5zcGlyZWQubGlvbmZpc2gtMzgubGNsLmRldiQ

Description

Steps to reproduce:

Prerequisites:

  • Enable your environment so that you can run an Expo Go app on iOS or Android: https://docs.expo.dev/ (basically, install Xcode or Android)
  • Check out https://github.com/statico/repro-clerk-connectivity and run pnpm install
  • Add a .env file with CLERK_PUBLISHABLE_KEY=
  • Run pnpm start and then hit i or a to open the app in iOS or Android
  • Use my publishable key or any Clerk instance with email/password and Google login enabled

Bug No. 1

  1. Ensure your network connection is working
  2. Open the app and click "Sign in with OAuth"
  3. Observe that OAuth sign in works just fine
  4. Click the Sign Out button
  5. Disable your network
  6. Optionally, reload the app to observe how long it takes for Clerk to load (presumably because it's retrying connections)
  7. Click the "Sign in with OAuth" button
  8. Observe the error "Cannot read property 'toString' of null"
  9. Observe "ClerkJS: Network error" in the console

Bug No. 2

  1. Ensure your network connection is working
  2. Open the app, enter a test email like ian@pickleheads.com, and click "Sign In"
  3. Observe the message "supportedFirstFactors is OK" โ€” this means that the SignIn object was initialized with one or more first factor authentication strategies
  4. Disable your network
  5. Optionally, reload the app to observe how long it takes for Clerk to load (presumably because it's retrying connections)
  6. Again, enter a test email like ian@pickleheads.com, and click "Sign In"
  7. Observe the that supportedFirstFactors is empty, likely because the SignIn object was not initialized
  8. Observe "ClerkJS: Network error" in the console

Expected behavior:

  1. It takes a long time to wait for isLoaded. If Clerk can't load itself, we should be able to propagate an error message to the user. Checking the NetInfo module for connectivity is not enough โ€” the internet could appear to be up, but it might just be Clerk that is inaccessible.
  2. When the network connections fail, Clerk prints an error, but there is no way to catch this error in order to report it to Sentry or the user.
  3. For OAuth, there should be a better catchable error message when the OAuth session cannot be started
  4. For identifier-based login, there should be a better catchable error message when the network is down

Thanks, Clerk team!

Environment

System:
    OS: macOS 14.4.1
    CPU: (12) arm64 Apple M2 Max
    Memory: 1.92 GB / 64.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 20.13.1 - ~/.nvm/versions/node/v20.13.1/bin/node
    npm: 10.8.1 - ~/.nvm/versions/node/v20.13.1/bin/npm
    pnpm: 9.1.1 - ~/.nvm/versions/node/v20.13.1/bin/pnpm
  Browsers:
    Brave Browser: 125.1.66.113
    Chrome: 125.0.6422.142
    Safari: 17.4.1
  npmPackages:
    @types/uuid: 9.0.8 => 9.0.8
    core-js: 3.37.1 => 3.37.1
    jsdom: 24.0.0 => 24.0.0
    p-limit: 5.0.0 => 5.0.0
    react-select: 5.8.0 => 5.8.0
    react-virtuoso: 4.7.10 => 4.7.10
$ grep clerk packages/*/package.json
packages/mobile/package.json: "@clerk/clerk-expo": "1.1.4",
packages/mobile/package.json: "@clerk/clerk-js": "5.4.0",
packages/mobile/package.json: "@clerk/types": "4.4.0",
packages/web/package.json: "@clerk/clerk-sdk-node": "5.0.7",
packages/web/package.json: "@clerk/nextjs": "5.0.12",

image

Hey, This is also happening to us on any type of login action (oauth google/apple and sign in through email)

stack: "TypeError: Cannot read property 'toString' of null
    at ?anon_0_ (http://192.168.3.107:8081/index.bundle//&platform=android&dev=true&hot=false&transform.routerRoot=app:264064:49)
    at next (native)
    at asyncGeneratorStep (http://192.168.3.107:8081/index.bundle//&platform=android&dev=true&hot=false&transform.routerRoot=app:7085:19)
    at _next (http://192.168.3.107:8081/index.bundle//&platform=android&dev=true&hot=false&transform.routerRoot=app:7099:29)
    at tryCallOne (address at InternalBytecode.js:1:1180)
    at anonymous (address at InternalBytecode.js:1:1874)
    at apply (native)
    at anonymous (http://192.168.3.107:8081/index.bundle//&platform=android&dev=true&hot=false&transform.routerRoot=app:49452:26)
    at _callTimer (http://192.168.3.107:8081/index.bundle//&platform=android&dev=true&hot=false&transform.routerRoot=app:49331:17)
    at _callReactNativeMicrotasksPass (http://192.168.3.107:8081/index.bundle//&platform=android&dev=true&hot=false&transform.routerRoot=app:49376:17)
    at callReactNativeMicrotasks (http://192.168.3.107:8081/index.bundle//&platform=android&dev=true&hot=false&transform.routerRoot=app:49582:44)
    at __callReactNativeMicrotasks (http://192.168.3.107:8081/index.bundle//&platform=android&dev=true&hot=false&transform.routerRoot=app:8307:48)
    at anonymous (http://192.168.3.107:8081/index.bundle//&platform=android&dev=true&hot=false&transform.routerRoot=app:8080:45)
    at __guard (http://192.168.3.107:8081/index.bundle//&platform=android&dev=true&hot=false&transform.routerRoot=app:8279:15)
    at flushedQueue (http://192.168.3.107:8081/index.bundle//&platform=android&dev=true&hot=false&transform.routerRoot=app:8079:21)
    at callFunctionReturnFlushedQueue (http://192.168.3.107:8081/index.bundle//&platform=android&dev=true&hot=false&transform.routerRoot=app:8064:33)"

It only seems to happen on Andriod from time to time.

We did manage to fix it on some in-house testing devices by erasing all cache and deleting all data before reinstalling the app, this is not acceptable since we can't find a way to seamlessly catch or automate that for the end user.

As a side note, we observe a warning at the app startup

ClerkJS: Network error at "https://immune-dinosaur-53.clerk.accounts.dev/v1/client/sign_ins?_clerk_js_version=5.7.0&_is_native=1" - TypeError: Network request failed. Please try again.

Maybe it is related to this?

Our app is expo 51 with current @clerk/clerk-expo latest version (1.2.1)

FWIW, our workaround is to catch this these types of errors and propagate them to the user. For example,

    try {
      setLoading.on()
      const { createdSessionId, setActive } = await startOAuthFlow()
      if (createdSessionId) {
        setActive?.({ session: createdSessionId })
      }
    } catch (err) {
      const message = extractClerkErrors(err)

      if (/Cannot read property 'toString' of null/.test(message)) {
        log.warn(`Probable network failure: to-String-of-null error during OAuth sign-in`)
        Alert.alert(
          "Oops!",
          `We couldn't sign you in, likely due to a network error. Please check your network connection and try again.\n\nIf you're still having trouble, please email us at support@example.com`,
        )
        return
      }

      log.error("error starting oauth flow for %s: %s", strategy, message)
      Alert.alert("Sign In Error", message)
    } finally {
      setLoading.off()
    }

// elsewhere...

export const extractClerkErrors = (error: any) => {
  if (error?.errors) {
    return (
      "Error: " +
      error.errors.map((e: any) => e.longMessage ?? e.message).join(", ")
    )
  } else {
    return "Error: " + (error?.message ?? String(error))
  }
}

I wonder if this is related to the fact that Clerk mutes network errors on non-browser environments, which is the case in React Native and probably on Expo too.

Because of this, in my React Native app (not Expo), network errors cause API operations to fail without throwing (it only logs an error message to the console, which is not very helpful).

I wonder if this is related to the fact that Clerk mutes network errors on non-browser environments, which is the case in React Native and probably on Expo too.

@anagstef Could this be the cause?

(I'm tagging you because you created the PR which introduced this behaviour.)

@statico @patr0cl0 Try adding this line as early as possible in the execution of your app:

window.navigator.onLine = true

This will prevent Clerk from suppressing errors by making isValidBrowserOnline() return true.

same for me, any update ?

window.navigator.onLine = true

Yes, this makes Clerk network errors appear. However, I'll have to research window.navigator.onLine and I'm worried that enabling it in production could be high risk, or maybe flood users with errors if they're on unstable connections.

I'm experimenting the same behavior on my Expo App

image

I'm getting this Network error with Expo too, usually happens when restoring the app from the background. The issue is that the auth state becomes out of sync, clerk thinks it is signed out, and as a result, the user is prompted to log in. Logging in then fails because the user is already signed in.

Has anybody figured out a way to handle this?

Yes. Ditch the Clerk's state handling and reimplement it yourself. Clerk in the current state is unusable on Expo, and after looking at its source code, it is not clear how this can be easily fixed. It's written by architecture astronauts, not caring at all about robustness.

I'm planning to do the following:

  1. Implement the session token retrieval.

DO NOT MOUNT CLERK.

You need to get the current __clerk_client_jwt token from the token cache. If you don't have this token, goto 2.

Then you need to call https://clerk.com/docs/reference/frontend-api/tag/Client#operation/getClient to get the list of active sessions, and then use the active session to call the https://clerk.com/docs/reference/frontend-api/tag/Sessions#operation/createSessionToken to get the session token. This can be done in a nice linear async/await code, without all the crazy state transitions.

  1. If you don't have the __clerk_client_jwt in your token cache, or if you get an "unauthenticated" error from the Clerk API in step 1, then you go to the login workflow.

  2. At the start of the login workflow, clear the token cache (delete the "__clerk_client_jwt" key). This will ensure, that the Clerk will not get into a wedged state once connection is re-established. Then mount the ClerkProvider and show the usual login/register stuff. Once the login succeeds, and you have the __clerk_client_jwt in the token cache, unmount the entire Clerk tree. And goto 1.

Hello everyone! Thank you for opening this issue and providing information and reproduction steps. ๐Ÿ™

Expo support is at the top of our priority list, so we are investigating how to resolve this without introducing breaking changes to existing apps.

I'm experimenting the same behavior on my Expo App

image

My team is having the same issue. Even when the app is open all the time, it will throw this error now and then. Operations that require our users to be logged in fail with a 401 error, it fixes itself on the second try. It seems very unstable.

Hello everyone! This week, in the latest version of the @clerk/clerk-expo package, we released a new experimental option for ClerkProvider, experimental.rethrowOfflineNetworkErrors, which rethrows errors caused by network issues.

Example usage on ClerkProvider:

<ClerkProvider
   experimental={{
      rethrowOfflineNetworkErrors: true
   }}
/>

Example of handling the error on Expo:

  import { isClerkRuntimeError } from "@clerk/clerk-js"

  const handleSignIn = async () => {
    try {
      const { createdSessionId, supportedFirstFactors } = await signIn.create({
        identifier: email
      });
    } catch (err) {
      if (isClerkRuntimeError(err) && err.code === 'network_error') {
         handleNetworkError();
      }
    }
  }

ref: #4525

โ— We are still actively working on providing better support for the Offline state on Expo. Thank you everyone for providing information regarding the issues you are facing, it is very helpful and valuable for us! ๐Ÿ™ Sorry for the inconvenience caused by this!

I'm experiencing the same thing. It appears like the token is expiring and not properly refreshing like it does on web.

Hello everyone! ๐Ÿ‘‹

Weโ€™re excited to share that weโ€™ve just released an experimental feature addressing the issues discussed here! ๐ŸŽ‰

You can learn more about it in our documentation: https://clerk.com/docs/references/expo/offline-support

As this should resolve the concerns raised, Iโ€™ll be closing this issue. However, if you encounter any unexpected behavior, please donโ€™t hesitate to open a new issue.

Thank you all for your feedbackโ€”itโ€™s greatly appreciated! ๐Ÿ™