clerk / javascript

Official Javascript repository for Clerk authentication
https://clerk.com
MIT License
959 stars 212 forks source link

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

Open statico opened 3 weeks ago

statico commented 3 weeks ago

Preliminary Checks

Reproduction

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

Publishable key

pk_test_Y2xlcmsuaW5zcGlyZWQubGlvbmZpc2gtMzgubGNsLmRldiQ

Description

Steps to reproduce:

Prerequisites:

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

patr0cl0 commented 3 weeks ago

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)

statico commented 2 weeks ago

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))
  }
}