Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

[馃悰] AppCheck usage sky rockets after update #7766

Open
2 of 10 tasks
RomanKidoodle opened this issue Apr 25, 2024 · 2 comments
Open
2 of 10 tasks

[馃悰] AppCheck usage sky rockets after update #7766

RomanKidoodle opened this issue Apr 25, 2024 · 2 comments
Labels
Help: Needs Triage Issue needs additional investigation/triaging. Impact: Bug New bug report

Comments

@RomanKidoodle
Copy link

Issue

We have noticed that our PlayIntegrity usage has skyrocketed after update of the react-native-firebase/* packages.
We have updated from 17.4.2 to 19.0.0

  "@react-native-firebase/analytics": "17.4.2",
  "@react-native-firebase/app": "17.4.2",
  "@react-native-firebase/app-check": "17.4.2",
  "@react-native-firebase/auth": "17.4.2",
  "@react-native-firebase/crashlytics": "17.4.2",
  "@react-native-firebase/dynamic-links": "17.4.2",
  "@react-native-firebase/in-app-messaging": "17.4.2",
  "@react-native-firebase/messaging": "17.4.2",
  "@react-native-firebase/remote-config": "^17.4.2",
  
  "@react-native-firebase/analytics": "19.0.0",
  "@react-native-firebase/app": "19.0.0",
  "@react-native-firebase/app-check": "19.0.0",
  "@react-native-firebase/auth": "19.0.0",
  "@react-native-firebase/crashlytics": "19.0.0",
  "@react-native-firebase/dynamic-links": "19.0.0",
  "@react-native-firebase/in-app-messaging": "19.0.0",
  "@react-native-firebase/messaging": "19.0.0",
  "@react-native-firebase/remote-config": "19.0.0",

We haven't done any other change apart from the version upgrade.
As for the usage, we run

await firebase.appCheck().initializeAppCheck({
      provider: provider,
      isTokenAutoRefreshEnabled: false,
    });

in the code outside the react


setupApp() // here the appCheck is called

export const RootApp = () => { ... }

And then apart from default usage for phone number auth, we use it for a custom provider like this

export const getAppCheckHeaders = async () => {
  const appCheck = await firebase.appCheck().getToken();

  return {
    ...getDefaultHeaders(),
    'X-token': appCheck.token,
  };
};

After the update, we see that the number of PlayIntegrity calls doubled and is raising. This is not caused by the raise in users numbers.
image

I'm not sure if for iOS there is also problem - we don't have(or don't know about) view into the details of Apple service behind the AppCheck.

Project Files

Javascript

Click To Expand

package.json:

# N/A

firebase.json for react-native-firebase v6:

# N/A

iOS

Click To Expand

ios/Podfile:

  • I'm not using Pods
  • I'm using Pods and my Podfile looks like:
# N/A

AppDelegate.m:

// N/A


Android

Click To Expand

Have you converted to AndroidX?

  • my application is an AndroidX application?
  • I am using android/gradle.settings jetifier=true for Android compatibility?
  • I am using the NPM package jetifier for react-native compatibility?

android/build.gradle:

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    ext {
        buildToolsVersion = "33.0.0"
        minSdkVersion = 24
        compileSdkVersion = 33
        targetSdkVersion = 33
        googlePlayServicesAuthVersion = "19.2.0"
        kotlinVersion = '1.7.10'
        castFrameworkVersion = "21.0.0"
        // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
        ndkVersion = "23.1.7779620"
    }
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath("com.android.tools.build:gradle:7.3.1")
        classpath("com.facebook.react:react-native-gradle-plugin")
        classpath 'com.google.gms:google-services:4.3.10'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
        classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

android/app/build.gradle:

apply plugin: "com.android.application"
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
apply plugin: "com.facebook.react"

import com.android.build.OutputFile

if (!System.getenv()["CI"] ) {
    project.ext.envConfigFiles = [
        dev: ".env.development",
    ]
}

apply from: "../../node_modules/@sentry/react-native/sentry.gradle"
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"


/**
 * This is the configuration block to customize your React Native Android app.
 * By default you don't need to apply any configuration, just uncomment the lines you need.
 */

react {
}

/**
 * Set this to true to create four separate APKs instead of one,
 * one for each native architecture. This is useful if you don't
 * use App Bundles (https://developer.android.com/guide/app-bundle/)
 * and want to have separate APKs to upload to the Play Store.
 */
def enableSeparateBuildPerCPUArchitecture = false

/**
 * Set this to true to Run Proguard on Release builds to minify the Java bytecode.
 */
