diff --git a/.changeset/v6-hide-serialization.md b/.changeset/v6-hide-serialization.md new file mode 100644 index 00000000..9eb46db6 --- /dev/null +++ b/.changeset/v6-hide-serialization.md @@ -0,0 +1,5 @@ +--- +"posthog_flutter": major +--- + +- **Breaking:** remove `toMap()` from the config classes and `PostHogFeatureFlagResult.fromMap()` from the public API — they serialize the internal platform-channel wire format and were never meant for app code diff --git a/.changeset/v6-platform-opt-in-out.md b/.changeset/v6-platform-opt-in-out.md new file mode 100644 index 00000000..0bdcaddf --- /dev/null +++ b/.changeset/v6-platform-opt-in-out.md @@ -0,0 +1,5 @@ +--- +"posthog_flutter": major +--- + +- **Breaking:** rename `enable()`/`disable()` to `optIn()`/`optOut()` on `PosthogFlutterPlatformInterface`, `PosthogFlutterIO` and `PosthogFlutterWeb`. Update direct calls to these platform classes, custom implementations, and test fakes/mocks. Calls to `Posthog().optIn()`/`optOut()` are unchanged diff --git a/.changeset/v6-remove-5x-deprecations.md b/.changeset/v6-remove-5x-deprecations.md new file mode 100644 index 00000000..7cde3baa --- /dev/null +++ b/.changeset/v6-remove-5x-deprecations.md @@ -0,0 +1,6 @@ +--- +"posthog_flutter": major +--- + +- **Breaking:** remove `Posthog().enable()` and `Posthog().disable()` — use `optIn()` and `optOut()` +- **Breaking:** remove `PostHogConfig.sendFeatureFlagEvents` — use `sendFeatureFlagEvent` diff --git a/.changeset/v6-remove-deprecated-apis.md b/.changeset/v6-remove-deprecated-apis.md new file mode 100644 index 00000000..566af215 --- /dev/null +++ b/.changeset/v6-remove-deprecated-apis.md @@ -0,0 +1,10 @@ +--- +"posthog_flutter": major +--- + +- **Breaking:** bump minimum iOS deployment target from 13.0 to 15.0 and macOS from 10.15 to 11.0 +- **Breaking:** remove `PostHogConfig.apiKey` and the `com.posthog.posthog.API_KEY` manifest/Info.plist key — use `projectToken` and `com.posthog.posthog.PROJECT_TOKEN` +- **Breaking:** remove `Posthog().getFeatureFlagPayload()` — use `getFeatureFlagResult(key, sendFeatureFlagEvent: false)` and read `payload` +- **Breaking:** remove the `PostHogSessionReplayConfig.debouncerDelay` setter — use `PostHogSessionReplayConfig.throttleDelay` +- **Breaking:** remove `PostHogDataMode.cellular` — use `PostHogDataMode.any`, which it always behaved like +- Declare Android `minSdkVersion` 23 to match `posthog-android`, which has required it since 3.39.0. Apps on minSdk 21 or 22 already could not build with this plugin, so nothing changes for them diff --git a/.changeset/v6-send-feature-flag-event-param.md b/.changeset/v6-send-feature-flag-event-param.md new file mode 100644 index 00000000..677d86fb --- /dev/null +++ b/.changeset/v6-send-feature-flag-event-param.md @@ -0,0 +1,5 @@ +--- +"posthog_flutter": major +--- + +- **Breaking:** `getFeatureFlagResult(sendEvent:)` is now `getFeatureFlagResult(sendFeatureFlagEvent:)`, matching `PostHogConfig.sendFeatureFlagEvent` and the native SDKs diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f88a2503..84257df9 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -24,7 +24,7 @@ jobs: runner: ubuntu-latest - language: swift build-mode: manual - runner: macos-15-xlarge + runner: macos-26 - language: actions build-mode: none runner: ubuntu-latest @@ -45,7 +45,7 @@ jobs: if: matrix.language == 'swift' uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 with: - xcode-version: '16.4' + xcode-version: '26.4' - name: Install dependencies (Swift) if: matrix.language == 'swift' diff --git a/api/posthog_flutter.api.json b/api/posthog_flutter.api.json index 33cae6ee..c7ad8302 100644 --- a/api/posthog_flutter.api.json +++ b/api/posthog_flutter.api.json @@ -13,7 +13,7 @@ "packageApi": { "androidPlatformConstraints": { "compileSdkVersion": null, - "minSdkVersion": 21, + "minSdkVersion": 23, "targetSdkVersion": null }, "executableDeclarations": [ @@ -565,7 +565,7 @@ "isDeprecated": false, "isExperimental": false, "isStatic": false, - "name": "disable", + "name": "optOut", "parameters": [], "relativePath": "lib/src/posthog_flutter_platform_interface.dart", "returnTypeName": "Future", @@ -577,7 +577,7 @@ "isDeprecated": false, "isExperimental": false, "isStatic": false, - "name": "enable", + "name": "optIn", "parameters": [], "relativePath": "lib/src/posthog_flutter_platform_interface.dart", "returnTypeName": "Future", @@ -876,28 +876,6 @@ "type": "method", "typeParameterNames": [] }, - { - "entryPoints": [], - "isDeprecated": false, - "isExperimental": false, - "isStatic": false, - "name": "getFeatureFlagPayload", - "parameters": [ - { - "isDeprecated": false, - "isExperimental": false, - "isNamed": true, - "isRequired": true, - "name": "key", - "relativePath": "lib/src/posthog_flutter_platform_interface.dart", - "typeName": "String" - } - ], - "relativePath": "lib/src/posthog_flutter_platform_interface.dart", - "returnTypeName": "Future", - "type": "method", - "typeParameterNames": [] - }, { "entryPoints": [], "isDeprecated": false, @@ -919,7 +897,7 @@ "isExperimental": false, "isNamed": true, "isRequired": false, - "name": "sendEvent", + "name": "sendFeatureFlagEvent", "relativePath": "lib/src/posthog_flutter_platform_interface.dart", "typeName": "bool" } @@ -1548,7 +1526,7 @@ "isDeprecated": false, "isExperimental": false, "isStatic": false, - "name": "disable", + "name": "optOut", "parameters": [], "relativePath": "lib/src/posthog_flutter_platform_interface.dart", "returnTypeName": "Future", @@ -1560,7 +1538,7 @@ "isDeprecated": false, "isExperimental": false, "isStatic": false, - "name": "enable", + "name": "optIn", "parameters": [], "relativePath": "lib/src/posthog_flutter_platform_interface.dart", "returnTypeName": "Future", @@ -1859,28 +1837,6 @@ "type": "method", "typeParameterNames": [] }, - { - "entryPoints": [], - "isDeprecated": false, - "isExperimental": false, - "isStatic": false, - "name": "getFeatureFlagPayload", - "parameters": [ - { - "isDeprecated": false, - "isExperimental": false, - "isNamed": true, - "isRequired": true, - "name": "key", - "relativePath": "lib/src/posthog_flutter_platform_interface.dart", - "typeName": "String" - } - ], - "relativePath": "lib/src/posthog_flutter_platform_interface.dart", - "returnTypeName": "Future", - "type": "method", - "typeParameterNames": [] - }, { "entryPoints": [], "isDeprecated": false, @@ -1902,7 +1858,7 @@ "isExperimental": false, "isNamed": true, "isRequired": false, - "name": "sendEvent", + "name": "sendFeatureFlagEvent", "relativePath": "lib/src/posthog_flutter_platform_interface.dart", "typeName": "bool" } @@ -2108,18 +2064,6 @@ "posthog_flutter.dart" ], "executableDeclarations": [ - { - "entryPoints": [], - "isDeprecated": false, - "isExperimental": false, - "isStatic": false, - "name": "toMap", - "parameters": [], - "relativePath": "lib/src/posthog_config.dart", - "returnTypeName": "Map", - "type": "method", - "typeParameterNames": [] - }, { "entryPoints": [], "isDeprecated": false, @@ -2173,17 +2117,6 @@ "relativePath": "lib/src/posthog_config.dart", "typeName": "String" }, - { - "entryPoints": [], - "isDeprecated": true, - "isExperimental": false, - "isReadable": true, - "isStatic": false, - "isWriteable": false, - "name": "apiKey", - "relativePath": "lib/src/posthog_config.dart", - "typeName": "String" - }, { "entryPoints": [], "isDeprecated": false, @@ -2250,17 +2183,6 @@ "relativePath": "lib/src/posthog_config.dart", "typeName": "bool" }, - { - "entryPoints": [], - "isDeprecated": true, - "isExperimental": false, - "isReadable": true, - "isStatic": false, - "isWriteable": true, - "name": "sendFeatureFlagEvents", - "relativePath": "lib/src/posthog_config.dart", - "typeName": "bool" - }, { "entryPoints": [], "isDeprecated": false, @@ -2487,18 +2409,6 @@ "posthog_flutter.dart" ], "executableDeclarations": [ - { - "entryPoints": [], - "isDeprecated": false, - "isExperimental": false, - "isStatic": false, - "name": "toMap", - "parameters": [], - "relativePath": "lib/src/posthog_config.dart", - "returnTypeName": "Map", - "type": "method", - "typeParameterNames": [] - }, { "entryPoints": [], "isDeprecated": false, @@ -2689,18 +2599,6 @@ "posthog_flutter.dart" ], "executableDeclarations": [ - { - "entryPoints": [], - "isDeprecated": false, - "isExperimental": false, - "isStatic": false, - "name": "toMap", - "parameters": [], - "relativePath": "lib/src/posthog_config.dart", - "returnTypeName": "Map", - "type": "method", - "typeParameterNames": [] - }, { "entryPoints": [], "isDeprecated": false, @@ -2770,17 +2668,6 @@ "relativePath": "lib/src/posthog_config.dart", "typeName": "PostHogTextMask Function(String, Widget)?" }, - { - "entryPoints": [], - "isDeprecated": true, - "isExperimental": false, - "isReadable": false, - "isStatic": false, - "isWriteable": true, - "name": "debouncerDelay", - "relativePath": "lib/src/posthog_config.dart", - "typeName": "Duration" - }, { "entryPoints": [], "isDeprecated": false, @@ -2950,17 +2837,6 @@ "relativePath": "lib/src/posthog_config.dart", "typeName": "PostHogDataMode" }, - { - "entryPoints": [], - "isDeprecated": true, - "isExperimental": false, - "isReadable": true, - "isStatic": true, - "isWriteable": false, - "name": "cellular", - "relativePath": "lib/src/posthog_config.dart", - "typeName": "PostHogDataMode" - }, { "entryPoints": [], "isDeprecated": false, @@ -3001,18 +2877,6 @@ "posthog_flutter.dart" ], "executableDeclarations": [ - { - "entryPoints": [], - "isDeprecated": false, - "isExperimental": false, - "isStatic": false, - "name": "toMap", - "parameters": [], - "relativePath": "lib/src/posthog_config.dart", - "returnTypeName": "Map", - "type": "method", - "typeParameterNames": [] - }, { "entryPoints": [], "isDeprecated": false, @@ -3154,18 +3018,6 @@ "posthog_flutter.dart" ], "executableDeclarations": [ - { - "entryPoints": [], - "isDeprecated": false, - "isExperimental": false, - "isStatic": false, - "name": "toMap", - "parameters": [], - "relativePath": "lib/src/posthog_config.dart", - "returnTypeName": "Map", - "type": "method", - "typeParameterNames": [] - }, { "entryPoints": [], "isDeprecated": false, @@ -3219,18 +3071,6 @@ "posthog_flutter.dart" ], "executableDeclarations": [ - { - "entryPoints": [], - "isDeprecated": false, - "isExperimental": false, - "isStatic": false, - "name": "toMap", - "parameters": [], - "relativePath": "lib/src/posthog_config.dart", - "returnTypeName": "Map", - "type": "method", - "typeParameterNames": [] - }, { "entryPoints": [], "isDeprecated": false, @@ -3383,18 +3223,6 @@ "posthog_flutter.dart" ], "executableDeclarations": [ - { - "entryPoints": [], - "isDeprecated": false, - "isExperimental": false, - "isStatic": false, - "name": "toMap", - "parameters": [], - "relativePath": "lib/src/posthog_config.dart", - "returnTypeName": "Map", - "type": "method", - "typeParameterNames": [] - }, { "entryPoints": [], "isDeprecated": false, @@ -3603,37 +3431,6 @@ "posthog_flutter.dart" ], "executableDeclarations": [ - { - "entryPoints": [], - "isDeprecated": false, - "isExperimental": false, - "isStatic": true, - "name": "fromMap", - "parameters": [ - { - "isDeprecated": false, - "isExperimental": false, - "isNamed": false, - "isRequired": true, - "name": "result", - "relativePath": "lib/src/feature_flag_result.dart", - "typeName": "Object?" - }, - { - "isDeprecated": false, - "isExperimental": false, - "isNamed": false, - "isRequired": true, - "name": "fallbackKey", - "relativePath": "lib/src/feature_flag_result.dart", - "typeName": "String" - } - ], - "relativePath": "lib/src/feature_flag_result.dart", - "returnTypeName": "PostHogFeatureFlagResult?", - "type": "method", - "typeParameterNames": [] - }, { "entryPoints": [], "isDeprecated": false, @@ -4363,30 +4160,6 @@ "type": "method", "typeParameterNames": [] }, - { - "entryPoints": [], - "isDeprecated": true, - "isExperimental": false, - "isStatic": false, - "name": "disable", - "parameters": [], - "relativePath": "lib/src/posthog.dart", - "returnTypeName": "Future", - "type": "method", - "typeParameterNames": [] - }, - { - "entryPoints": [], - "isDeprecated": true, - "isExperimental": false, - "isStatic": false, - "name": "enable", - "parameters": [], - "relativePath": "lib/src/posthog.dart", - "returnTypeName": "Future", - "type": "method", - "typeParameterNames": [] - }, { "entryPoints": [], "isDeprecated": false, @@ -4816,7 +4589,7 @@ "isExperimental": false, "isNamed": true, "isRequired": false, - "name": "sendEvent", + "name": "sendFeatureFlagEvent", "relativePath": "lib/src/posthog.dart", "typeName": "bool" } @@ -4826,28 +4599,6 @@ "type": "method", "typeParameterNames": [] }, - { - "entryPoints": [], - "isDeprecated": true, - "isExperimental": false, - "isStatic": false, - "name": "getFeatureFlagPayload", - "parameters": [ - { - "isDeprecated": false, - "isExperimental": false, - "isNamed": false, - "isRequired": true, - "name": "key", - "relativePath": "lib/src/posthog.dart", - "typeName": "String" - } - ], - "relativePath": "lib/src/posthog.dart", - "returnTypeName": "Future", - "type": "method", - "typeParameterNames": [] - }, { "entryPoints": [], "isDeprecated": false, diff --git a/example/ios/Podfile b/example/ios/Podfile index 266a1cd0..e20ea2b7 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '14.0' +platform :ios, '15.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 6356c2c8..70f8356f 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -33,6 +33,6 @@ SPEC CHECKSUMS: google_maps_flutter_ios: 17552876e72723da1d41accc22f03b5f7afbde69 GoogleMaps: 8939898920281c649150e0af74aa291c60f2e77d -PODFILE CHECKSUM: 3c864ae2f1db9f17d3418f22aa4b78eac050c10b +PODFILE CHECKSUM: 583e7c5826ead1663c2849ec27bee3d398ceebac COCOAPODS: 1.16.2 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index b6975b16..535d541f 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -478,7 +478,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -497,7 +497,7 @@ DEVELOPMENT_TEAM = PNC2XCH2XP; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -518,7 +518,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.example.posthogFlutterExample.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -537,7 +537,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.example.posthogFlutterExample.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -554,7 +554,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.example.posthogFlutterExample.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -610,7 +610,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -659,7 +659,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -680,7 +680,7 @@ DEVELOPMENT_TEAM = PNC2XCH2XP; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -704,7 +704,7 @@ DEVELOPMENT_TEAM = PNC2XCH2XP; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/example/macos/Podfile b/example/macos/Podfile index b52666a1..dbccf89c 100644 --- a/example/macos/Podfile +++ b/example/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.15' +platform :osx, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 9d359e12..36151c7c 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -11,6 +11,6 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 -PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 +PODFILE CHECKSUM: c2e95c8c0fe03c5c57e438583cae4cc732296009 COCOAPODS: 1.16.2 diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index f6c36aa8..c0dda7a1 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -549,7 +549,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -628,7 +628,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -675,7 +675,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/posthog_flutter/android/build.gradle b/posthog_flutter/android/build.gradle index ba6dcb54..2ae6e792 100644 --- a/posthog_flutter/android/build.gradle +++ b/posthog_flutter/android/build.gradle @@ -58,7 +58,7 @@ android { } defaultConfig { - minSdkVersion 21 + minSdkVersion 23 } dependencies { diff --git a/posthog_flutter/android/src/main/kotlin/com/posthog/flutter/PosthogFlutterPlugin.kt b/posthog_flutter/android/src/main/kotlin/com/posthog/flutter/PosthogFlutterPlugin.kt index 5474b8cd..378353bb 100644 --- a/posthog_flutter/android/src/main/kotlin/com/posthog/flutter/PosthogFlutterPlugin.kt +++ b/posthog_flutter/android/src/main/kotlin/com/posthog/flutter/PosthogFlutterPlugin.kt @@ -185,21 +185,10 @@ class PosthogFlutterPlugin : return } - val projectToken = - ( - bundle.getString("com.posthog.posthog.PROJECT_TOKEN") - ?: bundle.getString("com.posthog.posthog.API_KEY") - )?.trim() - - if (!bundle.containsKey("com.posthog.posthog.PROJECT_TOKEN") && bundle.containsKey("com.posthog.posthog.API_KEY")) { - Log.w( - "PostHog", - "com.posthog.posthog.API_KEY is deprecated and will be removed in the next major version. Use com.posthog.posthog.PROJECT_TOKEN instead!", - ) - } + val projectToken = bundle.getString("com.posthog.posthog.PROJECT_TOKEN")?.trim() if (projectToken.isNullOrEmpty()) { - Log.e("PostHog", "Either com.posthog.posthog.PROJECT_TOKEN or com.posthog.posthog.API_KEY must be provided!") + Log.e("PostHog", "com.posthog.posthog.PROJECT_TOKEN must be provided!") return } @@ -224,7 +213,6 @@ class PosthogFlutterPlugin : val posthogConfig = mutableMapOf() posthogConfig["projectToken"] = projectToken - posthogConfig["apiKey"] = projectToken posthogConfig["host"] = host posthogConfig["captureApplicationLifecycleEvents"] = captureApplicationLifecycleEvents posthogConfig["debug"] = debug @@ -322,10 +310,6 @@ class PosthogFlutterPlugin : getFeatureFlag(call, result) } - "getFeatureFlagPayload" -> { - getFeatureFlagPayload(call, result) - } - "getFeatureFlagResult" -> { getFeatureFlagResult(call, result) } @@ -552,19 +536,9 @@ class PosthogFlutterPlugin : } private fun setupPostHog(posthogConfig: Map) { - val projectToken = - ( - (posthogConfig["projectToken"] as String?) - ?: (posthogConfig["apiKey"] as String?) - )?.trim() - if (!posthogConfig.containsKey("projectToken") && posthogConfig.containsKey("apiKey")) { - Log.w( - "PostHog", - "apiKey is deprecated and will be removed in the next major version. Use projectToken instead!", - ) - } + val projectToken = (posthogConfig["projectToken"] as String?)?.trim() if (projectToken.isNullOrEmpty()) { - Log.e("PostHog", "Either projectToken or apiKey must be provided!") + Log.e("PostHog", "projectToken must be provided!") return } @@ -1670,19 +1644,6 @@ class PosthogFlutterPlugin : } } - private fun getFeatureFlagPayload( - call: MethodCall, - result: Result, - ) { - try { - val featureFlagKey: String = call.argument("key")!! - val flag = PostHog.getFeatureFlagResult(featureFlagKey, sendFeatureFlagEvent = false)?.payload - result.success(flag) - } catch (e: Throwable) { - result.error("PosthogFlutterException", e.localizedMessage, null) - } - } - private fun getFeatureFlagResult( call: MethodCall, result: Result, diff --git a/posthog_flutter/darwin/posthog_flutter.podspec b/posthog_flutter/darwin/posthog_flutter.podspec index c39b30b2..98e3a7cf 100644 --- a/posthog_flutter/darwin/posthog_flutter.podspec +++ b/posthog_flutter/darwin/posthog_flutter.podspec @@ -21,12 +21,10 @@ Postog flutter plugin s.ios.dependency 'Flutter' s.osx.dependency 'FlutterMacOS' - # ~> Version 3.80.0 up to, but not including, 4.0.0 s.dependency 'PostHog', '>= 3.84.0', '< 4.0.0' - s.ios.deployment_target = '13.0' - # PH iOS SDK 3.0.0 requires >= 10.15 - s.osx.deployment_target = '10.15' + s.ios.deployment_target = '15.0' + s.osx.deployment_target = '11.0' # Flutter.framework does not contain a i386 slice. s.ios.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/posthog_flutter/darwin/posthog_flutter/Package.swift b/posthog_flutter/darwin/posthog_flutter/Package.swift index dd9a201d..1cf4470c 100644 --- a/posthog_flutter/darwin/posthog_flutter/Package.swift +++ b/posthog_flutter/darwin/posthog_flutter/Package.swift @@ -6,8 +6,8 @@ import PackageDescription let package = Package( name: "posthog_flutter", platforms: [ - .iOS("13.0"), - .macOS("10.15"), + .iOS("15.0"), + .macOS("11.0"), ], products: [ .library(name: "posthog-flutter", targets: ["posthog_flutter"]), diff --git a/posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift b/posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift index f00c34e9..a69380be 100644 --- a/posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift +++ b/posthog_flutter/darwin/posthog_flutter/Sources/posthog_flutter/PosthogFlutterPlugin.swift @@ -104,9 +104,7 @@ public class PosthogFlutterPlugin: NSObject, FlutterPlugin { /// a later `FlutterEngine` registers — that setup() has already run, so use the plist key there. private static func prewarmPushNotificationOpenCapture() { guard plistCapturePushNotificationOpened else { return } - if #available(iOS 14.0, macOS 11.0, *) { - PostHogSDK.prewarmPushNotificationOpenCapture() - } + PostHogSDK.prewarmPushNotificationOpenCapture() } private static var plistCapturePushNotificationOpened: Bool { @@ -120,17 +118,11 @@ public class PosthogFlutterPlugin: NSObject, FlutterPlugin { return } - let projectToken = ((Bundle.main.object(forInfoDictionaryKey: "com.posthog.posthog.PROJECT_TOKEN") as? String) ?? (Bundle.main.object(forInfoDictionaryKey: "com.posthog.posthog.API_KEY") as? String) ?? "") + let projectToken = (Bundle.main.object(forInfoDictionaryKey: "com.posthog.posthog.PROJECT_TOKEN") as? String ?? "") .trimmingCharacters(in: .whitespacesAndNewlines) - if Bundle.main.object(forInfoDictionaryKey: "com.posthog.posthog.PROJECT_TOKEN") == nil, - Bundle.main.object(forInfoDictionaryKey: "com.posthog.posthog.API_KEY") != nil - { - print("[PostHog] com.posthog.posthog.API_KEY is deprecated and will be removed in the next major version. Use com.posthog.posthog.PROJECT_TOKEN instead!") - } - if projectToken.isEmpty { - print("[PostHog] Either com.posthog.posthog.PROJECT_TOKEN or com.posthog.posthog.API_KEY must be provided!") + print("[PostHog] com.posthog.posthog.PROJECT_TOKEN must be provided!") return } @@ -144,7 +136,6 @@ public class PosthogFlutterPlugin: NSObject, FlutterPlugin { setupPostHog([ "projectToken": projectToken, - "apiKey": projectToken, "host": host, "captureApplicationLifecycleEvents": captureApplicationLifecycleEvents, "debug": debug, @@ -161,13 +152,10 @@ public class PosthogFlutterPlugin: NSObject, FlutterPlugin { print("[PostHog] Plugin instance not found!") return } - let projectToken = ((posthogConfig["projectToken"] as? String) ?? (posthogConfig["apiKey"] as? String) ?? "") + let projectToken = (posthogConfig["projectToken"] as? String ?? "") .trimmingCharacters(in: .whitespacesAndNewlines) - if posthogConfig["projectToken"] == nil, posthogConfig["apiKey"] != nil { - print("[PostHog] apiKey is deprecated and will be removed in the next major version. Use projectToken instead!") - } if projectToken.isEmpty { - print("[PostHog] Either projectToken or apiKey must be provided!") + print("[PostHog] projectToken must be provided!") return } @@ -235,7 +223,7 @@ public class PosthogFlutterPlugin: NSObject, FlutterPlugin { switch dataMode { case "wifi": config.dataMode = .wifi - case "any", "cellular": + case "any": config.dataMode = .any default: break @@ -290,13 +278,11 @@ public class PosthogFlutterPlugin: NSObject, FlutterPlugin { } // configure surveys - if #available(iOS 15.0, *) { - let surveys: Bool = posthogConfig["surveys"] as? Bool ?? false - config.surveys = surveys - if surveys { - // if surveys are enabled, assign this instance as the survey delegate (we'll take over rendering) - config.surveysConfig.surveysDelegate = instance - } + let surveys: Bool = posthogConfig["surveys"] as? Bool ?? false + config.surveys = surveys + if surveys { + // if surveys are enabled, assign this instance as the survey delegate (we'll take over rendering) + config.surveysConfig.surveysDelegate = instance } #endif @@ -457,8 +443,6 @@ public class PosthogFlutterPlugin: NSObject, FlutterPlugin { getFeatureFlag(call, result: result) case "isFeatureEnabled": isFeatureEnabled(call, result: result) - case "getFeatureFlagPayload": - getFeatureFlagPayload(call, result: result) case "getFeatureFlagResult": getFeatureFlagResult(call, result: result) case "identify": @@ -1352,20 +1336,6 @@ extension PosthogFlutterPlugin { } } - private func getFeatureFlagPayload( - _ call: FlutterMethodCall, - result: @escaping FlutterResult - ) { - if let args = call.arguments as? [String: Any], - let featureFlagKey = args["key"] as? String - { - let value = PostHogSDK.shared.getFeatureFlagResult(featureFlagKey, sendFeatureFlagEvent: false)?.payload - result(value) - } else { - _badArgumentError(result) - } - } - private func getFeatureFlagResult( _ call: FlutterMethodCall, result: @escaping FlutterResult diff --git a/posthog_flutter/lib/posthog_flutter_web.dart b/posthog_flutter/lib/posthog_flutter_web.dart index 698c9585..05483e49 100644 --- a/posthog_flutter/lib/posthog_flutter_web.dart +++ b/posthog_flutter/lib/posthog_flutter_web.dart @@ -7,6 +7,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:posthog_flutter/src/error_tracking/dart_exception_processor.dart'; import 'package:posthog_flutter/src/util/logging.dart'; +import 'package:posthog_flutter/src/utils/channel_serialization.dart'; import 'package:posthog_flutter/src/utils/property_normalizer.dart'; import 'src/feature_flag_result.dart'; @@ -315,12 +316,12 @@ class PosthogFlutterWeb extends PosthogFlutterPlatformInterface { } @override - Future disable() async { + Future optOut() async { return handleWebMethodCall(const MethodCall('disable')); } @override - Future enable() async { + Future optIn() async { return handleWebMethodCall(const MethodCall('enable')); } @@ -420,24 +421,18 @@ class PosthogFlutterWeb extends PosthogFlutterPlatformInterface { return handleWebMethodCall(MethodCall('getFeatureFlag', {'key': key})); } - @override - Future getFeatureFlagPayload({required String key}) async { - return handleWebMethodCall( - MethodCall('getFeatureFlagPayload', {'key': key}), - ); - } - @override Future getFeatureFlagResult({ required String key, - bool sendEvent = true, + bool sendFeatureFlagEvent = true, }) async { final result = await handleWebMethodCall( - MethodCall('getFeatureFlagResult', {'key': key, 'sendEvent': sendEvent}), + MethodCall('getFeatureFlagResult', + {'key': key, 'sendEvent': sendFeatureFlagEvent}), ); // Web SDK returns: { key, enabled, variant, payload } - return PostHogFeatureFlagResult.fromMap(result, key); + return featureFlagResultFromMap(result, key); } @override diff --git a/posthog_flutter/lib/src/feature_flag_result.dart b/posthog_flutter/lib/src/feature_flag_result.dart index 9a85fef5..14298af1 100644 --- a/posthog_flutter/lib/src/feature_flag_result.dart +++ b/posthog_flutter/lib/src/feature_flag_result.dart @@ -53,23 +53,4 @@ class PostHogFeatureFlagResult { @override int get hashCode => Object.hash(key, enabled, variant); - - /// Creates a [PostHogFeatureFlagResult] from a native SDK response map. - /// - /// The [result] should be a map containing `key`, `enabled`, `variant`, and - /// `payload` entries. Falls back to [fallbackKey] if the map does not include - /// a key. - /// - /// Returns `null` if [result] is `null` or is not a [Map]. - static PostHogFeatureFlagResult? fromMap(Object? result, String fallbackKey) { - if (result == null) return null; - if (result is! Map) return null; - - return PostHogFeatureFlagResult( - key: result['key'] as String? ?? fallbackKey, - enabled: result['enabled'] as bool? ?? false, - variant: result['variant'] as String?, - payload: result['payload'], - ); - } } diff --git a/posthog_flutter/lib/src/posthog.dart b/posthog_flutter/lib/src/posthog.dart index 5546bd0c..ed459a5e 100644 --- a/posthog_flutter/lib/src/posthog.dart +++ b/posthog_flutter/lib/src/posthog.dart @@ -402,7 +402,7 @@ class Posthog { stopReplay = _posthog.stopSessionRecording(); } - await Future.wait([if (stopReplay != null) stopReplay, _posthog.disable()]); + await Future.wait([if (stopReplay != null) stopReplay, _posthog.optOut()]); } /// Opts the current user back in to data collection. @@ -417,7 +417,7 @@ class Posthog { _installFlutterIntegrations(config); } - await _posthog.enable(); + await _posthog.optIn(); final restartReplay = _restartSessionReplayOnEnable; _restartSessionReplayOnEnable = false; if (restartReplay) { @@ -427,18 +427,6 @@ class Posthog { } } - /// Opts the current user out of data collection. - @Deprecated( - 'Use optOut() instead. This will be removed in the next major version.', - ) - Future disable() => optOut(); - - /// Opts the current user back in to data collection. - @Deprecated( - 'Use optIn() instead. This will be removed in the next major version.', - ) - Future enable() => optIn(); - /// Returns whether the current user has opted out of data collection. Future isOptOut() => _posthog.isOptOut(); @@ -761,9 +749,9 @@ class Posthog { /// This is the canonical method for getting feature flag data. /// Returns `null` if the flag does not exist or cannot be loaded. /// - /// Set [sendEvent] to `false` to suppress the `$feature_flag_called` event. - /// This is useful when you only need the payload and do not want to emit the - /// event. + /// Set [sendFeatureFlagEvent] to `false` to suppress the + /// `$feature_flag_called` event. This is useful when you only need the + /// payload and do not want to emit the event. /// /// **Example:** /// ```dart @@ -775,19 +763,10 @@ class Posthog { /// ``` Future getFeatureFlagResult( String key, { - bool sendEvent = true, + bool sendFeatureFlagEvent = true, }) => - _posthog.getFeatureFlagResult(key: key, sendEvent: sendEvent); - - /// Returns the payload for the feature flag [key]. - /// - /// Returns `null` when the flag does not exist, has no payload, or the payload - /// cannot be loaded. - @Deprecated( - 'Use getFeatureFlagResult instead, which returns both value and payload.', - ) - Future getFeatureFlagPayload(String key) => - _posthog.getFeatureFlagPayload(key: key); + _posthog.getFeatureFlagResult( + key: key, sendFeatureFlagEvent: sendFeatureFlagEvent); /// Flushes queued events immediately where supported by the platform. /// diff --git a/posthog_flutter/lib/src/posthog_config.dart b/posthog_flutter/lib/src/posthog_config.dart index 93284849..c501e432 100644 --- a/posthog_flutter/lib/src/posthog_config.dart +++ b/posthog_flutter/lib/src/posthog_config.dart @@ -7,7 +7,6 @@ import 'posthog.dart'; import 'posthog_event.dart'; import 'posthog_flutter_platform_interface.dart'; import 'replay/mask/posthog_text_mask.dart'; -import 'util/logging.dart'; /// Callback to intercept and modify events before they are sent to PostHog. /// @@ -72,17 +71,6 @@ enum PostHogDataMode { /// Send data only on Wi-Fi connections. wifi, - /// Send data on any available connection, same as [any]. - /// - /// Despite its name, this never restricted sending to cellular connections. - /// It will be removed in the next major version. - /// - /// ```dart - /// config.dataMode = PostHogDataMode.any; - /// ``` - @Deprecated('Behaves like `any`. Use `PostHogDataMode.any` instead.') - cellular, - /// Send data on any available connection. any, } @@ -98,16 +86,8 @@ class PostHogConfig { /// /// You can find it at: /// https://us.posthog.com/settings/project-details#variables - /// - /// This field was formerly named [apiKey]. final String projectToken; - /// Deprecated alias for [projectToken]. - @Deprecated( - 'Deprecated in favor of [projectToken]. This will be removed in the next major version.', - ) - String get apiKey => projectToken; - String _host = _defaultHost; /// The PostHog ingestion host. @@ -143,17 +123,6 @@ class PostHogConfig { /// Defaults to `true`. var sendFeatureFlagEvent = true; - /// Deprecated alias for [sendFeatureFlagEvent]. - @Deprecated( - 'Use sendFeatureFlagEvent instead. This will be removed in the next major version.', - ) - bool get sendFeatureFlagEvents => sendFeatureFlagEvent; - - @Deprecated( - 'Use sendFeatureFlagEvent instead. This will be removed in the next major version.', - ) - set sendFeatureFlagEvents(bool value) => sendFeatureFlagEvent = value; - /// Whether feature flags are loaded when the SDK starts. /// /// Defaults to `true`, which means every SDK start issues a `/flags` request. @@ -424,43 +393,6 @@ class PostHogConfig { final trimmedHost = host.trim(); return trimmedHost.isEmpty ? _defaultHost : trimmedHost; } - - /// Converts this configuration to a platform-channel map. - /// - /// Returns the values consumed by the Android, Apple, and web implementations. - Map toMap() { - return { - 'projectToken': projectToken, - 'apiKey': projectToken, - 'host': host, - 'flushAt': flushAt, - 'maxQueueSize': maxQueueSize, - 'maxBatchSize': maxBatchSize, - 'flushInterval': flushInterval.inSeconds, - 'sendFeatureFlagEvents': sendFeatureFlagEvent, - 'preloadFeatureFlags': preloadFeatureFlags, - 'captureApplicationLifecycleEvents': captureApplicationLifecycleEvents, - 'rageClickConfig': rageClickConfig.toMap(), - 'debug': debug, - 'optOut': optOut, - 'surveys': surveys, - 'personProfiles': personProfiles.name, - 'compression': compression.name, - 'sessionReplay': sessionReplay, - 'dataMode': dataMode.name, - 'sessionReplayConfig': sessionReplayConfig.toMap(), - 'errorTrackingConfig': errorTrackingConfig.toMap(), - 'logs': logsConfig.toMap(), - 'capturePushNotificationSubscriptions': - capturePushNotificationSubscriptions, - 'capturePushNotificationOpened': capturePushNotificationOpened, - // A closure can't cross the channel. This tells native whether to install - // a bridging provider at all — installing one the host didn't ask for - // would change how the native SDK handles a 401 on the subscription call. - 'pushIdentityProviderEnabled': pushIdentityProvider != null, - if (bootstrap != null) 'bootstrap': bootstrap!.toMap(), - }; - } } /// Pre-seeded identity and feature-flag state applied on the very first SDK @@ -517,34 +449,6 @@ class PostHogBootstrapConfig { /// JSON payloads paired with [featureFlags], keyed by flag key. Each value is /// the already-decoded payload (map, list, string, number, `null`, ...). final Map? featureFlagPayloads; - - /// Converts this configuration to a platform-channel map. - /// - /// Only the dimensions that were set are included; [isIdentifiedId] is always - /// sent so the native SDK doesn't have to infer it. - Map toMap() { - final flags = featureFlags; - if (flags != null) { - for (final entry in flags.entries) { - // Only bool/String are served (see [featureFlags]); the native SDKs drop - // anything else silently, so warn instead of leaving no trace. - if (entry.value is! bool && entry.value is! String) { - printIfDebug( - '[PostHog] bootstrap featureFlags["${entry.key}"] is ' - '${entry.value.runtimeType}; only bool and String values are served, ' - 'so this entry will be ignored.', - ); - } - } - } - return { - if (distinctId != null) 'distinctId': distinctId, - 'isIdentifiedId': isIdentifiedId, - if (featureFlags != null) 'featureFlags': featureFlags, - if (featureFlagPayloads != null) - 'featureFlagPayloads': featureFlagPayloads, - }; - } } /// Configuration for the logs subsystem. @@ -646,36 +550,6 @@ class PostHogLogsConfig { /// record. Keep anything sensitive out of those fields; put it in [body] or /// [PostHogLogRecord.attributes], which a callback can scrub or drop. List beforeSend = []; - - /// Converts the identity and tuning options to a platform-channel map. - /// - /// Only fields the user set are included, so unset fields keep the native - /// default. [beforeSend] is intentionally omitted: it runs in Dart and never - /// crosses the platform channel. - Map toMap() { - return { - if (serviceName != null) 'serviceName': serviceName, - if (serviceVersion != null) 'serviceVersion': serviceVersion, - if (environment != null) 'environment': environment, - if (resourceAttributes.isNotEmpty) - 'resourceAttributes': resourceAttributes, - if (flushInterval != null) - 'flushIntervalSeconds': _wholeSeconds(flushInterval!), - if (flushAt != null) 'flushAt': flushAt, - if (maxBatchSize != null) 'maxBatchSize': maxBatchSize, - if (maxBufferSize != null) 'maxBufferSize': maxBufferSize, - if (rateCapMaxLogs != null) 'rateCapMaxLogs': rateCapMaxLogs, - if (rateCapWindow != null) - 'rateCapWindowSeconds': _wholeSeconds(rateCapWindow!), - }; - } - - /// The native flush interval and rate-cap window are whole seconds. A - /// sub-second [Duration] truncates to `0`, which the native SDK treats as - /// "disabled" (rate cap) or continuous flushing — surprising for a caller who - /// set, say, 500ms. Floor at 1s, the smallest value the native API can honor. - static int _wholeSeconds(Duration duration) => - duration.inSeconds < 1 ? 1 : duration.inSeconds; } /// Configuration for rage-click autocapture on iOS and Mac Catalyst. @@ -705,17 +579,6 @@ class PostHogRageClickConfig { /// /// Defaults to `3`. var minimumTapCount = 3; - - /// Converts this rage-click configuration to a platform-channel map. - Map toMap() { - return { - 'enabled': enabled, - 'thresholdPoints': thresholdPoints, - 'timeoutInterval': - timeoutInterval.inMicroseconds / Duration.microsecondsPerSecond, - 'minimumTapCount': minimumTapCount, - }; - } } /// Pixel format for Android native-screen captures in session replay. @@ -865,17 +728,6 @@ class PostHogSessionReplayConfig { /// Default: null. PostHogTextMaskPolicy? textMaskPolicy; - /// Deprecated setter that forwards assigned values to [throttleDelay]. - /// - /// Debouncer delay used to reduce the number of snapshots captured and reduce - /// performance impact. This is used for capturing the view as a screenshot. - /// The lower the number, the more snapshots will be captured but higher the - /// performance impact. Defaults to 1s. - @Deprecated('Deprecated in favor of [throttleDelay] from v4.8.0.') - set debouncerDelay(Duration debouncerDelay) { - throttleDelay = debouncerDelay; - } - /// Throttling delay used to reduce the number of snapshots captured and reduce /// performance impact. /// @@ -1008,26 +860,6 @@ class PostHogSessionReplayConfig { /// /// Default: false. var verifyScreenshotMaskAlignment = false; - - /// Converts this session replay configuration to a platform-channel map. - /// - /// Returns values consumed by the Android and Apple session replay - /// implementations. - Map toMap() { - return { - 'captureTouches': captureTouches, - 'maskAllImages': maskAllImages, - 'maskAllTexts': maskAllTexts, - 'throttleDelayMs': throttleDelay.inMilliseconds, - 'maskAllPlatformViews': maskAllPlatformViews, - 'captureNativeScreens': captureNativeScreens, - 'verifyScreenshotMaskAlignment': verifyScreenshotMaskAlignment, - 'screenshotScale': screenshotScale, - 'screenshotCompressionQuality': screenshotCompressionQuality, - 'screenshotColorMode': screenshotColorMode.name, - if (sampleRate != null) 'sampleRate': sampleRate, - }; - } } /// Configuration for PostHog error tracking and exception capture. @@ -1201,25 +1033,6 @@ class PostHogErrorTrackingConfig { /// /// Record steps with `Posthog().addExceptionStep()`. final exceptionSteps = PostHogExceptionStepsConfig(); - - /// Converts this error tracking configuration to a platform-channel map. - /// - /// Returns values consumed by the Android, Apple, and Dart exception capture - /// implementations. - Map toMap() { - return { - 'inAppIncludes': inAppIncludes, - 'inAppExcludes': inAppExcludes, - 'inAppByDefault': inAppByDefault, - 'captureFlutterErrors': captureFlutterErrors, - 'captureSilentFlutterErrors': captureSilentFlutterErrors, - 'capturePlatformDispatcherErrors': capturePlatformDispatcherErrors, - 'captureNativeExceptions': captureNativeExceptions, - 'captureNativeCrashes': captureNativeCrashes, - 'captureIsolateErrors': captureIsolateErrors, - 'exceptionSteps': exceptionSteps.toMap(), - }; - } } /// Configuration for exception steps. @@ -1253,12 +1066,4 @@ class PostHogExceptionStepsConfig { /// until the total fits. A single step larger than the budget is rejected /// outright. Defaults to `32768` (32 KiB). var maxBytes = 32768; - - /// Converts this configuration to a platform-channel map. - Map toMap() { - return { - 'enabled': enabled, - 'maxBytes': maxBytes, - }; - } } diff --git a/posthog_flutter/lib/src/posthog_flutter_io.dart b/posthog_flutter/lib/src/posthog_flutter_io.dart index 4ed438be..bf923708 100644 --- a/posthog_flutter/lib/src/posthog_flutter_io.dart +++ b/posthog_flutter/lib/src/posthog_flutter_io.dart @@ -12,6 +12,7 @@ import 'surveys/models/survey_callbacks.dart'; import 'error_tracking/dart_exception_processor.dart'; import 'utils/before_send.dart'; import 'utils/capture_utils.dart'; +import 'utils/channel_serialization.dart'; import 'utils/flutter_version.dart'; import 'utils/property_normalizer.dart'; @@ -491,7 +492,7 @@ class PosthogFlutterIO extends PosthogFlutterPlatformInterface { } @override - Future disable() async { + Future optOut() async { if (!isSupportedPlatform()) { return; } @@ -504,7 +505,7 @@ class PosthogFlutterIO extends PosthogFlutterPlatformInterface { } @override - Future enable() async { + Future optIn() async { if (!isSupportedPlatform()) { return; } @@ -683,26 +684,10 @@ class PosthogFlutterIO extends PosthogFlutterPlatformInterface { } } - @override - Future getFeatureFlagPayload({required String key}) async { - if (!isSupportedPlatform()) { - return null; - } - - try { - return await _methodChannel.invokeMethod('getFeatureFlagPayload', { - 'key': key, - }); - } on PlatformException catch (exception) { - printIfDebug('Exeption on getFeatureFlagPayload: $exception'); - return null; - } - } - @override Future getFeatureFlagResult({ required String key, - bool sendEvent = true, + bool sendFeatureFlagEvent = true, }) async { if (!isSupportedPlatform()) { return null; @@ -711,11 +696,11 @@ class PosthogFlutterIO extends PosthogFlutterPlatformInterface { try { final result = await _methodChannel.invokeMethod('getFeatureFlagResult', { 'key': key, - 'sendEvent': sendEvent, + 'sendEvent': sendFeatureFlagEvent, }); // Native returns: { key, enabled, variant, payload } - return PostHogFeatureFlagResult.fromMap(result, key); + return featureFlagResultFromMap(result, key); } on PlatformException catch (exception) { printIfDebug('Exception on getFeatureFlagResult: $exception'); return null; diff --git a/posthog_flutter/lib/src/posthog_flutter_platform_interface.dart b/posthog_flutter/lib/src/posthog_flutter_platform_interface.dart index ee37df82..729f4de3 100644 --- a/posthog_flutter/lib/src/posthog_flutter_platform_interface.dart +++ b/posthog_flutter/lib/src/posthog_flutter_platform_interface.dart @@ -129,12 +129,12 @@ abstract class PosthogFlutterPlatformInterface extends PlatformInterface { throw UnimplementedError('reset() has not been implemented.'); } - Future disable() { - throw UnimplementedError('disable() has not been implemented.'); + Future optOut() { + throw UnimplementedError('optOut() has not been implemented.'); } - Future enable() { - throw UnimplementedError('enable() has not been implemented.'); + Future optIn() { + throw UnimplementedError('optIn() has not been implemented.'); } Future isOptOut() { @@ -204,15 +204,9 @@ abstract class PosthogFlutterPlatformInterface extends PlatformInterface { throw UnimplementedError('getFeatureFlag() has not been implemented.'); } - Future getFeatureFlagPayload({required String key}) { - throw UnimplementedError( - 'getFeatureFlagPayload() has not been implemented.', - ); - } - Future getFeatureFlagResult({ required String key, - bool sendEvent = true, + bool sendFeatureFlagEvent = true, }) { throw UnimplementedError( 'getFeatureFlagResult() has not been implemented.', diff --git a/posthog_flutter/lib/src/posthog_flutter_web_handler.dart b/posthog_flutter/lib/src/posthog_flutter_web_handler.dart index 211a8dca..7f0b5406 100644 --- a/posthog_flutter/lib/src/posthog_flutter_web_handler.dart +++ b/posthog_flutter/lib/src/posthog_flutter_web_handler.dart @@ -57,7 +57,6 @@ extension PostHogExtension on PostHog { // ignore: non_constant_identifier_names external bool has_opted_out_capturing(); external JSAny? getFeatureFlag(JSAny key); - external JSAny? getFeatureFlagPayload(JSAny key); external JSAny? getFeatureFlagResult(JSAny key, [JSAny? options]); external void register(JSAny properties); external void unregister(JSAny key); @@ -367,11 +366,6 @@ Future handleWebMethodCall(MethodCall call) async { final featureFlag = posthog?.getFeatureFlag(stringToJSAny(key)); return featureFlag?.dartify(); - case 'getFeatureFlagPayload': - final key = args['key'] as String; - - final featureFlag = posthog?.getFeatureFlagPayload(stringToJSAny(key)); - return featureFlag?.dartify(); case 'getFeatureFlagResult': final key = args['key'] as String; final sendEvent = args['sendEvent'] as bool? ?? true; diff --git a/posthog_flutter/lib/src/utils/channel_serialization.dart b/posthog_flutter/lib/src/utils/channel_serialization.dart new file mode 100644 index 00000000..25e770c4 --- /dev/null +++ b/posthog_flutter/lib/src/utils/channel_serialization.dart @@ -0,0 +1,171 @@ +import '../feature_flag_result.dart'; +import '../posthog_config.dart'; +import '../util/logging.dart'; + +extension PostHogConfigSerialization on PostHogConfig { + Map toMap() { + return { + 'projectToken': projectToken, + 'host': host, + 'flushAt': flushAt, + 'maxQueueSize': maxQueueSize, + 'maxBatchSize': maxBatchSize, + 'flushInterval': flushInterval.inSeconds, + 'sendFeatureFlagEvents': sendFeatureFlagEvent, + 'preloadFeatureFlags': preloadFeatureFlags, + 'captureApplicationLifecycleEvents': captureApplicationLifecycleEvents, + 'rageClickConfig': rageClickConfig.toMap(), + 'debug': debug, + 'optOut': optOut, + 'surveys': surveys, + 'personProfiles': personProfiles.name, + 'compression': compression.name, + 'sessionReplay': sessionReplay, + 'dataMode': dataMode.name, + 'sessionReplayConfig': sessionReplayConfig.toMap(), + 'errorTrackingConfig': errorTrackingConfig.toMap(), + 'logs': logsConfig.toMap(), + 'capturePushNotificationSubscriptions': + capturePushNotificationSubscriptions, + 'capturePushNotificationOpened': capturePushNotificationOpened, + // A closure can't cross the channel. This tells native whether to install + // a bridging provider at all — installing one the host didn't ask for + // would change how the native SDK handles a 401 on the subscription call. + 'pushIdentityProviderEnabled': pushIdentityProvider != null, + if (bootstrap != null) 'bootstrap': bootstrap!.toMap(), + }; + } +} + +extension PostHogBootstrapConfigSerialization on PostHogBootstrapConfig { + /// Only the dimensions that were set are included; [isIdentifiedId] is always + /// sent so the native SDK doesn't have to infer it. + Map toMap() { + final flags = featureFlags; + if (flags != null) { + for (final entry in flags.entries) { + // Only bool/String are served (see [featureFlags]); the native SDKs drop + // anything else silently, so warn instead of leaving no trace. + if (entry.value is! bool && entry.value is! String) { + printIfDebug( + '[PostHog] bootstrap featureFlags["${entry.key}"] is ' + '${entry.value.runtimeType}; only bool and String values are served, ' + 'so this entry will be ignored.', + ); + } + } + } + return { + if (distinctId != null) 'distinctId': distinctId, + 'isIdentifiedId': isIdentifiedId, + if (featureFlags != null) 'featureFlags': featureFlags, + if (featureFlagPayloads != null) + 'featureFlagPayloads': featureFlagPayloads, + }; + } +} + +extension PostHogLogsConfigSerialization on PostHogLogsConfig { + /// Only fields the user set are included, so unset fields keep the native + /// default. [beforeSend] is intentionally omitted: it runs in Dart and never + /// crosses the platform channel. + Map toMap() { + return { + if (serviceName != null) 'serviceName': serviceName, + if (serviceVersion != null) 'serviceVersion': serviceVersion, + if (environment != null) 'environment': environment, + if (resourceAttributes.isNotEmpty) + 'resourceAttributes': resourceAttributes, + if (flushInterval != null) + 'flushIntervalSeconds': _wholeSeconds(flushInterval!), + if (flushAt != null) 'flushAt': flushAt, + if (maxBatchSize != null) 'maxBatchSize': maxBatchSize, + if (maxBufferSize != null) 'maxBufferSize': maxBufferSize, + if (rateCapMaxLogs != null) 'rateCapMaxLogs': rateCapMaxLogs, + if (rateCapWindow != null) + 'rateCapWindowSeconds': _wholeSeconds(rateCapWindow!), + }; + } + + /// The native flush interval and rate-cap window are whole seconds. A + /// sub-second [Duration] truncates to `0`, which the native SDK treats as + /// "disabled" (rate cap) or continuous flushing — surprising for a caller who + /// set, say, 500ms. Floor at 1s, the smallest value the native API can honor. + static int _wholeSeconds(Duration duration) => + duration.inSeconds < 1 ? 1 : duration.inSeconds; +} + +extension PostHogRageClickConfigSerialization on PostHogRageClickConfig { + Map toMap() { + return { + 'enabled': enabled, + 'thresholdPoints': thresholdPoints, + 'timeoutInterval': + timeoutInterval.inMicroseconds / Duration.microsecondsPerSecond, + 'minimumTapCount': minimumTapCount, + }; + } +} + +extension PostHogSessionReplayConfigSerialization + on PostHogSessionReplayConfig { + Map toMap() { + return { + 'captureTouches': captureTouches, + 'maskAllImages': maskAllImages, + 'maskAllTexts': maskAllTexts, + 'throttleDelayMs': throttleDelay.inMilliseconds, + 'maskAllPlatformViews': maskAllPlatformViews, + 'captureNativeScreens': captureNativeScreens, + 'verifyScreenshotMaskAlignment': verifyScreenshotMaskAlignment, + 'screenshotScale': screenshotScale, + 'screenshotCompressionQuality': screenshotCompressionQuality, + 'screenshotColorMode': screenshotColorMode.name, + if (sampleRate != null) 'sampleRate': sampleRate, + }; + } +} + +extension PostHogErrorTrackingConfigSerialization + on PostHogErrorTrackingConfig { + Map toMap() { + return { + 'inAppIncludes': inAppIncludes, + 'inAppExcludes': inAppExcludes, + 'inAppByDefault': inAppByDefault, + 'captureFlutterErrors': captureFlutterErrors, + 'captureSilentFlutterErrors': captureSilentFlutterErrors, + 'capturePlatformDispatcherErrors': capturePlatformDispatcherErrors, + 'captureNativeExceptions': captureNativeExceptions, + 'captureNativeCrashes': captureNativeCrashes, + 'captureIsolateErrors': captureIsolateErrors, + 'exceptionSteps': exceptionSteps.toMap(), + }; + } +} + +extension PostHogExceptionStepsConfigSerialization + on PostHogExceptionStepsConfig { + Map toMap() { + return { + 'enabled': enabled, + 'maxBytes': maxBytes, + }; + } +} + +/// Returns `null` if [result] is not a [Map]. Falls back to [fallbackKey] when +/// the map has no `key`. +PostHogFeatureFlagResult? featureFlagResultFromMap( + Object? result, + String fallbackKey, +) { + if (result is! Map) return null; + + return PostHogFeatureFlagResult( + key: result['key'] as String? ?? fallbackKey, + enabled: result['enabled'] as bool? ?? false, + variant: result['variant'] as String?, + payload: result['payload'], + ); +} diff --git a/posthog_flutter/test/posthog_flutter_io_test.dart b/posthog_flutter/test/posthog_flutter_io_test.dart index b2469903..fa0b838d 100644 --- a/posthog_flutter/test/posthog_flutter_io_test.dart +++ b/posthog_flutter/test/posthog_flutter_io_test.dart @@ -67,7 +67,7 @@ void main() { }; }); final result = await posthogFlutterIO.getFeatureFlagResult( - key: 'flag', sendEvent: false); + key: 'flag', sendFeatureFlagEvent: false); expect(result, isNotNull); expect(result!.key, 'flag'); expect(result.enabled, enabled); @@ -112,14 +112,14 @@ void main() { }, ); - test('setup sends projectToken and deprecated apiKey alias', () async { + test('setup sends projectToken', () async { testConfig = PostHogConfig(' \n test_project_token\t '); await posthogFlutterIO.setup(testConfig); final call = log.firstWhere((c) => c.method == 'setup'); final args = Map.from(call.arguments as Map); expect(args['projectToken'], equals('test_project_token')); - expect(args['apiKey'], equals('test_project_token')); + expect(args.containsKey('apiKey'), isFalse); }); test( diff --git a/posthog_flutter/test/posthog_flutter_platform_interface_fake.dart b/posthog_flutter/test/posthog_flutter_platform_interface_fake.dart index 8b0bb563..08b9c662 100644 --- a/posthog_flutter/test/posthog_flutter_platform_interface_fake.dart +++ b/posthog_flutter/test/posthog_flutter_platform_interface_fake.dart @@ -95,7 +95,6 @@ class PosthogFlutterPlatformFake extends PosthogFlutterPlatformInterface { // Feature flag test data final Map featureFlagValues = {}; - final Map featureFlagPayloads = {}; // Call tracking for getFeatureFlagResult final List> getFeatureFlagResultCalls = []; @@ -229,10 +228,10 @@ class PosthogFlutterPlatformFake extends PosthogFlutterPlatformInterface { } @override - Future disable() async {} + Future optOut() async {} @override - Future enable() async {} + Future optIn() async {} @override Future close() async { @@ -255,19 +254,15 @@ class PosthogFlutterPlatformFake extends PosthogFlutterPlatformInterface { return featureFlagValues[key]; } - @override - Future getFeatureFlagPayload({required String key}) async { - return featureFlagPayloads[key]; - } - PostHogFeatureFlagResult? featureFlagResult; @override Future getFeatureFlagResult({ required String key, - bool sendEvent = true, + bool sendFeatureFlagEvent = true, }) async { - getFeatureFlagResultCalls.add({'key': key, 'sendEvent': sendEvent}); + getFeatureFlagResultCalls + .add({'key': key, 'sendFeatureFlagEvent': sendFeatureFlagEvent}); return featureFlagResult; } diff --git a/posthog_flutter/test/posthog_logs_test.dart b/posthog_flutter/test/posthog_logs_test.dart index 8d0d46f3..624822a8 100644 --- a/posthog_flutter/test/posthog_logs_test.dart +++ b/posthog_flutter/test/posthog_logs_test.dart @@ -1,6 +1,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:posthog_flutter/posthog_flutter.dart'; import 'package:posthog_flutter/src/posthog_flutter_platform_interface.dart'; +import 'package:posthog_flutter/src/utils/channel_serialization.dart'; import 'posthog_flutter_platform_interface_fake.dart'; diff --git a/posthog_flutter/test/posthog_test.dart b/posthog_flutter/test/posthog_test.dart index f526f3bd..2c229270 100644 --- a/posthog_flutter/test/posthog_test.dart +++ b/posthog_flutter/test/posthog_test.dart @@ -7,6 +7,7 @@ import 'package:posthog_flutter/src/posthog_flutter_platform_interface.dart'; import 'package:posthog_flutter/src/posthog_internal_events.dart'; import 'package:posthog_flutter/src/replay/mask/posthog_mask_controller.dart'; import 'package:posthog_flutter/src/replay/screenshot/screenshot_capturer.dart'; +import 'package:posthog_flutter/src/utils/channel_serialization.dart'; import 'posthog_flutter_platform_interface_fake.dart'; @@ -161,25 +162,13 @@ void main() { }); group('PostHogConfig', () { - test('sendFeatureFlagEvents forwards to sendFeatureFlagEvent', () { - final config = PostHogConfig('test_project_token'); - - // ignore: deprecated_member_use_from_same_package - config.sendFeatureFlagEvents = false; - - expect(config.sendFeatureFlagEvent, isFalse); - expect(config.toMap()['sendFeatureFlagEvents'], isFalse); - }); - test('trims whitespace-sensitive config values in config and toMap', () { final config = PostHogConfig(' \n test_project_token\t '); config.host = ' \nhttps://eu.i.posthog.com/\t '; expect(config.projectToken, equals('test_project_token')); - expect(config.apiKey, equals('test_project_token')); expect(config.host, equals('https://eu.i.posthog.com/')); expect(config.toMap()['projectToken'], equals('test_project_token')); - expect(config.toMap()['apiKey'], equals('test_project_token')); expect(config.toMap()['host'], equals('https://eu.i.posthog.com/')); }); @@ -387,7 +376,7 @@ void main() { final result = await Posthog().getFeatureFlagResult('non-existent'); expect(result, isNull); expect(fakePlatformInterface.getFeatureFlagResultCalls, [ - {'key': 'non-existent', 'sendEvent': true} + {'key': 'non-existent', 'sendFeatureFlagEvent': true} ]); }); @@ -402,24 +391,26 @@ void main() { expect( await Posthog().getFeatureFlagResult('variant-flag'), same(expected)); expect(fakePlatformInterface.getFeatureFlagResultCalls, [ - {'key': 'variant-flag', 'sendEvent': true} + {'key': 'variant-flag', 'sendFeatureFlagEvent': true} ]); }); - test('passes sendEvent=true by default', () async { + test('passes sendFeatureFlagEvent=true by default', () async { await Posthog().getFeatureFlagResult('test'); expect( - fakePlatformInterface.getFeatureFlagResultCalls.last['sendEvent'], + fakePlatformInterface + .getFeatureFlagResultCalls.last['sendFeatureFlagEvent'], isTrue, ); }); - test('passes sendEvent=false when specified', () async { - await Posthog().getFeatureFlagResult('test', sendEvent: false); + test('passes sendFeatureFlagEvent=false when specified', () async { + await Posthog().getFeatureFlagResult('test', sendFeatureFlagEvent: false); expect( - fakePlatformInterface.getFeatureFlagResultCalls.last['sendEvent'], + fakePlatformInterface + .getFeatureFlagResultCalls.last['sendFeatureFlagEvent'], isFalse, ); }); @@ -523,12 +514,12 @@ void main() { }); test('fromMap returns null for null input', () { - final result = PostHogFeatureFlagResult.fromMap(null, 'fallback'); + final result = featureFlagResultFromMap(null, 'fallback'); expect(result, isNull); }); test('fromMap returns null for non-Map input', () { - final result = PostHogFeatureFlagResult.fromMap('not a map', 'fallback'); + final result = featureFlagResultFromMap('not a map', 'fallback'); expect(result, isNull); }); @@ -540,7 +531,7 @@ void main() { 'payload': {'data': 123}, }; - final result = PostHogFeatureFlagResult.fromMap(map, 'fallback'); + final result = featureFlagResultFromMap(map, 'fallback'); expect(result, isNotNull); expect(result!.key, equals('my-flag')); @@ -552,7 +543,7 @@ void main() { test('fromMap uses fallback key when map key is null', () { final map = {'enabled': true, 'variant': null, 'payload': null}; - final result = PostHogFeatureFlagResult.fromMap(map, 'fallback-key'); + final result = featureFlagResultFromMap(map, 'fallback-key'); expect(result, isNotNull); expect(result!.key, equals('fallback-key')); @@ -561,7 +552,7 @@ void main() { test('fromMap defaults enabled to false when not in map', () { final map = {'key': 'my-flag'}; - final result = PostHogFeatureFlagResult.fromMap(map, 'fallback'); + final result = featureFlagResultFromMap(map, 'fallback'); expect(result, isNotNull); expect(result!.enabled, isFalse); diff --git a/posthog_flutter/test/replay_capture_touches_test.dart b/posthog_flutter/test/replay_capture_touches_test.dart index 485c5cb2..1a4f9d1d 100644 --- a/posthog_flutter/test/replay_capture_touches_test.dart +++ b/posthog_flutter/test/replay_capture_touches_test.dart @@ -4,6 +4,7 @@ import 'package:posthog_flutter/posthog_flutter.dart'; import 'package:posthog_flutter/src/posthog_flutter_io.dart'; import 'package:posthog_flutter/src/posthog_flutter_platform_interface.dart'; import 'package:posthog_flutter/src/posthog_internal_events.dart'; +import 'package:posthog_flutter/src/utils/channel_serialization.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); diff --git a/posthog_flutter/test/replay_screenshot_config_test.dart b/posthog_flutter/test/replay_screenshot_config_test.dart index edc745a2..d562fd4f 100644 --- a/posthog_flutter/test/replay_screenshot_config_test.dart +++ b/posthog_flutter/test/replay_screenshot_config_test.dart @@ -1,5 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:posthog_flutter/posthog_flutter.dart'; +import 'package:posthog_flutter/src/utils/channel_serialization.dart'; void main() { test('screenshot controls preserve native defaults', () { diff --git a/posthog_flutter/test/snapshots/setup_request.json b/posthog_flutter/test/snapshots/setup_request.json index 58543998..367138b4 100644 --- a/posthog_flutter/test/snapshots/setup_request.json +++ b/posthog_flutter/test/snapshots/setup_request.json @@ -3,7 +3,6 @@ "method": "setup", "arguments": { "projectToken": "snapshot_project_token", - "apiKey": "snapshot_project_token", "host": "https://eu.i.posthog.com", "flushAt": 12, "maxQueueSize": 800,