mirror of
https://seed.radicle.dev/rad:z4D5UCArafTzTQpDZNQRuqswh3ury.git
synced 2026-10-18 19:10:45 +08:00
Open rad: links in the app
This commit is contained in:
Generated
+480
@@ -260,6 +260,137 @@ dependencies = [
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-broadcast"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"event-listener-strategy",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"event-listener-strategy",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-executor"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a"
|
||||
dependencies = [
|
||||
"async-task",
|
||||
"concurrent-queue",
|
||||
"fastrand",
|
||||
"futures-lite",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-io"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"futures-io",
|
||||
"futures-lite",
|
||||
"parking",
|
||||
"polling",
|
||||
"rustix",
|
||||
"slab",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "3.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"event-listener-strategy",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-process"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"async-signal",
|
||||
"async-task",
|
||||
"blocking",
|
||||
"cfg-if",
|
||||
"event-listener",
|
||||
"futures-lite",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-recursion"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f8abc12baad266b1c8cec146854c195b5864b4221d4b2ca7296a7ae82d9e451"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-signal"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485"
|
||||
dependencies = [
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"atomic-waker",
|
||||
"cfg-if",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"rustix",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-task"
|
||||
version = "4.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atk"
|
||||
version = "0.18.2"
|
||||
@@ -474,6 +605,19 @@ dependencies = [
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blocking"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-task",
|
||||
"futures-io",
|
||||
"futures-lite",
|
||||
"piper",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blowfish"
|
||||
version = "0.9.1"
|
||||
@@ -809,12 +953,41 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
|
||||
dependencies = [
|
||||
"const-random-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random-macro"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
"once_cell",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-str"
|
||||
version = "0.4.3"
|
||||
@@ -1311,6 +1484,15 @@ dependencies = [
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlv-list"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f"
|
||||
dependencies = [
|
||||
"const-random",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dom_query"
|
||||
version = "0.27.0"
|
||||
@@ -1554,6 +1736,33 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endi"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099"
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2"
|
||||
version = "0.7.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"
|
||||
dependencies = [
|
||||
"enumflags2_derive",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2_derive"
|
||||
version = "0.7.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "0.1.4"
|
||||
@@ -1597,6 +1806,26 @@ version = "3.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "5.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2"
|
||||
dependencies = [
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener-strategy"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fast-glob"
|
||||
version = "0.3.3"
|
||||
@@ -1777,6 +2006,19 @@ version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.33"
|
||||
@@ -2211,6 +2453,12 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
@@ -3359,6 +3607,26 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-multimap"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79"
|
||||
dependencies = [
|
||||
"dlv-list",
|
||||
"hashbrown 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordered-stream"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_pipe"
|
||||
version = "1.2.3"
|
||||
@@ -3432,6 +3700,12 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
@@ -3591,6 +3865,17 @@ version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "piper"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"fastrand",
|
||||
"futures-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs1"
|
||||
version = "0.7.5"
|
||||
@@ -3657,6 +3942,20 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "3.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"hermit-abi",
|
||||
"pin-project-lite",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "poly1305"
|
||||
version = "0.8.0"
|
||||
@@ -4043,9 +4342,11 @@ dependencies = [
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-clipboard-manager",
|
||||
"tauri-plugin-deep-link",
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-log",
|
||||
"tauri-plugin-shell",
|
||||
"tauri-plugin-single-instance",
|
||||
"tauri-plugin-window-state",
|
||||
"thiserror 2.0.19",
|
||||
"tokio",
|
||||
@@ -4276,6 +4577,16 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-ini"
|
||||
version = "0.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"ordered-multimap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.3"
|
||||
@@ -5436,6 +5747,27 @@ dependencies = [
|
||||
"thiserror 2.0.19",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-deep-link"
|
||||
version = "2.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92d489b8ecceae1cd09f6e1f7606f2095ac721cc8d54cf2f0e6bb377cc52cff6"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"plist",
|
||||
"rust-ini",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.19",
|
||||
"tracing",
|
||||
"url",
|
||||
"windows-registry",
|
||||
"windows-result 0.3.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-dialog"
|
||||
version = "2.7.2"
|
||||
@@ -5520,6 +5852,23 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-single-instance"
|
||||
version = "2.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db817fe9295e19b7d8357e900af31edb93703dd9fb6de524b007b47b6afc63b0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin-deep-link",
|
||||
"thiserror 2.0.19",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"windows-sys 0.60.2",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-window-state"
|
||||
version = "2.4.1"
|
||||
@@ -5791,6 +6140,15 @@ dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.3"
|
||||
@@ -6807,6 +7165,17 @@ dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
|
||||
dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
"windows-result 0.3.4",
|
||||
"windows-strings 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.2.0"
|
||||
@@ -7284,6 +7653,76 @@ dependencies = [
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "5.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907"
|
||||
dependencies = [
|
||||
"async-broadcast",
|
||||
"async-executor",
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"async-process",
|
||||
"async-recursion",
|
||||
"async-task",
|
||||
"async-trait",
|
||||
"blocking",
|
||||
"enumflags2",
|
||||
"event-listener",
|
||||
"futures-core",
|
||||
"futures-lite",
|
||||
"hex",
|
||||
"libc",
|
||||
"ordered-stream",
|
||||
"rustix",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"tracing",
|
||||
"uds_windows",
|
||||
"uuid",
|
||||
"windows-sys 0.61.2",
|
||||
"winnow 1.0.4",
|
||||
"zbus_macros",
|
||||
"zbus_names",
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_macros"
|
||||
version = "5.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.2",
|
||||
"zbus_names",
|
||||
"zvariant",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_names"
|
||||
version = "4.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"winnow 1.0.4",
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zcheapstr"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.54"
|
||||
@@ -7387,3 +7826,44 @@ checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
|
||||
dependencies = [
|
||||
"zune-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "5.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147"
|
||||
dependencies = [
|
||||
"endi",
|
||||
"enumflags2",
|
||||
"serde",
|
||||
"winnow 1.0.4",
|
||||
"zcheapstr",
|
||||
"zvariant_derive",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_derive"
|
||||
version = "5.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.2",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_utils"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 3.0.2",
|
||||
"winnow 1.0.4",
|
||||
]
|
||||
|
||||
@@ -27,9 +27,11 @@ serde = { version = "1.0.0", features = ["derive"] }
|
||||
serde_json = { version = "1.0.0" }
|
||||
tauri = { version = "2.11.1", features = ["isolation"] }
|
||||
tauri-plugin-clipboard-manager = { version = "2.3.2" }
|
||||
tauri-plugin-deep-link = { version = "2.4.10" }
|
||||
tauri-plugin-dialog = { version = "2.7.0" }
|
||||
tauri-plugin-log = { version = "2.8.0" }
|
||||
tauri-plugin-shell = { version = "2.3.5" }
|
||||
tauri-plugin-single-instance = { version = "2.4.5", features = ["deep-link"] }
|
||||
tauri-plugin-window-state = { version = "2.4.1" }
|
||||
thiserror = { version = "2.0.12" }
|
||||
tokio = { version = "1.45.0", features = ["time"] }
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"core:window:allow-set-badge-count",
|
||||
"core:window:allow-start-dragging",
|
||||
"core:window:default",
|
||||
"deep-link:default",
|
||||
"dialog:default",
|
||||
"log:default",
|
||||
"shell:allow-open"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
mod commands;
|
||||
|
||||
use radicle_types::AppState;
|
||||
use tauri::Manager;
|
||||
use tauri_plugin_deep_link::DeepLinkExt;
|
||||
use tauri_plugin_log::{Target, TargetKind};
|
||||
|
||||
use commands::{auth, cob, diff, identity, inbox, profile, repo, startup, thread};
|
||||
@@ -22,24 +24,48 @@ pub fn run() {
|
||||
// survives the pipes that `npm run tauri dev` puts between the app and the
|
||||
// terminal, which `std::io::IsTerminal` on our own stdout would not.
|
||||
let terminal = std::env::var_os("TERM").is_some();
|
||||
let builder = tauri::Builder::default().plugin(
|
||||
tauri_plugin_log::Builder::new()
|
||||
.level(log::LevelFilter::Info)
|
||||
.max_file_size(5_000_000)
|
||||
.rotation_strategy(tauri_plugin_log::RotationStrategy::KeepOne)
|
||||
.target(Target::new(if terminal {
|
||||
TargetKind::Stdout
|
||||
} else {
|
||||
TargetKind::LogDir { file_name: None }
|
||||
}))
|
||||
.build(),
|
||||
);
|
||||
// A `rad:` link opened while the app runs starts a second instance, which
|
||||
// hands the link to this one and exits; the deep-link plugin delivers it.
|
||||
let builder = tauri::Builder::default()
|
||||
.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.unminimize();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}))
|
||||
.plugin(
|
||||
tauri_plugin_log::Builder::new()
|
||||
.level(log::LevelFilter::Info)
|
||||
.max_file_size(5_000_000)
|
||||
.rotation_strategy(tauri_plugin_log::RotationStrategy::KeepOne)
|
||||
.target(Target::new(if terminal {
|
||||
TargetKind::Stdout
|
||||
} else {
|
||||
TargetKind::LogDir { file_name: None }
|
||||
}))
|
||||
.build(),
|
||||
);
|
||||
|
||||
builder
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
.setup(|app| {
|
||||
// Installers register the scheme; an AppImage or a dev build has to
|
||||
// do it at runtime. It must never keep the app from starting, nor
|
||||
// take the scheme back from a handler the user chose.
|
||||
let unpackaged = cfg!(debug_assertions) || is_appimage(app);
|
||||
if cfg!(any(target_os = "linux", windows))
|
||||
&& unpackaged
|
||||
&& !app.deep_link().is_registered("rad").unwrap_or(false)
|
||||
&& let Err(e) = app.deep_link().register_all()
|
||||
{
|
||||
log::warn!("Unable to register the rad: scheme: {e}");
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
auth::authenticate,
|
||||
auth::init,
|
||||
@@ -105,3 +131,13 @@ pub fn run() {
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn is_appimage(app: &tauri::App) -> bool {
|
||||
app.env().appimage.is_some()
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
fn is_appimage(_app: &tauri::App) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -69,5 +69,12 @@
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
},
|
||||
"plugins": {
|
||||
"deep-link": {
|
||||
"desktop": {
|
||||
"schemes": ["rad"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,8 @@ labelled with the repo name, such as `heartwood: src/lib.rs`. Either
|
||||
navigates in-app when the repo is local and falls back to the configured
|
||||
`publicExplorer`, which a right click also offers. A resource a client does
|
||||
not support renders as a plain link. Sanitizers allow `rad:` and `did:`
|
||||
hrefs, which are inert without a registered handler.
|
||||
hrefs, which cannot execute; this app also registers as the OS `rad:`
|
||||
handler, so a link clicked elsewhere opens in it.
|
||||
|
||||
## Consequences
|
||||
|
||||
|
||||
Generated
+10
@@ -15,6 +15,7 @@
|
||||
"@pierre/trees": "1.0.0-beta.5",
|
||||
"@tauri-apps/api": "^2.11.1",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
|
||||
"@tauri-apps/plugin-deep-link": "^2.4.10",
|
||||
"@tauri-apps/plugin-dialog": "^2.7.2",
|
||||
"@tauri-apps/plugin-log": "^2.9.0",
|
||||
"@tauri-apps/plugin-shell": "^2.3.5",
|
||||
@@ -1267,6 +1268,15 @@
|
||||
"@tauri-apps/api": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-deep-link": {
|
||||
"version": "2.4.10",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-deep-link/-/plugin-deep-link-2.4.10.tgz",
|
||||
"integrity": "sha512-SuXI47ORUzYuwG6c8dluLDrStTvli5bAZDRa2+VuUEzK/9TEa7FZz7UKXBQriL7+CPFXRHQsz5tSocc47tRXQQ==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-dialog": {
|
||||
"version": "2.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.7.2.tgz",
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"@pierre/trees": "1.0.0-beta.5",
|
||||
"@tauri-apps/api": "^2.11.1",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
|
||||
"@tauri-apps/plugin-deep-link": "^2.4.10",
|
||||
"@tauri-apps/plugin-dialog": "^2.7.2",
|
||||
"@tauri-apps/plugin-log": "^2.9.0",
|
||||
"@tauri-apps/plugin-shell": "^2.3.5",
|
||||
|
||||
+3
-1
@@ -14,6 +14,7 @@
|
||||
resetFontSize,
|
||||
} from "@app/lib/appearance.svelte";
|
||||
import { checkAuth, startup } from "@app/lib/auth.svelte";
|
||||
import { deepLinksReady, listenForDeepLinks } from "@app/lib/deepLink";
|
||||
import { nodeRunning } from "@app/lib/events";
|
||||
import { dynamicInterval } from "@app/lib/interval";
|
||||
import { invoke } from "@app/lib/invoke";
|
||||
@@ -197,9 +198,10 @@
|
||||
setUnlistenNodeEvents(() => clearInterval(interval));
|
||||
}
|
||||
|
||||
void listenForDeepLinks();
|
||||
try {
|
||||
await invoke("authenticate");
|
||||
void router.loadFromLocation();
|
||||
void router.loadFromLocation().then(deepLinksReady);
|
||||
dynamicInterval(
|
||||
"auth",
|
||||
checkAuth,
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { ErrorWrapper } from "@bindings/error/ErrorWrapper";
|
||||
|
||||
import { get } from "svelte/store";
|
||||
|
||||
import { deepLinksReady } from "@app/lib/deepLink";
|
||||
import { dynamicInterval } from "@app/lib/interval";
|
||||
import { invoke } from "@app/lib/invoke";
|
||||
import * as router from "@app/lib/router";
|
||||
@@ -23,7 +24,9 @@ export async function checkAuth() {
|
||||
import.meta.env.VITE_AUTH_LONG_DELAY || 30_000,
|
||||
);
|
||||
if (get(router.activeRouteStore).resource === "booting") {
|
||||
void router.push({ resource: "inbox" });
|
||||
void router.push({ resource: "inbox" }).then(deepLinksReady);
|
||||
} else {
|
||||
deepLinksReady();
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as ErrorWrapper;
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
import type { RepoRoute } from "@app/views/repo/router";
|
||||
import type { Config } from "@bindings/config/Config";
|
||||
|
||||
import { cachedConfig, cachedRepoById } from "@app/lib/invoke";
|
||||
import { referenceRoute } from "@app/lib/mentions";
|
||||
import { show } from "@app/lib/modal";
|
||||
import type { RadReference, RadUri } from "@app/lib/radUri";
|
||||
import { parseRadUri } from "@app/lib/radUri";
|
||||
import * as router from "@app/lib/router";
|
||||
import { explorerHost, explorerLink } from "@app/lib/utils";
|
||||
|
||||
import OpenInBrowser from "@app/modals/OpenInBrowser.svelte";
|
||||
|
||||
export type DeepLinkTarget =
|
||||
{ type: "route"; route: RepoRoute } | { type: "external"; url: string };
|
||||
|
||||
export function parseDeepLink(
|
||||
link: string,
|
||||
): { type: "uri"; uri: RadUri } | undefined {
|
||||
const uri = parseRadUri(link.trim());
|
||||
|
||||
return uri && { type: "uri", uri };
|
||||
}
|
||||
|
||||
export function deepLinkTarget(
|
||||
reference: RadReference,
|
||||
local: boolean,
|
||||
config: Config,
|
||||
): DeepLinkTarget | undefined {
|
||||
const route = local ? referenceRoute(reference) : undefined;
|
||||
if (route) return { type: "route", route };
|
||||
|
||||
const url = explorerLink(reference, config);
|
||||
|
||||
return url ? { type: "external", url } : undefined;
|
||||
}
|
||||
|
||||
// Links can arrive before the app has authenticated and routed, so they wait
|
||||
// until it has, and are opened one at a time.
|
||||
export function createLinkQueue(open: (link: string) => Promise<void>) {
|
||||
const pending: string[] = [];
|
||||
let ready = false;
|
||||
let flushing = false;
|
||||
|
||||
async function flush() {
|
||||
if (!ready || flushing) return;
|
||||
flushing = true;
|
||||
try {
|
||||
for (let link = pending.shift(); link; link = pending.shift()) {
|
||||
await open(link).catch(console.error);
|
||||
}
|
||||
} finally {
|
||||
flushing = false;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
receive(links: string[]) {
|
||||
pending.push(...links);
|
||||
void flush();
|
||||
},
|
||||
ready() {
|
||||
ready = true;
|
||||
void flush();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// An untrusted link only ever navigates; it never fetches or seeds, and
|
||||
// leaving the app for the browser needs the user's confirmation.
|
||||
async function openDeepLink(link: string) {
|
||||
const reference = parseDeepLink(link);
|
||||
if (!reference) return;
|
||||
|
||||
const [config, repo] = await Promise.all([
|
||||
cachedConfig(),
|
||||
cachedRepoById(`rad:${reference.uri.repo}`).catch(() => undefined),
|
||||
]);
|
||||
const target = deepLinkTarget(reference, Boolean(repo), config);
|
||||
if (target?.type === "route") {
|
||||
await router.push(target.route);
|
||||
} else if (target?.type === "external") {
|
||||
show({
|
||||
component: OpenInBrowser,
|
||||
props: { url: target.url, host: explorerHost(config) },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const queue = createLinkQueue(openDeepLink);
|
||||
const launchKey = "deepLink:launch";
|
||||
|
||||
export function deepLinksReady() {
|
||||
queue.ready();
|
||||
}
|
||||
|
||||
export async function listenForDeepLinks() {
|
||||
if (!window.__TAURI_INTERNALS__) return;
|
||||
|
||||
const { getCurrent, onOpenUrl } =
|
||||
await import("@tauri-apps/plugin-deep-link");
|
||||
await onOpenUrl(links => queue.receive(links));
|
||||
|
||||
// A reload of the webview reports the same launch link again.
|
||||
const launched = (await getCurrent()) ?? [];
|
||||
const seen = readLaunch();
|
||||
if (launched.length > 0 && JSON.stringify(launched) !== seen) {
|
||||
writeLaunch(JSON.stringify(launched));
|
||||
queue.receive(launched);
|
||||
}
|
||||
}
|
||||
|
||||
function readLaunch(): string | null {
|
||||
try {
|
||||
return sessionStorage.getItem(launchKey);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function writeLaunch(value: string) {
|
||||
try {
|
||||
sessionStorage.setItem(launchKey, value);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<script lang="ts">
|
||||
import { hide } from "@app/lib/modal";
|
||||
|
||||
import Button from "@app/components/Button.svelte";
|
||||
import Icon from "@app/components/Icon.svelte";
|
||||
|
||||
interface Props {
|
||||
url: string;
|
||||
host: string;
|
||||
}
|
||||
|
||||
const { url, host }: Props = $props();
|
||||
|
||||
async function open() {
|
||||
const { open } = await import("@tauri-apps/plugin-shell");
|
||||
await open(url);
|
||||
hide();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.modal {
|
||||
width: 26rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
border-radius: var(--border-radius-lg);
|
||||
background-color: var(--color-surface-canvas);
|
||||
overflow: hidden;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 1.5rem;
|
||||
height: 3.25rem;
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid var(--color-border-subtle);
|
||||
}
|
||||
.title {
|
||||
font: var(--txt-heading-s);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
padding: 1.5rem;
|
||||
font: var(--txt-body-m-regular);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
.url {
|
||||
font: var(--txt-code-regular);
|
||||
color: var(--color-text-tertiary);
|
||||
word-break: break-all;
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
padding: 0 1.5rem 1.5rem;
|
||||
}
|
||||
.confirm-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="modal">
|
||||
<div class="header">
|
||||
<span class="title">Open in browser</span>
|
||||
<Button variant="naked" onclick={hide}>
|
||||
<span style:color="var(--color-text-tertiary)">
|
||||
<Icon name="close" />
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="body">
|
||||
<span>
|
||||
A link was opened for something that isn't on this node. It can be viewed
|
||||
on {host} instead.
|
||||
</span>
|
||||
<span class="url">{url}</span>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<Button variant="outline" onclick={hide}>Cancel</Button>
|
||||
<Button variant="ghost" onclick={() => void open()}>
|
||||
<span class="confirm-label">
|
||||
<Icon name="open-external" />
|
||||
Open in {host}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,120 @@
|
||||
import type { Config } from "@bindings/config/Config";
|
||||
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import {
|
||||
createLinkQueue,
|
||||
deepLinkTarget,
|
||||
parseDeepLink,
|
||||
} from "@app/lib/deepLink";
|
||||
|
||||
const repo = "z3gqcJUoA1n9HaHKufZs5FCSGazv5";
|
||||
const rid = `rad:${repo}`;
|
||||
const oid = "5e4683023476778201ab25c87e1343fbfbe67c89";
|
||||
const nid = "z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi";
|
||||
const config = {
|
||||
publicExplorer: "https://radicle.network/nodes/$host/$rid$path",
|
||||
preferredSeeds: [],
|
||||
} as unknown as Config;
|
||||
const explorer = `https://radicle.network/nodes/rosa.radicle.network/${rid}`;
|
||||
|
||||
function target(link: string, local = true) {
|
||||
return deepLinkTarget(parseDeepLink(link)!, local, config);
|
||||
}
|
||||
|
||||
describe("parseDeepLink", () => {
|
||||
test.each([rid, `rad:///${repo}`, ` ${rid}\n`])("accepts %j", link => {
|
||||
expect(parseDeepLink(link)).toEqual({ type: "uri", uri: { repo } });
|
||||
});
|
||||
|
||||
test.each([
|
||||
`did:key:${nid}`,
|
||||
"https://radicle.network",
|
||||
`web+${rid}`,
|
||||
"rad:bogus",
|
||||
])("rejects %j", link => {
|
||||
expect(parseDeepLink(link)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("deepLinkTarget", () => {
|
||||
test.each([
|
||||
[rid, { resource: "repo.home", rid }],
|
||||
[
|
||||
`${rid}/cob/xyz.radicle.patch/${oid}`,
|
||||
{
|
||||
resource: "repo.patch",
|
||||
rid,
|
||||
patch: oid,
|
||||
status: undefined,
|
||||
reviewId: undefined,
|
||||
},
|
||||
],
|
||||
[`${rid}/commit/${oid}`, { resource: "repo.commit", rid, commit: oid }],
|
||||
[
|
||||
`${rid}/commit/main?path=src/lib.rs#L10`,
|
||||
{ resource: "repo.home", rid, revision: "main", path: "src/lib.rs" },
|
||||
],
|
||||
])("opens %s in-app when the repo is here", (link, route) => {
|
||||
expect(target(link)).toEqual({ type: "route", route });
|
||||
});
|
||||
|
||||
test("opens the explorer when the repo is not here", () => {
|
||||
expect(target(`${rid}/cob/xyz.radicle.issue/${oid}`, false)).toEqual({
|
||||
type: "external",
|
||||
url: `${explorer}/issues/${oid}`,
|
||||
});
|
||||
});
|
||||
|
||||
test("opens the explorer for a page the app lacks", () => {
|
||||
expect(target(`${rid}/cob/dev.radicle.artifact/${oid}`)).toEqual({
|
||||
type: "external",
|
||||
url: `${explorer}/releases/${oid}`,
|
||||
});
|
||||
});
|
||||
|
||||
test("goes nowhere when neither has a page", () => {
|
||||
expect(target(`${rid}/tree/${oid}`)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("createLinkQueue", () => {
|
||||
function setup(fail?: string) {
|
||||
const opened: string[] = [];
|
||||
let running = 0;
|
||||
let overlapped = false;
|
||||
const queue = createLinkQueue(async link => {
|
||||
running++;
|
||||
overlapped ||= running > 1;
|
||||
await new Promise(resolve => setTimeout(resolve, 1));
|
||||
running--;
|
||||
if (link === fail) throw new Error(link);
|
||||
opened.push(link);
|
||||
});
|
||||
return { queue, opened, overlapped: () => overlapped };
|
||||
}
|
||||
const settle = () => new Promise(resolve => setTimeout(resolve, 20));
|
||||
|
||||
test("holds links until the app is ready", async () => {
|
||||
const { queue, opened } = setup();
|
||||
queue.receive(["a"]);
|
||||
await settle();
|
||||
expect(opened).toEqual([]);
|
||||
|
||||
queue.ready();
|
||||
queue.receive(["b"]);
|
||||
await settle();
|
||||
expect(opened).toEqual(["a", "b"]);
|
||||
});
|
||||
|
||||
test("opens links one at a time, past a failing one", async () => {
|
||||
vi.spyOn(console, "error").mockImplementation(() => undefined);
|
||||
const { queue, opened, overlapped } = setup("b");
|
||||
queue.ready();
|
||||
queue.receive(["a", "b"]);
|
||||
queue.receive(["c"]);
|
||||
await settle();
|
||||
expect(opened).toEqual(["a", "c"]);
|
||||
expect(overlapped()).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user