googlearchive / firebase-dart

Dart wrapper for Firebase
https://pub.dev/packages/firebase
402 stars 143 forks source link

[firebase_analytics] Adblocker crashes the whole app. #335

Open ciriousjoker opened 4 years ago

ciriousjoker commented 4 years ago

Describe the bug A Flutter Web app will crash if the user has an adblocker enabled and <script src="https://www.gstatic.com/firebasejs/.../firebase-analytics.js"></script> can't be loaded. No widget is drawn, it crashes during the registerPlugin phase.

To Reproduce Steps to reproduce the behavior:

  1. Create default app and add firebase_analytics as a dependency
  2. Skip the part where you would normally set up the script tags
  3. flutter run web

Expected behavior App should still at least launch (even though analytics won't work obviously).

Additional context Temporary solution is to add this anywhere before loading main.js:

// Add stub so the app doesn't crash when an adblocker is active
if (typeof firebase.analytics !== "function") {
    firebase.analytics = () => console.log("Adblocker detected. Created fake analytics so the app doesn't crash.");
}

Flutter doctor Run flutter doctor and paste the output below:

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel unknown, 1.19.0-4.1.pre, on macOS 11.0 20A5343i, locale de-DE)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
[!] Xcode - develop for iOS and macOS (Xcode 11.6)
    ✗ CocoaPods not installed.
        CocoaPods is used to retrieve the iOS and macOS platform side's plugin code that responds to your plugin usage on the Dart side.
        Without CocoaPods, plugins will not work on iOS or macOS.
        For more info, see https://flutter.dev/platform-plugins
      To install:
        sudo gem install cocoapods
[✓] Chrome - develop for the web
[✓] Android Studio (version 4.0)
[✓] VS Code (version 1.48.1)
[✓] Connected device (2 available)

! Doctor found issues in 1 category.

This issue was originally created here: https://github.com/FirebaseExtended/flutterfire/issues/3346

aaahrens commented 4 years ago

Thanks for the workaround, it was driving me nuts

ochmist commented 4 years ago

Thank you so much for this workaround.

ciriousjoker commented 4 years ago

@aaahrens @ochmist Here's an improved version that also removes all the error messages (adapt as needed):

return {
   setAnalyticsCollectionEnabled: () => null,
   setCurrentScreen: () => null,
   logEvent: () => null,
};
creativecreatorormaybenot commented 3 years ago

The previously proposed fixes are okay, however, they simply make the app not crash. Firebase Analytics will still be broken.

Here is a script that you can insert in your <body> after loading at least firebase-app.js. This is useful if you adhere to GDPR for example and asynchronously load Firebase analytics (say after the user consented via Google Tag Manager). Anyway, here is a mock that completely fixes the problem and allows loading Firebase Analytics later:

<!--
  We need to create a mock for Firebase Analytics that ensures that it *does not matter **when***
  the JS library is loaded. This is because Google Tag Manager will load firebase-analytics.js
  and this 1. happens asynchronously and 2. only after the user consented.
  The firebase.dart Dart library will crash if the firebase.analytics object does not exist,
  which is why this is absolutely crucial for starting the app.
  https://github.com/FirebaseExtended/firebase-dart/issues/335#issuecomment-797003830
-->
<script>
  // This mock ensures that if the firebase_analytics Flutter plugin uses this mock as its
  // instance (which does not change over time), the plugin will *still* be able to reach out
  // to the actual firebase.analytics() instance because the object will be overridden once the
  // firebase-analytics.js library is loaded in.
  firebase.analytics = function () {
    return {
      mock: true,
      app: function () {
        var instance = firebase.analytics()
        // Prevent infinite recursion if the real instance has not yet been loaded.
        if (instance.mock === true) return
        return instance.app
      },
      logEvent: function () {
        var instance = firebase.analytics()
        if (instance.mock === true) return
        return instance.logEvent(...arguments)
      },
      setAnalyticsCollectionEnabled: function () {
        var instance = firebase.analytics()
        if (instance.mock === true) return
        return instance.setAnalyticsCollectionEnabled(...arguments)
      },
      setCurrentScreen: function () {
        var instance = firebase.analytics()
        if (instance.mock === true) return
        return instance.setCurrentScreen(...arguments)
      },
      setUserId: function () {
        var instance = firebase.analytics()
        if (instance.mock === true) return
        return instance.setUserId(...arguments)
      },
      setUserProperties: function () {
        var instance = firebase.analytics()
        if (instance.mock === true) return
        return instance.setUserProperties(...arguments)
      },
    }
  }
</script>

See also: https://stackoverflow.com/a/66589887/6509751