Repository navigation
feat: record the landing campaign when tracking is enabled after navigation - #237
Merged
Merged
Conversation
…e use, and tracking after consent
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When an app starts with tracking disabled,
enableTracking()captured only the page the visitor was on when it ran. A visitor who landed on/?utm_source=google, followed a client-side link, and then accepted the consent banner lost the campaign that brought them.The plugin now holds the landing route in memory when tracking is disabled as the app mounts.
enableTracking()records that route first, with the time the app mounted, and then the current page if its URL differs. The held route never reaches storage and lives only as long as the page, so a reload or a new visit starts without it. It is dropped once recorded and whenclearData()runs. Hooks run when the held route is processed, so autm:before-trackconsent check still decides whether it is saved. Matomo's consent mode and Segment's consent wrapper take a similar approach: they keep a page's calls in memory and send them once consent is given, and neither carries them across a reload.disableTracking()on its own keeps the held route, so a visitor who declines and then accepts during the same visit still has the landing page recorded, and an app that switches tracking off at startup still recovers it; the README's refusal path also callsclearData(), which drops it. Apps that keep tracking enabled, the default, don't hold anything and behave as before.A new
captureLandingOnEnableoption controls this and defaults totrue. Withfalse, nothing is held andenableTracking()records only the current page. Only the landing route is held, not the pages in between. When tracking is already enabled at startup, for example through a saved preference that outlived expired consent, the landing page is recorded right away, so nothing stays held unlessdisableTracking()cancels that capture.The held route is a copy of the landing route, because Nuxt's router for apps without
pages/updates one route object in place on navigation; holding the live object would make the landing look like the current page. The hold is released only once its capture completes. A capture thatdisableTracking()cancels, whether in the same tick or while autm:before-trackhook is still pending, hands the route back, so the nextenableTracking()still records it; a hook that skips it, or a deduplicated or retained-out entry, drops it as intended.The README's Privacy and consent section now lists the three ways an app can combine the module with consent: track regardless of consent, the default; keep tracking and check consent where the data leaves the browser; or track only after consent. For the second, it shows guarding
getAttribution()with the consent tool's check and recording consent on each entry from autm:before-persisthook. The section also describes the held landing route and its limits, replacing the note that campaigns from pages visited before consent are lost. It says to read the page fromcontext.routein hooks, since the landing page's hooks run when tracking is enabled, and to callclearData()only on an actual refusal, since some consent tools report a refusal before the visitor chooses. The Configuration section no longer says that all new options are opt-in, sincecaptureLandingOnEnableis on by default; it affects only apps that start with tracking disabled.Release note: when tracking starts disabled,
enableTracking()now records the landing page of the current page load, timestamped when the visitor arrived, before the current page. SetcaptureLandingOnEnable: falseto record only the current page.Validation: three new unit tests in
test/runtime-plugin.test.tsfailed onmain, either because the landing entry was missing or because it carried the time ofenableTracking()instead of the arrival time; they pass now, as do a test showing that clearing data drops the held route and one showing that always-on tracking is unchanged. A unit test that turnscaptureLandingOnEnableoff failed before the option existed, because the landing entry was still recorded, and passes now. Four more unit tests failed before the hold was copied and kept across cancellation, each missing the landing entry: one models the router of apps withoutpages/, which updates the route in place, and three cancel the landing capture withdisableTracking(), in the same tick, during a pending hook, and during the startup capture of an app that starts enabled. A browser test intest/integration-route-change.test.ts, whose fixture has nopages/, lands with tracking disabled, navigates withnavigateTo, enables tracking, and finds both entries stored. A new browser test intest/integration-rendering-modes.test.tslands on a campaign URL with tracking disabled, follows a client-side link, and enables tracking. Onmainit stored only the page after the link for the server-rendered, prerendered async, prerendered synchronous, and client-rendered pages; it now stores both entries for all four.test:compatibilitygained the same late-consent scenario for the packed module: withmain's runtime it failed on Nuxt 3.21.11 for the server-rendered, client-rendered, and static pages, and with this change it passes on Nuxt 3.21.11, 4.6.1, and the 5.0.0 nightly.pnpm test(163 passed, 1 skipped),pnpm lint --max-warnings 0,pnpm format:check,pnpm test:types, andpnpm test:package, whose consumer now sets the new option, pass.