strait_sdk 0.10.4
strait_sdk: ^0.10.4 copied to clipboard
Deep linking for Dart and Flutter apps. Direct links (App Links, Universal Links, custom schemes) and deferred links that survive the install.
Changelog #
0.10.4 (9 Nov 2026) #
- README: Android release builds need
<uses-permission android:name="android.permission.INTERNET"/>inandroid/app/src/main/AndroidManifest.xml.flutter createadds it only to the debug and profile manifests, so a release build could not reach Strait. Now in Install and the quick start. No code change.
0.10.3 (9 Nov 2026) #
- iPhone install matching across IPv4 / IPv6 (contract B24, engine ADR 0101): when Safari tapped the link over IPv4
and the app's first launch asks over IPv6, the engine's answer names its IPv4-only URL and the SDK asks once more
there, with the same request (same openId, same fields, nothing new collected). A match there is
probable(neverexact). Nothing is sent while Strait is switched off, and the read-onlycheckDeferred()never asks twice.
0.10.2 (8 Nov 2026) #
doctor()(contract B23, shared-spec ADR 0002 amendment of 7 Nov): on a Strait link domain before Dashboard → Apps & stores is filled in (it answers 404{"error":"not configured"}, the usual first-time state) the finding is nowapp.nonewith "Fill in Dashboard → Apps & stores (package name and SHA-256 fingerprint / Team ID and bundle ID), wait a minute, then check again", nothost.file_missing("use your Strait link domain as endpoint", which you already were). Everyapp.nonefix now names those fields. 51 shared doctor vectors (was 43).- README: starts with the 5-minute path,
npx @straitlink/cli init(writes the integration file and prints the steps left), before the manual quick start. - README: the iPhone badge says "iPhone links: live · install matching: opt-in", the same words as the website and platform status (was "iPhone: live").
0.10.1 (7 Nov 2026) #
- README: the Quick start install step renders as a code block again (the fence sat on the same line as
"1. Install." and broke the page on pub.dev), and it installs the five packages the
StraitLinkssetup imports (app_links,shared_preferences,play_install_referrer,package_info_plus,flutter_timezone). No code change.
0.10.0 (7 Nov 2026) #
- New: every app open and install the SDK reports (
/v1/open,/v1/resolve,/v1/referrer,/v1/match,/v1/handoff/claim) now says which version of your app and of this SDK sent it, so "links stopped working" can be traced to a release. An open saved offline keeps the versions it happened on, even if it is sent after an update. PassappVersion:toStraitLinks(for examplePackageInfo.fromPlatform()frompackage_info_plus; the SDK stays pure Dart and adds no dependency); leave it out and only the SDK version (straitSdkVersion) is sent. Trimmed and capped at 64 characters; anything not plain ASCII is left out. Never sent with/v1/eventor/v1/lookup, and nothing is sent whilesetEnabled(false)is set. The legacyresolveDeferredLinktakesappVersiontoo. ADR 0002. - Fix (privacy, F7): switching the SDK off during the first-launch check no longer reads the iPhone clipboard (paste handoff, which shows iOS's paste prompt). The switch is checked before the prompt-free "is there a link?" check and again before the read; nothing is claimed.
- Fix (privacy): the legacy
resolveDeferredLinkignored the opt-out switch and still called/v1/match(one install row per call). It now takesstorage(pass the one you giveStraitLinks); whilesetEnabled(false)is set there it sends nothing and returnsMatchResult.none. Action required if you call it and use the opt-out: passstorage, or it can't see the switch and still runs. - Fix (Flutter web): a short link handed to
handleUrlon the web now asks/v1/lookup(key + host + path), which records nothing. It used to call/v1/resolvewithout an openId, which the engine counts as a tap (and fireslink.clicked), although the SDK said it recorded nothing. A lookup with no answer isreason: 'network'. - Fix: switching the SDK off while a short link's
/v1/resolveis still out no longer answersreason: 'network'; the link routes through/v1/lookupas an opted-out tap does, and nothing is queued. A reply that lands after the switch went off keeps no tap. - New:
setEnabled(false), an opt-out switch for consent flows (DPDP, GDPR). While off the SDK sends no open reports or events and does no deferred or clipboard matching; reports saved on the device and the remembered tap are deleted. Kept across launches (strait.enabledinstorage);setEnabled(true)resumes and runs a first-launch check that was skipped while off.isEnabled()reads it. Default on. Links that carry their own destination still reachonLink. A Strait short link still routes: while off the SDK asks the engine's/v1/lookup(key + host + path, no query), which records no tap, open or tap id, and queues no report; if that lookup fails (offline, or an engine without it) the link comes backreason: 'disabled'. Flutter web follows the same rules. ADR 0001 (doc/adr); engine ADR 0083. - Fix: reinstalls from a link are no longer lost. Android Auto Backup and restored iPhones put back
the "deferred check done" flag, so a reinstall skipped its deferred link and was never counted.
New optional
installTimeonStraitLinks(e.g.package_info_plus(await PackageInfo.fromPlatform()).installTime?.millisecondsSinceEpoch): the SDK stores the install time asstrait.installTimeand runs the check again when it changed; an app update, or an app updated from an older SDK, is never a new install. Web builds are unchanged (no first launch on the web) (contract B6a, shared-spec ADR 0003). - New:
trackEvent(name, eventId: ...), your order id; the engine stores each eventId once. Left out, the SDK makes one, so a retry never counts twice. Events that can't be sent (offline, 429, 5xx) are kept in the open-report queue (strait.pendingOpens, without the publishable key) and sent later to/v1/event;pendingOpenReports()counts them (contract B13a, ADR 0004). - Fix: when the server is busy (429 or 5xx) the saved queue no longer sends its whole backlog on
every foreground. A flush stops at the first 429/5xx and the queue waits 15–30 s, doubling each
time in a row up to 1 hour, and at least the server's
Retry-After(contract B14 backoff, C1, shared-spec ADR 0005). New opens and events are still sent once when they happen. - Conformance vectors v11 (
firstLaunchState,retryDelay).
0.9.7 #
- Flutter web: web opens are counted only for landings from a Strait tap. A page URL that carries
a valid
strait_click(the tap id the engine appends when it sends a tap to the web) sends one/v1/open(platformweb, joined to the tap, openIdweb-<tap id>so a reload or a shared landing URL is de-duplicated by the engine) and attributes later events to that tap. Any other page URL still reachesonLinkfor routing but is not reported (0.9.6 reported every page load with a URL as a link open, so web "opens" were page views). A short link handed tohandleUrlon the web is looked up without an openId, so it is not counted either. Needs an engine that adds web tap ids. Apps and native builds change nothing.
0.9.6 #
- Flutter web: a web build has no install, so
StraitLinks.start()no longer runs the once-per-install deferred check there (0.9.5 and earlier sent/v1/matchon every first visit, which the engine recorded as an install), and an open from the page URL is never reported as a first launch. A link in the page URL still opens, is reported and attributes events. Detected at compile time (dart.library.js_interop, the same test as Flutter'skIsWeb); apps change nothing.checkDeferred()still asks when called. - README: the
StraitLinksexample useskIsWeb/defaultTargetPlatforminstead ofdart:io'sPlatform, which throws on the web. - CI runs the whole suite in Chrome too (dart2js and dart2wasm); the shared vectors load there
through
test/support/fixture.dart. Install-path tests are VM only.
0.9.5 #
- Faster start-up:
StraitLinks(...)no longer builds aRegExp. An isolate's firstRegExpmade the constructor take 0.41 ms on the UI isolate; it now takes 0.03 ms (the trailing-slash trim and the link-host checks are plain ASCII scans with the same rules, tested against the old RegExp code). No API or behaviour change. - README: a "Size and speed" table (package and AOT size, start-up time, how each was measured).
0.9.4 #
- New:
links.doctor({appId, certSha256}), a setup check for debug builds (contract B23, shared-spec ADR 0002). One GET of your link domain's/.well-known/assetlinks.json(Android) or/.well-known/apple-app-site-association(iOS), then checks the key (secret key in the app?), the endpoint, whether the file lists your app and (Android, when you pass it) this build's signing certificate. Never throws; prints each finding with[Strait doctor]only when asserts are on (debug builds); returns aDoctorReport(ok,findings). The same finding ids and wording as every Strait SDK. Also exported: the puredoctorFindings(...)andDoctorFinding/DoctorReport/DoctorFile. Pure Dart can't read the package or bundle ID, so passappId(e.g. frompackage_info_plus; left out:app.unknown). No new dependency, no other behaviour change.
0.9.3 #
- New optional
deviceClasscallback onStraitLinks(contract B14, engine ADR 0039):'phone'or'tablet'is sent with/v1/resolve,/v1/open(queued retries too) and/v1/store-sheet. NewdeviceClassForSize(shortestSide): >= 600 logical px = tablet. The README shows the Flutter wiring. Left out when not supplied; older engines ignore it. Conformance vectors v9. - Docs: iPhone links are live, iPhone install matching is opt-in (off until you choose it) and the store sheet is new, in the README and the doc comments (no "beta" for iPhone). No behaviour change.
0.9.2 #
- Fix: a link that opens the app while the first-launch deferred check is still running is no
longer followed by that check's match either (0.9.1 held back only its "no link" event). The
app stays on the screen the person just tapped instead of jumping to a stale pre-install
destination. The engine still counts the install and records and attributes the match as before,
the check is still marked done, and the match is not added to
events(no replay to lateonLinklisteners).checkDeferred()still returns (and delivers) its answer (contract B6). - iPhone paste handoff: once a link has opened the app in this launch, the first-launch check no longer reads the clipboard, so iOS shows no "Allow Paste" prompt for an answer that won't be delivered. Checked again right after the prompt-free URL detect; a clipboard already read is claimed as before (contract B6/B19).
- Known gap (no API change): if the check had already sent its
onLinkStartwhen the link arrived, noonLinkevent with that start'sidfollows. The documented pattern (loading on at anyonLinkStart, off at anyonLink) stays correct because the link's own start and event come after it; an app that tracks loading peridshould also clear pending deferred ids when adirectevent arrives.
0.9.1 #
- Fix: a link that opens the app while the first-launch deferred check is still running is no
longer followed by that check's "no link" event (
matched: false,no_matchornetwork). Found with the React Native SDK on an Android emulator (13 Oct): the link was delivered (/product/42) and the check'sfalsecame right after it, so an app that routes home on!matchedleft the right screen. Flutter had the same race (start(urls: ...)listens for links before the check finishes). The check still runs and counts the install, and is marked done (or retried next launch) as before; only its no-link event is not delivered, and it sends noonLinkStartonce a link is here (a loading state would never end). A deferred match is still delivered;checkDeferred()is unchanged (contract B6).
0.9.0 #
- Smaller public API (SDK-23, decision record: shared-spec
docs/adr/0001-sdk-public-api-surface.md).package:strait_sdk/strait_sdk.dartno longer exports the contract helpers insrc/core.dart(classifyUrl,splitUrl,pruneOpenQueue,rememberTap,shouldRetryReport,newOpenId,parseStraitLink,pasteHandoffOn,AppStateTracker, the queue and window constants, ...). They were never in the docs. Still public from core:portraitScreenWidth,AppLifecycle,AppStateAtLinkandLinkRoute. Nothing else changed:StraitLinks,resolveDeferredLink,DeviceFields,MatchResultand the store sheet are as before. If you called one of the helpers anyway, importpackage:strait_sdk/src/core.dart(not a supported API). - Migration: if you copied the dashboard's Flutter setup before 13 Oct, it called
browserScreenWidth(w). UseportraitScreenWidth(w, h)(what the docs and the dashboard show now). It is also the correct width when the app starts in landscape (contract B17). example/example.dartshows onlyresolveDeferredLink.
0.8.3 #
checkDeferred()(the debug / settings re-check) now sends"record": falseon/v1/matchand/v1/referrer, so the engine answers the match without adding an install, an open or aninstall.matchedwebhook (contract B6: it never adds an install). The once-per-install check is unchanged. Takes effect once the engine withrecordsupport is live; an older engine ignores the field.- Every request to the engine now gives up after 10 s (like the Android SDK's timeouts) and takes
the existing
networkpath: the deferred check is retried next launch and open reports stay queued. Before, a reply that never came hungstart()and the open-report queue.
0.8.2 #
- First version on pub.dev.
v0.8.1is a GitHub tag only and was never uploaded, so pub.dev starts at 0.8.2. Install lines written asstrait_sdk: ^0.8.1resolve to it. - Package metadata for pub.dev: homepage, repository, issue tracker, topics, and a runnable
example/example.dart. The release workflow pins its actions by commit SHA. - README: the common Strait SDK header (logo, the promise "Straight to the screen. On the record.", badges, links to docs, platform status and the changelog), a platform features table in the availability words (Live / Beta / Planned / Not yet) with the iPhone beta truth (the method is the customer's choice, device matching off by default), and a "Docs and support" section (Talk to the Strait team). The same structure in all seven SDK READMEs (design system v5).
example/example.dartuses the sample Hilltop Shoes link host instead ofgo.yourbrand.com.- iPhone deferred method is the customer's choice, at runtime (founder decision 10 Oct 2026):
the SDK tries the paste handoff only when the engine's
/v1/matchreply has no match and saysios.pasteHandoff: true(Dashboard → Settings → iPhone installs). Nothing is stored or baked into the build, so a dashboard change needs no app release. No answer from the engine = the clipboard stays untouched (network, retried next launch). An older engine without theiosfield = off. NewpasteHandoffOn(reply)helper.clipboardBoostis deprecated and ignored. Tests cover off/off, device only, paste only and both. New workspaces start with device matching off. - Clipboard boost order (B19): the first-launch iPhone check now runs device matching
(
/v1/match) first and reads the clipboard / claims the handoff only when it returns no match or fails. A device match no longer shows iOS's "Allow Paste" prompt. SameopenIdacross both attempts; one event.claimHandoffis unchanged. - Store sheet (beta):
StraitLinks.openStoreSheet(url, opener, [options])callsPOST /v1/store-sheet, then on Android tries Google Play inline install,market://and the Play web page (referrer carriesstrait_link+strait_click); on iPhone saves the device match (/v1/match-save), optionally copies the handoff link, and shows the App Store through yourStoreSheetOpener(MethodChannel example in the README). Newlib/src/store_sheet.dart. - Docs and tests only, no code change. README gains a Support section; AGENTS.md gains a
"stop and ask the human" list (signup and keys, handle, dashboard app settings, signing and
store builds, the real-phone test) and a no-phone verify loop (
/v1/tools/app-links,/v1/simulate), and says plainly that the package is not on pub.dev yet. - Demo names: test and doc hosts move to reserved names (
hilltop.links.test,go.hilltop.example,jo@hilltop.example);conformance-vectors.jsonre-copied from shared-spec 11779b1 (inputs renamed, every expected value unchanged). - Old Firebase Dynamic Links (shared-spec/SDK-CONTRACT.md B22, conformance vectors v8). Add your
<x>.page.linkhost tolinkHosts(and keepapplinks:<x>.page.link/ the page.link intent filter in your app build): a page.link short link is looked up through/v1/resolvelike any short link (the engine matches the old host + code of links you imported), and a page.link long link (https://<x>.page.link/?link=<url>&…) opens itslink=destination straight away, on the device, with no lookup. Only*.page.linkhosts at the root path, only an http(s)link. New core functionpageLinkLongLink. Not yet tested on a real iPhone.
0.8.1 #
- First version meant for pub.dev: the package is built with
.pubignore, so tests, CI, release tooling and internalCOUNCIL-*.mdnotes stay out of the published archive. No API removed or renamed; 0.8.0 apps update without code changes. - Referral codes (preview; shared-spec/proposals/referral-code.md, B21): a matched
deferred
LinkEvent(Play referrer, iPhone match, clipboard claim,claimHandoff) carriesreferralCodewhen the engine's reply has a valid one (also intoJson). LegacyMatchResult.referralCodetoo. New core functionreplyReferralCode. (This code is already in thev0.8.0tag; its changelog listed it under Unreleased.)
0.8.0 #
- Optional iPhone clipboard boost (shared-spec/SDK-CONTRACT.md B19). New config
clipboardBoost(default false) and an app-suppliedStraitClipboardadapter (hasProbableWebUrl,readText; README shows a MethodChannel version). With it on, the once-per-install check on iOS asks without a prompt whether the clipboard holds a web URL, reads it only then (iOS shows its paste prompt), and claims a Strait handoff link viaPOST /v1/handoff/claimfor an exact match (LinkRoute.clipboard), else falls back to the signal match. With it off (the default) the clipboard is never touched. - New
claimHandoff(text)for a paste button (no prompt). - New core export
parseHandoffUrl(conformance vectors v7).
0.7.2 #
- Privacy hardening (shared-spec/SDK-CONTRACT.md B18): the URL sent with an open report
(
/v1/open,/v1/resolve) and saved in the offline queue (strait.pendingOpens) no longer carries the query string or fragment, except the firstutm_sourcepair, which the engine uses for channel attribution. Reports queued by an older version are stripped the next time the queue is read. Your app'sLinkEvent(rawUrl,url,params) is unchanged, and so is what the engine records. - An expired remembered tap id (
strait.lastTap, older than 7 days) is now deleted atstart()and bytrackEvent, instead of only being ignored. - New core exports:
reportUrl,staleTap(conformance vectors v6).
0.7.1 #
- Report the portrait screen width so a first launch in landscape still matches the tap
(shared-spec/SDK-CONTRACT.md B17): new core export
portraitScreenWidth(width, height)(the shorter side, rounded like the browser; conformance vectors v5). BuildDeviceFields.screenWidthwith it, as the README now shows.
0.7.0 #
- Every attributed open now supplies the tap id (shared-spec/SDK-CONTRACT.md B16):
when
/v1/resolve(verified short link),/v1/match(fingerprint) or/v1/referrer(Play install) returnsclickId, it is remembered asstrait.lastTapand sent with conversion events. A reply without one (older engine) keeps the 0.6.0 behaviour. - New core export:
replyClickId(conformance vectors v4).
0.6.0 #
- Conversion events carry the tap id (shared-spec/SDK-CONTRACT.md B15): the tap id
of the last attributed link open (browser hand-off
strait_click, or the Play referrer on a deferred install) is remembered understrait.lastTapand sent asclickIdwithtrackEventfor 7 days. A newer short-link or fingerprint open forgets it.trackEvent(name, clickId: …)overrides it. - New core exports:
eventClickId,rememberTap,attributionWindowMs(conformance vectors v3).
0.5.0 #
- Breaking: renamed to Strait. The package is now
strait_sdk(import 'package:strait_sdk/strait_sdk.dart';). New names:StraitLinks,parseStraitLink,parseStraitClick; source inlib/src/strait.dart. - Breaking (clean break, no aliases): wire params are now
strait_clickandstrait_link(Play Install Referrer and hand-off URLs); the params from before the rename are no longer read. Storage keys are nowstrait.*(strait.deferredChecked,strait.pendingOpens); values saved under the old keys are ignored, so a deferred link may be checked once more after upgrading. - Publishable keys are issued as
st_pub_live_…/st_pub_test_….
0.4.0 #
- New (contract B14): every link open is reported exactly once. Each open
gets an id from
newOpenId(o_<base36 ms>_<12 chars>), also used as theLinkEvent.id. Short links sendopenId,appState,firstLaunch,atwith/v1/resolve(falling back to/v1/openwhen the engine didn't record it); custom-scheme hand-offs and your own https links are reported viaPOST /v1/openwithout delaying the event. Unsent reports are kept instorageunderstrait.pendingOpensand retried on start, on resume and after any successful report (≤7 days, ≤100). NewpendingOpenReports()andflushOpenReports(). - New (B4): a
strait_clicktap id is removed from the destination (takeClickId) and returned asClassifiedUrl.clickId. - Changed (B6/B7/B8): the deferred check is marked done only once the
engine answered (no answer / 429 / 5xx →
reason: 'network', retried next launch). The once-per-install run sendsopenId+aton/v1/referrerand/v1/match, plus the tap id (parseStraitClick) on/v1/referrer;checkDeferred()sends neither. - New pure helpers:
parseStraitClick,takeClickId,pruneOpenQueue,shouldRetryReport,newOpenId,openQueueMax,openQueueMaxAgeMs— checked against conformance vectors v2.
Packaging #
- Publish-ready for pub.dev: homepage / repository / issue_tracker / topics,
MIT
LICENSE, anexample/, and.pubignoreso tests, fixtures and repo tooling don't ship.dart pub publish --dry-runreports 0 warnings. - The package name and URLs come from
brand.json(applied byscripts/brand.mjs), so the brand switch is one command. - Tag
vX.Y.Z→ GitHub Actions runs analyze + tests and publishes via pub.dev automated publishing (OIDC, no stored secret). See PUBLISHING.md.
0.3.0 #
- New:
StraitLinksclient — full parity with the React Native SDK (shared-spec/SDK-CONTRACT.mdB1–B13): direct links (verified App Links / Universal Links resolved viaPOST /v1/resolve, custom-scheme hand-offs), deferred links once per install (strait.deferredChecked),onLinkstream with replay,onLinkStartloading signal, app-state labels (closed/background/foreground),checkDeferred,reportFingerprint,compareFingerprint,trackEvent. Never throws; network failure →matched: false, reason: 'network'. - New: pure helpers in
core.dart—browserScreenWidth,splitUrl,classifyUrl,AppStateTracker— checked againsttest/conformance-vectors.json. - Fix:
parseStraitLinknow matches the contract exactly (firststrait_linkpair wins; empty value → null). Moved tocore.dart; still exported from the same places. resolveDeferredLinkis unchanged and still supported.
0.2.0 #
- Breaking:
resolveDeferredLinknow takespublishableKey(your workspace publishable key,bk_pub_live_…/bk_pub_test_…, from Dashboard → Get started) instead ofappId. Requests to/v1/matchand/v1/referrersendpublishableKeyin the JSON body; the server rejects requests without it (401).
0.1.0 #
- Initial release: deferred deep-link resolution (Android Install Referrer, iOS/Android device-fingerprint match).