fix(attribution): match deferred installs against their click - #44
Merged
Conversation
Deferred attribution could never succeed on iOS or Android. Two of the five scored signals are captured differently by the SDKs than by the web click they are compared against, so neither could ever match: - The SDK sends "MyApp/1.0.0 iOS/17.5", which carries no browser token and no platform token that normalizeUserAgent recognises, so it reduced to "|" against a browser's "iphone|safari". The 30-point user agent factor was therefore unreachable for every install. - The SDK sends native pixels (bounds * scale) while the web sends CSS pixels, so the 10-point screen factor never matched either. That capped every mobile install at 40 (ip) + 10 (timezone) + 10 (language) = 60, below the 70 threshold, and every install was recorded as organic no matter how well it actually matched. When one side of a comparison comes from an SDK, compare platform and OS major version instead of the raw user agent, splitting the same 30 points 20/10 so the total still sums to 100. A known platform mismatch now disqualifies the candidate outright, since a device cannot have clicked from a different operating system and the remaining signals are all shared-network coincidences. Screen sizes are compared by proportion across that boundary, as the two differ by the device pixel ratio. Browser-to-browser matching is unchanged: same weights, same exact screen comparison, existing tests untouched. A same-device, same-network install now scores 100 where it scored 60.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Deferred attribution was inconsistent on iOS or Android.
Cause
calculateConfidenceScorecompares five signals worth 100 points and attributes at ≥70. Two of them are captured differently by the SDKs than by the web click they get compared against, so neither would match:…(iPhone; CPU iPhone OS 17_5…) Safari/604.1→iphone|safariMyApp/1.0.0 iOS/17.5→|393×852(CSS px)1179×2556(native px)normalizeUserAgentextracts a platform token (iPhone|iPad|Android|…) and a browser token. The SDK string built byFingerprintCollector.generateUserAgent()contains neither —iOSis not in the platform pattern — so it always reduced to"|".That capped every mobile install at 40 (ip) + 10 (timezone) + 10 (language) = 60, permanently below the threshold. Android hit the same ceiling via
"android|"vs"android|chrome".The unit tests missed it because they compare a browser UA against another browser UA on both sides, which never happens in the real deferred flow — the install side always comes from an SDK.
Fix
When either side of a comparison comes from an SDK user agent:
Major version only, because Safari reports the OS version less precisely than
UIDevice.systemVersionand comparing point releases reintroduces false misses.Browser-to-browser matching is unchanged — same weights, same exact-equality screen comparison. All pre-existing tests pass untouched.
Results
Scores from the real function, same device and network, click then install:
fp_sw/fp_shThe last two are the guardrails: a genuinely unrelated install stays organic, and a cross-platform candidate is now rejected outright rather than squeaking over the line.
Testing
7 new cases covering the deferred flow the suite never exercised, including both guardrails. Full suite: 220 passed,
tsc --noEmitclean.Note
One adjacent case is left alone deliberately: in browser-to-browser matching, two different platforms can still reach 70 on ip + timezone + language + screen. That is pre-existing behaviour on a path this bug does not affect, so it is not changed here.