diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 255dfe46e..51ab39f0b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,6 +8,8 @@ permissions: env: GOPRIVATE: github.com/getlantern S3_BUCKET: lantern + DATADOG_API_KEY: ${{ secrets.DATADOG_API_KEY }} + DATADOG_SITE: datadoghq.eu jobs: set-version: runs-on: ubuntu-latest @@ -80,17 +82,6 @@ jobs: run: | git config --global url."https://${{ secrets.GH_TOKEN }}:x-oauth-basic@github.com/".insteadOf "https://github.com/" - - name: Build Lantern core Android library - run: make android-lib - - - name: Setup Sentry CLI - uses: mathieu-bour/setup-sentry-cli@v1 - with: - version: latest - token: ${{ SECRETS.SENTRY_TOKEN }} # from GitHub secrets - organization: getlantern - project: android - - name: Setup JDK 11 uses: actions/setup-java@v3 with: @@ -125,9 +116,14 @@ jobs: fileDir: './android/app' encodedString: ${{ secrets.KEYSTORE }} + - name: Install Datadog CI + run: npm install -g @datadog/datadog-ci + - name: Build Android installers run: make package-android env: + DD_APPLICATION_ID: ${{ secrets.DD_APPLICATION_ID }} + DD_CLIENT_TOKEN: ${{ secrets.DD_CLIENT_TOKEN }} INTERSTITIAL_AD_UNIT_ID: "${{ secrets.INTERSTITIAL_AD_UNIT_ID }}" VERSION: "${{ env.version }}" diff --git a/Makefile b/Makefile index e4d8e0a64..2d2944606 100644 --- a/Makefile +++ b/Makefile @@ -53,6 +53,7 @@ ADB := $(call get-command,adb) OPENSSL := $(call get-command,openssl) GMSAAS := $(call get-command,gmsaas) SENTRY := $(call get-command,sentry-cli) +DATADOGCI := $(call get-command,datadog-ci) BASE64 := $(call get-command,base64) GIT_REVISION_SHORTCODE := $(shell git rev-parse --short HEAD) @@ -241,6 +242,10 @@ require-magick: require-sentry: @if [[ -z "$(SENTRY)" ]]; then echo 'Missing "sentry-cli" command. See sentry.io for installation instructions.'; exit 1; fi +.PHONY: require-datadog-ci +require-datadog-ci: + @if [[ -z "$(DATADOGCI)" ]]; then echo 'Missing "datadog-ci" command. See https://www.npmjs.com/package/@datadog/datadog-ci for installation instructions.'; exit 1; fi + release-autoupdate: require-version @TAG_COMMIT=$$(git rev-list --abbrev-commit -1 $(TAG)) && \ if [[ -z "$$TAG_COMMIT" ]]; then \ @@ -263,7 +268,7 @@ $(ANDROID_LIB): $(GO_SOURCES) gomobile bind \ -target=$(ANDROID_ARCH_GOMOBILE) \ -tags='headless lantern' -o=$(ANDROID_LIB) \ - -androidapi=19 \ + -androidapi=23 \ -ldflags="$(LDFLAGS)" \ $(GOMOBILE_EXTRA_BUILD_FLAGS) \ $(ANDROID_LIB_PKG) @@ -282,27 +287,31 @@ $(MOBILE_TEST_APK) $(MOBILE_TESTS_APK): $(MOBILE_SOURCES) $(MOBILE_ANDROID_LIB) -b $(MOBILE_DIR)/app/build.gradle \ :app:assembleAutoTestDebug :app:assembleAutoTestDebugAndroidTest -vault-secret: +vault-secret-%: + @SECRET=$(shell cd $$GOPATH/src/github.com/getlantern/lantern-cloud && bin/vault kv get -field=${*} ${VAULT_DD_SECRETS_PATH}); \ + printf "$$SECRET" + +vault-secret-base64: @SECRET=$(shell cd $$GOPATH/src/github.com/getlantern/lantern-cloud && bin/vault kv get -field=$(VAULT_FIELD) $(VAULT_PATH)); \ echo "Retrieved secret: $$SECRET" 1>&2; \ printf "$$VAULT_FIELD=$$SECRET" | ${BASE64} dart-defines-debug: - @DART_DEFINES=$(shell make vault-secret VAULT_FIELD=INTERSTITIAL_AD_UNIT_ID VAULT_PATH=secret/googleAds); \ + @DART_DEFINES=$(shell make vault-secret-base64 VAULT_FIELD=INTERSTITIAL_AD_UNIT_ID VAULT_PATH=secret/googleAds); \ + DART_DEFINES+=$(shell printf ',' && make vault-secret-base64 VAULT_FIELD=DD_APPLICATION_ID VAULT_PATH=secret/apps/datadog/android); \ + DART_DEFINES+=$(shell printf ',' && make vault-secret-base64 VAULT_FIELD=DD_CLIENT_TOKEN VAULT_PATH=secret/apps/datadog/android); \ DART_DEFINES+=",$(CIBASE)"; \ echo "$$DART_DEFINES" do-android-debug: $(MOBILE_SOURCES) $(MOBILE_ANDROID_LIB) @ln -fs $(MOBILE_DIR)/gradle.properties . && \ DART_DEFINES=`make dart-defines-debug` && \ - COUNTRY="$$COUNTRY" && \ - PAYMENT_PROVIDER="$$PAYMENT_PROVIDER" && \ - STAGING="$$STAGING" && \ - STICKY_CONFIG="$$STICKY_CONFIG" && \ - CI="$$CI" && \ - echo "DART_DEFINES values: $$DART_DEFINES" && \ - $(GRADLE) -PlanternVersion=$(DEBUG_VERSION) -Pdart-defines="$$DART_DEFINES" -PproServerUrl=$(PRO_SERVER_URL) -PpaymentProvider=$(PAYMENT_PROVIDER) -Pcountry=$(COUNTRY) -PplayVersion=$(FORCE_PLAY_VERSION) -PuseStaging=$(STAGING) -PstickyConfig=$(STICKY_CONFIG) -PlanternRevisionDate=$(REVISION_DATE) -PandroidArch=$(ANDROID_ARCH) -PandroidArchJava="$(ANDROID_ARCH_JAVA)" -PdevelopmentMode="true" -Pci=$(CI) -b $(MOBILE_DIR)/app/build.gradle \ - assembleProdDebug + CI="$$CI" && $(GRADLE) -Pdart-defines="$$DART_DEFINES" -PlanternVersion=$(DEBUG_VERSION) -PddClientToken=$$DD_CLIENT_TOKEN -PddApplicationID=$$DD_APPLICATION_ID \ + -PproServerUrl=$(PRO_SERVER_URL) -PpaymentProvider=$(PAYMENT_PROVIDER) -Pcountry=$(COUNTRY) \ + -PplayVersion=$(FORCE_PLAY_VERSION) -PuseStaging=$(STAGING) -PstickyConfig=$(STICKY_CONFIG) \ + -PlanternRevisionDate=$(REVISION_DATE) -PandroidArch=$(ANDROID_ARCH) \ + -PandroidArchJava="$(ANDROID_ARCH_JAVA)" -PdevelopmentMode="true" \ + -Pci=$(CI) -b $(MOBILE_DIR)/app/build.gradle assembleProdDebug pubget: @flutter pub get @@ -321,10 +330,12 @@ dart-defines-release: DART_DEFINES+=`printf ',' && $(CIBASE)`; \ printf $$DART_DEFINES -$(MOBILE_RELEASE_APK): $(MOBILE_SOURCES) $(GO_SOURCES) $(MOBILE_ANDROID_LIB) require-sentry +$(MOBILE_RELEASE_APK): $(MOBILE_SOURCES) $(GO_SOURCES) $(MOBILE_ANDROID_LIB) require-datadog-ci echo $(MOBILE_ANDROID_LIB) && \ mkdir -p ~/.gradle && \ ln -fs $(MOBILE_DIR)/gradle.properties . && \ + DD_CLIENT_TOKEN="$$DD_CLIENT_TOKEN" && \ + DD_APPLICATION_ID="$$DD_APPLICATION_ID" && \ COUNTRY="$$COUNTRY" && \ STAGING="$$STAGING" && \ STICKY_CONFIG="$$STICKY_CONFIG" && \ @@ -332,27 +343,33 @@ $(MOBILE_RELEASE_APK): $(MOBILE_SOURCES) $(GO_SOURCES) $(MOBILE_ANDROID_LIB) req VERSION_CODE="$$VERSION_CODE" && \ DEVELOPMENT_MODE="$$DEVELOPMENT_MODE" && \ DART_DEFINES=`make dart-defines-release` && \ - $(GRADLE) -PlanternVersion=$$VERSION -PlanternRevisionDate=$(REVISION_DATE) -Pdart-defines="$$DART_DEFINES" -PandroidArch=$(ANDROID_ARCH) -PandroidArchJava="$(ANDROID_ARCH_JAVA)" -PproServerUrl=$(PRO_SERVER_URL) -PpaymentProvider=$(PAYMENT_PROVIDER) -Pcountry=$(COUNTRY) -PplayVersion=$(FORCE_PLAY_VERSION) -PuseStaging=$(STAGING) -PstickyConfig=$(STICKY_CONFIG) -PversionCode=$(VERSION_CODE) -PdevelopmentMode=$(DEVELOPMENT_MODE) -b $(MOBILE_DIR)/app/build.gradle \ - assembleProdSideload && \ - sentry-cli upload-dif --wait -o getlantern -p android build/app/intermediates/merged_native_libs/prodSideload/out/lib && \ + $(GRADLE) -PlanternVersion=$$VERSION -Pdart-defines="$$DART_DEFINES" -PlanternRevisionDate=$(REVISION_DATE) -PddClientToken="$(DD_CLIENT_TOKEN)" \ + -PddApplicationID="$(DD_APPLICATION_ID)" -PandroidArch=$(ANDROID_ARCH) -PandroidArchJava="$(ANDROID_ARCH_JAVA)" -PproServerUrl=$(PRO_SERVER_URL) \ + -PpaymentProvider=$(PAYMENT_PROVIDER) -Pcountry=$(COUNTRY) -PplayVersion=$(FORCE_PLAY_VERSION) -PuseStaging=$(STAGING) -PstickyConfig=$(STICKY_CONFIG) \ + -PversionCode=$(VERSION_CODE) -PdevelopmentMode=$(DEVELOPMENT_MODE) -b $(MOBILE_DIR)/app/build.gradle assembleProdSideload && \ + datadog-ci flutter-symbols upload --service-name lantern-android --dart-symbols-location build/app/intermediates/merged_native_libs/prodSideload/out/lib \ + --android-mapping-location build/app/outputs/mapping/prodSideload/mapping.txt --android-mapping --ios-dsyms && \ cp $(MOBILE_ANDROID_RELEASE) $(MOBILE_RELEASE_APK) && \ cat $(MOBILE_RELEASE_APK) | bzip2 > lantern_update_android_arm.bz2 - -$(MOBILE_BUNDLE): $(MOBILE_SOURCES) $(GO_SOURCES) $(MOBILE_ANDROID_LIB) require-sentry +$(MOBILE_BUNDLE): $(MOBILE_SOURCES) $(GO_SOURCES) $(MOBILE_ANDROID_LIB) require-datadog-ci @mkdir -p ~/.gradle && \ ln -fs $(MOBILE_DIR)/gradle.properties . && \ + DD_CLIENT_TOKEN="$$DD_CLIENT_TOKEN" && \ + DD_APPLICATION_ID="$$DD_APPLICATION_ID" && \ COUNTRY="$$COUNTRY" && \ STAGING="$$STAGING" && \ STICKY_CONFIG="$$STICKY_CONFIG" && \ PAYMENT_PROVIDER="$$PAYMENT_PROVIDER" && \ - $(GRADLE) -PlanternVersion=$$VERSION -PlanternRevisionDate=$(REVISION_DATE) -PandroidArch=$(ANDROID_ARCH) -PandroidArchJava="$(ANDROID_ARCH_JAVA)" -PproServerUrl=$(PRO_SERVER_URL) -PpaymentProvider=$(PAYMENT_PROVIDER) -Pcountry=$(COUNTRY) -PplayVersion=true -PuseStaging=$(STAGING) -PstickyConfig=$(STICKY_CONFIG) -b $(MOBILE_DIR)/app/build.gradle \ - bundlePlay && \ - sentry-cli upload-dif --wait -o getlantern -p android build/app/intermediates/merged_native_libs/prodPlay/out/lib && \ + $(GRADLE) -PlanternVersion=$$VERSION -PlanternRevisionDate=$(REVISION_DATE) -PandroidArch=$(ANDROID_ARCH) -PandroidArchJava="$(ANDROID_ARCH_JAVA)" \ + -PddClientToken=$(DD_CLIENT_TOKEN) -PddApplicationID=$(DD_APPLICATION_ID) -PproServerUrl=$(PRO_SERVER_URL) -PpaymentProvider=$(PAYMENT_PROVIDER) \ + -Pcountry=$(COUNTRY) -PplayVersion=true -PuseStaging=$(STAGING) -PstickyConfig=$(STICKY_CONFIG) -b $(MOBILE_DIR)/app/build.gradle bundlePlay && \ + datadog-ci flutter-symbols upload --service-name lantern-android --dart-symbols-location build/app/intermediates/merged_native_libs/prodPlay/out/lib \ + --android-mapping-location build/app/outputs/mapping/prodPlay/mapping.txt --ios-dsyms && \ cp $(MOBILE_ANDROID_BUNDLE) $(MOBILE_BUNDLE) - -android-debug: $(MOBILE_DEBUG_APK) +android-debug: + DD_APPLICATION_ID=`make vault-secret-DD_APPLICATION_ID` DD_CLIENT_TOKEN=`make vault-secret-DD_CLIENT_TOKEN` make $(MOBILE_DEBUG_APK) android-release: pubget $(MOBILE_RELEASE_APK) @@ -364,7 +381,7 @@ android-debug-install: $(MOBILE_DEBUG_APK) android-release-install: $(MOBILE_RELEASE_APK) $(ADB) install -r $(MOBILE_RELEASE_APK) -package-android: require-version clean +package-android: pubget require-version @ANDROID_ARCH=all make android-release && \ ANDROID_ARCH=all make android-bundle && \ echo "-> $(MOBILE_RELEASE_APK)" diff --git a/android/app/build.gradle b/android/app/build.gradle index 4f4e104bb..2298deacc 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,5 +1,4 @@ plugins { - id "io.sentry.android.gradle" version "3.4.2" id 'com.android.application' id 'kotlin-android' id 'org.jlleitschuh.gradle.ktlint' @@ -7,6 +6,7 @@ plugins { id 'kotlin-parcelize' id 'kotlin-kapt' id 'com.google.protobuf' + id("com.datadoghq.dd-sdk-android-gradle-plugin") version "1.10.0" } def localProperties = new Properties() @@ -19,7 +19,10 @@ if (localPropertiesFile.exists()) { def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") + flutterRoot = System.env.FLUTTER_ROOT + if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") + } } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') @@ -177,6 +180,8 @@ android { buildConfigField "boolean", "STICKY_CONFIG", getBoolean("stickyConfig") buildConfigField "boolean", "STAGING", getBoolean("useStaging") buildConfigField "boolean", "PLAY_VERSION", getBoolean("playVersion") + buildConfigField "String", "DD_CLIENT_TOKEN", ddClientToken() + buildConfigField "String", "DD_APPLICATION_ID", ddApplicationID() buildConfigField "String", "COUNTRY", userCountry() buildConfigField "String", "PAYMENT_PROVIDER", paymentProvider() buildConfigField "String", "PRO_SERVER_URL", proServerUrl() @@ -195,6 +200,8 @@ android { buildConfigField "boolean", "STICKY_CONFIG", getBoolean("stickyConfig") buildConfigField "boolean", "STAGING", getBoolean("useStaging") buildConfigField "boolean", "PLAY_VERSION", getBoolean("playVersion") + buildConfigField "String", "DD_CLIENT_TOKEN", ddClientToken() + buildConfigField "String", "DD_APPLICATION_ID", ddApplicationID() buildConfigField "String", "COUNTRY", userCountry() buildConfigField "String", "SIGNING_CERTIFICATE_SHA256", "\"\"" buildConfigField "String", "PAYMENT_PROVIDER", paymentProvider() @@ -213,6 +220,8 @@ android { buildConfigField "boolean", "STICKY_CONFIG", getBoolean("stickyConfig") buildConfigField "boolean", "STAGING", getBoolean("useStaging") buildConfigField "boolean", "PLAY_VERSION", getBoolean("playVersion") + buildConfigField "String", "DD_CLIENT_TOKEN", ddClientToken() + buildConfigField "String", "DD_APPLICATION_ID", ddApplicationID() buildConfigField "String", "COUNTRY", userCountry() buildConfigField "String", "SIGNING_CERTIFICATE_SHA256", "\"108f612ae55354078ec12b10bb705362840d48fa78b9262c11b6d0adeff6f289\"" buildConfigField "String", "PAYMENT_PROVIDER", paymentProvider() @@ -318,6 +327,22 @@ def getInt(name) { return value.toInteger() } +def ddApplicationID() { + def value = project.getProperties().get("ddApplicationID") + if (value == null || !value?.trim()) { + return "\"\"" + } + return String.format("\"%s\"", value) +} + +def ddClientToken() { + def value = project.getProperties().get("ddClientToken") + if (value == null || !value?.trim()) { + return "\"\"" + } + return String.format("\"%s\"", value) +} + def userCountry() { def value = project.getProperties().get("country") if (value == null || !value?.trim()) { @@ -423,6 +448,8 @@ dependencies { implementation 'com.stripe:stripe-android:20.17.0' + implementation 'com.datadoghq:dd-sdk-android:1.19.3' + annotationProcessor "org.androidannotations:androidannotations:$androidAnnotationsVersion" implementation("org.androidannotations:androidannotations-api:$androidAnnotationsVersion") kapt "org.androidannotations:androidannotations:$androidAnnotationsVersion" @@ -451,22 +478,3 @@ dependencies { } apply plugin: 'com.google.gms.google-services' - -sentry { - // Enables or disables the automatic upload of mapping files - // during a build. If you disable this, you'll need to manually - // upload the mapping files with sentry-cli when you do a release. - autoUpload = true - - // Disables or enables the automatic configuration of Native Symbols - // for Sentry. This executes sentry-cli automatically so - // you don't need to do it manually. - // Default is disabled. - uploadNativeSymbols = true - - // Does or doesn't include the source code of native code for Sentry. - // This executes sentry-cli with the --include-sources param. automatically so - // you don't need to do it manually. - // Default is disabled. - includeNativeSources = false -} diff --git a/android/app/src/main/kotlin/io/lantern/model/SessionModel.kt b/android/app/src/main/kotlin/io/lantern/model/SessionModel.kt index 06b083d44..b2369cebc 100644 --- a/android/app/src/main/kotlin/io/lantern/model/SessionModel.kt +++ b/android/app/src/main/kotlin/io/lantern/model/SessionModel.kt @@ -24,6 +24,7 @@ import org.getlantern.lantern.R import org.getlantern.lantern.activity.FreeKassaActivity_ import org.getlantern.lantern.activity.WebViewActivity_ import org.getlantern.mobilesdk.model.IssueReporter +import org.getlantern.lantern.datadog.Datadog import org.getlantern.lantern.model.LanternHttpClient import org.getlantern.lantern.model.LanternHttpClient.ProCallback import org.getlantern.lantern.model.LanternHttpClient.ProUserCallback @@ -157,6 +158,9 @@ class SessionModel( activity.startActivity(intent) } } + "trackUserAction" -> { + Datadog.trackUserClick(call.argument("message")!!) + } "acceptTerms" -> { LanternApp.getSession().acceptTerms() } diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/LanternApp.kt b/android/app/src/main/kotlin/org/getlantern/lantern/LanternApp.kt index 72344df97..50a685d0b 100644 --- a/android/app/src/main/kotlin/org/getlantern/lantern/LanternApp.kt +++ b/android/app/src/main/kotlin/org/getlantern/lantern/LanternApp.kt @@ -3,14 +3,17 @@ package org.getlantern.lantern import android.app.Application import android.content.Context import android.os.StrictMode +import android.util.Log import androidx.appcompat.app.AppCompatDelegate import androidx.multidex.MultiDex +import org.getlantern.lantern.datadog.Datadog +import org.getlantern.lantern.datadog.FlutterExcludingComponentPredicate import org.getlantern.lantern.model.InAppBilling import org.getlantern.lantern.model.LanternHttpClient import org.getlantern.lantern.model.LanternSessionManager import org.getlantern.lantern.util.debugOnly import org.getlantern.lantern.util.LanternProxySelector -import org.getlantern.lantern.util.SentryUtil +import org.getlantern.mobilesdk.Logger import org.getlantern.mobilesdk.util.HttpClient open class LanternApp : Application() { @@ -39,12 +42,13 @@ open class LanternApp : Application() { override fun onCreate() { super.onCreate() - SentryUtil.enableGoPanicEnrichment(this) + // Necessary to locate a back arrow resource we use from the // support library. See http://stackoverflow.com/questions/37615470/support-library-vectordrawable-resourcesnotfoundexception AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) appContext = applicationContext session = LanternSessionManager(this) + Datadog.initialize() LanternProxySelector(session) if (session.isPlayVersion) inAppBilling = InAppBilling(this) diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/MainActivity.kt b/android/app/src/main/kotlin/org/getlantern/lantern/MainActivity.kt index 54ddc1798..afb2f8552 100644 --- a/android/app/src/main/kotlin/org/getlantern/lantern/MainActivity.kt +++ b/android/app/src/main/kotlin/org/getlantern/lantern/MainActivity.kt @@ -14,6 +14,7 @@ import androidx.appcompat.app.AlertDialog import androidx.core.app.ActivityCompat import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine +import io.flutter.embedding.engine.FlutterEngineCache import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.lantern.model.MessagingModel @@ -24,6 +25,7 @@ import io.lantern.model.VpnModel import kotlinx.coroutines.* import okhttp3.Response import org.getlantern.lantern.activity.WebViewActivity_ +import org.getlantern.lantern.datadog.Datadog import org.getlantern.lantern.event.EventManager import org.getlantern.lantern.model.AccountInitializationStatus import org.getlantern.lantern.model.Bandwidth @@ -77,7 +79,7 @@ class MainActivity : override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { val start = System.currentTimeMillis() super.configureFlutterEngine(flutterEngine) - + FlutterEngineCache.getInstance().put("datadoghq_engine", flutterEngine) messagingModel = MessagingModel(this, flutterEngine) vpnModel = VpnModel(this, flutterEngine, ::switchLantern) sessionModel = SessionModel(this, flutterEngine) @@ -87,6 +89,7 @@ class MainActivity : eventManager = object : EventManager("lantern_event_channel", flutterEngine) { override fun onListen(event: Event) { if (LanternApp.getSession().lanternDidStart()) { + flutterNavigation.invokeMethod("initDatadog", null) fetchLoConf() Logger.debug( TAG, @@ -300,7 +303,7 @@ class MainActivity : private fun updateUserData() { lanternClient.userData(object : ProUserCallback { override fun onFailure(throwable: Throwable?, error: ProError?) { - Logger.error(TAG, "Unable to fetch user data: $error", throwable) + Datadog.addError("Unable to fetch user data: $error", throwable) } override fun onSuccess(response: Response, user: ProUser) { @@ -325,7 +328,7 @@ class MainActivity : private fun updatePlans() { lanternClient.plans(object : PlansCallback { override fun onFailure(throwable: Throwable?, error: ProError?) { - Logger.error(TAG, "Unable to fetch user plans: $error", throwable) + Datadog.addError("Unable to fetch user plans: $error", throwable) } override fun onSuccess(proPlans: Map) { @@ -342,7 +345,7 @@ class MainActivity : private fun updatePaymentMethods() { lanternClient.plansV3(object : PlansV3Callback { override fun onFailure(throwable: Throwable?, error: ProError?) { - Logger.error(TAG, "Unable to fetch user plans: $error", throwable) + Datadog.addError("Unable to fetch payment methods: $error", throwable) } override fun onSuccess( diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/datadog/Datadog.kt b/android/app/src/main/kotlin/org/getlantern/lantern/datadog/Datadog.kt new file mode 100644 index 000000000..ccfcffa1d --- /dev/null +++ b/android/app/src/main/kotlin/org/getlantern/lantern/datadog/Datadog.kt @@ -0,0 +1,131 @@ +package org.getlantern.lantern.datadog + +import android.util.Log +import com.datadog.android.Datadog as DatadogMain +import com.datadog.android.DatadogSite +import com.datadog.android.core.configuration.BatchSize +import com.datadog.android.core.configuration.Configuration +import com.datadog.android.core.configuration.Credentials +import com.datadog.android.core.configuration.UploadFrequency +import com.datadog.android.privacy.TrackingConsent +import com.datadog.android.rum.GlobalRum +import com.datadog.android.rum.RumActionType +import com.datadog.android.rum.RumErrorSource +import com.datadog.android.rum.RumMonitor +import com.datadog.android.rum.tracking.ActivityViewTrackingStrategy +import org.getlantern.lantern.BuildConfig +import org.getlantern.lantern.LanternApp +import org.getlantern.mobilesdk.Logger +import java.net.InetSocketAddress +import java.net.Proxy +import java.util.concurrent.atomic.AtomicBoolean + +object Datadog { + private val tracedHosts = listOf( + "datadoghq.com", + "127.0.0.1", + ) + private val initialized = AtomicBoolean() + private lateinit var datadogConfig: Configuration + + fun initialize() { + if (initialized.get()) return + + DatadogMain.setVerbosity(Log.VERBOSE) + datadogConfig = createDatadogConfiguration() + + val datadogCredentials = Credentials( + clientToken = BuildConfig.DD_CLIENT_TOKEN, + envName = "prod", + rumApplicationId = BuildConfig.DD_APPLICATION_ID, + variant = "release", + serviceName = "lantern-android", + ) + + DatadogMain.initialize( + LanternApp.getAppContext(), + credentials = datadogCredentials, + configuration = datadogConfig, + TrackingConsent.GRANTED, + ) + + DatadogMain.setUserInfo( + id = LanternApp.getSession().userId().toString(), + ) + + val monitor = RumMonitor.Builder().build() + GlobalRum.registerIfAbsent(monitor) + initialized.set(true) + } + + fun addError( + message: String, + throwable: Throwable? = null, + attributes: Map = emptyMap(), + ) { + Logger.e(TAG, message, throwable) + GlobalRum.get().addError(message, RumErrorSource.SOURCE, throwable, attributes) + } + + // trackUserAction is used to track specific user actions (such as taps, clicks, and scrolls) + // with RumMonitor + fun trackUserAction( + actionType: RumActionType, + name: String, + actionAttributes: Map = emptyMap(), + ) { + GlobalRum.get().addUserAction(actionType, name, actionAttributes) + } + + // trackUserClick is used to track user clicks with RumMonitor + fun trackUserClick( + name: String, + actionAttributes: Map = emptyMap(), + ) { + trackUserAction(RumActionType.CLICK, name, actionAttributes) + } + + // trackUserTap is used to track user taps with RumMonitor + fun trackUserTap( + name: String, + actionAttributes: Map = emptyMap(), + ) { + trackUserAction(RumActionType.TAP, name, actionAttributes) + } + + private fun createDatadogConfiguration(): Configuration { + val session = LanternApp.getSession() + return Configuration.Builder( + logsEnabled = true, + tracesEnabled = true, + crashReportsEnabled = true, + rumEnabled = true, + ) + .setBatchSize(BatchSize.SMALL) + .setProxy( + Proxy( + Proxy.Type.HTTP, + InetSocketAddress( + session.settings.httpProxyHost, + session.settings.httpProxyPort.toInt(), + ), + ), + null, + ) + .sampleRumSessions(100f) + .setUploadFrequency(UploadFrequency.FREQUENT) + .useSite(DatadogSite.EU1) + .trackInteractions() + .trackLongTasks() + .setFirstPartyHosts(tracedHosts) + .useViewTrackingStrategy( + ActivityViewTrackingStrategy( + trackExtras = false, + componentPredicate = FlutterExcludingComponentPredicate(), + ), + ) + .build() + } + + private val TAG = Datadog::class.java.name +} diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/datadog/FlutterExcludingComponentPredicate.kt b/android/app/src/main/kotlin/org/getlantern/lantern/datadog/FlutterExcludingComponentPredicate.kt new file mode 100644 index 000000000..49d227731 --- /dev/null +++ b/android/app/src/main/kotlin/org/getlantern/lantern/datadog/FlutterExcludingComponentPredicate.kt @@ -0,0 +1,22 @@ +package org.getlantern.lantern.datadog + +import android.app.Activity +import com.datadog.android.rum.tracking.AcceptAllActivities +import com.datadog.android.rum.tracking.ComponentPredicate +import io.flutter.embedding.android.FlutterActivity + +class FlutterExcludingComponentPredicate : ComponentPredicate { + val innerPredicate = AcceptAllActivities() + + override fun accept(component: Activity): Boolean { + if (component is FlutterActivity) { + return false + } + + return innerPredicate.accept(component) + } + + override fun getViewName(component: Activity): String? { + return innerPredicate.getViewName(component) + } +} diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/util/PaymentsUtil.kt b/android/app/src/main/kotlin/org/getlantern/lantern/util/PaymentsUtil.kt index 0aaaa2a76..bbcfcae64 100644 --- a/android/app/src/main/kotlin/org/getlantern/lantern/util/PaymentsUtil.kt +++ b/android/app/src/main/kotlin/org/getlantern/lantern/util/PaymentsUtil.kt @@ -17,6 +17,7 @@ import okhttp3.FormBody import okhttp3.Response import org.getlantern.lantern.LanternApp import org.getlantern.lantern.R +import org.getlantern.lantern.datadog.Datadog import org.getlantern.lantern.model.LanternHttpClient import org.getlantern.lantern.model.LanternHttpClient.ProCallback import org.getlantern.lantern.model.LanternSessionManager @@ -40,6 +41,10 @@ class PaymentsUtil(private val activity: Activity) { methodCallResult: MethodChannel.Result, ) { try { + Datadog.trackUserClick("submitStripePayment", mapOf( + "email" to email, + "planID" to planID, + )) val date = expirationDate.split("/").toTypedArray() val card: CardParams = CardParams( cardNumber.replace("[\\s]", ""), @@ -64,6 +69,7 @@ class PaymentsUtil(private val activity: Activity) { override fun onError(@NonNull error: Exception) { dialog?.dismiss() + Datadog.addError("Error submitting to Stripe: $error") methodCallResult.error( "errorSubmittingToStripe", error.getLocalizedMessage(), @@ -73,8 +79,8 @@ class PaymentsUtil(private val activity: Activity) { }, ) } catch (t: Throwable) { - Logger.error(TAG, "Error submitting to Stripe", t) dialog?.dismiss() + Datadog.addError("Error submitting to Stripe", t) methodCallResult.error( "errorSubmittingToStripe", activity.getString(R.string.error_making_purchase), @@ -90,6 +96,10 @@ class PaymentsUtil(private val activity: Activity) { methodCallResult: MethodChannel.Result, ) { try { + Datadog.trackUserClick("submitBitcoinPayment", mapOf( + "email" to email, + "planID" to planID, + )) val provider = PaymentProvider.BTCPay.toString().lowercase() val params = mutableMapOf( "email" to email, @@ -101,6 +111,7 @@ class PaymentsUtil(private val activity: Activity) { LanternHttpClient.createProUrl("/payment-redirect", params), object : ProCallback { override fun onFailure(throwable: Throwable?, error: ProError?) { + Datadog.addError("BTCPay is unavailable", throwable) methodCallResult.error( "unknownError", "BTCPay is unavailable", // This error message is localized Flutter-side @@ -119,7 +130,7 @@ class PaymentsUtil(private val activity: Activity) { }, ) } catch (t: Throwable) { - Logger.error(TAG, "BTCPay is unavailable", t) + Datadog.addError("BTCPay is unavailable", t) methodCallResult.error( "unknownError", "BTCPay is unavailable", // This error message is localized Flutter-side @@ -131,6 +142,10 @@ class PaymentsUtil(private val activity: Activity) { // Handles Google Play transactions fun submitGooglePlayPayment(planID: String, methodCallResult: MethodChannel.Result) { val inAppBilling = LanternApp.getInAppBilling() + Datadog.trackUserClick("googlePlayPayment", mapOf( + "planID" to planID, + )) + if (inAppBilling == null) { Logger.error(TAG, "Missing inAppBilling") methodCallResult.error( @@ -154,6 +169,7 @@ class PaymentsUtil(private val activity: Activity) { activity.resources.getString(R.string.error_making_purchase), null, ) + Datadog.addError("Google Play: error making purchase") return } @@ -197,10 +213,7 @@ class PaymentsUtil(private val activity: Activity) { formBody, object : ProCallback { override fun onFailure(throwable: Throwable?, error: ProError?) { - Logger.error( - TAG, - "Error retrieving referral code: $error", - ) + Datadog.addError("Error retrieving referral code: $error", throwable) if (error != null && error.message != null) { methodCallResult.error( "unknownError", @@ -222,7 +235,7 @@ class PaymentsUtil(private val activity: Activity) { }, ) } catch (t: Throwable) { - Logger.error(TAG, "Unable to apply referral code", t) + Datadog.addError("Unable to apply referral code", t) methodCallResult.error( "unknownError", "Something went wrong while applying your referral code", @@ -315,7 +328,11 @@ class PaymentsUtil(private val activity: Activity) { } override fun onFailure(t: Throwable?, error: ProError?) { - Logger.e(TAG, "Error with purchase request: $error") + Datadog.addError("Error with purchase request: $error", t, mapOf( + "provider" to provider.toString().lowercase(), + "plan" to planID, + "deviceName" to session.deviceName(), + )) dialog?.dismiss() methodCallResult.error( "errorMakingPurchase", diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/util/SentryUtil.kt b/android/app/src/main/kotlin/org/getlantern/lantern/util/SentryUtil.kt deleted file mode 100644 index 6bb485387..000000000 --- a/android/app/src/main/kotlin/org/getlantern/lantern/util/SentryUtil.kt +++ /dev/null @@ -1,56 +0,0 @@ -package org.getlantern.lantern.util - -import android.content.Context -import android.os.Process -import io.sentry.SentryOptions -import io.sentry.android.core.SentryAndroid -import org.getlantern.mobilesdk.Logger -import java.io.BufferedReader -import java.io.InputStreamReader - -object SentryUtil { - private val TAG = SentryUtil::class.java.name - - /** - * Enables enrichment of sentry crash reports with the most recent Go panic from logcat. - * Keep in mind that Sentry only finds panics the next time that it runs after the process - * actually panicked. So, we can safely exclude logs from our current run. - * - * Keep in mind also that there's no guarantee that the panic log in question belongs to our - * specific panic, we're just picking up the most recent panic log information. - */ - @JvmStatic - fun enableGoPanicEnrichment(ctx: Context) { - SentryAndroid.init(ctx) { options -> - options.beforeSend = SentryOptions.BeforeSendCallback { event, _ -> - // enable enrichment only for exceptions related to OS signals like SIGABRT - if (event.exceptions?.firstOrNull()?.type?.startsWith("SIG") == true) { - val myPid = Process.myPid().toString() - val goErrorLog = StringBuilder() - val process = Runtime.getRuntime().exec( - "logcat -d -v brief" - ) - BufferedReader(InputStreamReader(process.inputStream)).use { reader -> - reader.forEachLine { line -> - if (!line.contains(myPid) && line.startsWith("E/Go ")) { - if (line.contains("panic: ")) { - // this is the first line of the most recent panic, remove old rows - // from what must be prior panics - goErrorLog.clear() - } - goErrorLog.appendLine(line) - } - } - } - - if (goErrorLog.isNotEmpty()) { - Logger.debug(TAG, "Attaching latestgopanic to event") - event.setExtra("latestgopanic", goErrorLog.toString()) - } - } - - event - } - } - } -} diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/vpn/LanternVpnService.kt b/android/app/src/main/kotlin/org/getlantern/lantern/vpn/LanternVpnService.kt index 70f551a93..9b0e29f72 100644 --- a/android/app/src/main/kotlin/org/getlantern/lantern/vpn/LanternVpnService.kt +++ b/android/app/src/main/kotlin/org/getlantern/lantern/vpn/LanternVpnService.kt @@ -7,6 +7,7 @@ import android.content.ServiceConnection import android.net.VpnService import android.os.IBinder import org.getlantern.lantern.LanternApp +import org.getlantern.lantern.datadog.Datadog import org.getlantern.lantern.service.LanternService_ import org.getlantern.mobilesdk.Logger @@ -58,6 +59,7 @@ class LanternVpnService : VpnService(), Runnable { return START_STICKY } return if (intent.action == ACTION_DISCONNECT) { + Datadog.trackUserTap("switchVPN", mapOf("status" to "disconnect")) stop() START_NOT_STICKY } else { @@ -69,6 +71,7 @@ class LanternVpnService : VpnService(), Runnable { private fun connect() { Logger.d(TAG, "connect") + Datadog.trackUserTap("switchVPN", mapOf("status" to "connect")) Thread(this, "VpnService").start() } diff --git a/android/build.gradle b/android/build.gradle index 6a47028cb..e45a58ec5 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -19,6 +19,7 @@ buildscript { classpath "com.google.protobuf:protobuf-gradle-plugin:0.9.1" classpath 'com.google.gms:google-services:4.3.15' classpath("org.jlleitschuh.gradle:ktlint-gradle:11.3.1") + } } @@ -28,15 +29,14 @@ ext { enableTestCodeCoverage = true - compileSdkVersion = 33 - targetSdkVersion = 33 - minSdkVersion = 21 + compileSdkVersion = 34 + targetSdkVersion = 34 + minSdkVersion = 23 androidAnnotationsVersion = '4.8.0' androidAnnotationsAPIVersion = '4.8.0' - buildToolsVersion = '33.0.1' + buildToolsVersion = '34.0.0' buildNumber = 'dev' espressoVersion = '3.5.0' - supportLibVersion = '28.0.0' supportTestVersion = '1.0' lanternDir = projectDir.parentFile.parentFile diff --git a/android/settings.gradle b/android/settings.gradle index 44e62bcf0..31a9ce0ec 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -3,9 +3,13 @@ include ':app' def localPropertiesFile = new File(rootProject.projectDir, "local.properties") def properties = new Properties() -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } +} def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +if (flutterSdkPath == null) { + flutterSdkPath = System.env.FLUTTER_ROOT +} +assert flutterSdkPath != null, "flutter.sdk not set" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/lib/account/split_tunneling.dart b/lib/account/split_tunneling.dart index 63844c95b..785cff384 100644 --- a/lib/account/split_tunneling.dart +++ b/lib/account/split_tunneling.dart @@ -25,6 +25,7 @@ class _SplitTunnelingState extends State { void init() async { unawaited(sessionModel.refreshAppsList()); + await sessionModel.trackUserAction('Split tunneling screen shown to user'); var _vpnConnected = await vpnModel.isVpnConnected(); setState(() { vpnConnected = _vpnConnected; diff --git a/lib/ad_helper.dart b/lib/ad_helper.dart index 66079b996..36c46d169 100644 --- a/lib/ad_helper.dart +++ b/lib/ad_helper.dart @@ -12,6 +12,7 @@ import 'package:clever_ads_solutions/public/MediationManager.dart'; import 'package:clever_ads_solutions/public/OnDismissListener.dart'; import 'package:flutter/foundation.dart'; import 'package:google_mobile_ads/google_mobile_ads.dart'; +import 'package:lantern/common/datadog.dart'; import 'package:lantern/replica/common.dart'; import 'common/session_model.dart'; @@ -20,6 +21,14 @@ enum AdType { Google, CAS } const privacyPolicy = 'https://lantern.io/privacy'; +const googleAttributes = { + 'provider': AdType.Google, +}; + +const casAttributes = { + 'provider': AdType.CAS, +}; + class AdHelper { static final AdHelper _instance = AdHelper._internal(); @@ -96,13 +105,17 @@ class AdHelper { ad.fullScreenContentCallback = FullScreenContentCallback( onAdClicked: (ad) { logger.i('[Ads Manager] onAdClicked callback'); + Datadog.trackUserTap('User tapped on interstitial ad', googleAttributes); }, onAdShowedFullScreenContent: (ad) { logger.i('[Ads Manager] Showing Ads'); + Datadog.trackUserCustom('User shown interstitial ad', googleAttributes); }, onAdFailedToShowFullScreenContent: (ad, error) { logger.i( '[Ads Manager] onAdFailedToShowFullScreenContent callback'); + Datadog.addError('Ad failed to show full screen content: $error', + attributes: googleAttributes); //if ads fail to load let user turn on VPN _postShowingAds(); }, @@ -113,10 +126,12 @@ class AdHelper { ); _interstitialAd = ad; logger.i('[Ads Manager] to loaded $ad'); + Datadog.trackUserCustom('Interstitial ad loaded', googleAttributes); }, onAdFailedToLoad: (err) { _failedLoadAttempts++; // increment the count on failure logger.i('[Ads Manager] failed to load $err'); + Datadog.addError('failed to load interstitial ad: $err', attributes: googleAttributes); _postShowingAds(); }, ), @@ -183,6 +198,7 @@ class AdHelper { if (casMediationManager != null) { await casMediationManager!.loadInterstitial(); logger.i('[Ads Manager] Request: Initiating CAS Interstitial loading.'); + Datadog.trackUserCustom('Interstitial ad loaded', casAttributes); } } @@ -195,12 +211,14 @@ class AdHelper { void _onCASAdShowFailed() { logger.e('[Ads Manager] Error: CAS Interstitial failed to display.'); + Datadog.addError('Failed to display interstitial ad', attributes: casAttributes); _failedCASLoadAttempts++; _postShowingAds(); // Reload or decide the next action } void _onCASAdClosedOrComplete() { logger.i('[Ads Manager] Completion: CAS Interstitial closed or completed.'); + Datadog.trackUserCustom('Interstitial ad closed or completed', casAttributes); // Reset the counter when the ad successfully shows and closes/completes _failedCASLoadAttempts = 0; _postShowingAds(); @@ -260,11 +278,13 @@ class InterstitialListenerWrapper extends AdCallback { onFailed.call(); logger.i( '[CASIntegrationHelper] - InterstitialListenerWrapper onShowFailed-:$message'); + Datadog.addError('Interstitial ad onShowFailed: $message', attributes: casAttributes); } @override void onShown() { // Called when ad is shown. logger.i('[CASIntegrationHelper] - InterstitialListenerWrapper onShown'); + Datadog.trackUserCustom('User shown interstitial ad', casAttributes); } } diff --git a/lib/catcher_setup.dart b/lib/catcher_setup.dart deleted file mode 100644 index f3e296044..000000000 --- a/lib/catcher_setup.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:catcher/catcher.dart'; -import 'package:flutter/material.dart'; -import 'package:sentry/sentry.dart'; - -final debugOption = CatcherOptions( - SilentReportMode(), - [ - ConsoleHandler( - enableApplicationParameters: true, - enableDeviceParameters: true, - enableCustomParameters: true, - enableStackTrace: true, - ), - ], -); - -final releaseOption = CatcherOptions( - SilentReportMode(), - [ - ConsoleHandler( - enableApplicationParameters: true, - enableDeviceParameters: true, - enableCustomParameters: true, - enableStackTrace: true, - ), - SentryHandler( - SentryClient( - SentryOptions( - dsn: - 'https://4753d78f885f4b79a497435907ce4210@o75725.ingest.sentry.io/5850353', - ), - ), - printLogs: true, - ), - ], -); - -Catcher setupCatcherAndRun(StatelessWidget root) { - return Catcher( - rootWidget: root, - debugConfig: debugOption, - releaseConfig: releaseOption, - ); -} diff --git a/lib/common/common.dart b/lib/common/common.dart index 64a1b3506..db237aa49 100644 --- a/lib/common/common.dart +++ b/lib/common/common.dart @@ -28,8 +28,11 @@ export 'package:provider/provider.dart'; export 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; export 'package:stop_watch_timer/stop_watch_timer.dart'; export 'package:wakelock/wakelock.dart'; +export 'package:datadog_flutter_plugin/datadog_flutter_plugin.dart'; +export 'package:datadog_tracking_http_client/datadog_tracking_http_client.dart'; export 'add_nonbreaking_spaces.dart'; +export 'datadog.dart'; export 'disable_back_button.dart'; export 'iterable_extension.dart'; export 'list_subscriber.dart'; diff --git a/lib/common/datadog.dart b/lib/common/datadog.dart new file mode 100644 index 000000000..6fbee1efa --- /dev/null +++ b/lib/common/datadog.dart @@ -0,0 +1,29 @@ +import 'package:datadog_flutter_plugin/datadog_flutter_plugin.dart'; +import 'package:datadog_tracking_http_client/datadog_tracking_http_client.dart'; + +class Datadog { + static final DatadogSdk _instance = DatadogSdk.instance; + + static trackUserTap(String message, [Map attributes = const {}]) { + _instance.rum?.addUserAction(RumUserActionType.tap, message, attributes); + } + + static trackUserCustom(String message, [Map attributes = const {}]) { + _instance.rum?.addUserAction(RumUserActionType.custom, message, attributes); + } + + // Notifies Datadog that an Exception or Error [error] occurred in the currently + // presented View + static addError( + Object error, { + StackTrace? st, + Map attributes = const {}, + }) { + _instance.rum?.addErrorInfo( + error.toString(), + RumErrorSource.source, + stackTrace: st, + attributes: attributes, + ); + } +} diff --git a/lib/common/session_model.dart b/lib/common/session_model.dart index 427f6a218..406bf81b5 100644 --- a/lib/common/session_model.dart +++ b/lib/common/session_model.dart @@ -345,6 +345,14 @@ class SessionModel extends Model { ); } + Future trackUserAction( + String message, + ) async { + return methodChannel.invokeMethod('trackUserAction', { + 'message': message, + }); + } + Future redeemResellerCode( String email, String resellerCode, diff --git a/lib/home.dart b/lib/home.dart index eb8babf8e..9a407c52a 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -97,6 +97,12 @@ class _HomePageState extends State { Future _handleNativeNavigationRequest(MethodCall methodCall) async { switch (methodCall.method) { + case 'initDatadog': + final config = DdSdkExistingConfiguration( + loggingConfiguration: LoggingConfiguration(), + detectLongTasks: true, + )..enableHttpTracking(); + await DatadogSdk.instance.attachToExisting(config); case 'openConversation': final contact = Contact.fromBuffer(methodCall.arguments as Uint8List); await _context!.router.push(Conversation(contactId: contact.contactId)); diff --git a/lib/main.dart b/lib/main.dart index 3d0a8fb1d..1497af6f8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,6 @@ import 'package:flutter_driver/driver_extension.dart'; import 'package:google_mobile_ads/google_mobile_ads.dart'; import 'package:lantern/app.dart'; -import 'package:lantern/catcher_setup.dart'; import 'package:lantern/common/common.dart'; @@ -12,10 +11,11 @@ Future main() async { if (CI == 'true') { enableFlutterDriverExtension(); } + WidgetsFlutterBinding.ensureInitialized(); await _initGoogleMobileAds(); await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); - setupCatcherAndRun(LanternApp()); + runApp(LanternApp()); } Future _initGoogleMobileAds() async { diff --git a/pubspec.lock b/pubspec.lock index 442ccb02d..bcd58524a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -346,6 +346,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.10.1" + datadog_flutter_plugin: + dependency: "direct main" + description: + name: datadog_flutter_plugin + sha256: "2db71372f12f250a549f249046912594f15bf59113dc64a7e2fd5ae85ad90729" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + datadog_tracking_http_client: + dependency: "direct main" + description: + name: datadog_tracking_http_client + sha256: "7274fee927d8ec012a6c4fd3e3333616035236875129f83525fa0bbbe2a66bbb" + url: "https://pub.dev" + source: hosted + version: "1.4.0" dbus: dependency: transitive description: @@ -355,13 +371,13 @@ packages: source: hosted version: "0.7.8" device_info_plus: - dependency: transitive + dependency: "direct main" description: name: device_info_plus - sha256: "2c35b6d1682b028e42d07b3aee4b98fa62996c10bc12cb651ec856a80d6a761b" + sha256: "86add5ef97215562d2e090535b0a16f197902b10c369c558a100e74ea06e8659" url: "https://pub.dev" source: hosted - version: "9.0.2" + version: "9.0.3" device_info_plus_platform_interface: dependency: transitive description: @@ -371,13 +387,13 @@ packages: source: hosted version: "7.0.0" dio: - dependency: transitive + dependency: "direct main" description: name: dio - sha256: a9d76e72985d7087eb7c5e7903224ae52b337131518d127c554b9405936752b8 + sha256: ce75a1b40947fea0a0e16ce73337122a86762e38b982e1ccb909daa3b9bc4197 url: "https://pub.dev" source: hosted - version: "5.2.1+1" + version: "5.3.2" dotted_border: dependency: "direct main" description: @@ -586,13 +602,13 @@ packages: source: sdk version: "0.0.0" flutter_mailer: - dependency: transitive + dependency: "direct main" description: name: flutter_mailer - sha256: "5ec538be34233a62129c3aedc8cfcfaca0c4de390ca43f331f52e972d410b84d" + sha256: a935e9caa842877e8ed56109afb75b86e6488edbcd4696a5ac02b327a48fcd8a url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.1.1" flutter_markdown: dependency: "direct main" description: @@ -652,7 +668,7 @@ packages: source: sdk version: "0.0.0" fluttertoast: - dependency: transitive + dependency: "direct main" description: name: fluttertoast sha256: "474f7d506230897a3cd28c965ec21c5328ae5605fc9c400cd330e9e9d6ac175c" @@ -1182,7 +1198,7 @@ packages: source: hosted version: "0.3.8" sentry: - dependency: "direct main" + dependency: transitive description: name: sentry sha256: "39c23342fc96105da449914f7774139a17a0ca8a4e70d9ad5200171f7e47d6ba" diff --git a/pubspec.yaml b/pubspec.yaml index 5a54da8e8..8d5589b86 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,14 +47,19 @@ dependencies: wakelock: ^0.6.2 email_validator: ^2.1.17 credit_card_validator: ^2.1.0 + # Media & File handling audioplayers: ^5.1.0 video_player: ^2.7.0 video_thumbnail: ^0.5.3 file_picker: ^5.3.3 filesize: ^2.0.1 + + # Networking cached_network_image: ^3.2.3 -# change this with flutter_downloader + dio: ^5.3.2 + + # change this with flutter_downloader flutter_uploader: ^3.0.0-beta.3 mime: ^1.0.4 flutter_pdfview: ^1.3.1 @@ -63,6 +68,7 @@ dependencies: auto_route: ^7.8.0 i18n_extension: ^7.0.0 intl: ^0.18.0 + # QR qr_flutter: ^4.1.0 qr_code_scanner: ^1.0.1 @@ -83,23 +89,26 @@ dependencies: flutter_local_notifications: ^15.1.0+1 logger: ^2.0.1 - # Error handling & Package information - catcher: - git: - url: https://github.com/ThexXTURBOXx/catcher.git - sentry: ^7.9.0 + # Error handling + device_info_plus: ^9.0.3 + flutter_mailer: ^2.0.0 + fluttertoast: ^8.2.2 + datadog_flutter_plugin: ^1.4.0 + datadog_tracking_http_client: ^1.4.0 + + # Package information package_info_plus: ^4.1.0 # Path, permission & Markdown handling path_provider: ^2.1.0 permission_handler: ^10.4.3 flutter_markdown: ^0.6.17+1 - # Ads + + # Ads google_mobile_ads: ^3.0.0 clever_ads_solutions: ^0.1.0 - # wakelock ^0.6.2 requires win32 ^2.0.0 or ^3.0.0 # See https://github.com/creativecreatorormaybenot/wakelock/issues/211 dependency_overrides: @@ -135,6 +144,10 @@ flutter: # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true + + module: + androidX: true + # To add assets to your application, add an assets section, like this: assets: - assets/ diff --git a/test/catcher_test.dart b/test/catcher_test.dart deleted file mode 100644 index 5f97f7f95..000000000 --- a/test/catcher_test.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:catcher/catcher.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:lantern/app.dart'; -import 'package:lantern/catcher_setup.dart'; - -void main() { - setUp(() { - setupCatcherAndRun(LanternApp()); - }); - - testWidgets('Test Catcher', (WidgetTester tester) async { - print('Catcher is just a wrapper that notify any 3rd party error handler'); - print( - 'Catcher should throw a custom exception and the logger should be displayed on the console', - ); - expect(() => Catcher.sendTestException(), throwsA(isA())); - }); -}