Blox pairing hand-off to blox.fx.land (rebuilt on current main) - #115
Merged
Conversation
…tive) Adds lib/core/services/blox_pairing_links.dart (dart:io-free): the buildBloxWebPairUrl / buildBloxNativePairUrl builders, the v1 return template in the https FRAGMENT form (the bearer secret never reaches a server), and parseAutopinCompleteParams(Uri) which reads the fragment first, then the query, plus AutopinCompleteParams validation (non-empty secret, per-field length caps, no control chars). The four $placeholders stay literal (raw strings). Unit-tested. Sender (blox_pairing_screen.dart): builds the blox.fx.land URL with the SAME params; on kIsWeb uses it directly; on native tries fxblox:// first and, when launchUrl returns false or throws, offers "Pair in browser" instead of the old "app not installed" dead end. Desktop's manual pairing dialog gains a "Pair in browser" button. Receiver (deep_link_service.dart): /autopin-complete arm in the universal-link handler reusing _handleAutoPinComplete, which now uses the shared parser + validation. Android app-link <data> for /autopin-complete and the AASA path so the OS opens FxFiles directly (both preserve the fragment). Contract: docs/AUTOPIN-HANDOFF.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QyQw3WtVXDTtvJKN7ykczw
captureAutopinReturn() runs in main_web.dart BEFORE runApp: it reads the location, stashes the params (memory + sessionStorage so a refresh mid-sign-in does not lose them) and history.replaceState-strips them to redirect cannot drop it. The web home's post-login init takes the pending return and navigates to /blox-pairing with the params as go_router extra (never a query). /autopin-complete also exists as a router fallback (logged out: the redirect parks the params; signed in: the screen persists them then cleans the URL). New dart:io-free lib/web/screens/web_blox_pairing_screen.dart: shows the paired state from SecureStorageKeys.blox*, validates + writes incoming params, "Pair Blox" opens buildBloxWebPairUrl(...) in the same tab, "Unpair" clears the keys, reveal/copy of the secret behind a confirm, and the inline LAN-gateway limitation note. Settings gains a "My Devices" section linking to /blox-pairing. The pure parsing/stripping/session-encoding lives in web_autopin_return_logic.dart (VM-tested) behind the same conditional-export pattern as web_hosted_oauth. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QyQw3WtVXDTtvJKN7ykczw
site/autopin-complete/index.html (cloned from nft-claim): reads secret/hardwareId/bloxPeerId/bloxName from the fragment (query fallback); on mobile UA auto-tries fxfiles://autopin-complete?... with the 2.5 s document.hidden fallback (store links only, never a second navigation); otherwise "Open in FxFiles" + "Continue in web app" -> https://files.fx.land/app/#/autopin-complete?... (params stay in the hash, client-side). Docs: docs/AUTOPIN-HANDOFF.md (copy of the v1 contract), architecture.md section 2 (entry points + URL schemes incl. the web hand-off and the web LAN limitation), README web section. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QyQw3WtVXDTtvJKN7ykczw
From the advisor review of the hand-off: - Web fallback route: after persisting the /autopin-complete?secret=... params, REPLACE the route (context.replace) instead of go() so the secret-carrying history entry is overwritten and Back cannot re-run the persist. - Forwarder (site/autopin-complete): history.replaceState-strip the fragment as soon as the params are captured so the secret does not linger in history / a shared URL; a reload shows "incomplete or already used". - Shared isAutopinReturnRoutePath() (tolerates a trailing slash) used by the parser, the web strip logic and the native universal-link arm; tests added for the trailing-slash forms. Not changed (contract): the outbound token stays in the query per AUTOPIN-HANDOFF.md v1; the fragment-form recommendation is recorded in the status doc for the spec owner. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QyQw3WtVXDTtvJKN7ykczw
…MENT (v1.1) buildBloxWebPairUrl now emits https://blox.fx.land/autopin-pair#token=<t>&endpoint=<e>&returnUrl=<r> (no query) so the cloud JWT never reaches the blox.fx.land server / CDN logs, a Referer header, or synced history. The native fxblox:// URL is unchanged (query; OS-routed, no server). Call sites are untouched (they use the builder). Contract bumped to v1.1 in docs/AUTOPIN-HANDOFF.md with the receiver rule: read location.hash first, the v1 query form stays accepted as a fallback. architecture.md updated. Tests updated + a new "no query / JWT not before the #" test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QyQw3WtVXDTtvJKN7ykczw
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Rebuilt on top of the current
main(e94b769). Supersedes #114, which had been branched before ~79 commits landed and would have reverted them — deleted test files and rolled backweb/pkg/fula_flutter_bg.wasm,windows/fula_flutter.dllandweb/pkg/VERSION. Those artifacts are now byte-identical to main and nothing is deleted.What this adds
FxFiles already pairs with a Blox:
blox_pairing_screen.dartbuildsfxblox://autopin-pair?token=&endpoint=&returnUrl=and, if the native app is not installed, dead-ends on a snackbar. The Blox web app (blox.fx.land) has no URL scheme, so that hand-off can never reach it.This adds the web arm of the same flow: an
https://blox.fx.land/autopin-pair?...link alongside the existing scheme, a receiver for the return, and the static/autopin-completeforwarder. The return params travel in the URL fragment, so the pairing secret is never sent to a server or written into the address bar.Resolved against main rather than reimposed
Three files had moved under me and were resolved on main's terms:
web_home_screen.dart— main removed_completeHostedOauthIfAny; only the Blox hand-off call was re-applied, not the deleted OAuth code.web_settings_screen.dart— main reorganised settings behind aMoreexpander (Settings: profile + billing on top, everything else behind More #113), so the Blox entry was added inside_moreSectioninstead of restoring the old flat list. The old_aiConnectionsSectionwas dropped since main now covers it with_integrationsSection.main_web.dart— both imports kept.Verified
flutter analyzeon the changed files: no issuesflutter teston both suites: 52 passed🤖 Generated with Claude Code