ReviewOS

also looking at this

craft-native/craft

feat(packaging): register the URL schemes an app declares

#57
Merged glennmichael123 wants to merge feat/macos-url-schemes into main
3 files +177 -7
11 days ago

Closes #53.

The receive path was genuinely finished — kAEGetURL AppleEvent handler, delivery through __craftDeliverDeepLink, a buffer for a URL that arrives before the page loads — and it could never fire, because macOS only dispatches a URL to an app whose bundle claims the scheme under CFBundleURLTypes, and macOSInfoPlist emitted no such key. urlSchemes was a config field nothing read: an app could declare ['myapp'], typecheck, package, and never receive a link.

Verified against LaunchServices, not against the plist text

Two bundles, identical but for the key, registered and then asked -[NSWorkspace URLForApplicationToOpenURL:] for craftlsprobe://pair?code=123:

baseline                        HANDLER: <none>
registered WITHOUT urlSchemes   HANDLER: <none>     ← today
registered WITH urlSchemes      HANDLER: .../with.app

Both test bundles were unregistered and deleted afterwards; lsregister -dump | grep -c dev.craft.lsprobe → 0.

Getting that probe to mean anything took two corrections worth recording, because both are silent:

  • A bundle under /private/tmp is flagged in-temp-dir and its URL claims are ignored outright.
  • A bundle whose CFBundleExecutable is a shell script is flagged launch-disabled, same result.

Either one produces exactly the same <none> as a missing key, which is a good illustration of why "the plist contains the string" is not evidence for this particular fix.

Decisions

One URL type, all schemes. They are alternative ways to reach the same app, not different kinds of document; splitting them across dicts would invent a distinction LaunchServices does not make. CFBundleURLName is the bundle id — the one identifier guaranteed unique to the app.

A malformed scheme is now a package-time error. RFC 3986 syntax, checked. That failure is otherwise invisible in precisely the way this whole issue is about: LaunchServices drops the claim, the app installs and runs fine, and links never arrive.

Case-insensitive dedup, since that is how LaunchServices matches — ['harness', 'Harness'] is one scheme declared twice, not two.

Nothing is emitted when no schemes are declared, so every app packaged before this key existed produces a byte-identical plist.

What I did not do

The issue also asks to "fail loudly if deepLink.onUrl is reachable but the bundle declares no schemes". I don't think that one is deliverable as stated: craft.deepLink.onUrl is a pure-JS event subscription that never reaches native, so nothing at package time or at runtime can see it. Detecting it properly needs the page to be able to ask the native side what is actually wired — which is #49.

Related, and still true after this PR: MacOSConfig.urlSchemes (types.ts:1723) remains unconsumed, because CraftAppConfig/MacOSConfig as a whole are exported types with no runtime path into packageApp. The field that now works is PackageConfig.macos.urlSchemes, which is what packageApp actually reads. Worth its own issue rather than being smuggled in here.

The macos_deep_link.zig doc comment saying craft "can't do that for them" has been updated, since it is no longer true.

bun test 476 pass, zig build clean, tsc --noEmit clean, pickier clean. Control-checked: deleting the urlSchemes: opts.urlSchemes wiring fails the integration test, and deleting the plist rendering fails three unit tests.

3 changed files on the files tab, with 0 review threads.

Sign in to comment on this pull request.