From d57204abd5beb40d1a48b82374da999cdb0edd16 Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Thu, 29 Oct 2026 17:20:31 -0400 Subject: [PATCH 1/4] fix(replay): let native opt-out and opt-in own the replay stop and restart --- .../darwin/posthog_flutter.podspec | 2 +- .../darwin/posthog_flutter/Package.swift | 2 +- posthog_flutter/lib/src/posthog.dart | 20 +++++----- .../test/opt_out_session_replay_test.dart | 38 ++++++++++--------- 4 files changed, 32 insertions(+), 30 deletions(-) diff --git a/posthog_flutter/darwin/posthog_flutter.podspec b/posthog_flutter/darwin/posthog_flutter.podspec index c39b30b2..4f38e0ab 100644 --- a/posthog_flutter/darwin/posthog_flutter.podspec +++ b/posthog_flutter/darwin/posthog_flutter.podspec @@ -22,7 +22,7 @@ Postog flutter plugin 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.dependency 'PostHog', '>= 3.92.3', '< 4.0.0' s.ios.deployment_target = '13.0' # PH iOS SDK 3.0.0 requires >= 10.15 diff --git a/posthog_flutter/darwin/posthog_flutter/Package.swift b/posthog_flutter/darwin/posthog_flutter/Package.swift index dd9a201d..c34f1946 100644 --- a/posthog_flutter/darwin/posthog_flutter/Package.swift +++ b/posthog_flutter/darwin/posthog_flutter/Package.swift @@ -14,7 +14,7 @@ let package = Package( ], dependencies: [ .package(name: "FlutterFramework", path: "../FlutterFramework"), - .package(url: "https://github.com/PostHog/posthog-ios", "3.84.0" ..< "4.0.0"), + .package(url: "https://github.com/PostHog/posthog-ios", "3.92.3" ..< "4.0.0"), ], targets: [ .target( diff --git a/posthog_flutter/lib/src/posthog.dart b/posthog_flutter/lib/src/posthog.dart index b0d95263..0cdf26d8 100644 --- a/posthog_flutter/lib/src/posthog.dart +++ b/posthog_flutter/lib/src/posthog.dart @@ -28,8 +28,8 @@ class Posthog { PostHogConfig? _config; /// Set when [optOut] stops an in-progress Flutter replay. [optIn] consumes - /// it so opt-in starts a new recording, and a replay the caller already - /// stopped is left stopped. + /// it to re-arm Flutter capture for the recording native opt-in restarts, + /// and a replay the caller already stopped is left stopped. bool _restartSessionReplayOnEnable = false; /// Returns the singleton PostHog client instance. @@ -399,19 +399,17 @@ class Posthog { Future optOut() async { // Uninstall Flutter-specific integrations when opting out _uninstallFlutterIntegrations(); - Future? stopReplay; // Skipped on web: posthog-js drops replay while opted out, and restarting // it on optIn would override a replay disabled in posthog.init. if (!kIsWeb && PostHogInternalEvents.sessionRecordingActive.value) { _restartSessionReplayOnEnable = true; PostHogInternalEvents.sessionRecordingActive.value = false; - // Native stopSessionRecording is a no-op once the SDK is already opted - // out, so it is sent first. Not awaited before disable, so a capture() - // right after an un-awaited optOut() still reaches native after it. - stopReplay = _posthog.stopSessionRecording(); } - await Future.wait([if (stopReplay != null) stopReplay, _posthog.disable()]); + // Native opt-out stops replay itself. A native stopSessionRecording() here + // would record that the app stopped replay, so native opt-in would not + // restart it. + await _posthog.disable(); } /// Opts the current user back in to data collection. @@ -426,13 +424,13 @@ class Posthog { _installFlutterIntegrations(config); } + // Native opt-in restarts the replay the app wanted, in a new session. await _posthog.enable(); final restartReplay = _restartSessionReplayOnEnable; _restartSessionReplayOnEnable = false; if (restartReplay) { - // On iOS, startSessionRecording is ignored while the SDK is still opted - // out, so the platform opt-in has to finish first. - await startSessionRecording(resumeCurrent: false); + PostHogInternalEvents.requestReplaySessionReset(); + PostHogInternalEvents.sessionRecordingActive.value = true; } } diff --git a/posthog_flutter/test/opt_out_session_replay_test.dart b/posthog_flutter/test/opt_out_session_replay_test.dart index 1138f13e..f467bf87 100644 --- a/posthog_flutter/test/opt_out_session_replay_test.dart +++ b/posthog_flutter/test/opt_out_session_replay_test.dart @@ -73,41 +73,45 @@ void main() { expect(methodsOf(recordedCalls), isNot(contains('sendFullSnapshot'))); }); - test('optOut stops replay before opting out, optIn starts a new recording', - () async { + test('optOut and optIn leave the replay restart to native', () async { PosthogFlutterPlatformInterface.instance = PosthogFlutterIO(); await Posthog().setup(replayConfig()); recordedCalls.clear(); await Posthog().optOut(); - final disabled = methodsOf(recordedCalls); - expect(disabled, contains('stopSessionRecording')); - expect(disabled, contains('disable')); + expect(methodsOf(recordedCalls), contains('disable')); expect( - disabled.indexOf('stopSessionRecording'), - lessThan(disabled.indexOf('disable')), - reason: 'native stop is a no-op once the SDK is already opted out', + methodsOf(recordedCalls), + isNot(contains('stopSessionRecording')), + reason: 'a native stop while opted in keeps replay off after opt-in', ); expect(PostHogInternalEvents.sessionRecordingActive.value, isFalse); recordedCalls.clear(); await Posthog().optIn(); - final enabled = methodsOf(recordedCalls); - expect(enabled, contains('enable')); - expect(enabled, contains('startSessionRecording')); + expect(methodsOf(recordedCalls), contains('enable')); expect( - enabled.indexOf('enable'), - lessThan(enabled.indexOf('startSessionRecording')), - reason: 'startSessionRecording returns immediately while opted out', + methodsOf(recordedCalls), + isNot(contains('startSessionRecording')), + reason: 'native opt-in already starts a new session', ); - final start = recordedCalls - .firstWhere((call) => call.method == 'startSessionRecording'); - expect(start.arguments, isFalse); expect(PostHogInternalEvents.sessionRecordingActive.value, isTrue); }); + test('optIn leaves Flutter replay off when the app stopped it first', + () async { + PosthogFlutterPlatformInterface.instance = PosthogFlutterIO(); + await Posthog().setup(replayConfig()); + await Posthog().stopSessionRecording(); + + await Posthog().optOut(); + await Posthog().optIn(); + + expect(PostHogInternalEvents.sessionRecordingActive.value, isFalse); + }); + test('capture after an un-awaited optOut reaches native after the opt-out', () async { PosthogFlutterPlatformInterface.instance = PosthogFlutterIO(); From 262dd7c9f07fbedad68d1d57b5091626ec43b4f5 Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Thu, 29 Oct 2026 17:32:54 -0400 Subject: [PATCH 2/4] test: cover the replay reset on opt-in; pin examples to posthog-ios 3.92.7; add changeset --- .changeset/opt-in-native-replay-restart.md | 5 +++++ .../xcshareddata/swiftpm/Package.resolved | 4 ++-- .../Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- .../Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- posthog_flutter/test/opt_out_session_replay_test.dart | 5 +++++ 6 files changed, 18 insertions(+), 8 deletions(-) create mode 100644 .changeset/opt-in-native-replay-restart.md diff --git a/.changeset/opt-in-native-replay-restart.md b/.changeset/opt-in-native-replay-restart.md new file mode 100644 index 00000000..e9b0de7f --- /dev/null +++ b/.changeset/opt-in-native-replay-restart.md @@ -0,0 +1,5 @@ +--- +'posthog_flutter': patch +--- + +Restart session replay on `optIn()` through the native SDK, so replay the app wanted at opt-out comes back in a new session; requires PostHog iOS SDK 3.92.3 or later. diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 5389a599..fb608a5d 100644 --- a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/PostHog/posthog-ios", "state" : { - "revision" : "e751c190562878700599243971a04fae17798480", - "version" : "3.84.0" + "revision" : "d9b043a551dd1906b5af0fa8232f2845cbba5761", + "version" : "3.92.7" } } ], diff --git a/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved index 5389a599..fb608a5d 100644 --- a/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/PostHog/posthog-ios", "state" : { - "revision" : "e751c190562878700599243971a04fae17798480", - "version" : "3.84.0" + "revision" : "d9b043a551dd1906b5af0fa8232f2845cbba5761", + "version" : "3.92.7" } } ], diff --git a/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 5389a599..fb608a5d 100644 --- a/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/PostHog/posthog-ios", "state" : { - "revision" : "e751c190562878700599243971a04fae17798480", - "version" : "3.84.0" + "revision" : "d9b043a551dd1906b5af0fa8232f2845cbba5761", + "version" : "3.92.7" } } ], diff --git a/example/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/example/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved index 5389a599..fb608a5d 100644 --- a/example/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/example/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/PostHog/posthog-ios", "state" : { - "revision" : "e751c190562878700599243971a04fae17798480", - "version" : "3.84.0" + "revision" : "d9b043a551dd1906b5af0fa8232f2845cbba5761", + "version" : "3.92.7" } } ], diff --git a/posthog_flutter/test/opt_out_session_replay_test.dart b/posthog_flutter/test/opt_out_session_replay_test.dart index f467bf87..ca2c2d8a 100644 --- a/posthog_flutter/test/opt_out_session_replay_test.dart +++ b/posthog_flutter/test/opt_out_session_replay_test.dart @@ -89,8 +89,13 @@ void main() { expect(PostHogInternalEvents.sessionRecordingActive.value, isFalse); recordedCalls.clear(); + final resetsBefore = PostHogInternalEvents.forceReplaySessionReset.value; await Posthog().optIn(); + expect( + PostHogInternalEvents.forceReplaySessionReset.value, + greaterThan(resetsBefore), + ); expect(methodsOf(recordedCalls), contains('enable')); expect( methodsOf(recordedCalls), From 7209cf21cbe5535f1b0855d8268ae84a5f8591a7 Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Thu, 29 Oct 2026 17:53:11 -0400 Subject: [PATCH 3/4] chore: pin examples to the 3.92.7 commit, tighten changeset --- .changeset/opt-in-native-replay-restart.md | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- .../Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- .../Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- posthog_flutter/lib/src/posthog.dart | 1 - 6 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.changeset/opt-in-native-replay-restart.md b/.changeset/opt-in-native-replay-restart.md index e9b0de7f..4b81274d 100644 --- a/.changeset/opt-in-native-replay-restart.md +++ b/.changeset/opt-in-native-replay-restart.md @@ -2,4 +2,4 @@ 'posthog_flutter': patch --- -Restart session replay on `optIn()` through the native SDK, so replay the app wanted at opt-out comes back in a new session; requires PostHog iOS SDK 3.92.3 or later. +Fix session replay after `optIn()` continuing the session that was active while opted out, and raise the minimum PostHog iOS SDK to 3.92.3. diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index fb608a5d..a287dc5f 100644 --- a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,7 +5,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/PostHog/posthog-ios", "state" : { - "revision" : "d9b043a551dd1906b5af0fa8232f2845cbba5761", + "revision" : "2b6b6b4301c14c3b51cb1a7827c954bb17d1b083", "version" : "3.92.7" } } diff --git a/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved index fb608a5d..a287dc5f 100644 --- a/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/example/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,7 +5,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/PostHog/posthog-ios", "state" : { - "revision" : "d9b043a551dd1906b5af0fa8232f2845cbba5761", + "revision" : "2b6b6b4301c14c3b51cb1a7827c954bb17d1b083", "version" : "3.92.7" } } diff --git a/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index fb608a5d..a287dc5f 100644 --- a/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,7 +5,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/PostHog/posthog-ios", "state" : { - "revision" : "d9b043a551dd1906b5af0fa8232f2845cbba5761", + "revision" : "2b6b6b4301c14c3b51cb1a7827c954bb17d1b083", "version" : "3.92.7" } } diff --git a/example/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/example/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved index fb608a5d..a287dc5f 100644 --- a/example/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/example/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,7 +5,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/PostHog/posthog-ios", "state" : { - "revision" : "d9b043a551dd1906b5af0fa8232f2845cbba5761", + "revision" : "2b6b6b4301c14c3b51cb1a7827c954bb17d1b083", "version" : "3.92.7" } } diff --git a/posthog_flutter/lib/src/posthog.dart b/posthog_flutter/lib/src/posthog.dart index 0cdf26d8..e16f36d1 100644 --- a/posthog_flutter/lib/src/posthog.dart +++ b/posthog_flutter/lib/src/posthog.dart @@ -424,7 +424,6 @@ class Posthog { _installFlutterIntegrations(config); } - // Native opt-in restarts the replay the app wanted, in a new session. await _posthog.enable(); final restartReplay = _restartSessionReplayOnEnable; _restartSessionReplayOnEnable = false; From c816d7a18104d328d68b931c958b70c8c4cea76d Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Fri, 30 Oct 2026 10:24:24 -0400 Subject: [PATCH 4/4] chore: drop the stale podspec version comment Co-Authored-By: Claude Opus 5.5 --- posthog_flutter/darwin/posthog_flutter.podspec | 1 - 1 file changed, 1 deletion(-) diff --git a/posthog_flutter/darwin/posthog_flutter.podspec b/posthog_flutter/darwin/posthog_flutter.podspec index 4f38e0ab..f99fd00d 100644 --- a/posthog_flutter/darwin/posthog_flutter.podspec +++ b/posthog_flutter/darwin/posthog_flutter.podspec @@ -21,7 +21,6 @@ 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.92.3', '< 4.0.0' s.ios.deployment_target = '13.0'