Skip to content

feat: Universal Links handler#2

Open
VasilyPolyuhovich wants to merge 11 commits into
mainfrom
feat/universal-links
Open

feat: Universal Links handler#2
VasilyPolyuhovich wants to merge 11 commits into
mainfrom
feat/universal-links

Conversation

@VasilyPolyuhovich

Copy link
Copy Markdown
Owner

Summary

Adds native Universal Links (UL) handling to AppRouterPlus, mirroring the existing custom-scheme deep-link API. Fully parameterized — no hard-coded domains/schemes/prefixes.

New API:

  • UniversalLinkConfig — value type bundling allowedHosts / pathPrefix / allowedSchemes
  • URLNavigationHelper.parseUniversalLink(...) — inline + config overloads
  • URLNavigationHelper.buildUniversalLink(...) — UL URL builder (with/without tab)
  • Router.navigate(toUniversalLink:) — inline + config overloads
  • Router.handleUserActivity(_:config:)NSUserActivity (BrowseWeb) handler
  • Same API on SimpleRouter (plus backfilled navigate(to: URL) for custom-scheme parity)
  • AppRouterBrowseWebActivityType constant (avoids UIKit import for one constant)

Key semantics:

  • Host validated case-insensitively against allowedHosts (RFC 3986)
  • Path prefix matched per-segment (rejects lexical false-positives like /appstore vs /app)
  • tab query: LAST wins on multi-value (matches existing parse)
  • deepPush + .replace runs async on MainActor Task; returns true immediately
  • Returns false on invalid input (no throws, no Result — matches existing navigate(to: URL))

Internal change: Router.deepPushTo visibility relaxed from private to internal so the new extension can reuse it. No external API impact.

Tests: First test target ever added to the library. 57 tests across 6 suites, all passing on iOS 18.5 simulator.

Docs: README section + Examples/UniversalLinkExample.swift.

Test Plan

  • CI: xcodebuild -scheme AppRouterPlus -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.5' test — expect 57/57 passing
  • Visually inspect README "Universal Links" section renders correctly
  • Try the UniversalLinkExample.swift integration in a sample iOS app:
    • Open custom-scheme URL myapp://home?tab=profile → tab switches, home pushed
    • Open universal link https://example.com/profile?userId=42&tab=profile → profile tab + profile screen
    • Pass non-BrowseWeb NSUserActivityhandleUserActivity returns false
  • Build Examples/UniversalLinkExample.swift against the library to verify the public API surface is ergonomic

@VasilyPolyuhovich VasilyPolyuhovich self-assigned this May 23, 2026
Remove Router.handleUserActivity / SimpleRouter.handleUserActivity and the
AppRouterBrowseWebActivityType constant. The wrapper saved 2 lines but
coupled the library to a specific URL-delivery API (NSUserActivity) and
duplicated a system constant.

Public surface is now strictly URL-based. Consumers extract webpageURL
themselves from .onContinueUserActivity and call navigate(toUniversalLink:),
which also makes the library reusable for push payloads, share extensions,
and any other URL source.

README + UniversalLinkExample updated to show the 2-line integration.
Test count: 57 -> 52 (5 handleUserActivity tests removed).
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