fix(replay): honor changed masking flags on iOS/Android across setup() calls - #507
Conversation
9457bab to
5e6f650
Compare
|
posthog-flutter Compliance ReportDate: 2026-07-30 19:00:31 UTC ✅ All Tests Passed!45/45 tests passed Capture Tests✅ 29/29 tests passed View Details
Feature_Flags Tests✅ 16/16 tests passed View Details
|
| await Posthog().setup(first); | ||
| final capturer = ScreenshotCapturer(first); | ||
| expect(capturer.effectiveConfig, same(first)); | ||
|
|
||
| final second = PostHogConfig('test_project_token'); | ||
| await Posthog().setup(second); |
There was a problem hiding this comment.
fwiw, i wouldn't be surprised if we had more gaps with duplicate setup calls, this looks like a really fringe scenario, and probably enters undefined behavior territory (unless i'm missing something)
i'm surprised you can initialize twice - imo the second should be a no-op
…lose()+setup() reconfigure path

💡 Motivation and Context
Follow-up to the review note on #499 (thread):
refreshParsers()was only wired into the web setup path, so on iOS/Android a secondsetup()with changedmaskAllTexts/maskAllImageskept masking replay screenshots from the stale parser map built at first singleton access — e.g.maskAllImages: false → truewould leave images unmasked (a narrow PII-leak direction). Pre-existing onmain, not a regression from #499.Two changes close it:
setup()refreshes the parser map —PostHogMaskController.instance.refreshParsers(config.sessionReplayConfig)now runs on every platform. The web-side call inWebCanvasMaskProvider.register()stays: it is idempotent, and keeps the provider correct on its own if its entry point ever changes.ScreenshotCapturerresolves the live config at capture time (Posthog().config ?? _config). A device test of change 1 alone showed masking still stuck: the capturer is built once per widget lifecycle and gated the mask walk on themaskAllTexts/maskAllImagesflags of the config object captured at construction, so a secondsetup()'s new config never reached it — the parser map refreshed, but the walk was skipped before consulting it.#499 merged while this was being opened, so this targets
maindirectly (rebased to a single commit on top of the #499 squash merge).💚 How did you test it?
setup()with different masking flags rebuilds the parser map, and the capturer resolves the config of the latestsetup()(both fail without their fix — verified by reverting each and re-running).flutter analyzeanddart formatclean.maskAll*: false, triggered an in-app secondsetup()withmaskAll*: true, and confirmed in the PostHog replay that frames flip from readable to fully masked at the re-setup point. The same run with only change 1 left phase 2 unmasked, which is how change 2 was found.📝 Checklist
If releasing new changes
pnpm changesetto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
consider/bug), which validated the gap and its trigger conditions; the fix follows the bot's suggested wire-up point.register()call rather than remove it: removal would touch reviewed feat(replay): mask canvas session replay recordings on Flutter web #499 code for no behavior change, and the duplicate refresh is idempotent.config.sessionReplay) so toggling replay flags acrosssetup()calls always lands; the singleton instantiation this forces is a few small allocations.