Deferred deep linking + click attribution for iOS. Swift Package Manager binary distribution, zero dependencies beyond Foundation/URLSession; floor iOS 15 (tools 5.9).
Compatible with OptoLink backend v2.1.0+.
dependencies: [
.package(url: "https://github.com/Optomatica/optolink-ios-sdk.git", from: "0.2.0"),
],
targets: [
.target(name: "YourApp", dependencies: [
.product(name: "OptoLink", package: "optolink-ios-sdk"),
]),
]import OptoLinkThe package resolves anonymously — no SSH keys, tokens, or netrc entries. This
repository is a releases-only mirror: it carries the prebuilt OptoLink.xcframework
(binaries for iOS, iOS Simulator, and macOS), not source code.
One config, one init, both ingestion surfaces, one collector:
import OptoLink
@main
struct MyApp: App {
init() {
Task {
await OptoLink.initialize(config: OptoLinkConfig(
apiKey: "opl_sdk_…", // CLIENT-tier key (portal → API keys)
orgKey: "your-org",
baseUrl: URL(string: "https://api.optolink.app")!
))
}
}
var body: some Scene {
WindowGroup { ContentView() }
.onOpenURL { OptoLink.instance().handle(url: $0) }
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
if let url = activity.webpageURL { OptoLink.instance().handle(url: url) }
}
}
}// Route matched links — e.g. in your root view:
Task { for await link in OptoLink.instance().links { route(link) } }Notes:
initializeis idempotent and concurrency-safe (first config wins). It awaits the once-per-install deferred match; a network failure there is logged, not thrown — startup never blocks. Re-attempt later withtry await OptoLink.instance().resolveDeferredLink()(returnsnilon no match).handle(url:)is safe beforeinitialize— an early URL parks and is flushed after init (deferred link first, parked URL second).linksis a freshAsyncStream<OptoLinkData>per access with replay-last semantics: the latest emitted link is delivered even when you subscribe after the emission. It never errors and never finishes.- To delay routing (e.g. until onboarding finishes), subscribe to
linksonly when ready — replay-last delivers the held link then. The SDK never opens anything on the app's behalf.
| Capability | Surface | Notes |
|---|---|---|
| Direct links | handle(url:) → links |
Universal Links (primary) and custom-scheme URLs both go through the one entry point; resolves {orgKey}/{shortCode} to link data via the backend. |
| Deferred deep linking | once-per-install on initialize; resolveDeferredLink() for manual re-attempt |
Deterministic clipboard token only: the backend runs no fingerprint or IP matching for Apple platforms. The pasteboard is cleared only when it held our token, nothing was copied since, and /match succeeded. No install referrer exists on iOS (see Limitations). |
| Identity | setIdentity(externalId:), clearIdentity() |
Throwing trio; success is implicit. |
| Events | trackEvent(name:properties:) |
String name + [String: String] properties; oversize payloads surface as OptoLinkApiError. |
Every matched link carries OptoLinkData: linkId, path (verbatim; may carry a query),
params, queryParams (the query embedded in path, parsed; nil when none), matchMethod
(CLIPBOARD / FINGERPRINT_EXACT / FINGERPRINT_SCORED / IP_FUZZY / DIRECT / NONE),
confidence, isDeferred. Errors are OptoLinkApiError (HTTP, carries status,
requestId, retryAfterSeconds) and OptoLinkConnectionError (transport). Wire in an
OptoLinkLogger via OptoLinkConfig.logger to see warnings/errors. dispose() resets state
(tests only). clipboardEnabled defaults to true; opt out in the config.
Universal Links are the primary ingestion entry. Add the associated-domains entitlement with your org's link domain:
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:go.demo.optolink.io</string>
</array>The apple-app-site-association file is served by the OptoLink backend from the org's IOS
AppConfig (bundleId, teamId, paths). A custom scheme may be registered as a fallback
entry; the SDK treats any URL through handle(url:) identically.
https://api.optolink.app (used in the snippets) is the chosen public default, not live
anywhere today — point baseUrl at your OptoLink backend for real matches.
- No install-referrer channel exists on iOS. The App Store carries no referrer, so all deferred attribution rides the clipboard token. FDL-style guarantees are impossible by platform design; a user who bounces to the App Store before the redirect page's user-gesture clipboard write fires gets no deferred match (direct links are unaffected).
- The clipboard leg is user-gesture-bound. The copy happens via the redirect page's copy
affordance, not a silent background write. On every supported version (iOS 15+) the SDK
first checks silently via
detectPatternsand reads only when a web URL is present; the system paste prompt then appears once. Declining leaves the pasteboard untouched. - Timezone asymmetry is deliberate. The iOS match body omits
timezone(Safari's click side stores none); Android sends IANA tz — the two collectors are intentionally different. Flutter integrators additionally take a scored-pathdeviceModelpenalty (a lower fingerprint score) until the Flutter plugin converts to this SDK. - Custom-scheme fallback links carry no Universal Link security — any app can claim a scheme; the SDK treats scheme URLs as raw-direct data only.
- Universal Link verification is delivery-gated: on-device AASA verification needs the org's link-domain DNS + Apple-CDN AASA publication to land (backend/ops side); the SDK itself is unaffected.
Proprietary — see LICENSE. No right to use, copy, modify, or distribute this software is granted except under a valid written agreement with Optomatica (for example, an OptoLink subscription or SDK license agreement). For licensing inquiries, contact Optomatica.