def enableProguardInReleaseBuilds = false

/**
 * The preferred build flavor of JavaScriptCore (JSC)
 *
 * For example, to use the international variant, you can use:
 * `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
 *
 * The international variant includes ICU i18n library and necessary data
 * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
 * give correct results when using with locales other than en-US. Note that
 * this variant is about 6MiB larger per architecture than default.
 */
def jscFlavor = 'org.webkit:android-jsc:+'

/**
 * Private function to get the list of Native Architectures you want to build.
 * This reads the value from reactNativeArchitectures in your gradle.properties
 * file and works together with the --active-arch-only flag of react-native run-android.
 */
def reactNativeArchitectures() {
    def value = project.getProperties().get("reactNativeArchitectures")
    return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}

// ! SegiTV is excluded from this versioning
def getProductionVersionName = { ->
    return "1.1.1"
}

def getProductionVersionCode = { ->
    return 1
}

// https://react-native-google-cast.github.io/docs/getting-started/installation
def safeExtGet(prop, fallback) {
  rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}

android {
    compileSdkVersion rootProject.ext.compileSdkVersion

    configurations.all {
        resolutionStrategy {
            force 'org.xerial:sqlite-jdbc:3.34.0'
            force 'androidx.browser:browser:1.3.0'
        }
    }

    

    lintOptions {
        checkReleaseBuilds false // Add this
        abortOnError false
    }

    def keystoreProperties = new Properties()
    def keystorePropertiesFile = rootProject.file('key.properties')
    if (keystorePropertiesFile.exists()) {
        keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    defaultConfig {
        applicationId "xxx.xxx.xxx"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode 1
        versionName "1.0"
        multiDexEnabled true
        missingDimensionStrategy 'store', 'play'
    }
    splits {
        abi {
            reset()
            enable enableSeparateBuildPerCPUArchitecture
            universalApk false  // If true, also generate a universal APK
            include (*reactNativeArchitectures())
        }
    }
    signingConfigs {
        release {
            if (project.hasProperty('MYAPP_UPLOAD_STORE_FILE')) {
                storeFile file(MYAPP_UPLOAD_STORE_FILE)
                storePassword MYAPP_UPLOAD_STORE_PASSWORD
                keyAlias MYAPP_UPLOAD_KEY_ALIAS
                keyPassword MYAPP_UPLOAD_KEY_PASSWORD
            }
            if (System.getenv()["CI"] && keystorePropertiesFile.exists()) {
                keyAlias keystoreProperties['keyAlias']
                keyPassword keystoreProperties['keyPassword']
                storeFile file(keystoreProperties['storeFile'])
                storePassword keystoreProperties['storePassword']
            }
        }
        // Uncomment to build aab bundle. Pass path pointing to dev key file on your computer in storeFile
        // dev {
        //     storeFile file('')
        //     storePassword 'devTest'
        //     keyAlias 'devAlias'
        //     keyPassword 'devTest'
        // }
        debug {
            storeFile file('debug.keystore')
            storePassword 'android'
            keyAlias 'androiddebugkey'
            keyPassword 'android'
        }
    }
    buildTypes {
        debug {
            signingConfig null
        }
        release {
            // Caution! In production, you need to generate your own keystore file.
            // see https://reactnative.dev/docs/signed-apk-android.
            minifyEnabled enableProguardInReleaseBuilds
            proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
            firebaseCrashlytics {
                nativeSymbolUploadEnabled true
                unstrippedNativeLibsDir 'build/intermediates/merged_native_libs/release/out/lib'
            }
        }
    }

    // applicationVariants are e.g. debug, release
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            // For each separate APK per architecture, set a unique version code as described here:
            // https://developer.android.com/studio/build/configure-apk-splits.html
            def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
            def abi = output.getFilter(OutputFile.ABI)
            if (abi != null) {  // null for the universal-debug, universal-release variants
                output.versionCodeOverride =
                        versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
            }

        }
    }
    flavorDimensions "default"
	productFlavors {
        dev {
            signingConfig signingConfigs.debug
            minSdkVersion rootProject.ext.minSdkVersion
            applicationId 'video.laminar.dev.android'
            targetSdkVersion rootProject.ext.targetSdkVersion
            resValue "string", "build_config_package", "xxx.xxx.xxx"
            manifestPlaceholders = [firebaseDeepLink: "laminar.page.link", appDeepLink:"client.dev.laminar.video"]
            versionCode Integer.valueOf(System.getenv().getOrDefault('BUILD_NUMBER', '1'))
            versionName "1.0.0"
        }
    }
}

