Skip to content

fix(deeplink): keep URL parameters on a direct open - #5

Merged
onamfc merged 1 commit into
mainfrom
brandon/sit-429-merge-url-params
Sep 10, 2026
Merged

fix(deeplink): keep URL parameters on a direct open#5
onamfc merged 1 commit into
mainfrom
brandon/sit-429-merge-url-params

Conversation

@onamfc

@onamfc onamfc commented Sep 10, 2026

Copy link
Copy Markdown
Member

Summary

A link shared as ?slug=titanic reached the app after a deferred install but not on a direct open. This fixes that, so customParameters carries URL parameters on both paths.

Fourth of five. React Native is LinkForty/mobile-sdk-react-native#7, Expo is LinkForty/mobile-sdk-expo#6, iOS is LinkForty/mobile-sdk-ios#6; Flutter still to come.

What was happening

resolveUrl(uri, fallback) returned the server payload on success and used the local parse only as a failure fallback — so a successful resolve discarded the parsed URL parameters entirely.

That payload is the link's stored configuration. The server cannot know what was appended to the URL that was actually tapped; only the SDK does, having just parsed it.

The value was never lost from the callback — the Uri is still delivered — but it was absent from the field the deferred path teaches people to read, so the two open types disagreed.

The rules it follows

  • URL values win on a collision — the precedence the server already applies on the deferred path.
  • mergingUrlParameters is a copy() touching customParameters alone. linkId, deepLinkPath, appScheme, the store URLs and utmParameters remain server truth.
  • The resolve-failure fallback is unchanged.

A live leak this also closes

extractCustomParameters excluded the five UTM keys by exact match but not fp_ or lf_click.

Those are LinkForty plumbing: fp_* are fingerprint signals the redirect reads server-side for attribution, lf_click is the id the redirect appends to a destination URL for downstream analytics. Neither is the customer's data, and the server's extractor excludes both.

Not hypothetical — the resolve-failure path already returns the local parse directly, so an app hitting it today can receive fp_tz and lf_click inside customParameters as though the customer had set them.

The new predicate matches on prefix rather than an exact set, so utm_* and fp_* are covered whatever the suffix — the previous exact-match set would have missed utm_id or fp_anything. Tests pin the near-misses (utmost, fps, lf_clicks) as not reserved.

Confirmed unaffected: the fingerprint is built server-side from the query string, and lf_click is consumed on the landing page. Neither reads customParameters.

Verification

  • ./gradlew :sdk:testDebugUnitTest144 tests, 0 failures, 0 errors, 0 skipped
  • Parsed from the JUnit XML rather than inferred from a quiet exit, and all 7 new tests confirmed present in the results rather than silently skipped
  • Every pre-existing test passes unchanged, which is the check that matters: the reserved-name change alters no behaviour they pin down

One thing worth a look in review

The merge extension initially landed between @JsonClass(generateAdapter = true) and the data class it annotates, which would have detached the annotation and broken Moshi's generated adapter. It is now below the class. Worth confirming the generated DeepLinkDataJsonAdapter still looks right after a clean build.

Part of SIT-429

A link shared as ?slug=titanic delivered that value to the app after a
deferred install but not when the app was already installed. resolveUrl
returned the server payload and dropped the local parse — and that payload
is the link's stored configuration, which cannot know what was appended to
the URL that was tapped.

customParameters now carries both, with URL values winning on a collision:
the precedence the server already applies on the deferred path.

mergingUrlParameters is a copy() touching customParameters alone. linkId,
deepLinkPath, appScheme, the store URLs and utmParameters stay server truth;
a local parse cannot know them.

Also aligns the reserved-name filter with the server's. It excluded the
five UTM keys but not fp_ or lf_click, so fingerprint signals and the
click-correlation id could reach an app as if the customer had set them —
already possible on the resolve-failure path, which returns the local parse
directly. The predicate matches on prefix rather than an exact set, so
utm_* and fp_* are covered whatever the suffix.
@onamfc
onamfc merged commit 0167a24 into main Sep 10, 2026
3 checks passed
@onamfc
onamfc deleted the brandon/sit-429-merge-url-params branch September 10, 2026 05:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant