From 5e6f650f89b1badbfd401a7b138391695d70bb0f Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Thu, 30 Jul 2026 18:16:11 +0300 Subject: [PATCH 1/3] fix(replay): refresh mask parsers from shared setup() so native honors changed flags --- .changeset/refresh-parsers-native.md | 5 +++++ posthog_flutter/lib/src/posthog.dart | 6 ++++++ posthog_flutter/test/posthog_test.dart | 19 +++++++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 .changeset/refresh-parsers-native.md diff --git a/.changeset/refresh-parsers-native.md b/.changeset/refresh-parsers-native.md new file mode 100644 index 00000000..7a2850e7 --- /dev/null +++ b/.changeset/refresh-parsers-native.md @@ -0,0 +1,5 @@ +--- +"posthog_flutter": patch +--- + +Fix session replay masking on iOS and Android ignoring changed `maskAllTexts`/`maskAllImages` flags when `setup()` is called again diff --git a/posthog_flutter/lib/src/posthog.dart b/posthog_flutter/lib/src/posthog.dart index b38368e9..eb67afc9 100644 --- a/posthog_flutter/lib/src/posthog.dart +++ b/posthog_flutter/lib/src/posthog.dart @@ -12,6 +12,7 @@ import 'posthog_config.dart'; import 'posthog_flutter_platform_interface.dart'; import 'posthog_internal_events.dart'; import 'posthog_observer.dart'; +import 'replay/mask/posthog_mask_controller.dart'; import 'utils/before_send.dart'; /// Entry point for the PostHog Flutter SDK. @@ -67,6 +68,11 @@ class Posthog { _config = config; // Store the config + // The mask controller singleton may predate this setup() (or a previous + // setup() built it with different masking flags); without a refresh the + // stale parser map would keep deciding what replay masks on every platform. + PostHogMaskController.instance.refreshParsers(config.sessionReplayConfig); + if (config.sessionReplay) { PostHogInternalEvents.sessionRecordingActive.value = true; } diff --git a/posthog_flutter/test/posthog_test.dart b/posthog_flutter/test/posthog_test.dart index d4ffa76c..6d92e47e 100644 --- a/posthog_flutter/test/posthog_test.dart +++ b/posthog_flutter/test/posthog_test.dart @@ -5,6 +5,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/posthog_internal_events.dart'; +import 'package:posthog_flutter/src/replay/mask/posthog_mask_controller.dart'; import 'posthog_flutter_platform_interface_fake.dart'; @@ -40,6 +41,24 @@ void main() { }, ); + test( + 'a second setup with different masking flags rebuilds the parser map', + () async { + final controller = PostHogMaskController.instance; + addTearDown(() => controller.refreshParsers(null)); + + final imagesMasked = PostHogConfig('test_project_token'); + await Posthog().setup(imagesMasked); + expect(controller.parsers.keys, contains('RenderImage')); + + final imagesUnmasked = PostHogConfig('test_project_token') + ..sessionReplayConfig.maskAllImages = false; + await Posthog().setup(imagesUnmasked); + expect(controller.parsers.keys, isNot(contains('RenderImage'))); + expect(controller.parsers.keys, contains('RenderParagraph')); + }, + ); + group('setup with blank project token', () { const blankProjectTokens = { 'empty string': '', From f17f5198b1e41ac92b3c556c2ede24ff587b4e55 Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Thu, 30 Jul 2026 19:01:33 +0300 Subject: [PATCH 2/3] fix(replay): screenshot capturer reads the live config, not its construction-time copy --- .../replay/screenshot/screenshot_capturer.dart | 8 +++++++- posthog_flutter/test/posthog_test.dart | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/posthog_flutter/lib/src/replay/screenshot/screenshot_capturer.dart b/posthog_flutter/lib/src/replay/screenshot/screenshot_capturer.dart index 7bae3cdb..46012b23 100644 --- a/posthog_flutter/lib/src/replay/screenshot/screenshot_capturer.dart +++ b/posthog_flutter/lib/src/replay/screenshot/screenshot_capturer.dart @@ -81,6 +81,12 @@ class ScreenshotCapturer { ScreenshotCapturer(this._config); + /// A second `setup()` replaces the SDK config object, but the capturer is + /// built once per widget lifecycle — resolving the live config at capture + /// time keeps masking flags from freezing at their first-setup values. + @visibleForTesting + PostHogConfig get effectiveConfig => Posthog().config ?? _config; + void cancel() { _cancelled = true; } @@ -448,7 +454,7 @@ class ScreenshotCapturer { srcHeight: srcHeight, ); - final replayConfig = _config.sessionReplayConfig; + final replayConfig = effectiveConfig.sessionReplayConfig; final postHogWidgetWrapperElements = PostHogMaskController.instance.getPostHogWidgetWrapperElements(); diff --git a/posthog_flutter/test/posthog_test.dart b/posthog_flutter/test/posthog_test.dart index 6d92e47e..e2cbd5ea 100644 --- a/posthog_flutter/test/posthog_test.dart +++ b/posthog_flutter/test/posthog_test.dart @@ -6,6 +6,7 @@ import 'package:posthog_flutter/posthog_flutter.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/replay/mask/posthog_mask_controller.dart'; +import 'package:posthog_flutter/src/replay/screenshot/screenshot_capturer.dart'; import 'posthog_flutter_platform_interface_fake.dart'; @@ -59,6 +60,21 @@ void main() { }, ); + test( + 'screenshot capturer resolves the live config after a second setup', + () async { + final first = PostHogConfig('test_project_token') + ..sessionReplayConfig.maskAllImages = false; + await Posthog().setup(first); + final capturer = ScreenshotCapturer(first); + expect(capturer.effectiveConfig, same(first)); + + final second = PostHogConfig('test_project_token'); + await Posthog().setup(second); + expect(capturer.effectiveConfig, same(second)); + }, + ); + group('setup with blank project token', () { const blankProjectTokens = { 'empty string': '', From 998a8de7862a5c1f44972feff2f996d671b8f5d6 Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Thu, 30 Jul 2026 21:54:50 +0300 Subject: [PATCH 3/3] test(replay): exercise the masking-flag fixes through the supported close()+setup() reconfigure path --- posthog_flutter/test/posthog_test.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/posthog_flutter/test/posthog_test.dart b/posthog_flutter/test/posthog_test.dart index e2cbd5ea..e5050eb2 100644 --- a/posthog_flutter/test/posthog_test.dart +++ b/posthog_flutter/test/posthog_test.dart @@ -43,7 +43,7 @@ void main() { ); test( - 'a second setup with different masking flags rebuilds the parser map', + 'setup after close with different masking flags rebuilds the parser map', () async { final controller = PostHogMaskController.instance; addTearDown(() => controller.refreshParsers(null)); @@ -52,6 +52,7 @@ void main() { await Posthog().setup(imagesMasked); expect(controller.parsers.keys, contains('RenderImage')); + await Posthog().close(); final imagesUnmasked = PostHogConfig('test_project_token') ..sessionReplayConfig.maskAllImages = false; await Posthog().setup(imagesUnmasked); @@ -61,14 +62,18 @@ void main() { ); test( - 'screenshot capturer resolves the live config after a second setup', + 'screenshot capturer resolves the live config after close and re-setup', () async { final first = PostHogConfig('test_project_token') ..sessionReplayConfig.maskAllImages = false; await Posthog().setup(first); + // PostHogWidget builds its capturer once and keeps it across a + // close()/setup() reconfigure, so the capturer must follow the live + // config rather than the one it was constructed with. final capturer = ScreenshotCapturer(first); expect(capturer.effectiveConfig, same(first)); + await Posthog().close(); final second = PostHogConfig('test_project_token'); await Posthog().setup(second); expect(capturer.effectiveConfig, same(second));