dependencies {
    // The version of react-native is set by the React Native Gradle Plugin
    implementation("com.facebook.react:react-android")

    implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.0.0")

    implementation "com.google.android.gms:play-services-cast-framework:${safeExtGet('castFrameworkVersion', '+')}"
    implementation project(':react-native-google-cast')

    debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}")
    debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
        exclude group:'com.squareup.okhttp3', module:'okhttp'
    }

    debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}")
    if (hermesEnabled.toBoolean()) {
        implementation("com.facebook.react:hermes-android")
    } else {
        implementation jscFlavor
    }
}

apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
apply plugin: 'com.google.gms.google-services'

android/settings.gradle:

// N/A

MainApplication.java:

package video.laminar.mobile;

import android.app.Application;
import android.content.Context;

import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.FacebookSdk;
import com.facebook.LoggingBehavior;
import com.reactnative.googlecast.GoogleCastPackage;
import com.horcrux.svg.SvgPackage;
import com.lugg.ReactNativeConfig.ReactNativeConfigPackage;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactNativeHost;
import com.facebook.soloader.SoLoader;
import java.util.List;
import org.wonday.orientation.OrientationActivityLifecycle;
import android.webkit.WebView;

import java.lang.reflect.InvocationTargetException;


public class MainApplication extends Application implements ReactApplication {

  private final ReactNativeHost mReactNativeHost =
      new DefaultReactNativeHost(this) {
        @Override
        public boolean getUseDeveloperSupport() {
          return BuildConfig.DEBUG;
        }

        @Override
        protected List<ReactPackage> getPackages() {
          @SuppressWarnings("UnnecessaryLocalVariable")
          List<ReactPackage> packages = new PackageList(this).getPackages();
//           packages.add(new ReactVideoPackage());
          // Packages that cannot be autolinked yet can be added manually here, for example:
          // packages.add(new MyReactNativePackage());
          return packages;
        }

        @Override
        protected String getJSMainModuleName() {
            return "index";
        }

        @Override
        protected boolean isNewArchEnabled() {
          return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
        }
        @Override
        protected Boolean isHermesEnabled() {
          return BuildConfig.IS_HERMES_ENABLED;
        }
      };

  @Override
  public ReactNativeHost getReactNativeHost() {
    return mReactNativeHost;
  }

