Skip to content

Commit 77fa556

Browse files
piwi3910claude
andcommitted
feat(tauri): kryton:// deep link handling
- add tauri-plugin-deep-link crate (already added to Cargo.toml in prior commit) - register kryton:// scheme in tauri.conf.json plugins.deep-link.desktop.schemes - add global-shortcut and deep-link permissions to capabilities/default.json - add fs:allow-read-file for drag-drop binary reads - add src/tauri/deep-link-bridge.ts: parseDeepLink() handles note, daily, search, settings URLs; setupDeepLinks() opens target account window then calls onUrl callback Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 7395ca9 commit 77fa556

5 files changed

Lines changed: 126 additions & 1 deletion

File tree

package-lock.json

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@
2828
"@azrtydxb/core-react": "4.4.0-pre.9",
2929
"@azrtydxb/ui": "4.4.0-pre.9",
3030
"@tauri-apps/api": "^2",
31+
"@tauri-apps/plugin-deep-link": "^2.4.8",
3132
"@tauri-apps/plugin-dialog": "^2.7.0",
3233
"@tauri-apps/plugin-fs": "^2.5.0",
34+
"@tauri-apps/plugin-global-shortcut": "^2.3.1",
3335
"@tauri-apps/plugin-window-state": "^2.4.1",
3436
"react": "^19.2.0",
3537
"react-dom": "^19.2.0",

src-tauri/capabilities/default.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
"dialog:allow-open",
1313
"dialog:allow-save",
1414
"fs:allow-read-text-file",
15-
"fs:allow-write-text-file"
15+
"fs:allow-write-text-file",
16+
"fs:allow-read-file",
17+
"global-shortcut:allow-register",
18+
"global-shortcut:allow-unregister",
19+
"global-shortcut:allow-is-registered",
20+
"deep-link:default"
1621
]
1722
}

src-tauri/tauri.conf.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,12 @@
2828
"android": {
2929
"debugApplicationIdSuffix": ".debug"
3030
}
31+
},
32+
"plugins": {
33+
"deep-link": {
34+
"desktop": {
35+
"schemes": ["kryton"]
36+
}
37+
}
3138
}
3239
}

src/tauri/deep-link-bridge.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/**
2+
* deep-link-bridge.ts — handles `kryton://` deep-link URLs in the desktop app.
3+
*
4+
* Supported URLs:
5+
* kryton://note/<path>?account=<id>
6+
* kryton://daily?account=<id>
7+
* kryton://search?q=<query>&account=<id>
8+
* kryton://settings/<section>?account=<id>
9+
*/
10+
11+
import { onOpenUrl } from "@tauri-apps/plugin-deep-link";
12+
import { invoke } from "@tauri-apps/api/core";
13+
14+
// ---------------------------------------------------------------------------
15+
// Types
16+
// ---------------------------------------------------------------------------
17+
18+
export type ParsedDeepLink =
19+
| { kind: "note"; path: string; account: string }
20+
| { kind: "daily"; account: string }
21+
| { kind: "search"; q: string; account: string }
22+
| { kind: "settings"; section: string; account: string };
23+
24+
// ---------------------------------------------------------------------------
25+
// Setup
26+
// ---------------------------------------------------------------------------
27+
28+
/**
29+
* Register the deep-link listener. Call once on app mount.
30+
*
31+
* @param onUrl Callback invoked for each successfully-parsed incoming URL.
32+
*/
33+
export async function setupDeepLinks(
34+
onUrl: (parsed: ParsedDeepLink) => void,
35+
): Promise<void> {
36+
await onOpenUrl(async (urls) => {
37+
for (const url of urls) {
38+
try {
39+
const parsed = parseDeepLink(url);
40+
if (!parsed) {
41+
console.warn("[deep-link] unrecognised URL, ignoring:", url);
42+
continue;
43+
}
44+
45+
// Open (or focus) the account window for the target account.
46+
await invoke("open_account_window", { accountId: parsed.account });
47+
48+
onUrl(parsed);
49+
} catch (e) {
50+
console.warn("[deep-link] failed to handle URL", url, e);
51+
}
52+
}
53+
});
54+
}
55+
56+
// ---------------------------------------------------------------------------
57+
// Parser
58+
// ---------------------------------------------------------------------------
59+
60+
function parseDeepLink(url: string): ParsedDeepLink | null {
61+
let u: URL;
62+
try {
63+
u = new URL(url);
64+
} catch {
65+
return null;
66+
}
67+
68+
if (u.protocol !== "kryton:") return null;
69+
70+
const account = u.searchParams.get("account") ?? "";
71+
72+
switch (u.host) {
73+
case "note": {
74+
const path = decodeURIComponent(u.pathname.replace(/^\//, ""));
75+
return { kind: "note", path, account };
76+
}
77+
case "daily": {
78+
return { kind: "daily", account };
79+
}
80+
case "search": {
81+
const q = u.searchParams.get("q") ?? "";
82+
return { kind: "search", q, account };
83+
}
84+
case "settings": {
85+
const section = decodeURIComponent(u.pathname.replace(/^\//, ""));
86+
return { kind: "settings", section, account };
87+
}
88+
default:
89+
return null;
90+
}
91+
}

0 commit comments

Comments
 (0)