PostHog / posthog-js-lite

Reimplementation of posthog-js to be as light and modular as possible.
https://posthog.com/docs/libraries
MIT License
69 stars 36 forks source link

React native app crashes during startup #182

Closed johnslemmer closed 9 months ago

johnslemmer commented 9 months ago

Bug description

First, everything works fine in development. I build my own expo dev client app for development. However, after I just made an official build of our app and install via Apple's TestFlight the app immediately crashes and I get this error in my Sentry logs:

TypeError
undefined is not a function

/Users/expo/workingdir/build/node_modules/posthog-react-native/lib/posthog-react-native/src/hooks/useLifecycleTracker.js in anonymous at line 1:520

{snip} kedRef.current){openTrackedRef.current=true;posthog.capture('Application Opened');}var subscription=_reactNative.AppState.addEventListener(' {snip}

I did dig through the PostHog code and saw that this errored code will never run while the posthog client is undefined.

https://github.com/PostHog/posthog-js-lite/blob/2911a8fbc17db974ec5ed2913a47f2b52bc1586b/posthog-react-native/src/hooks/useLifecycleTracker.ts#L12

Plus I would have gotten a different error (eg. Uncaught TypeError: Cannot read properties of undefined). So my assumption is that capture is undefined somehow causing this error to happen, but that doesn't really make sense as posthog is a class instance and capture is defined on that class. Any ideas?

Picture for reference:

image

How to reproduce

  1. Setup up posthog "without the posthog provider" https://posthog.com/docs/libraries/react-native#without-the-posthogprovider but still using PosthogProvider. Example setup:
import { PostHog } from "posthog-react-native";

export const analytics = PostHog.initAsync(process.env.POSTHOG_API_KEY, {
  host: "https://app.posthog.com",
});

let analyticsSync: PostHog | undefined;
analytics.then((res) => (analyticsSync = res)).catch(console.error);

// wrap your app with this
export function AnalyticsProvider({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <PostHogProvider
      client={analytics}
      autocapture={{
        captureTouches: true,
        captureLifecycleEvents: true,
        captureScreens: true,
        ...
  1. Build a standalone version of the iOS app (not a dev client).
  2. Install on device and open.

I understand this isn't a full recreation. I could try and spend some time recreating this with a simpler repo but it would have to wait a few days. Let me know.

Related sub-libraries

Additional context

App is on Expo 50 and posthog-react-native 2.11.3

johnslemmer commented 9 months ago

Certainly a work around is to NOT do this:

captureLifecycleEvents: true,

But that wouldn't be ideal.

Another workaroud idea is to manually wait for the posthog client initAsync to resolve before creating the provider.

johnslemmer commented 9 months ago

This workaround works... just tested it with a production build of our app:

import { PostHog, PostHobProvider } from "posthog-react-native";

// For accessing analytics outside of the React tree
export let analytics: PostHog | undefined;

// wrap your app with this
export function AnalyticsProvider({
  children,
}: {
  children: React.ReactNode;
}) {
  const [client, setClient] = React.useState<PostHog>();

  React.useEffect(() => {
    if (client) return;

    PostHog.initAsync(env.POSTHOG_API_KEY, {
      enable: !IS_DEBUG,
      host: "https://app.posthog.com",
    })
      .then((client) => {
        setClient(client);
        analytics = client;
      })
      .catch(console.error);
  }, [client]);

  if (!client) return;

  return (
    <PostHogProvider
      client={client}
      autocapture={{
        captureTouches: true,
        captureLifecycleEvents: true,
        captureScreens: true,
        ...
marandaneto commented 9 months ago

Hi @johnslemmer thanks for reporting.

I've tried to reproduce this and it works normally either with the PostHogProvider or just with the analyticsSync, see the example.

Can you provide a minimal reproducible example?

johnslemmer commented 9 months ago

@marandaneto here is a reproduction for you: https://github.com/johnslemmer/ph-crash-bug

It appears to be some sort of conflict between sentry and posthog in this case because when I remove sentry posthog no longer causes a crash.

marandaneto commented 9 months ago

Thanks @johnslemmer I hope https://github.com/PostHog/posthog-js-lite/pull/184 resolves this issue, I don't think it is a conflict but rather more libraries trying to resolve promises, and trying to use them before it's ready.

marandaneto commented 9 months ago

@johnslemmer mind testing the latest version?

johnslemmer commented 9 months ago

@marandaneto just updated to 2.11.5, rebuilt and installed. Sorry to say it is still crashing.

marandaneto commented 9 months ago

@johnslemmer can you reproduce on Android as well? I could not reproduce it on iOS anymore. Where is the crash happening now? can you enable the debug mode and copy-paste the logs?

johnslemmer commented 9 months ago

@marandaneto I don't have a way to test on an android device.... I've only been building an iOS app currently. You want me to turn on debug in Sentry?

marandaneto commented 9 months ago

@marandaneto I don't have a way to test on an android device.... I've only been building an iOS app currently. You want me to turn on debug in Sentry?

Yes, posthog also has a debug mode.

    <PostHogProvider
      client={client}
      autocapture={{
        captureTouches: true,
        captureLifecycleEvents: true,
        captureScreens: true,
        ...
      debug={true}
      ...

The other question is how you install the downloaded file from Expo (.ipa), are you using any CLI or something?

johnslemmer commented 9 months ago

@marandaneto I'm installing via the expo website. Specifically for all of this reproduction testing I've been creating an internal preview build and then on the website for a particular build I open that build page in safari and tap install (I also delete the old app first).

I'll try turning on those debugs later tonight and see what I get.... but won't those debugs just print to stdout and be lost in my "production-like" build of the app?

antoinerousseau commented 9 months ago

I'm having the same issue, so I though OK I will implement it the simple way as per the docs, that is using the posthog provider with apiKey instead of client and let it "take care [of the async loading] under the hood"... https://posthog.com/docs/libraries/react-native#with-the-posthogprovider

Thing is, it means I cannot track or use posthog hook functions at app load since the client is undefined, and I don't want to just bail out and not track: I want to track once it's loaded!

So for that, the provider should handle a queuing system for when its client is still loading, instead of returning an undefined client. Or return a Promise<client> from the hook.

marandaneto commented 9 months ago

@antoinerousseau do you also experience a crash? I agree, the RN should provide a sync. init as well so you don't run into those issues, a bit of this work relates to https://github.com/PostHog/posthog-js-lite/issues/96 since it requires the storage to be initialized and React Native is almost everything async.

marandaneto commented 9 months ago
export const posthog = new PostHog(
  '...,
  {
    host: 'https://app.posthog.com',
  },
);

    <PostHogProvider
      client={posthog}
      autocapture={{
        captureLifecycleEvents: true,
        captureScreens: true,
        captureTouches: true,
      }}
      debug={true}>
      {props.children}
    </PostHogProvider>

This should work because the PostHog creation is sync but it leads to this warning, so I guess it's not recommended as well.

@benjackwhite ideas what's going on here? just asking before I spend more time digging into it since you have worked on it.

marandaneto commented 9 months ago

I'm having the same issue, so I though OK I will implement it the simple way as per the docs, that is using the posthog provider with apiKey instead of client and let it "take care [of the async loading] under the hood"... posthog.com/docs/libraries/react-native#with-the-posthogprovider

Thing is, it means I cannot track or use posthog hook functions at app load since the client is undefined, and I don't want to just bail out and not track: I want to track once it's loaded!

So for that, the provider should handle a queuing system for when its client is still loading, instead of returning an undefined client. Or return a Promise<client> from the hook.

That's what I tried, it installs the app, I see it on my device, but when I click on it, it still says "this app cannot be installed because its integrity could not be verified", similar to this issue.

antoinerousseau commented 9 months ago

@antoinerousseau do you also experience a crash? I agree, the RN should provide a sync. init as well so you don't run into those issues, a bit of this work relates to #96 since it requires the storage to be initialized and React Native is almost everything async.

Yes it sometimes crashes at startup!

The provider init does not need to be async: the events queue can just use the memory through a variable instead of local async storage, then when the client and the storage are initialized it reads and executes the queue as a FIFO list

marandaneto commented 9 months ago

@antoinerousseau Can you reproduce it? Do you also happen to use sentry or was it just a coincidence? Do you use Expo EAS as well? Can you describe your setup? Does it happen on Android as well? Do you have stack traces?

The provider init does not need to be async: the events queue can just use the memory through a variable instead of local async storage, then when the client and the storage are initialized it reads and executes the queue as a FIFO list

Yes, that is correct, but I'll focus on the crash right now and later improve that.

antoinerousseau commented 9 months ago

@antoinerousseau Can you reproduce it? Do you also happen to use sentry or was it just a coincidence? Do you use Expo EAS as well? Can you describe your setup? Does it happen on Android as well? Do you have stack traces?

I didn't reproduce locally, I just received an alert from Google Play Store about our Android vitals dropping since we released an app update that included the async client init of PostHog, and the stacktrace matches the issue:

Capture d’écran 2024-02-22 à 09 14 29 Capture d’écran 2024-02-22 à 09 14 50 Capture d’écran 2024-02-22 à 09 15 13

We do happen to use Sentry too, but I don't see how it could be related? We don't use EAS, we build with AppCenter. I don't know if it happens on iOS but the report if for Android.

johnslemmer commented 9 months ago

Here is a log dump from a crashed app that has both sentry and posthog debug on. I don't see any posthog messages... just sentry. Also for future reference this article was super helpful in pulling the logs off a production build of an app that crashes at boot: https://docs.expo.dev/debugging/runtime-issues/#production-errors

logs2024.02.22.txt

marandaneto commented 9 months ago

@johnslemmer can you try to disable onunhandledrejection and patchGlobalPromise and test it? Or install the promise package as in the logs.

info 08:05:38.670741-0800 Bar 'Sentry Logger [warn]:', 'You appear to have multiple versions of the "promise" package installed. This may cause unexpected behavior like undefined Promise.allSettled. Please install the promise package manually using the exact version as the React Native package. See https://docs.sentry.io/platforms/react-native/troubleshooting/ for more details.'

I wonder if the promise mismatch is causing issues resolving the promises or something, shooting in the dark since I cannot reproduce it.

johnslemmer commented 9 months ago

@marandaneto installed promise@8.3.0. The app still crashes. Here are more logs:

logs2024.02.22.2.txt

marandaneto commented 9 months ago

@johnslemmer Sorry for throwing you under the bus and making you our guinea pig. We suspect its something with the patched Promise, can you test the latest version again, please? https://github.com/PostHog/posthog-js-lite/releases/tag/posthog-react-native-v2.11.6 Thanks.

johnslemmer commented 9 months ago

That fixed it!

@marandaneto thanks for digging in to this for me!

marandaneto commented 9 months ago

@antoinerousseau for the other issue, follow this one.