  @Override
  public void onCreate() {
    super.onCreate();
    SoLoader.init(this, /* native exopackage */ false);
    registerActivityLifecycleCallbacks(OrientationActivityLifecycle.getInstance());

    if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
      // If you opted-in for the New Architecture, we load the native entry point for this app.
      DefaultNewArchitectureEntryPoint.load();
    }
    initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
    FacebookSdk.addLoggingBehavior(LoggingBehavior.REQUESTS);
  }

  /**
     * Loads Flipper in React Native templates. Call this in the onCreate method with something like
     * initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
     *
     * @param context
     * @param reactInstanceManager
     */
    private static void initializeFlipper(
            Context context, ReactInstanceManager reactInstanceManager) {
        if (BuildConfig.DEBUG) {
            try {
                /**
                 * We use reflection here to pick up the class that initializes Flipper,
                 * since Flipper library is not available in release mode
                 */
                Class<?> aClass = Class.forName("com.mobile.ReactNativeFlipper");
                aClass
                        .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
                        .invoke(null, context, reactInstanceManager);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
}

AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="video.laminar.mobile">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="com.android.vending.BILLING" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE" />
    <uses-permission android:name="android.permission.REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND"/>

    <application
        android:name=".MainApplication"
        android:allowBackup="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:usesCleartextTraffic="true"
        tools:node="merge"
        tools:replace="android:usesCleartextTraffic">
        <activity
            android:name=".MainActivity"
            android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
            android:exported="true"
            android:label="@string/app_name"
            android:launchMode="singleTask"
            android:theme="@style/AppTheme"
            android:windowSoftInputMode="stateVisible">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />

                <data
                    android:host="${appDeepLink}"
                    android:scheme="https" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />

                <data
                    android:host="${firebaseDeepLink}"
                    android:scheme="https" />
            </intent-filter>
        </activity>
        <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
        <activity android:name="com.reactnative.googlecast.RNGCExpandedControllerActivity" />

        <meta-data
            android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
            android:value="com.reactnative.googlecast.GoogleCastOptionsProvider" />
        <meta-data
            android:name="com.reactnative.googlecast.RECEIVER_APPLICATION_ID"
            android:value="@string/chromecast_receiver_id" />

        <meta-data android:name="com.facebook.sdk.ApplicationId" android:value="@string/facebook_app_id"/>
        <meta-data android:name="com.facebook.sdk.ClientToken" android:value="@string/facebook_client_token"/>  
    </application>

</manifest>


Environment

Click To Expand

react-native info output:

info Fetching system and libraries information...
System:
    OS: macOS 14.1.1
    CPU: (8) arm64 Apple M1 Pro
    Memory: 168.02 MB / 32.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 18.19.1 - ~/.nvm/versions/node/v18.19.1/bin/node
    Yarn: 1.22.19 - /opt/homebrew/bin/yarn
    npm: 10.2.4 - ~/.nvm/versions/node/v18.19.1/bin/npm
    Watchman: 2024.01.22.00 - /opt/homebrew/bin/watchman
  Managers:
    CocoaPods: 1.15.2 - /opt/homebrew/bin/pod
  SDKs:
    iOS SDK:
      Platforms: DriverKit 23.2, iOS 17.2, macOS 14.2, tvOS 17.2, watchOS 10.2
    Android SDK: Not Found
  IDEs:
    Android Studio: 2022.3 AI-223.8836.35.2231.11005911
    Xcode: 15.1/15C65 - /usr/bin/xcodebuild
  Languages:
    Java: 17.0.10 - /usr/bin/javac
  npmPackages:
    @react-native-community/cli: Not Found
    react: 18.2.0 => 18.2.0 
    react-native: 0.71.6 => 0.71.6 
    react-native-macos: Not Found
  npmGlobalPackages:
    *react-native*: Not Found
info React Native v0.74.0 is now available (your project is running on v0.71.6).
info Changelog: https://github.com/facebook/react-native/releases/tag/v0.74.0
info Diff: https://react-native-community.github.io/upgrade-helper/?from=0.71.6
info For more info, check out "https://reactnative.dev/docs/upgrading".
  • Platform that you're experiencing the issue on:
    • iOS
    • Android
    • iOS but have not tested behavior on Android
    • Android but have not tested behavior on iOS
    • Both
  • react-native-firebase version you're using that has this issue:
    • 19.0.0
  • Firebase module(s) you're using that has the issue:
    • @react-native-firebase/app-check
  • Are you using TypeScript?
    • Y & 4.8.4


@RomanKidoodle RomanKidoodle added Help: Needs Triage Issue needs additional investigation/triaging. Impact: Bug New bug report labels Apr 25, 2024
@mikehardy
Copy link
Collaborator

Not sure what could cause it - investigation process would be:

  • examine CHANGELOG here for the difference between the two versions (we release small updates with just one or two things normally, so this is likely a very small set of changes)
  • examine firebase-android-sdk for whatever differences they shipped between the versions we had in your starting version of react-native-firebase and your ending version of react-native-firebase - there may be an indicator there

If there is no obvious explanation from their CHANGELOG or release notes, you might open an issue on firebase-android-sdk to see what they think

@RomanKidoodle
Copy link
Author

RomanKidoodle commented May 6, 2024

I will create an issue in the native sdk as well.
I'll add some more information to this ticket:

  • we actually tried the update from 17.4.2 to 18.5.0 some time ago and it failed with the same problem - app check usage skyrocketed
  • the changelog here has only few things that might have impact on this: in 18.0.0 SafetyNet was removed and in 18.4.0 an appCheck token changed event listener was added

For clarity, this is how we initialize the app check:

import { firebase } from '@react-native-firebase/app-check';

try {
    const provider = firebase
      .appCheck()
      .newReactNativeFirebaseAppCheckProvider();

    await provider.configure({
      android: {
        ...(isEnvValueSet(Config.ANDROID_APP_CHECK_DEBUG_TOKEN)
          ? {
              provider: 'debug',
              debugToken: Config.ANDROID_APP_CHECK_DEBUG_TOKEN,
            }
          : {
              provider: 'playIntegrity',
            }),
      },
      apple: {
        provider: 'appAttest',
      },
    });

    await firebase.appCheck().initializeAppCheck({
      provider: provider,
      isTokenAutoRefreshEnabled: false,
    });
  } catch (err: any) {
    captureError(err);
  }

EDIT:
I've just noticed that the change in usage is actually for both Android and iOS:
image

So this might be an issue with the library here 馃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Help: Needs Triage Issue needs additional investigation/triaging. Impact: Bug New bug report
Projects
None yet
Development

No branches or pull requests

2 participants