Rewild Your Web
18
fork

Configure Feed

Select the types of activity you want to include in your feed.

atproto: explorer app

Signed-off-by: webbeef <me@webbeef.org>

webbeef e4be8e94 834061d0

+1667 -549
+113 -191
Cargo.lock
··· 225 225 226 226 [[package]] 227 227 name = "android-activity" 228 - version = "0.6.0" 228 + version = "0.6.1" 229 229 source = "registry+https://github.com/rust-lang/crates.io-index" 230 - checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" 230 + checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" 231 231 dependencies = [ 232 232 "android-properties", 233 233 "bitflags 2.11.0", 234 234 "cc", 235 - "cesu8", 236 - "jni 0.21.1", 237 - "jni-sys 0.3.1", 235 + "jni 0.22.4", 238 236 "libc", 239 237 "log", 240 238 "ndk", 241 239 "ndk-context", 242 240 "ndk-sys", 243 241 "num_enum", 244 - "thiserror 1.0.69", 242 + "thiserror 2.0.18", 245 243 ] 246 244 247 245 [[package]] ··· 267 265 268 266 [[package]] 269 267 name = "anstream" 270 - version = "0.6.21" 271 - source = "registry+https://github.com/rust-lang/crates.io-index" 272 - checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" 273 - dependencies = [ 274 - "anstyle", 275 - "anstyle-parse 0.2.7", 276 - "anstyle-query", 277 - "anstyle-wincon", 278 - "colorchoice", 279 - "is_terminal_polyfill", 280 - "utf8parse", 281 - ] 282 - 283 - [[package]] 284 - name = "anstream" 285 268 version = "1.0.0" 286 269 source = "registry+https://github.com/rust-lang/crates.io-index" 287 270 checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" 288 271 dependencies = [ 289 272 "anstyle", 290 - "anstyle-parse 1.0.0", 273 + "anstyle-parse", 291 274 "anstyle-query", 292 275 "anstyle-wincon", 293 276 "colorchoice", ··· 300 283 version = "1.0.14" 301 284 source = "registry+https://github.com/rust-lang/crates.io-index" 302 285 checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" 303 - 304 - [[package]] 305 - name = "anstyle-parse" 306 - version = "0.2.7" 307 - source = "registry+https://github.com/rust-lang/crates.io-index" 308 - checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 309 - dependencies = [ 310 - "utf8parse", 311 - ] 312 286 313 287 [[package]] 314 288 name = "anstyle-parse" ··· 566 540 checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c" 567 541 568 542 [[package]] 543 + name = "atproto-dasl" 544 + version = "0.14.3" 545 + source = "registry+https://github.com/rust-lang/crates.io-index" 546 + checksum = "e5f634261c10c472d173f65d8d509d173698ed32fda8b038a8429fc6557bec8f" 547 + dependencies = [ 548 + "blake3", 549 + "cid", 550 + "futures", 551 + "multihash", 552 + "serde", 553 + "serde_bytes", 554 + "sha2 0.10.9", 555 + "tempfile", 556 + "thiserror 2.0.18", 557 + "tokio", 558 + "tracing", 559 + "url", 560 + ] 561 + 562 + [[package]] 569 563 name = "atproto-identity" 570 - version = "0.13.0" 564 + version = "0.14.3" 571 565 source = "registry+https://github.com/rust-lang/crates.io-index" 572 - checksum = "b956c07726fce812630be63c5cb31b1961cbb70f0a05614278523102d78c3a48" 566 + checksum = "8b74b36a5562cc745842699e3baf92a541b983fc2e07bcd1d88f4df245098f4a" 573 567 dependencies = [ 574 568 "anyhow", 575 569 "async-trait", 570 + "atproto-dasl", 571 + "base64 0.22.1", 572 + "chrono", 573 + "data-encoding", 576 574 "ecdsa", 577 575 "elliptic-curve", 578 576 "hickory-resolver", ··· 584 582 "rand 0.8.5", 585 583 "reqwest 0.12.28", 586 584 "serde", 587 - "serde_ipld_dagcbor", 588 585 "serde_json", 586 + "sha2 0.10.9", 589 587 "thiserror 2.0.18", 590 588 "tokio", 591 589 "tracing", 592 - "urlencoding", 590 + "url", 593 591 ] 594 592 595 593 [[package]] ··· 1255 1253 ] 1256 1254 1257 1255 [[package]] 1258 - name = "cbor4ii" 1259 - version = "0.2.14" 1260 - source = "registry+https://github.com/rust-lang/crates.io-index" 1261 - checksum = "b544cf8c89359205f4f990d0e6f3828db42df85b5dac95d09157a250eb0749c4" 1262 - dependencies = [ 1263 - "serde", 1264 - ] 1265 - 1266 - [[package]] 1267 1256 name = "cc" 1268 1257 version = "1.2.57" 1269 1258 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1428 1417 "core2", 1429 1418 "multibase", 1430 1419 "multihash", 1431 - "serde", 1432 - "serde_bytes", 1433 1420 "unsigned-varint", 1434 1421 ] 1435 1422 ··· 1483 1470 source = "registry+https://github.com/rust-lang/crates.io-index" 1484 1471 checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" 1485 1472 dependencies = [ 1486 - "anstream 1.0.0", 1473 + "anstream", 1487 1474 "anstyle", 1488 1475 "clap_lex", 1489 1476 "strsim", ··· 2582 2569 2583 2570 [[package]] 2584 2571 name = "env_filter" 2585 - version = "1.0.0" 2572 + version = "1.0.1" 2586 2573 source = "registry+https://github.com/rust-lang/crates.io-index" 2587 - checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" 2574 + checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" 2588 2575 dependencies = [ 2589 2576 "log", 2590 2577 "regex", ··· 2592 2579 2593 2580 [[package]] 2594 2581 name = "env_logger" 2595 - version = "0.11.9" 2582 + version = "0.11.10" 2596 2583 source = "registry+https://github.com/rust-lang/crates.io-index" 2597 - checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" 2584 + checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" 2598 2585 dependencies = [ 2599 - "anstream 0.6.21", 2586 + "anstream", 2600 2587 "anstyle", 2601 2588 "env_filter", 2602 2589 "jiff", ··· 5270 5257 5271 5258 [[package]] 5272 5259 name = "ipconfig" 5273 - version = "0.3.2" 5260 + version = "0.3.4" 5274 5261 source = "registry+https://github.com/rust-lang/crates.io-index" 5275 - checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" 5262 + checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" 5276 5263 dependencies = [ 5277 - "socket2 0.5.10", 5264 + "socket2 0.6.3", 5278 5265 "widestring", 5279 - "windows-sys 0.48.0", 5280 - "winreg", 5281 - ] 5282 - 5283 - [[package]] 5284 - name = "ipld-core" 5285 - version = "0.4.3" 5286 - source = "registry+https://github.com/rust-lang/crates.io-index" 5287 - checksum = "090f624976d72f0b0bb71b86d58dc16c15e069193067cb3a3a09d655246cbbda" 5288 - dependencies = [ 5289 - "cid", 5290 - "serde", 5291 - "serde_bytes", 5266 + "windows-registry", 5267 + "windows-result 0.4.1", 5268 + "windows-sys 0.61.2", 5292 5269 ] 5293 5270 5294 5271 [[package]] ··· 5645 5622 ] 5646 5623 5647 5624 [[package]] 5625 + name = "jni" 5626 + version = "0.22.4" 5627 + source = "registry+https://github.com/rust-lang/crates.io-index" 5628 + checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" 5629 + dependencies = [ 5630 + "cfg-if", 5631 + "combine", 5632 + "jni-macros", 5633 + "jni-sys 0.4.1", 5634 + "log", 5635 + "simd_cesu8", 5636 + "thiserror 2.0.18", 5637 + "walkdir", 5638 + "windows-link 0.2.1", 5639 + ] 5640 + 5641 + [[package]] 5642 + name = "jni-macros" 5643 + version = "0.22.4" 5644 + source = "registry+https://github.com/rust-lang/crates.io-index" 5645 + checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" 5646 + dependencies = [ 5647 + "proc-macro2", 5648 + "quote", 5649 + "rustc_version", 5650 + "simd_cesu8", 5651 + "syn 2.0.117", 5652 + ] 5653 + 5654 + [[package]] 5648 5655 name = "jni-sys" 5649 5656 version = "0.3.1" 5650 5657 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 5894 5901 5895 5902 [[package]] 5896 5903 name = "libredox" 5897 - version = "0.1.14" 5904 + version = "0.1.15" 5898 5905 source = "registry+https://github.com/rust-lang/crates.io-index" 5899 - checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" 5906 + checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" 5900 5907 dependencies = [ 5901 5908 "bitflags 2.11.0", 5902 5909 "libc", ··· 6366 6373 checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" 6367 6374 dependencies = [ 6368 6375 "core2", 6369 - "serde", 6370 6376 "unsigned-varint", 6371 6377 ] 6372 6378 ··· 8062 8068 source = "registry+https://github.com/rust-lang/crates.io-index" 8063 8069 checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" 8064 8070 dependencies = [ 8065 - "toml_edit 0.25.5+spec-1.1.0", 8071 + "toml_edit 0.25.8+spec-1.1.0", 8066 8072 ] 8067 8073 8068 8074 [[package]] ··· 9050 9056 [[package]] 9051 9057 name = "selectors" 9052 9058 version = "0.36.1" 9053 - source = "git+https://github.com/servo/stylo?rev=f9d940629a7fd0389cc4ecb58c12927b09286478#f9d940629a7fd0389cc4ecb58c12927b09286478" 9059 + source = "git+https://github.com/servo/stylo?rev=1e32f24e7eec55f45c5be6c6cd090c38e8318e96#1e32f24e7eec55f45c5be6c6cd090c38e8318e96" 9054 9060 dependencies = [ 9055 9061 "bitflags 2.11.0", 9056 9062 "cssparser", ··· 9138 9144 ] 9139 9145 9140 9146 [[package]] 9141 - name = "serde_ipld_dagcbor" 9142 - version = "0.6.4" 9143 - source = "registry+https://github.com/rust-lang/crates.io-index" 9144 - checksum = "46182f4f08349a02b45c998ba3215d3f9de826246ba02bb9dddfe9a2a2100778" 9145 - dependencies = [ 9146 - "cbor4ii", 9147 - "ipld-core", 9148 - "scopeguard", 9149 - "serde", 9150 - ] 9151 - 9152 - [[package]] 9153 9147 name = "serde_json" 9154 9148 version = "1.0.149" 9155 9149 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 9185 9179 9186 9180 [[package]] 9187 9181 name = "serde_spanned" 9188 - version = "1.0.4" 9182 + version = "1.1.0" 9189 9183 source = "registry+https://github.com/rust-lang/crates.io-index" 9190 - checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" 9184 + checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98" 9191 9185 dependencies = [ 9192 9186 "serde_core", 9193 9187 ] ··· 10698 10692 [[package]] 10699 10693 name = "servo_arc" 10700 10694 version = "0.4.3" 10701 - source = "git+https://github.com/servo/stylo?rev=f9d940629a7fd0389cc4ecb58c12927b09286478#f9d940629a7fd0389cc4ecb58c12927b09286478" 10695 + source = "git+https://github.com/servo/stylo?rev=1e32f24e7eec55f45c5be6c6cd090c38e8318e96#1e32f24e7eec55f45c5be6c6cd090c38e8318e96" 10702 10696 dependencies = [ 10703 10697 "serde", 10704 10698 "stable_deref_trait", ··· 10799 10793 version = "0.3.8" 10800 10794 source = "registry+https://github.com/rust-lang/crates.io-index" 10801 10795 checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" 10796 + 10797 + [[package]] 10798 + name = "simd_cesu8" 10799 + version = "1.1.1" 10800 + source = "registry+https://github.com/rust-lang/crates.io-index" 10801 + checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" 10802 + dependencies = [ 10803 + "rustc_version", 10804 + "simdutf8", 10805 + ] 10802 10806 10803 10807 [[package]] 10804 10808 name = "simd_helpers" ··· 11163 11167 [[package]] 11164 11168 name = "stylo" 11165 11169 version = "0.14.0" 11166 - source = "git+https://github.com/servo/stylo?rev=f9d940629a7fd0389cc4ecb58c12927b09286478#f9d940629a7fd0389cc4ecb58c12927b09286478" 11170 + source = "git+https://github.com/servo/stylo?rev=1e32f24e7eec55f45c5be6c6cd090c38e8318e96#1e32f24e7eec55f45c5be6c6cd090c38e8318e96" 11167 11171 dependencies = [ 11168 11172 "app_units", 11169 11173 "arrayvec", ··· 11219 11223 [[package]] 11220 11224 name = "stylo_atoms" 11221 11225 version = "0.14.0" 11222 - source = "git+https://github.com/servo/stylo?rev=f9d940629a7fd0389cc4ecb58c12927b09286478#f9d940629a7fd0389cc4ecb58c12927b09286478" 11226 + source = "git+https://github.com/servo/stylo?rev=1e32f24e7eec55f45c5be6c6cd090c38e8318e96#1e32f24e7eec55f45c5be6c6cd090c38e8318e96" 11223 11227 dependencies = [ 11224 11228 "string_cache", 11225 11229 "string_cache_codegen", ··· 11228 11232 [[package]] 11229 11233 name = "stylo_derive" 11230 11234 version = "0.14.0" 11231 - source = "git+https://github.com/servo/stylo?rev=f9d940629a7fd0389cc4ecb58c12927b09286478#f9d940629a7fd0389cc4ecb58c12927b09286478" 11235 + source = "git+https://github.com/servo/stylo?rev=1e32f24e7eec55f45c5be6c6cd090c38e8318e96#1e32f24e7eec55f45c5be6c6cd090c38e8318e96" 11232 11236 dependencies = [ 11233 11237 "darling", 11234 11238 "proc-macro2", ··· 11240 11244 [[package]] 11241 11245 name = "stylo_dom" 11242 11246 version = "0.14.0" 11243 - source = "git+https://github.com/servo/stylo?rev=f9d940629a7fd0389cc4ecb58c12927b09286478#f9d940629a7fd0389cc4ecb58c12927b09286478" 11247 + source = "git+https://github.com/servo/stylo?rev=1e32f24e7eec55f45c5be6c6cd090c38e8318e96#1e32f24e7eec55f45c5be6c6cd090c38e8318e96" 11244 11248 dependencies = [ 11245 11249 "bitflags 2.11.0", 11246 11250 "stylo_malloc_size_of", ··· 11249 11253 [[package]] 11250 11254 name = "stylo_malloc_size_of" 11251 11255 version = "0.14.0" 11252 - source = "git+https://github.com/servo/stylo?rev=f9d940629a7fd0389cc4ecb58c12927b09286478#f9d940629a7fd0389cc4ecb58c12927b09286478" 11256 + source = "git+https://github.com/servo/stylo?rev=1e32f24e7eec55f45c5be6c6cd090c38e8318e96#1e32f24e7eec55f45c5be6c6cd090c38e8318e96" 11253 11257 dependencies = [ 11254 11258 "app_units", 11255 11259 "cssparser", ··· 11266 11270 [[package]] 11267 11271 name = "stylo_static_prefs" 11268 11272 version = "0.14.0" 11269 - source = "git+https://github.com/servo/stylo?rev=f9d940629a7fd0389cc4ecb58c12927b09286478#f9d940629a7fd0389cc4ecb58c12927b09286478" 11273 + source = "git+https://github.com/servo/stylo?rev=1e32f24e7eec55f45c5be6c6cd090c38e8318e96#1e32f24e7eec55f45c5be6c6cd090c38e8318e96" 11270 11274 11271 11275 [[package]] 11272 11276 name = "stylo_traits" 11273 11277 version = "0.14.0" 11274 - source = "git+https://github.com/servo/stylo?rev=f9d940629a7fd0389cc4ecb58c12927b09286478#f9d940629a7fd0389cc4ecb58c12927b09286478" 11278 + source = "git+https://github.com/servo/stylo?rev=1e32f24e7eec55f45c5be6c6cd090c38e8318e96#1e32f24e7eec55f45c5be6c6cd090c38e8318e96" 11275 11279 dependencies = [ 11276 11280 "app_units", 11277 11281 "bitflags 2.11.0", ··· 11773 11777 [[package]] 11774 11778 name = "to_shmem" 11775 11779 version = "0.3.0" 11776 - source = "git+https://github.com/servo/stylo?rev=f9d940629a7fd0389cc4ecb58c12927b09286478#f9d940629a7fd0389cc4ecb58c12927b09286478" 11780 + source = "git+https://github.com/servo/stylo?rev=1e32f24e7eec55f45c5be6c6cd090c38e8318e96#1e32f24e7eec55f45c5be6c6cd090c38e8318e96" 11777 11781 dependencies = [ 11778 11782 "cssparser", 11779 11783 "servo_arc", ··· 11786 11790 [[package]] 11787 11791 name = "to_shmem_derive" 11788 11792 version = "0.1.0" 11789 - source = "git+https://github.com/servo/stylo?rev=f9d940629a7fd0389cc4ecb58c12927b09286478#f9d940629a7fd0389cc4ecb58c12927b09286478" 11793 + source = "git+https://github.com/servo/stylo?rev=1e32f24e7eec55f45c5be6c6cd090c38e8318e96#1e32f24e7eec55f45c5be6c6cd090c38e8318e96" 11790 11794 dependencies = [ 11791 11795 "darling", 11792 11796 "proc-macro2", ··· 11910 11914 dependencies = [ 11911 11915 "indexmap", 11912 11916 "serde_core", 11913 - "serde_spanned 1.0.4", 11917 + "serde_spanned 1.1.0", 11914 11918 "toml_datetime 0.7.5+spec-1.1.0", 11915 11919 "toml_parser", 11916 11920 "toml_writer", ··· 11937 11941 11938 11942 [[package]] 11939 11943 name = "toml_datetime" 11940 - version = "1.0.1+spec-1.1.0" 11944 + version = "1.1.0+spec-1.1.0" 11941 11945 source = "registry+https://github.com/rust-lang/crates.io-index" 11942 - checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" 11946 + checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" 11943 11947 dependencies = [ 11944 11948 "serde_core", 11945 11949 ] ··· 11970 11974 11971 11975 [[package]] 11972 11976 name = "toml_edit" 11973 - version = "0.25.5+spec-1.1.0" 11977 + version = "0.25.8+spec-1.1.0" 11974 11978 source = "registry+https://github.com/rust-lang/crates.io-index" 11975 - checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1" 11979 + checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c" 11976 11980 dependencies = [ 11977 11981 "indexmap", 11978 - "toml_datetime 1.0.1+spec-1.1.0", 11982 + "toml_datetime 1.1.0+spec-1.1.0", 11979 11983 "toml_parser", 11980 11984 "winnow 1.0.0", 11981 11985 ] 11982 11986 11983 11987 [[package]] 11984 11988 name = "toml_parser" 11985 - version = "1.0.10+spec-1.1.0" 11989 + version = "1.1.0+spec-1.1.0" 11986 11990 source = "registry+https://github.com/rust-lang/crates.io-index" 11987 - checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" 11991 + checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" 11988 11992 dependencies = [ 11989 11993 "winnow 1.0.0", 11990 11994 ] 11991 11995 11992 11996 [[package]] 11993 11997 name = "toml_writer" 11994 - version = "1.0.7+spec-1.1.0" 11998 + version = "1.1.0+spec-1.1.0" 11995 11999 source = "registry+https://github.com/rust-lang/crates.io-index" 11996 - checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d" 12000 + checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed" 11997 12001 11998 12002 [[package]] 11999 12003 name = "topological-sort" ··· 12297 12301 12298 12302 [[package]] 12299 12303 name = "unicode-segmentation" 12300 - version = "1.12.0" 12304 + version = "1.13.0" 12301 12305 source = "registry+https://github.com/rust-lang/crates.io-index" 12302 - checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 12306 + checksum = "a559e63b5d8004e12f9bce88af5c6d939c58de839b7532cfe9653846cedd2a9e" 12303 12307 12304 12308 [[package]] 12305 12309 name = "unicode-vo" ··· 12369 12373 "serde", 12370 12374 "serde_derive", 12371 12375 ] 12372 - 12373 - [[package]] 12374 - name = "urlencoding" 12375 - version = "2.1.3" 12376 - source = "registry+https://github.com/rust-lang/crates.io-index" 12377 - checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" 12378 12376 12379 12377 [[package]] 12380 12378 name = "urlpattern" ··· 13504 13502 13505 13503 [[package]] 13506 13504 name = "windows-sys" 13507 - version = "0.48.0" 13508 - source = "registry+https://github.com/rust-lang/crates.io-index" 13509 - checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 13510 - dependencies = [ 13511 - "windows-targets 0.48.5", 13512 - ] 13513 - 13514 - [[package]] 13515 - name = "windows-sys" 13516 13505 version = "0.52.0" 13517 13506 source = "registry+https://github.com/rust-lang/crates.io-index" 13518 13507 checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" ··· 13564 13553 13565 13554 [[package]] 13566 13555 name = "windows-targets" 13567 - version = "0.48.5" 13568 - source = "registry+https://github.com/rust-lang/crates.io-index" 13569 - checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 13570 - dependencies = [ 13571 - "windows_aarch64_gnullvm 0.48.5", 13572 - "windows_aarch64_msvc 0.48.5", 13573 - "windows_i686_gnu 0.48.5", 13574 - "windows_i686_msvc 0.48.5", 13575 - "windows_x86_64_gnu 0.48.5", 13576 - "windows_x86_64_gnullvm 0.48.5", 13577 - "windows_x86_64_msvc 0.48.5", 13578 - ] 13579 - 13580 - [[package]] 13581 - name = "windows-targets" 13582 13556 version = "0.52.6" 13583 13557 source = "registry+https://github.com/rust-lang/crates.io-index" 13584 13558 checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" ··· 13636 13610 13637 13611 [[package]] 13638 13612 name = "windows_aarch64_gnullvm" 13639 - version = "0.48.5" 13640 - source = "registry+https://github.com/rust-lang/crates.io-index" 13641 - checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 13642 - 13643 - [[package]] 13644 - name = "windows_aarch64_gnullvm" 13645 13613 version = "0.52.6" 13646 13614 source = "registry+https://github.com/rust-lang/crates.io-index" 13647 13615 checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" ··· 13660 13628 13661 13629 [[package]] 13662 13630 name = "windows_aarch64_msvc" 13663 - version = "0.48.5" 13664 - source = "registry+https://github.com/rust-lang/crates.io-index" 13665 - checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 13666 - 13667 - [[package]] 13668 - name = "windows_aarch64_msvc" 13669 13631 version = "0.52.6" 13670 13632 source = "registry+https://github.com/rust-lang/crates.io-index" 13671 13633 checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" ··· 13684 13646 13685 13647 [[package]] 13686 13648 name = "windows_i686_gnu" 13687 - version = "0.48.5" 13688 - source = "registry+https://github.com/rust-lang/crates.io-index" 13689 - checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 13690 - 13691 - [[package]] 13692 - name = "windows_i686_gnu" 13693 13649 version = "0.52.6" 13694 13650 source = "registry+https://github.com/rust-lang/crates.io-index" 13695 13651 checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" ··· 13717 13673 version = "0.42.2" 13718 13674 source = "registry+https://github.com/rust-lang/crates.io-index" 13719 13675 checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 13720 - 13721 - [[package]] 13722 - name = "windows_i686_msvc" 13723 - version = "0.48.5" 13724 - source = "registry+https://github.com/rust-lang/crates.io-index" 13725 - checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 13726 13676 13727 13677 [[package]] 13728 13678 name = "windows_i686_msvc" ··· 13744 13694 13745 13695 [[package]] 13746 13696 name = "windows_x86_64_gnu" 13747 - version = "0.48.5" 13748 - source = "registry+https://github.com/rust-lang/crates.io-index" 13749 - checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 13750 - 13751 - [[package]] 13752 - name = "windows_x86_64_gnu" 13753 13697 version = "0.52.6" 13754 13698 source = "registry+https://github.com/rust-lang/crates.io-index" 13755 13699 checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" ··· 13768 13712 13769 13713 [[package]] 13770 13714 name = "windows_x86_64_gnullvm" 13771 - version = "0.48.5" 13772 - source = "registry+https://github.com/rust-lang/crates.io-index" 13773 - checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 13774 - 13775 - [[package]] 13776 - name = "windows_x86_64_gnullvm" 13777 13715 version = "0.52.6" 13778 13716 source = "registry+https://github.com/rust-lang/crates.io-index" 13779 13717 checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" ··· 13789 13727 version = "0.42.2" 13790 13728 source = "registry+https://github.com/rust-lang/crates.io-index" 13791 13729 checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 13792 - 13793 - [[package]] 13794 - name = "windows_x86_64_msvc" 13795 - version = "0.48.5" 13796 - source = "registry+https://github.com/rust-lang/crates.io-index" 13797 - checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 13798 13730 13799 13731 [[package]] 13800 13732 name = "windows_x86_64_msvc" ··· 13882 13814 checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" 13883 13815 dependencies = [ 13884 13816 "memchr", 13885 - ] 13886 - 13887 - [[package]] 13888 - name = "winreg" 13889 - version = "0.50.0" 13890 - source = "registry+https://github.com/rust-lang/crates.io-index" 13891 - checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 13892 - dependencies = [ 13893 - "cfg-if", 13894 - "windows-sys 0.48.0", 13895 13817 ] 13896 13818 13897 13819 [[package]]
+13 -13
Cargo.toml
··· 41 41 brotli = "8.0.2" 42 42 bytemuck = "1" 43 43 byteorder = "1.5" 44 - canvas = { package = "servo-canvas", version = "0.0.6", path = "source/components/canvas" } 45 - canvas_traits = { package = "servo-canvas-traits", path = "source/components/shared/canvas" } 46 44 cbc = "0.1.2" 47 45 cfg-if = "1.0.4" 48 46 chacha20poly1305 = "0.10" 49 47 chardetng = "0.1" 50 48 chrono = { version = "0.4", features = ["serde"] } 51 49 cipher = { version = "0.4.4", features = ["alloc"] } 52 - constellation = { package = "servo-constellation", version = "0.0.6", path = "source/components/constellation" } 53 - constellation_traits = { package = "servo-constellation-traits", path = "source/components/shared/constellation" } 54 50 content-security-policy = { version = "0.7.0", features = ["serde"] } 55 51 cookie = { package = "cookie", version = "0.18" } 56 52 crossbeam-channel = "0.5" ··· 183 179 sea-query = { version = "1.0.0-rc.30", default-features = false, features = ["backend-sqlite", "derive"] } 184 180 sea-query-rusqlite = { version = "0.8.0-rc.15" } 185 181 sec1 = "0.7" 186 - selectors = { git = "https://github.com/servo/stylo", rev = "f9d940629a7fd0389cc4ecb58c12927b09286478" } 182 + selectors = { git = "https://github.com/servo/stylo", rev = "1e32f24e7eec55f45c5be6c6cd090c38e8318e96" } 187 183 serde = "1.0.228" 188 184 serde_bytes = "0.11" 189 185 serde_core = "1.0.226" ··· 195 191 servo-base = { version = "0.0.6", path = "source/components/shared/base" } 196 192 servo-bluetooth = { version = "0.0.6", path = "source/components/bluetooth" } 197 193 servo-bluetooth-traits = { version = "0.0.6", path = "source/components/shared/bluetooth" } 194 + servo-canvas = { version = "0.0.6", path = "source/components/canvas" } 195 + servo-canvas-traits = { version = "0.0.6", path = "source/components/shared/canvas" } 198 196 servo-config = { package = "servo-config", version = "0.0.6", path = "source/components/config" } 199 197 servo-config-macro = { package = "servo-config-macro", version = "0.0.6", path = "source/components/config/macro" } 198 + servo-constellation = { version = "0.0.6", path = "source/components/constellation" } 199 + servo-constellation-traits = { version = "0.0.6", path = "source/components/shared/constellation" } 200 200 servo-geometry = { package = "servo-geometry", version = "0.0.6", path = "source/components/geometry" } 201 201 servo-media = { path = "source/components/media/servo-media" } 202 202 servo-media-audio = { version = "0.0.6", path = "source/components/media/audio" } ··· 212 212 servo-media-webrtc = { version = "0.0.6", path = "source/components/media/webrtc" } 213 213 servo_allocator = { package = "servo-allocator", version = "0.0.6", path = "source/components/allocator" } 214 214 servo-tracing = { path = "source/components/servo_tracing" } 215 - servo_arc = { git = "https://github.com/servo/stylo", rev = "f9d940629a7fd0389cc4ecb58c12927b09286478" } 215 + servo_arc = { git = "https://github.com/servo/stylo", rev = "1e32f24e7eec55f45c5be6c6cd090c38e8318e96" } 216 216 servo-url = { version = "0.0.6", path = "source/components/url" } 217 217 sha1 = "0.10" 218 218 sha2 = "0.10" ··· 223 223 storage_traits = { package = "servo-storage-traits", path = "source/components/shared/storage" } 224 224 string_cache = "0.9" 225 225 strum = { version = "0.27", features = ["derive"] } 226 - stylo = { git = "https://github.com/servo/stylo", rev = "f9d940629a7fd0389cc4ecb58c12927b09286478" } 227 - stylo_atoms = { git = "https://github.com/servo/stylo", rev = "f9d940629a7fd0389cc4ecb58c12927b09286478" } 228 - stylo_config = { git = "https://github.com/servo/stylo", rev = "f9d940629a7fd0389cc4ecb58c12927b09286478" } 229 - stylo_dom = { git = "https://github.com/servo/stylo", rev = "f9d940629a7fd0389cc4ecb58c12927b09286478" } 230 - stylo_malloc_size_of = { git = "https://github.com/servo/stylo", rev = "f9d940629a7fd0389cc4ecb58c12927b09286478" } 231 - stylo_static_prefs = { git = "https://github.com/servo/stylo", rev = "f9d940629a7fd0389cc4ecb58c12927b09286478" } 232 - stylo_traits = { git = "https://github.com/servo/stylo", rev = "f9d940629a7fd0389cc4ecb58c12927b09286478" } 226 + stylo = { git = "https://github.com/servo/stylo", rev = "1e32f24e7eec55f45c5be6c6cd090c38e8318e96" } 227 + stylo_atoms = { git = "https://github.com/servo/stylo", rev = "1e32f24e7eec55f45c5be6c6cd090c38e8318e96" } 228 + stylo_config = { git = "https://github.com/servo/stylo", rev = "1e32f24e7eec55f45c5be6c6cd090c38e8318e96" } 229 + stylo_dom = { git = "https://github.com/servo/stylo", rev = "1e32f24e7eec55f45c5be6c6cd090c38e8318e96" } 230 + stylo_malloc_size_of = { git = "https://github.com/servo/stylo", rev = "1e32f24e7eec55f45c5be6c6cd090c38e8318e96" } 231 + stylo_static_prefs = { git = "https://github.com/servo/stylo", rev = "1e32f24e7eec55f45c5be6c6cd090c38e8318e96" } 232 + stylo_traits = { git = "https://github.com/servo/stylo", rev = "1e32f24e7eec55f45c5be6c6cd090c38e8318e96" } 233 233 surfman = { version = "0.11.0", features = ["chains"] } 234 234 syn = { version = "2", default-features = false, features = ["clone-impls", "derive", "parsing"] } 235 235 synstructure = "0.13"
+2 -1
crates/beaver_shell/src/vhost.rs
··· 50 50 51 51 // Currently recognized: system, shared, keyboard, homescreen, theme 52 52 // TODO: don't hardcode 53 - if app != "homescreen" && 53 + if app != "atproto" && 54 + app != "homescreen" && 54 55 app != "keyboard" && 55 56 app != "settings" && 56 57 app != "shared" &&
+1 -1
forkme.lock
··· 1 - c9926615334912b17ea31ff95df10795c397c3ec 1 + e095bc4cef5ed58917a9a7f257cfe354418e8ccd
+33 -14
patches/Cargo.lock.patch
··· 518 518 name = "mozangle" 519 519 version = "0.5.5" 520 520 source = "registry+https://github.com/rust-lang/crates.io-index" 521 + @@ -5069,9 +5364,9 @@ 522 + 523 + [[package]] 524 + name = "mozjs" 525 + -version = "0.15.7" 526 + +version = "0.15.5" 527 + source = "registry+https://github.com/rust-lang/crates.io-index" 528 + -checksum = "226edc79aa3f990a61330d4011471910273f6ee6bbfd0dcb93b18f5a03505f6b" 529 + +checksum = "6efb4331060c22cc64e136c11c810eec22c063dd7cdeffdee394cb4e7f4d2880" 530 + dependencies = [ 531 + "bindgen", 532 + "cc", 533 + @@ -5084,9 +5379,9 @@ 534 + 535 + [[package]] 536 + name = "mozjs_sys" 537 + -version = "0.140.8-2" 538 + +version = "0.140.8-0" 539 + source = "registry+https://github.com/rust-lang/crates.io-index" 540 + -checksum = "4b50224a02c96e1e703f7d055377431333dfc5cd3a09ccf03f8a2c8a156c7af8" 541 + +checksum = "2d84f9f721dd87b500958f5815c118adb8adcd8e310e30b0188647c179ea7bcb" 542 + dependencies = [ 543 + "bindgen", 544 + "cc", 521 545 @@ -5107,6 +5402,29 @@ 522 546 checksum = "956787520e75e9bd233246045d19f42fb73242759cc57fba9611d940ae96d4b0" 523 547 ··· 987 1011 name = "tokio-rustls" 988 1012 version = "0.26.4" 989 1013 source = "registry+https://github.com/rust-lang/crates.io-index" 990 - @@ -10022,11 +10585,30 @@ 1014 + @@ -10022,6 +10585,25 @@ 991 1015 "futures-util", 992 1016 "pin-project-lite", 993 1017 "sync_wrapper", 994 1018 + "tokio", 995 - "tower-layer", 996 - "tower-service", 997 - ] 998 - 999 - [[package]] 1019 + + "tower-layer", 1020 + + "tower-service", 1021 + +] 1022 + + 1023 + +[[package]] 1000 1024 +name = "tower-http" 1001 1025 +version = "0.6.8" 1002 1026 +source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1010 1034 + "iri-string", 1011 1035 + "pin-project-lite", 1012 1036 + "tower", 1013 - + "tower-layer", 1014 - + "tower-service", 1015 - +] 1016 - + 1017 - +[[package]] 1018 - name = "tower-layer" 1019 - version = "0.3.3" 1020 - source = "registry+https://github.com/rust-lang/crates.io-index" 1037 + "tower-layer", 1038 + "tower-service", 1039 + ] 1021 1040 @@ -10305,6 +10887,12 @@ 1022 1041 ] 1023 1042
+11
patches/Cargo.toml.patch
··· 1 + --- original 2 + +++ modified 3 + @@ -108,7 +108,7 @@ 4 + indexmap = { version = "2.11.4", features = ["std"] } 5 + ipc-channel = "0.21" 6 + itertools = "0.14" 7 + -js = { package = "mozjs", version = "=0.15.7", default-features = false, features = ["libz-sys", "intl"] } 8 + +js = { package = "mozjs", version = "=0.15.5", default-features = false, features = ["libz-sys", "intl"] } 9 + keyboard-types = { version = "0.8.3", features = ["serde", "webdriver"] } 10 + kurbo = { version = "0.12", features = ["euclid"] } 11 + libc = "0.2"
+5 -5
patches/components/constellation/Cargo.toml.patch
··· 5 5 [dependencies] 6 6 backtrace = { workspace = true } 7 7 +beaver-p2p = { path = "../../../crates/beaver_p2p" } 8 - canvas = { workspace = true } 9 - canvas_traits = { workspace = true } 10 - constellation_traits = { workspace = true } 11 - @@ -37,6 +38,8 @@ 8 + content-security-policy = { workspace = true } 9 + crossbeam-channel = { workspace = true } 10 + devtools_traits = { workspace = true } 11 + @@ -34,6 +35,8 @@ 12 12 euclid = { workspace = true } 13 13 fonts = { workspace = true } 14 14 ipc-channel = { workspace = true } ··· 17 17 keyboard-types = { workspace = true } 18 18 layout_api = { workspace = true } 19 19 log = { workspace = true } 20 - @@ -45,11 +48,14 @@ 20 + @@ -42,11 +45,14 @@ 21 21 net_traits = { workspace = true } 22 22 paint_api = { workspace = true } 23 23 parking_lot = { workspace = true }
+62 -6
patches/components/constellation/atproto.rs.patch
··· 1 1 --- original 2 2 +++ modified 3 - @@ -0,0 +1,204 @@ 3 + @@ -0,0 +1,260 @@ 4 4 +// SPDX-License-Identifier: AGPL-3.0-or-later 5 5 + 6 6 +use std::sync::Arc; 7 7 + 8 8 +use constellation_traits::{AtProtoRequest, AtProtoResult}; 9 9 +use ipc_channel::ipc::channel; 10 - +use log::error; 10 + +use log::{debug, error}; 11 11 +use net::async_runtime::spawn_task; 12 12 +use net::atproto::session::SessionClient; 13 13 +use net_traits::{AtProtoSessionState, CoreResourceMsg, CoreResourceThread}; ··· 27 27 + let _ = resource_thread.send(CoreResourceMsg::GetAtProtoSession(tx)); 28 28 + let session = rx.recv().unwrap_or(None); 29 29 + 30 - + println!("ATProto session: {session:?}"); 30 + + debug!("ATProto session: {session:?}"); 31 31 + 32 32 + Self { 33 33 + resource_thread, ··· 44 44 + AtProtoRequest::Login(login, password) => self.login(login, password, response), 45 45 + AtProtoRequest::Logout => self.logout(response), 46 46 + AtProtoRequest::Current => self.current(response), 47 + + AtProtoRequest::AuthorizeOrigin(origin) => self.authorize_origin(origin, response), 48 + + AtProtoRequest::RevokeOrigin(origin) => self.revoke_origin(origin, response), 49 + + AtProtoRequest::GetAuthorizedOrigins => self.get_authorized_origins(response), 47 50 + } 48 51 + } 49 52 + ··· 154 157 + let local_session = Arc::clone(&self.session); 155 158 + 156 159 + spawn_task(async move { 157 - + let mut result = SessionClient::current(&session.endpoint, resource_thread).await; 160 + + let mut result = 161 + + SessionClient::current(&session.endpoint, resource_thread, &session.access_jwt) 162 + + .await; 158 163 + if let AtProtoResult::RefreshRequired = result { 159 164 + error!("RefreshRequired"); 160 165 + if let Some(refresh_session) = SessionClient::refresh( ··· 172 177 + local_session, 173 178 + ); 174 179 + 175 - + result = SessionClient::current(&session.endpoint, resource_thread4).await; 180 + + result = SessionClient::current( 181 + + &session.endpoint, 182 + + resource_thread4, 183 + + &refresh_session.access_jwt, 184 + + ) 185 + + .await; 176 186 + } else { 177 187 + result = AtProtoResult::Error; 178 188 + } ··· 192 202 + resource_thread: &CoreResourceThread, 193 203 + local_session: Arc<Mutex<Option<AtProtoSessionState>>>, 194 204 + ) { 195 - + // Update the HTTP state. 205 + + // Preserve authorized_origins from the existing session. 206 + + let authorized_origins = local_session 207 + + .lock() 208 + + .as_ref() 209 + + .map(|s| s.authorized_origins.clone()) 210 + + .unwrap_or_default(); 211 + + 196 212 + let session_msg = AtProtoSessionState { 197 213 + endpoint: endpoint.clone(), 198 214 + access_jwt: access_jwt.to_owned(), 199 215 + refresh_jwt: refresh_jwt.to_owned(), 216 + + authorized_origins, 200 217 + }; 201 218 + 202 219 + let mut lock = local_session.lock(); 203 220 + *lock = Some(session_msg.clone()); 204 221 + 205 222 + let _ = resource_thread.send(CoreResourceMsg::UpdateAtProtoSession(Some(session_msg))); 223 + + } 224 + + 225 + + fn authorize_origin(&self, origin: String, response: GenericCallback<AtProtoResult>) { 226 + + let mut session = self.session.lock(); 227 + + if let Some(ref mut session) = *session { 228 + + if !session.authorized_origins.contains(&origin) { 229 + + session.authorized_origins.push(origin); 230 + + } 231 + + let _ = self 232 + + .resource_thread 233 + + .send(CoreResourceMsg::UpdateAtProtoSession(Some(session.clone()))); 234 + + let _ = response.send(AtProtoResult::OriginUpdated); 235 + + } else { 236 + + let _ = response.send(AtProtoResult::Error); 237 + + } 238 + + } 239 + + 240 + + fn revoke_origin(&self, origin: String, response: GenericCallback<AtProtoResult>) { 241 + + let mut session = self.session.lock(); 242 + + if let Some(ref mut session) = *session { 243 + + session.authorized_origins.retain(|o| o != &origin); 244 + + let _ = self 245 + + .resource_thread 246 + + .send(CoreResourceMsg::UpdateAtProtoSession(Some(session.clone()))); 247 + + let _ = response.send(AtProtoResult::OriginUpdated); 248 + + } else { 249 + + let _ = response.send(AtProtoResult::Error); 250 + + } 251 + + } 252 + + 253 + + fn get_authorized_origins(&self, response: GenericCallback<AtProtoResult>) { 254 + + let session = self.session.lock(); 255 + + if let Some(ref session) = *session { 256 + + let _ = response.send(AtProtoResult::AuthorizedOrigins( 257 + + session.authorized_origins.clone(), 258 + + )); 259 + + } else { 260 + + let _ = response.send(AtProtoResult::AuthorizedOrigins(vec![])); 261 + + } 206 262 + } 207 263 +}
+76
patches/components/devtools/actors/browsing_context.rs.patch
··· 1 + --- original 2 + +++ modified 3 + @@ -207,9 +207,9 @@ 4 + pipeline_id: PipelineId, 5 + outer_window_id: DevtoolsOuterWindowId, 6 + script_sender: GenericSender<DevtoolScriptControlMsg>, 7 + - registry: &ActorRegistry, 8 + + actors: &ActorRegistry, 9 + ) -> BrowsingContextActor { 10 + - let name = registry.new_name::<BrowsingContextActor>(); 11 + + let name = actors.new_name::<BrowsingContextActor>(); 12 + let DevtoolsPageInfo { 13 + title, 14 + url, 15 + @@ -216,7 +216,7 @@ 16 + is_top_level_global, 17 + } = page_info; 18 + 19 + - let accessibility = AccessibilityActor::new(registry.new_name::<AccessibilityActor>()); 20 + + let accessibility = AccessibilityActor::new(actors.new_name::<AccessibilityActor>()); 21 + 22 + let properties = (|| { 23 + let (properties_sender, properties_receiver) = generic_channel::channel()?; 24 + @@ -225,24 +225,24 @@ 25 + })() 26 + .unwrap_or_default(); 27 + let css_properties = 28 + - CssPropertiesActor::new(registry.new_name::<CssPropertiesActor>(), properties); 29 + + CssPropertiesActor::new(actors.new_name::<CssPropertiesActor>(), properties); 30 + 31 + - let inspector = InspectorActor::register(registry, name.clone()); 32 + + let inspector = InspectorActor::register(actors, name.clone()); 33 + 34 + - let reflow = ReflowActor::new(registry.new_name::<ReflowActor>()); 35 + + let reflow = ReflowActor::new(actors.new_name::<ReflowActor>()); 36 + 37 + - let style_sheets = StyleSheetsActor::new(registry.new_name::<StyleSheetsActor>()); 38 + + let style_sheets = StyleSheetsActor::new(actors.new_name::<StyleSheetsActor>()); 39 + 40 + - let tabdesc = TabDescriptorActor::new(registry, name.clone(), is_top_level_global); 41 + + let tabdesc = TabDescriptorActor::new(actors, name.clone(), is_top_level_global); 42 + 43 + let thread = ThreadActor::new( 44 + - registry.new_name::<ThreadActor>(), 45 + + actors.new_name::<ThreadActor>(), 46 + script_sender.clone(), 47 + Some(name.clone()), 48 + ); 49 + 50 + let watcher = WatcherActor::new( 51 + - registry, 52 + + actors, 53 + name.clone(), 54 + SessionContext::new(SessionContextType::BrowserElement), 55 + ); 56 + @@ -270,13 +270,13 @@ 57 + watcher: watcher.name(), 58 + }; 59 + 60 + - registry.register(accessibility); 61 + - registry.register(css_properties); 62 + - registry.register(reflow); 63 + - registry.register(style_sheets); 64 + - registry.register(tabdesc); 65 + - registry.register(thread); 66 + - registry.register(watcher); 67 + + actors.register(accessibility); 68 + + actors.register(css_properties); 69 + + actors.register(reflow); 70 + + actors.register(style_sheets); 71 + + actors.register(tabdesc); 72 + + actors.register(thread); 73 + + actors.register(watcher); 74 + 75 + target 76 + }
+18
patches/components/devtools/actors/tab.rs.patch
··· 1 + --- original 2 + +++ modified 3 + @@ -167,12 +167,12 @@ 4 + 5 + impl TabDescriptorActor { 6 + pub(crate) fn new( 7 + - registry: &ActorRegistry, 8 + + actors: &ActorRegistry, 9 + browsing_context_actor: String, 10 + is_top_level_global: bool, 11 + ) -> TabDescriptorActor { 12 + - let name = registry.new_name::<Self>(); 13 + - let root = registry.find::<RootActor>("root"); 14 + + let name = actors.new_name::<Self>(); 15 + + let root = actors.find::<RootActor>("root"); 16 + root.tabs.borrow_mut().push(name.clone()); 17 + TabDescriptorActor { 18 + name,
+46
patches/components/devtools/actors/watcher.rs.patch
··· 1 + --- original 2 + +++ modified 3 + @@ -423,22 +423,22 @@ 4 + 5 + impl WatcherActor { 6 + pub fn new( 7 + - registry: &ActorRegistry, 8 + + actors: &ActorRegistry, 9 + browsing_context_actor: String, 10 + session_context: SessionContext, 11 + ) -> Self { 12 + - let network_parent = NetworkParentActor::new(registry.new_name::<NetworkParentActor>()); 13 + + let network_parent = NetworkParentActor::new(actors.new_name::<NetworkParentActor>()); 14 + let target_configuration = 15 + - TargetConfigurationActor::new(registry.new_name::<TargetConfigurationActor>()); 16 + + TargetConfigurationActor::new(actors.new_name::<TargetConfigurationActor>()); 17 + let thread_configuration = 18 + - ThreadConfigurationActor::new(registry.new_name::<ThreadConfigurationActor>()); 19 + + ThreadConfigurationActor::new(actors.new_name::<ThreadConfigurationActor>()); 20 + let breakpoint_list = BreakpointListActor::new( 21 + - registry.new_name::<BreakpointListActor>(), 22 + + actors.new_name::<BreakpointListActor>(), 23 + browsing_context_actor.clone(), 24 + ); 25 + 26 + let watcher = Self { 27 + - name: registry.new_name::<WatcherActor>(), 28 + + name: actors.new_name::<WatcherActor>(), 29 + browsing_context_actor, 30 + network_parent: network_parent.name(), 31 + target_configuration: target_configuration.name(), 32 + @@ -447,10 +447,10 @@ 33 + session_context, 34 + }; 35 + 36 + - registry.register(network_parent); 37 + - registry.register(target_configuration); 38 + - registry.register(thread_configuration); 39 + - registry.register(breakpoint_list); 40 + + actors.register(network_parent); 41 + + actors.register(target_configuration); 42 + + actors.register(thread_configuration); 43 + + actors.register(breakpoint_list); 44 + 45 + watcher 46 + }
+1 -1
patches/components/layout/replaced.rs.patch
··· 1 1 --- original 2 2 +++ modified 3 - @@ -540,6 +540,7 @@ 3 + @@ -542,6 +542,7 @@ 4 4 viewport_details: ViewportDetails { 5 5 size, 6 6 hidpi_scale_factor: Scale::new(hidpi_scale_factor.0),
+2 -2
patches/components/net/Cargo.toml.patch
··· 8 8 async-compression = { version = "0.4.12", default-features = false, features = ["brotli", "gzip", "tokio", "zlib", "zstd"] } 9 9 async-recursion = "1.1" 10 10 async-tungstenite = { workspace = true } 11 - +atproto-identity = "0.13" 11 + +atproto-identity = "0.14" 12 12 base64 = { workspace = true } 13 13 bytes = "1" 14 14 chrono = { workspace = true } 15 - +constellation_traits = { workspace = true } 15 + +servo-constellation-traits = { workspace = true } 16 16 content-security-policy = { workspace = true } 17 17 cookie = { workspace = true } 18 18 crossbeam-channel = { workspace = true }
+1 -1
patches/components/net/atproto/pds.rs.patch
··· 13 13 +use atproto_identity::resolve::{ 14 14 + HickoryDnsResolver, InnerIdentityResolver, SharedIdentityResolver, resolve_subject, 15 15 +}; 16 - +use atproto_identity::storage::DidDocumentStorage; 17 16 +use atproto_identity::storage_lru::LruDidDocumentStorage; 17 + +use atproto_identity::traits::DidDocumentStorage; 18 18 +use servo_url::ServoUrl; 19 19 + 20 20 +static PLC_HOSTNAME: &str = "plc.directory";
+7 -3
patches/components/net/atproto/session.rs.patch
··· 1 1 --- original 2 2 +++ modified 3 - @@ -0,0 +1,152 @@ 3 + @@ -0,0 +1,156 @@ 4 4 +// SPDX-License-Identifier: AGPL-3.0-or-later 5 5 + 6 6 +use constellation_traits::{ ··· 57 57 + pub async fn current( 58 58 + endpoint: &ServoUrl, 59 59 + resource_thread: SyncWrapper<CoreResourceThread>, 60 + + access_jwt: &str, 60 61 + ) -> AtProtoResult { 61 62 + let Ok(url) = endpoint.join("/xrpc/com.atproto.server.getSession") else { 62 63 + return AtProtoResult::Error; 63 64 + }; 65 + + 66 + + let mut headers = HeaderMap::new(); 67 + + headers.typed_insert(Authorization::bearer(access_jwt).unwrap()); 64 68 + 65 69 + match fetch_json::<AtProtoCurrentSession, AtProtoError>( 66 70 + url, 67 71 + &[], 68 72 + resource_thread, 69 73 + Method::GET, 70 - + None, 71 - + true, 74 + + Some(headers), 75 + + false, 72 76 + ) 73 77 + .await 74 78 + {
+23 -9
patches/components/net/atproto/xrpc.rs.patch
··· 1 1 --- original 2 2 +++ modified 3 - @@ -0,0 +1,268 @@ 3 + @@ -0,0 +1,282 @@ 4 4 +// SPDX-License-Identifier: AGPL-3.0-or-later 5 5 + 6 6 +use async_recursion::async_recursion; ··· 12 12 +use net_traits::AtProtoSessionState; 13 13 +use net_traits::fetch::utils::{SimpleFetchTarget, rewrite_response_url}; 14 14 +use net_traits::request::{ 15 - + CredentialsMode, Referrer, Request, RequestBody, RequestBuilder, 16 - + create_request_body_with_content, 15 + + Referrer, Request, RequestBody, RequestBuilder, create_request_body_with_content, 17 16 +}; 18 17 +use net_traits::response::{Response, ResponseType}; 19 18 +use serde_json::json; ··· 63 62 + url_params.append_pair(param.0, param.1); 64 63 + } 65 64 + } 66 - + let mut builder = RequestBuilder::new(None, xrpc_url.clone(), Referrer::NoReferrer) 65 + + let mut request_headers = headers.unwrap_or_default(); 66 + + // Inject bearer token directly from the session state instead of 67 + + // relying on CredentialsMode::Include + auth_cache, which would 68 + + // allow any page to get the token. 69 + + if requires_auth { 70 + + if let Some(ref session) = *self.context.state.atproto_session.read() { 71 + + request_headers.typed_insert(Authorization::bearer(&session.access_jwt).unwrap()); 72 + + } 73 + + } 74 + + let builder = RequestBuilder::new(None, xrpc_url.clone(), Referrer::NoReferrer) 67 75 + .method(method.unwrap_or(Method::GET)) 68 - + .headers(headers.unwrap_or_default()) 76 + + .headers(request_headers) 69 77 + .origin(xrpc_url.origin()) 70 78 + .body(body); 71 - + if requires_auth { 72 - + builder = builder.credentials_mode(CredentialsMode::Include); 73 - + } 74 79 + (builder.build(), xrpc_url) 75 80 + } 76 81 + ··· 145 150 + return Err(()); 146 151 + }; 147 152 + 148 - + // Update the http state. 153 + + // Update the http state, preserving authorized_origins. 154 + + let authorized_origins = self 155 + + .context 156 + + .state 157 + + .atproto_session 158 + + .read() 159 + + .as_ref() 160 + + .map(|s| s.authorized_origins.clone()) 161 + + .unwrap_or_default(); 149 162 + let session_state = AtProtoSessionState { 150 163 + endpoint: self.endpoint.clone(), 151 164 + access_jwt: new_session.access_jwt, 152 165 + refresh_jwt: new_session.refresh_jwt, 166 + + authorized_origins, 153 167 + }; 154 168 + self.context 155 169 + .state
+9 -24
patches/components/net/http_loader.rs.patch
··· 24 24 - CookieSource, DOCUMENT_ACCEPT_HEADER_VALUE, DebugVec, FetchMetadata, NetworkError, 25 25 - RedirectEndValue, RedirectStartValue, ReferrerPolicy, ResourceAttribute, ResourceFetchTiming, 26 26 - ResourceTimeValue, TlsSecurityInfo, TlsSecurityState, 27 - + AtProtoSessionState, AuthCacheEntry, BasicAuthCacheEntry, BearerAuthCacheEntry, CookieSource, 27 + + AtProtoSessionState, AuthCacheEntry, BasicAuthCacheEntry, CookieSource, 28 28 + DOCUMENT_ACCEPT_HEADER_VALUE, DebugVec, FetchMetadata, NetworkError, RedirectEndValue, 29 29 + RedirectStartValue, ReferrerPolicy, ResourceAttribute, ResourceFetchTiming, ResourceTimeValue, 30 30 + TlsSecurityInfo, TlsSecurityState, ··· 48 48 pub hsts_list: RwLock<HstsList>, 49 49 pub cookie_jar: RwLock<CookieStorage>, 50 50 pub http_cache: HttpCache, 51 - @@ -114,9 +117,40 @@ 51 + @@ -114,9 +117,25 @@ 52 52 pub client: ServoClient, 53 53 pub override_manager: CertificateErrorOverrideManager, 54 54 pub embedder_proxy: GenericEmbedderProxy<NetToEmbedderMsg>, ··· 58 58 impl HttpState { 59 59 + pub(crate) fn update_atproto_session(&self, session: Option<AtProtoSessionState>) { 60 60 + let mut atproto_session = self.atproto_session.write(); 61 - + let current_endpoint = atproto_session.clone().map(|s| s.endpoint); 62 61 + *atproto_session = session.clone(); 63 62 + if let Some(ref session) = session { 64 63 + if let Some(config_dir) = &self.config_dir { ··· 67 66 + } else if let Some(config_dir) = &self.config_dir { 68 67 + AtProtoSessionState::reset(config_dir); 69 68 + } 70 - + 71 - + if let Some(session) = session { 72 - + let mut auth_cache = self.auth_cache.write(); 73 - + let origin = session.endpoint.origin().ascii_serialization(); 74 - + auth_cache.entries.insert( 75 - + origin, 76 - + AuthCacheEntry::Bearer(BearerAuthCacheEntry { 77 - + token: session.access_jwt.clone(), 78 - + }), 79 - + ); 80 - + } else if let Some(endpoint) = current_endpoint { 81 - + let mut auth_cache = self.auth_cache.write(); 82 - + let origin = endpoint.origin().ascii_serialization(); 83 - + auth_cache.entries.remove(&origin); 84 - + } else { 85 - + error!("Failed to remove Bearer auth entry"); 86 - + } 69 + + // Note: we intentionally do NOT put the bearer token in auth_cache. 70 + + // ATProto auth is injected explicitly only in XrpcClient and SessionClient 71 + + // to prevent arbitrary web pages from getting the token. 87 72 + } 88 73 + 89 74 pub(crate) fn memory_reports(&self, suffix: &str, ops: &mut MallocSizeOfOps) -> Vec<Report> { 90 75 vec![ 91 76 Report { 92 - @@ -577,14 +611,40 @@ 77 + @@ -577,14 +596,40 @@ 93 78 } 94 79 } 95 80 ··· 134 119 } else { 135 120 None 136 121 } 137 - @@ -1645,15 +1705,15 @@ 122 + @@ -1645,15 +1690,15 @@ 138 123 authorization_value.is_none() && 139 124 has_credentials(&current_url) 140 125 { ··· 153 138 } 154 139 } 155 140 } 156 - @@ -1858,7 +1918,7 @@ 141 + @@ -1858,7 +1903,7 @@ 157 142 }; 158 143 159 144 // Store the credentials as a proxy-authentication entry. ··· 162 147 user_name: credentials.username, 163 148 password: credentials.password, 164 149 }; 165 - @@ -1865,7 +1925,7 @@ 150 + @@ -1865,7 +1910,7 @@ 166 151 { 167 152 let mut auth_cache = context.state.auth_cache.write(); 168 153 let key = request.current_url().origin().ascii_serialization();
+32 -1
patches/components/net/protocols/atproto/protocol.rs.patch
··· 1 1 --- original 2 2 +++ modified 3 - @@ -0,0 +1,179 @@ 3 + @@ -0,0 +1,210 @@ 4 4 +// SPDX-License-Identifier: AGPL-3.0-or-later 5 5 + 6 6 +use std::future::{self, Future}; ··· 62 62 + StatusCode::BAD_REQUEST, 63 63 + "Invalid method", 64 64 + ))); 65 + + } 66 + + 67 + + // Restrict write operations (POST/DELETE) to authorized origins. 68 + + // Privileged localhost pages are always allowed. 69 + + // Other origins must be explicitly authorized via navigator.atproto.authorizeOrigin(). 70 + + if method != Method::GET { 71 + + let is_authorized = match &request.origin { 72 + + net_traits::request::Origin::Origin(origin) => { 73 + + let origin_str = origin.ascii_serialization(); 74 + + // Privileged origins are always allowed. 75 + + if origin_str.contains("localhost:8888") { 76 + + true 77 + + } else { 78 + + // Check the session's authorized origins list. 79 + + context 80 + + .state 81 + + .atproto_session 82 + + .read() 83 + + .as_ref() 84 + + .is_some_and(|session| session.authorized_origins.contains(&origin_str)) 85 + + } 86 + + }, 87 + + _ => false, 88 + + }; 89 + + if !is_authorized { 90 + + return Box::pin(future::ready(http_response( 91 + + url, 92 + + StatusCode::FORBIDDEN, 93 + + "Write operations on at:// URLs require origin authorization", 94 + + ))); 95 + + } 65 96 + } 66 97 + 67 98 + let context2 = context.clone();
+1 -2
patches/components/net/protocols/trusted/protocol.rs.patch
··· 1 1 --- original 2 2 +++ modified 3 - @@ -0,0 +1,143 @@ 3 + @@ -0,0 +1,142 @@ 4 4 +/// SPDX-License-Identifier: AGPL-3.0-or-later 5 - + 6 5 +use std::future::{self, Future}; 7 6 +use std::pin::Pin; 8 7 +
+1 -1
patches/components/paint/Cargo.toml.patch
··· 1 1 --- original 2 2 +++ modified 3 - @@ -38,6 +38,7 @@ 3 + @@ -36,6 +36,7 @@ 4 4 malloc_size_of = { workspace = true } 5 5 media = { workspace = true } 6 6 paint_api = { workspace = true }
+27 -2
patches/components/script/dom/atproto.rs.patch
··· 1 1 --- original 2 2 +++ modified 3 - @@ -0,0 +1,118 @@ 3 + @@ -0,0 +1,143 @@ 4 4 +// SPDX-License-Identifier: AGPL-3.0-or-later 5 5 + 6 6 +use std::rc::Rc; ··· 9 9 +use dom_struct::dom_struct; 10 10 +use js::jsval::UndefinedValue; 11 11 +use script_bindings::error::Error; 12 - +use script_bindings::str::USVString; 12 + +use script_bindings::str::{DOMString, USVString}; 13 13 + 14 14 +use crate::dom::bindings::codegen::Bindings::AtProtoBinding::{AtProtoMethods, AtProtoSession}; 15 15 +use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object}; ··· 83 83 + fn Current(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> { 84 84 + self.request(AtProtoRequest::Current, comp, can_gc) 85 85 + } 86 + + 87 + + fn AuthorizeOrigin(&self, origin: USVString, comp: InRealm, can_gc: CanGc) -> Rc<Promise> { 88 + + self.request( 89 + + AtProtoRequest::AuthorizeOrigin(origin.to_string()), 90 + + comp, 91 + + can_gc, 92 + + ) 93 + + } 94 + + 95 + + fn RevokeOrigin(&self, origin: USVString, comp: InRealm, can_gc: CanGc) -> Rc<Promise> { 96 + + self.request( 97 + + AtProtoRequest::RevokeOrigin(origin.to_string()), 98 + + comp, 99 + + can_gc, 100 + + ) 101 + + } 102 + + 103 + + fn AuthorizedOrigins(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> { 104 + + self.request(AtProtoRequest::GetAuthorizedOrigins, comp, can_gc) 105 + + } 86 106 +} 87 107 + 88 108 +impl RoutedPromiseListener<AtProtoResult> for AtProto { ··· 105 125 + promise.resolve_native(&dom_session, can_gc); 106 126 + }, 107 127 + AtProtoResult::Logout => promise.resolve_native(&UndefinedValue(), can_gc), 128 + + AtProtoResult::OriginUpdated => promise.resolve_native(&UndefinedValue(), can_gc), 129 + + AtProtoResult::AuthorizedOrigins(origins) => { 130 + + let origins: Vec<DOMString> = origins.into_iter().map(DOMString::from).collect(); 131 + + promise.resolve_native(&origins, can_gc); 132 + + }, 108 133 + AtProtoResult::Error => { 109 134 + error!("ATProto error :("); 110 135 + promise.reject_error(Error::Operation(Some("ATProto error".to_owned())), can_gc)
patches/components/script/dom/document.rs.patch patches/components/script/dom/document/document.rs.patch
patches/components/script/dom/document_embedder_controls.rs.patch patches/components/script/dom/document/document_embedder_controls.rs.patch
patches/components/script/dom/document_event_handler.rs.patch patches/components/script/dom/document/document_event_handler.rs.patch
+2 -1
patches/components/script/dom/embedder.rs.patch
··· 1 1 --- original 2 2 +++ modified 3 - @@ -0,0 +1,280 @@ 3 + @@ -0,0 +1,281 @@ 4 4 +/* SPDX Id: AGPL-3.0-or-later */ 5 5 + 6 6 +//! The `Embedder` interface provides communication between web content and the embedder. ··· 193 193 + url.domain() == Some("system.localhost") || 194 194 + url.domain() == Some("settings.localhost") || 195 195 + url.domain() == Some("keyboard.localhost") || 196 + + url.domain() == Some("atproto.localhost") || 196 197 + url.domain() == Some("homescreen.localhost") 197 198 + } 198 199 +}
+3 -3
patches/components/script/dom/mod.rs.patch
··· 8 8 pub(crate) mod attr; 9 9 pub(crate) mod audio; 10 10 pub(crate) use self::audio::*; 11 - @@ -284,6 +285,7 @@ 11 + @@ -278,6 +279,7 @@ 12 12 pub(crate) mod elementinternals; 13 13 pub(crate) mod encoding; 14 14 pub(crate) use self::encoding::*; ··· 16 16 pub(crate) mod errorevent; 17 17 pub(crate) mod event; 18 18 pub(crate) mod eventsource; 19 - @@ -317,6 +319,7 @@ 19 + @@ -309,6 +311,7 @@ 20 20 pub(crate) mod inputevent; 21 21 pub(crate) mod intersectionobserver; 22 22 pub(crate) mod intersectionobserverentry; ··· 24 24 pub(crate) mod keyboardevent; 25 25 pub(crate) mod location; 26 26 pub(crate) mod media; 27 - @@ -344,6 +347,10 @@ 27 + @@ -336,6 +339,10 @@ 28 28 pub(crate) mod pagetransitionevent; 29 29 pub(crate) mod paintsize; 30 30 pub(crate) mod paintworkletglobalscope;
patches/components/script/dom/url.rs.patch patches/components/script/dom/url/url.rs.patch
+2 -2
patches/components/script_bindings/codegen/Bindings.conf.patch
··· 5 5 }, 6 6 7 7 +'AtProto': { 8 - + 'inRealms': ['Login', 'Logout', 'Current'], 9 - + 'canGc': ['Login', 'Logout', 'Current'], 8 + + 'inRealms': ['Login', 'Logout', 'Current', 'AuthorizeOrigin', 'RevokeOrigin', 'AuthorizedOrigins'], 9 + + 'canGc': ['Login', 'Logout', 'Current', 'AuthorizeOrigin', 'RevokeOrigin', 'AuthorizedOrigins'], 10 10 +}, 11 11 + 12 12 'Attr': {
+10 -1
patches/components/script_bindings/webidls/AtProto.webidl.patch
··· 1 1 --- original 2 2 +++ modified 3 - @@ -0,0 +1,29 @@ 3 + @@ -0,0 +1,38 @@ 4 4 +/* This Source Code Form is subject to the terms of the Mozilla Public 5 5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 6 6 + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ ··· 24 24 + 25 25 + // Resolves with the logged in user DID if any, rejects otherwise. 26 26 + Promise<AtProtoSession> current(); 27 + + 28 + + // Authorize an origin for write operations via at:// protocol. 29 + + Promise<undefined> authorizeOrigin(USVString origin); 30 + + 31 + + // Revoke an origin's write authorization. 32 + + Promise<undefined> revokeOrigin(USVString origin); 33 + + 34 + + // Get the list of authorized origins. 35 + + Promise<sequence<DOMString>> authorizedOrigins(); 27 36 +}; 28 37 + 29 38 +partial interface Navigator {
+17 -6
patches/components/shared/constellation/from_script_message.rs.patch
··· 86 86 /// Specifies the information required to load an iframe. 87 87 #[derive(Debug, Deserialize, Serialize)] 88 88 pub struct IFrameLoadInfo { 89 - @@ -541,6 +579,79 @@ 89 + @@ -541,6 +579,90 @@ 90 90 NoLongerActive, 91 91 } 92 92 ··· 96 96 + Login(String, String), 97 97 + Logout, 98 98 + Current, 99 + + /// Authorize an origin for write operations via at:// protocol. 100 + + AuthorizeOrigin(String), 101 + + /// Revoke an origin's write authorization. 102 + + RevokeOrigin(String), 103 + + /// Get the list of authorized origins. 104 + + GetAuthorizedOrigins, 99 105 +} 100 106 + 101 107 +/// Data returned by com.atproto.server.createSession xrpc calls. ··· 108 114 + pub did: String, 109 115 + pub email: String, 110 116 + pub email_confirmed: bool, 117 + + #[serde(default)] 111 118 + pub email_auth_factor: bool, 112 119 + pub active: bool, 113 120 + pub status: Option<String>, ··· 159 166 + NewSession(AtProtoNewSession, ServoUrl), // (session, endpoint_url) 160 167 + CurrentSession(AtProtoCurrentSession), 161 168 + RefreshRequired, 162 - + Logout, // For Logout success 169 + + Logout, 170 + + /// List of authorized origins for write operations. 171 + + AuthorizedOrigins(Vec<String>), 172 + + /// Origin authorization was updated successfully. 173 + + OriginUpdated, 163 174 + Error, 164 175 +} 165 176 + 166 177 /// Messages from the script to the constellation. 167 178 #[derive(Deserialize, IntoStaticStr, Serialize)] 168 179 pub enum ScriptToConstellationMessage { 169 - @@ -585,6 +696,10 @@ 180 + @@ -585,6 +707,10 @@ 170 181 NewBroadcastChannelNameInRouter(BroadcastChannelRouterId, String, ImmutableOrigin), 171 182 /// A global stopped managing broadcast channels for a given channel-name. 172 183 RemoveBroadcastChannelNameInRouter(BroadcastChannelRouterId, String, ImmutableOrigin), ··· 177 188 /// Broadcast a message to all same-origin broadcast channels, 178 189 /// excluding the source of the broadcast. 179 190 ScheduleBroadcast(BroadcastChannelRouterId, BroadcastChannelMsg), 180 - @@ -597,6 +712,9 @@ 191 + @@ -597,6 +723,9 @@ 181 192 Option<String>, 182 193 Option<String>, 183 194 ), ··· 187 198 /// Indicates whether this pipeline is currently running animations. 188 199 ChangeRunningAnimationsState(AnimationState), 189 200 /// Requests that a new 2D canvas thread be created. (This is done in the constellation because 190 - @@ -677,6 +795,10 @@ 201 + @@ -677,6 +806,10 @@ 191 202 ScriptNewIFrame(IFrameLoadInfoWithData), 192 203 /// Script has opened a new auxiliary browsing context. 193 204 CreateAuxiliaryWebView(AuxiliaryWebViewCreationRequest), ··· 198 209 /// Mark a new document as active 199 210 ActivateDocument, 200 211 /// Set the document state for a pipeline (used by screenshot / reftests) 201 - @@ -726,6 +848,79 @@ 212 + @@ -726,6 +859,79 @@ 202 213 RespondToScreenshotReadinessRequest(ScreenshotReadinessResponse), 203 214 /// Request the constellation to force garbage collection in all `ScriptThread`'s. 204 215 TriggerGarbageCollection,
+5 -2
patches/components/shared/net/lib.rs.patch
··· 26 26 } 27 27 28 28 /// A loading context, for context-specific sniffing, as defined in 29 - @@ -596,6 +601,61 @@ 29 + @@ -596,6 +601,64 @@ 30 30 Prefetch, 31 31 } 32 32 ··· 52 52 + pub endpoint: ServoUrl, 53 53 + pub access_jwt: String, 54 54 + pub refresh_jwt: String, 55 + + /// Origins authorized to perform write operations via at:// protocol. 56 + + #[serde(default)] 57 + + pub authorized_origins: Vec<String>, 55 58 +} 56 59 + 57 60 +static ATPROTO_SESSION_FILE: &str = "atproto_session.json"; ··· 88 91 #[derive(Debug, Deserialize, Serialize)] 89 92 pub enum CoreResourceMsg { 90 93 Fetch(RequestBuilder, FetchChannels), 91 - @@ -655,6 +715,10 @@ 94 + @@ -655,6 +718,10 @@ 92 95 /// and exit 93 96 Exit(GenericOneshotSender<()>), 94 97 CollectMemoryReport(ReportsChan),
+328
ui/atproto/explorer.css
··· 1 + /* SPDX-License-Identifier: AGPL-3.0-or-later */ 2 + 3 + * { 4 + margin: 0; 5 + padding: 0; 6 + box-sizing: border-box; 7 + } 8 + 9 + body { 10 + font-family: var(--font-family-base); 11 + background: var(--bg-surface); 12 + color: var(--color-text); 13 + min-height: 100vh; 14 + } 15 + 16 + header { 17 + padding: var(--spacing-md); 18 + border-bottom: 1px solid var(--color-border); 19 + } 20 + 21 + header h1 { 22 + font-size: 1.3rem; 23 + margin-bottom: var(--spacing-sm); 24 + display: flex; 25 + align-items: center; 26 + gap: var(--spacing-xs); 27 + } 28 + 29 + .search-bar { 30 + display: flex; 31 + gap: var(--spacing-sm); 32 + } 33 + 34 + .search-bar input { 35 + flex: 1; 36 + padding: var(--spacing-sm) var(--spacing-md); 37 + border: 1px solid var(--color-border); 38 + border-radius: var(--radius-sm); 39 + background: var(--bg-surface); 40 + color: var(--color-text); 41 + font-size: 0.9rem; 42 + font-family: inherit; 43 + } 44 + 45 + .search-bar button { 46 + padding: var(--spacing-sm) var(--spacing-md); 47 + border: 1px solid var(--color-border); 48 + border-radius: var(--radius-sm); 49 + background: var(--color-primary); 50 + color: white; 51 + cursor: pointer; 52 + font-size: 0.9rem; 53 + font-family: inherit; 54 + } 55 + 56 + .search-bar button:hover { 57 + opacity: 0.9; 58 + } 59 + 60 + .breadcrumb { 61 + padding: var(--spacing-sm) var(--spacing-md); 62 + font-size: var(--font-size-sm); 63 + color: var(--color-text-secondary); 64 + border-bottom: 1px solid var(--color-border); 65 + display: flex; 66 + align-items: center; 67 + gap: var(--spacing-xs); 68 + flex-wrap: wrap; 69 + } 70 + 71 + .breadcrumb:empty { 72 + display: none; 73 + } 74 + 75 + .breadcrumb a { 76 + color: var(--color-primary); 77 + text-decoration: none; 78 + cursor: pointer; 79 + } 80 + 81 + .breadcrumb a:hover { 82 + text-decoration: underline; 83 + } 84 + 85 + .breadcrumb .separator { 86 + opacity: 0.5; 87 + } 88 + 89 + main { 90 + padding: var(--spacing-md); 91 + } 92 + 93 + .empty-state { 94 + text-align: center; 95 + padding: var(--spacing-lg); 96 + color: var(--color-text-secondary); 97 + } 98 + 99 + .empty-state lucide-icon { 100 + font-size: 3em; 101 + margin-bottom: var(--spacing-md); 102 + display: block; 103 + opacity: var(--opacity-muted); 104 + } 105 + 106 + /* Repo info */ 107 + .repo-info { 108 + margin-bottom: var(--spacing-md); 109 + padding: var(--spacing-md); 110 + background: var(--bg-menu); 111 + border-radius: var(--radius-sm); 112 + border: 1px solid var(--color-border); 113 + } 114 + 115 + .repo-info h2 { 116 + font-size: 1.1rem; 117 + margin-bottom: var(--spacing-xs); 118 + } 119 + 120 + .repo-info .did { 121 + font-size: var(--font-size-sm); 122 + color: var(--color-text-secondary); 123 + word-break: break-all; 124 + } 125 + 126 + /* Collection list */ 127 + .collection-list { 128 + display: flex; 129 + flex-direction: column; 130 + gap: var(--spacing-xs); 131 + } 132 + 133 + .collection-item { 134 + display: flex; 135 + align-items: center; 136 + gap: var(--spacing-sm); 137 + padding: var(--spacing-sm) var(--spacing-md); 138 + background: var(--bg-menu); 139 + border: 1px solid var(--color-border); 140 + border-radius: var(--radius-sm); 141 + cursor: pointer; 142 + transition: background var(--transition-fast); 143 + } 144 + 145 + .collection-item:hover { 146 + background: var(--bg-hover); 147 + } 148 + 149 + .collection-item lucide-icon { 150 + color: var(--color-primary); 151 + } 152 + 153 + .collection-name { 154 + font-weight: var(--font-weight-bold); 155 + font-size: var(--font-size-menu); 156 + } 157 + 158 + /* Record list */ 159 + .record-list { 160 + display: flex; 161 + flex-direction: column; 162 + gap: var(--spacing-xs); 163 + } 164 + 165 + .record-item { 166 + display: flex; 167 + align-items: center; 168 + gap: var(--spacing-sm); 169 + padding: var(--spacing-sm) var(--spacing-md); 170 + background: var(--bg-menu); 171 + border: 1px solid var(--color-border); 172 + border-radius: var(--radius-sm); 173 + cursor: pointer; 174 + transition: background var(--transition-fast); 175 + } 176 + 177 + .record-item:hover { 178 + background: var(--bg-hover); 179 + } 180 + 181 + .record-item lucide-icon { 182 + color: var(--color-text-secondary); 183 + } 184 + 185 + .record-rkey { 186 + font-size: var(--font-size-menu); 187 + font-family: monospace; 188 + } 189 + 190 + /* Record detail */ 191 + .record-detail { 192 + background: var(--bg-menu); 193 + border: 1px solid var(--color-border); 194 + border-radius: var(--radius-sm); 195 + padding: var(--spacing-md); 196 + } 197 + 198 + .record-detail pre { 199 + white-space: pre-wrap; 200 + word-break: break-all; 201 + font-size: var(--font-size-sm); 202 + line-height: 1.5; 203 + max-height: 80vh; 204 + overflow-y: auto; 205 + } 206 + 207 + .record-detail .at-uri { 208 + font-size: var(--font-size-sm); 209 + color: var(--color-text-secondary); 210 + margin-bottom: var(--spacing-sm); 211 + word-break: break-all; 212 + } 213 + 214 + /* Loading / error */ 215 + .loading { 216 + text-align: center; 217 + padding: var(--spacing-lg); 218 + color: var(--color-text-secondary); 219 + } 220 + 221 + .error { 222 + text-align: center; 223 + padding: var(--spacing-md); 224 + color: var(--color-danger); 225 + } 226 + 227 + /* Delete button inline in record list */ 228 + .btn-delete-inline { 229 + background: none; 230 + border: none; 231 + color: var(--color-text-tertiary); 232 + cursor: pointer; 233 + padding: var(--spacing-xs); 234 + border-radius: var(--radius-sm); 235 + display: flex; 236 + align-items: center; 237 + opacity: 0; 238 + transition: opacity var(--transition-fast); 239 + } 240 + 241 + .record-item:hover .btn-delete-inline { 242 + opacity: 1; 243 + } 244 + 245 + .btn-delete-inline:hover { 246 + color: var(--color-danger); 247 + } 248 + 249 + /* Delete button in record detail */ 250 + .record-actions { 251 + margin-bottom: var(--spacing-sm); 252 + } 253 + 254 + .btn-delete { 255 + padding: var(--spacing-sm) var(--spacing-md); 256 + border: 1px solid var(--color-danger); 257 + border-radius: var(--radius-sm); 258 + background: none; 259 + color: var(--color-danger); 260 + cursor: pointer; 261 + font-size: var(--font-size-sm); 262 + font-family: inherit; 263 + display: inline-flex; 264 + align-items: center; 265 + gap: var(--spacing-xs); 266 + } 267 + 268 + .btn-delete:hover { 269 + background: var(--color-danger); 270 + color: white; 271 + } 272 + 273 + /* Create record form */ 274 + .create-record-form { 275 + margin-bottom: var(--spacing-md); 276 + background: var(--bg-menu); 277 + border: 1px solid var(--color-border); 278 + border-radius: var(--radius-sm); 279 + } 280 + 281 + .create-record-form summary { 282 + padding: var(--spacing-sm) var(--spacing-md); 283 + cursor: pointer; 284 + display: flex; 285 + align-items: center; 286 + gap: var(--spacing-sm); 287 + font-weight: var(--font-weight-bold); 288 + font-size: var(--font-size-menu); 289 + } 290 + 291 + .create-record-form summary:hover { 292 + background: var(--bg-hover); 293 + } 294 + 295 + .create-record-body { 296 + padding: 0 var(--spacing-md) var(--spacing-md); 297 + } 298 + 299 + .create-record-body textarea { 300 + width: 100%; 301 + padding: var(--spacing-sm); 302 + border: 1px solid var(--color-border); 303 + border-radius: var(--radius-sm); 304 + background: var(--bg-surface); 305 + color: var(--color-text); 306 + font-family: monospace; 307 + font-size: var(--font-size-sm); 308 + resize: vertical; 309 + margin-bottom: var(--spacing-sm); 310 + } 311 + 312 + .btn-create { 313 + padding: var(--spacing-sm) var(--spacing-md); 314 + border: 1px solid var(--color-primary); 315 + border-radius: var(--radius-sm); 316 + background: var(--color-primary); 317 + color: white; 318 + cursor: pointer; 319 + font-size: var(--font-size-sm); 320 + font-family: inherit; 321 + display: inline-flex; 322 + align-items: center; 323 + gap: var(--spacing-xs); 324 + } 325 + 326 + .btn-create:hover { 327 + opacity: 0.9; 328 + }
+426
ui/atproto/explorer.js
··· 1 + // SPDX-License-Identifier: AGPL-3.0-or-later 2 + 3 + const handleInput = document.getElementById("handle-input"); 4 + const btnExplore = document.getElementById("btn-explore"); 5 + const breadcrumb = document.getElementById("breadcrumb"); 6 + const content = document.getElementById("content"); 7 + 8 + // Known collection display names. 9 + const COLLECTION_ICONS = { 10 + "app.bsky.feed.post": "message-square", 11 + "app.bsky.feed.like": "heart", 12 + "app.bsky.feed.repost": "repeat", 13 + "app.bsky.graph.follow": "user-plus", 14 + "app.bsky.graph.block": "user-x", 15 + "app.bsky.graph.list": "list", 16 + "app.bsky.actor.profile": "user", 17 + "app.bsky.feed.generator": "rss", 18 + "app.bsky.labeler.service": "shield", 19 + }; 20 + 21 + function iconForCollection(name) { 22 + return COLLECTION_ICONS[name] || "folder"; 23 + } 24 + 25 + // State 26 + let currentHandle = ""; 27 + let currentDid = ""; 28 + let currentCollection = ""; 29 + let loggedInDid = null; 30 + let loggedInHandle = null; 31 + 32 + // Check login status 33 + async function checkLogin() { 34 + try { 35 + const session = await navigator.atproto.current(); 36 + loggedInDid = session.did; 37 + loggedInHandle = session.handle; 38 + if (!handleInput.value) { 39 + handleInput.value = session.handle; 40 + } 41 + } catch { 42 + loggedInDid = null; 43 + loggedInHandle = null; 44 + } 45 + } 46 + 47 + function isOwnRepo() { 48 + console.log("isOwnRepo:", { loggedInDid, currentDid }); 49 + return loggedInDid && currentDid && loggedInDid === currentDid; 50 + } 51 + 52 + // History-based navigation 53 + function navigate(handle, collection, rkey, pushState = true) { 54 + currentHandle = handle || ""; 55 + currentCollection = collection || ""; 56 + 57 + // Build hash for history 58 + let hash = ""; 59 + if (handle) { 60 + hash = handle; 61 + if (collection) { 62 + hash += "/" + collection; 63 + if (rkey) hash += "/" + rkey; 64 + } 65 + } 66 + 67 + if (pushState) { 68 + history.pushState({ handle, collection, rkey }, "", "#" + hash); 69 + } 70 + 71 + handleInput.value = handle || ""; 72 + 73 + if (!handle) { 74 + showEmpty(); 75 + return; 76 + } 77 + 78 + if (rkey) { 79 + loadRecord(handle, collection, rkey); 80 + } else if (collection) { 81 + loadCollection(handle, collection); 82 + } else { 83 + loadRepo(handle); 84 + } 85 + } 86 + 87 + window.addEventListener("popstate", (e) => { 88 + if (e.state) { 89 + navigate(e.state.handle, e.state.collection, e.state.rkey, false); 90 + } else { 91 + const hash = window.location.hash.slice(1); 92 + if (hash) { 93 + navigateFromHash(hash, false); 94 + } else { 95 + showEmpty(); 96 + } 97 + } 98 + }); 99 + 100 + function navigateFromHash(hash, pushState = true) { 101 + const parts = hash.split("/"); 102 + const handle = parts[0] || ""; 103 + const collection = parts[1] || ""; 104 + const rkey = parts[2] || ""; 105 + navigate(handle, collection || undefined, rkey || undefined, pushState); 106 + } 107 + 108 + function updateBreadcrumb(handle, collection, rkey) { 109 + const parts = []; 110 + if (handle) { 111 + parts.push(`<a onclick="navigate('${handle}')">${handle}</a>`); 112 + } 113 + if (collection) { 114 + parts.push( 115 + `<a onclick="navigate('${handle}', '${collection}')">${collection}</a>`, 116 + ); 117 + } 118 + if (rkey) { 119 + parts.push(`<span>${rkey}</span>`); 120 + } 121 + breadcrumb.innerHTML = parts.join('<span class="separator"> / </span>'); 122 + } 123 + 124 + function showEmpty() { 125 + breadcrumb.innerHTML = ""; 126 + content.innerHTML = ` 127 + <div class="empty-state"> 128 + <lucide-icon name="compass"></lucide-icon> 129 + <p>Enter a handle or DID to explore their ATProto repository.</p> 130 + </div> 131 + `; 132 + } 133 + 134 + function showLoading() { 135 + content.innerHTML = '<div class="loading">Loading...</div>'; 136 + } 137 + 138 + function showError(msg) { 139 + content.innerHTML = `<div class="error">${msg}</div>`; 140 + } 141 + 142 + // Fetch helpers — uses the at:// protocol handler 143 + async function fetchAtProto(url) { 144 + const response = await fetch(url); 145 + if (!response.ok) { 146 + throw new Error(`${response.status} ${response.statusText}`); 147 + } 148 + return response.json(); 149 + } 150 + 151 + // Load repo — show collections 152 + async function loadRepo(handle) { 153 + updateBreadcrumb(handle); 154 + showLoading(); 155 + 156 + try { 157 + const data = await fetchAtProto(`at://${handle}`); 158 + currentDid = data.did || handle; 159 + 160 + let html = ` 161 + <div class="repo-info"> 162 + <h2>${escapeHtml(data.handle || handle)}</h2> 163 + <div class="did">${escapeHtml(data.did || "")}</div> 164 + </div> 165 + `; 166 + 167 + if (data.collections && data.collections.length > 0) { 168 + html += '<div class="collection-list">'; 169 + for (const col of data.collections) { 170 + html += ` 171 + <div class="collection-item" onclick="navigate('${handle}', '${escapeHtml(col)}')"> 172 + <lucide-icon name="${iconForCollection(col)}"></lucide-icon> 173 + <span class="collection-name">${escapeHtml(col)}</span> 174 + </div> 175 + `; 176 + } 177 + html += "</div>"; 178 + } else { 179 + html += '<div class="empty-state"><p>No collections found.</p></div>'; 180 + } 181 + 182 + content.innerHTML = html; 183 + } catch (e) { 184 + showError(`Failed to load repo: ${e.message}`); 185 + } 186 + } 187 + 188 + // Load collection — show records 189 + async function loadCollection(handle, collection) { 190 + updateBreadcrumb(handle, collection); 191 + showLoading(); 192 + 193 + try { 194 + const data = await fetchAtProto(`at://${handle}/${collection}`); 195 + 196 + let html = ""; 197 + 198 + // Create record form for own repo 199 + if (isOwnRepo()) { 200 + html += ` 201 + <details class="create-record-form"> 202 + <summary> 203 + <lucide-icon name="plus-circle"></lucide-icon> 204 + Create Record 205 + </summary> 206 + <div class="create-record-body"> 207 + <textarea id="create-record-json" rows="8" 208 + placeholder='{"$type": "${escapeHtml(collection)}", ...}'>{ 209 + "$type": "${escapeHtml(collection)}" 210 + }</textarea> 211 + <button class="btn-create" onclick="createRecord('${escapeHtml(handle)}', '${escapeHtml(collection)}')"> 212 + <lucide-icon name="plus"></lucide-icon> Create 213 + </button> 214 + </div> 215 + </details> 216 + `; 217 + } 218 + 219 + if (data.records && data.records.length > 0) { 220 + html += '<div class="record-list">'; 221 + for (const record of data.records) { 222 + const uri = record.uri || ""; 223 + const rkey = uri.split("/").pop() || "unknown"; 224 + const value = record.value || {}; 225 + const preview = getRecordPreview(collection, value); 226 + 227 + html += ` 228 + <div class="record-item" onclick="navigate('${handle}', '${collection}', '${escapeHtml(rkey)}')"> 229 + <lucide-icon name="file-text"></lucide-icon> 230 + <div style="flex:1"> 231 + <div class="record-rkey">${escapeHtml(rkey)}</div> 232 + ${preview ? `<div style="font-size:0.8em;opacity:0.7;margin-top:2px">${preview}</div>` : ""} 233 + </div> 234 + ${isOwnRepo() ? `<button class="btn-delete-inline" onclick="event.stopPropagation(); deleteRecord('${escapeHtml(handle)}', '${escapeHtml(collection)}', '${escapeHtml(rkey)}')" title="Delete record"><lucide-icon name="trash-2"></lucide-icon></button>` : ""} 235 + </div> 236 + `; 237 + } 238 + html += "</div>"; 239 + } else { 240 + html += 241 + '<div class="empty-state"><p>No records in this collection.</p></div>'; 242 + } 243 + 244 + content.innerHTML = html; 245 + } catch (e) { 246 + showError(`Failed to load collection: ${e.message}`); 247 + } 248 + } 249 + 250 + // Load single record 251 + async function loadRecord(handle, collection, rkey) { 252 + updateBreadcrumb(handle, collection, rkey); 253 + showLoading(); 254 + 255 + try { 256 + const data = await fetchAtProto(`at://${handle}/${collection}/${rkey}`); 257 + 258 + const value = data.value || data; 259 + const uri = data.uri || `at://${handle}/${collection}/${rkey}`; 260 + 261 + let actions = ""; 262 + if (isOwnRepo()) { 263 + actions = ` 264 + <div class="record-actions"> 265 + <button class="btn-delete" onclick="deleteRecord('${escapeHtml(handle)}', '${escapeHtml(collection)}', '${escapeHtml(rkey)}')"> 266 + <lucide-icon name="trash-2"></lucide-icon> Delete Record 267 + </button> 268 + </div> 269 + `; 270 + } 271 + 272 + content.innerHTML = ` 273 + <div class="record-detail"> 274 + <div class="at-uri">${escapeHtml(uri)}</div> 275 + ${actions} 276 + <pre>${syntaxHighlight(JSON.stringify(value, null, 2))}</pre> 277 + </div> 278 + `; 279 + } catch (e) { 280 + showError(`Failed to load record: ${e.message}`); 281 + } 282 + } 283 + 284 + // Delete a record 285 + async function deleteRecord(handle, collection, rkey) { 286 + // Use a non-blocking confirmation (confirm() deadlocks in embedded webviews). 287 + const btn = event?.target?.closest("button"); 288 + if (btn && !btn.dataset.confirmed) { 289 + btn.dataset.confirmed = "true"; 290 + btn.innerHTML = 291 + '<lucide-icon name="alert-triangle"></lucide-icon> Confirm?'; 292 + btn.style.color = "white"; 293 + btn.style.background = "var(--color-danger)"; 294 + setTimeout(() => { 295 + // Reset after 3 seconds if not clicked again. 296 + delete btn.dataset.confirmed; 297 + btn.innerHTML = '<lucide-icon name="trash-2"></lucide-icon>'; 298 + btn.style.color = ""; 299 + btn.style.background = ""; 300 + }, 3000); 301 + return; 302 + } 303 + 304 + try { 305 + const response = await fetch(`at://${handle}/${collection}/${rkey}`, { 306 + method: "DELETE", 307 + }); 308 + if (!response.ok) { 309 + throw new Error(`${response.status} ${response.statusText}`); 310 + } 311 + // Reload the collection view 312 + navigate(handle, collection); 313 + } catch (e) { 314 + alert(`Failed to delete: ${e.message}`); 315 + } 316 + } 317 + 318 + // Create a record 319 + async function createRecord(handle, collection) { 320 + const textarea = document.getElementById("create-record-json"); 321 + if (!textarea) return; 322 + 323 + let record; 324 + try { 325 + record = JSON.parse(textarea.value); 326 + } catch (e) { 327 + alert(`Invalid JSON: ${e.message}`); 328 + return; 329 + } 330 + 331 + // The at:// protocol handler's POST expects the full createRecord body: 332 + // { repo, collection, record } 333 + const body = { 334 + repo: currentDid || handle, 335 + collection, 336 + record, 337 + }; 338 + 339 + try { 340 + const response = await fetch(`at://${handle}/${collection}`, { 341 + method: "POST", 342 + headers: { "Content-Type": "application/json" }, 343 + body: JSON.stringify(body), 344 + }); 345 + if (!response.ok) { 346 + const text = await response.text(); 347 + throw new Error(`${response.status}: ${text}`); 348 + } 349 + // Reload the collection view 350 + navigate(handle, collection); 351 + } catch (e) { 352 + alert(`Failed to create: ${e.message}`); 353 + } 354 + } 355 + 356 + // Record preview based on collection type 357 + function getRecordPreview(collection, value) { 358 + switch (collection) { 359 + case "app.bsky.feed.post": 360 + return escapeHtml(truncate(value.text || "", 100)); 361 + case "app.bsky.actor.profile": 362 + return escapeHtml(value.displayName || ""); 363 + case "app.bsky.graph.follow": 364 + return `→ ${escapeHtml(value.subject || "")}`; 365 + case "app.bsky.feed.like": 366 + return `♥ ${escapeHtml((value.subject && value.subject.uri) || "")}`; 367 + default: 368 + return ""; 369 + } 370 + } 371 + 372 + // Simple JSON syntax highlighting 373 + function syntaxHighlight(json) { 374 + return escapeHtml(json) 375 + .replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?)/g, (match) => { 376 + if (match.endsWith(":")) { 377 + return `<span style="color:var(--color-primary)">${match}</span>`; 378 + } 379 + const unquoted = match.slice(1, -1); 380 + if (unquoted.startsWith("at://")) { 381 + return `<span style="color:#8b5">"<a href="${unquoted}" style="color:#8b5">${escapeHtml(unquoted)}</a>"</span>`; 382 + } 383 + return `<span style="color:#b58">${match}</span>`; 384 + }) 385 + .replace(/\b(true|false|null)\b/g, '<span style="color:#f80">$1</span>') 386 + .replace(/\b(\d+\.?\d*)\b/g, '<span style="color:#0bf">$1</span>'); 387 + } 388 + 389 + function escapeHtml(str) { 390 + return str 391 + .replace(/&/g, "&amp;") 392 + .replace(/</g, "&lt;") 393 + .replace(/>/g, "&gt;") 394 + .replace(/"/g, "&quot;"); 395 + } 396 + 397 + function truncate(str, maxLen) { 398 + return str.length > maxLen ? str.slice(0, maxLen) + "…" : str; 399 + } 400 + 401 + // Make functions available globally for onclick handlers 402 + window.navigate = navigate; 403 + window.deleteRecord = deleteRecord; 404 + window.createRecord = createRecord; 405 + 406 + // Event handlers 407 + btnExplore.addEventListener("click", () => { 408 + const handle = handleInput.value.trim(); 409 + if (handle) { 410 + navigate(handle); 411 + } 412 + }); 413 + 414 + handleInput.addEventListener("keydown", (e) => { 415 + if (e.key === "Enter") { 416 + btnExplore.click(); 417 + } 418 + }); 419 + 420 + // Initialize 421 + checkLogin().then(() => { 422 + const hash = window.location.hash.slice(1); 423 + if (hash) { 424 + navigateFromHash(hash); 425 + } 426 + });
+41
ui/atproto/index.html
··· 1 + <!doctype html> 2 + <!-- SPDX-License-Identifier: AGPL-3.0-or-later --> 3 + <html> 4 + <head> 5 + <meta charset="utf-8" /> 6 + <meta name="viewport" content="width=device-width, initial-scale=1" /> 7 + <title>ATProto Explorer</title> 8 + <link rel="stylesheet" href="//theme.localhost:8888/index.css" /> 9 + <link rel="stylesheet" href="//shared.localhost:8888/fonts/fonts.css" /> 10 + <link 11 + rel="stylesheet" 12 + href="//shared.localhost:8888/third_party/lucide/lucide.css" 13 + /> 14 + <script type="module" src="//shared.localhost:8888/lucide_icon.js"></script> 15 + <link rel="stylesheet" href="explorer.css" /> 16 + </head> 17 + <body> 18 + <header> 19 + <h1><lucide-icon name="at-sign"></lucide-icon> ATProto Explorer</h1> 20 + <div class="search-bar"> 21 + <input 22 + type="text" 23 + id="handle-input" 24 + placeholder="Enter handle or DID (e.g. user.bsky.social)" 25 + /> 26 + <button id="btn-explore">Explore</button> 27 + </div> 28 + </header> 29 + 30 + <nav id="breadcrumb" class="breadcrumb"></nav> 31 + 32 + <main id="content"> 33 + <div class="empty-state"> 34 + <lucide-icon name="compass"></lucide-icon> 35 + <p>Enter a handle or DID to explore their ATProto repository.</p> 36 + </div> 37 + </main> 38 + 39 + <script type="module" src="explorer.js"></script> 40 + </body> 41 + </html>
+85
ui/settings/index.css
··· 617 617 opacity: 0.5; 618 618 cursor: not-allowed; 619 619 } 620 + 621 + /* ATProto */ 622 + .atproto-btn { 623 + padding: var(--spacing-sm) var(--spacing-md); 624 + border: 1px solid var(--color-border); 625 + border-radius: var(--radius-sm); 626 + background: var(--bg-webview); 627 + color: inherit; 628 + cursor: pointer; 629 + font-size: var(--font-size-sm); 630 + margin-top: var(--spacing-sm); 631 + } 632 + 633 + .atproto-btn:hover { 634 + background: var(--color-menu-item-hover); 635 + } 636 + 637 + .atproto-btn:disabled { 638 + opacity: 0.5; 639 + cursor: not-allowed; 640 + } 641 + 642 + .atproto-btn-danger { 643 + border-color: var(--color-danger); 644 + color: var(--color-danger); 645 + } 646 + 647 + .atproto-btn-danger:hover { 648 + background: var(--color-danger); 649 + color: white; 650 + } 651 + 652 + .atproto-error { 653 + color: var(--color-danger); 654 + font-size: var(--font-size-sm); 655 + margin-top: var(--spacing-xs); 656 + padding: var(--spacing-xs) var(--spacing-sm); 657 + background: rgba(255, 0, 0, 0.1); 658 + border-radius: var(--radius-sm); 659 + } 660 + 661 + .atproto-origins-list { 662 + margin-top: var(--spacing-xs); 663 + } 664 + 665 + .atproto-origin-item { 666 + display: flex; 667 + align-items: center; 668 + justify-content: space-between; 669 + padding: var(--spacing-xs) 0; 670 + border-bottom: 1px solid var(--color-border); 671 + } 672 + 673 + .atproto-origin-item:last-child { 674 + border-bottom: none; 675 + } 676 + 677 + .atproto-origin-url { 678 + font-size: var(--font-size-sm); 679 + word-break: break-all; 680 + } 681 + 682 + .atproto-origin-remove { 683 + background: none; 684 + border: 1px solid var(--color-danger); 685 + color: var(--color-danger); 686 + cursor: pointer; 687 + padding: var(--spacing-sm) var(--spacing-md); 688 + font-size: var(--font-size-sm); 689 + white-space: nowrap; 690 + border-radius: var(--radius-sm); 691 + display: flex; 692 + align-items: center; 693 + gap: var(--spacing-xs); 694 + } 695 + 696 + .atproto-origin-remove:hover { 697 + background: var(--color-danger); 698 + color: white; 699 + } 700 + 701 + .atproto-origin-remove:disabled { 702 + opacity: 0.5; 703 + cursor: not-allowed; 704 + }
+67
ui/settings/index.html
··· 42 42 <lucide-icon name="monitor-smartphone"></lucide-icon> 43 43 <span>P2P</span> 44 44 </a> 45 + <a href="#atproto" class="nav-item" data-category="atproto"> 46 + <lucide-icon name="at-sign"></lucide-icon> 47 + <span>ATProto</span> 48 + </a> 45 49 </nav> 46 50 </aside> 47 51 <main class="settings-content"> ··· 226 230 <div id="p2p-peer-list" class="p2p-peer-list"> 227 231 <span class="setting-description">No peers discovered yet.</span> 228 232 </div> 233 + </div> 234 + </div> 235 + </details> 236 + </section> 237 + <section id="atproto" class="settings-category"> 238 + <details> 239 + <summary> 240 + <lucide-icon name="at-sign"></lucide-icon> 241 + <span>ATProto</span> 242 + </summary> 243 + <div class="category-content"> 244 + <div id="atproto-status" class="setting-row"> 245 + <div class="setting-info"> 246 + <label class="setting-label">Status</label> 247 + <span class="setting-description" id="atproto-status-text">Checking...</span> 248 + </div> 249 + </div> 250 + 251 + <div id="atproto-logged-in" style="display:none"> 252 + <div class="setting-row"> 253 + <div class="setting-info"> 254 + <label class="setting-label">Handle</label> 255 + <span class="setting-description" id="atproto-handle"></span> 256 + </div> 257 + </div> 258 + <div class="setting-row"> 259 + <div class="setting-info"> 260 + <label class="setting-label">DID</label> 261 + <span class="setting-description"><code id="atproto-did"></code></span> 262 + </div> 263 + </div> 264 + <button id="atproto-logout" class="atproto-btn atproto-btn-danger">Log out</button> 265 + 266 + <div class="setting-row" style="margin-top:var(--spacing-md)"> 267 + <div class="setting-info"> 268 + <label class="setting-label">Authorized Origins</label> 269 + <span class="setting-description"> 270 + Sites allowed to write data via at:// protocol 271 + </span> 272 + </div> 273 + </div> 274 + <div id="atproto-origins-list" class="atproto-origins-list"> 275 + <span class="setting-description">No authorized origins.</span> 276 + </div> 277 + </div> 278 + 279 + <div id="atproto-logged-out" style="display:none"> 280 + <div class="setting-row"> 281 + <div class="setting-info"> 282 + <label for="atproto-login-handle" class="setting-label">Handle</label> 283 + <span class="setting-description">Your ATProto handle</span> 284 + </div> 285 + </div> 286 + <input type="text" id="atproto-login-handle" placeholder="user.bsky.social" /> 287 + <div class="setting-row"> 288 + <div class="setting-info"> 289 + <label for="atproto-login-password" class="setting-label">Password</label> 290 + <span class="setting-description">Your PDS password</span> 291 + </div> 292 + </div> 293 + <input type="password" id="atproto-login-password" placeholder="Password" /> 294 + <div id="atproto-error" class="atproto-error" style="display:none"></div> 295 + <button id="atproto-login" class="atproto-btn">Log in</button> 229 296 </div> 230 297 </div> 231 298 </details>
+141
ui/settings/index.js
··· 399 399 pairing.addEventListener("peerleft", refreshPeers); 400 400 } 401 401 402 + // ATProto login management 403 + function setupATProto() { 404 + const statusText = document.getElementById("atproto-status-text"); 405 + const loggedInSection = document.getElementById("atproto-logged-in"); 406 + const loggedOutSection = document.getElementById("atproto-logged-out"); 407 + const handleDisplay = document.getElementById("atproto-handle"); 408 + const didDisplay = document.getElementById("atproto-did"); 409 + const loginHandle = document.getElementById("atproto-login-handle"); 410 + const loginPassword = document.getElementById("atproto-login-password"); 411 + const loginBtn = document.getElementById("atproto-login"); 412 + const logoutBtn = document.getElementById("atproto-logout"); 413 + const errorEl = document.getElementById("atproto-error"); 414 + const originsList = document.getElementById("atproto-origins-list"); 415 + 416 + function showError(msg) { 417 + errorEl.textContent = msg; 418 + errorEl.style.display = ""; 419 + } 420 + 421 + function hideError() { 422 + errorEl.style.display = "none"; 423 + } 424 + 425 + function showLoggedIn(session) { 426 + statusText.textContent = `Logged in as ${session.handle}`; 427 + handleDisplay.textContent = session.handle; 428 + didDisplay.textContent = session.did; 429 + loggedInSection.style.display = ""; 430 + loggedOutSection.style.display = "none"; 431 + hideError(); 432 + refreshOrigins(); 433 + } 434 + 435 + function showLoggedOut() { 436 + statusText.textContent = "Not logged in"; 437 + loggedInSection.style.display = "none"; 438 + loggedOutSection.style.display = ""; 439 + hideError(); 440 + } 441 + 442 + // Check current session on load 443 + async function checkSession() { 444 + try { 445 + const session = await navigator.atproto.current(); 446 + showLoggedIn(session); 447 + } catch { 448 + showLoggedOut(); 449 + } 450 + } 451 + 452 + async function refreshOrigins() { 453 + try { 454 + const origins = await navigator.atproto.authorizedOrigins(); 455 + if (origins.length === 0) { 456 + originsList.innerHTML = 457 + '<span class="setting-description">No authorized origins.</span>'; 458 + return; 459 + } 460 + originsList.innerHTML = origins 461 + .map( 462 + (origin) => ` 463 + <div class="atproto-origin-item"> 464 + <span class="atproto-origin-url">${origin}</span> 465 + <button class="atproto-origin-remove" data-origin="${origin}"> 466 + <lucide-icon name="trash-2"></lucide-icon> Remove 467 + </button> 468 + </div>`, 469 + ) 470 + .join(""); 471 + 472 + originsList.querySelectorAll(".atproto-origin-remove").forEach((btn) => { 473 + btn.addEventListener("click", async () => { 474 + const origin = btn.dataset.origin; 475 + btn.disabled = true; 476 + btn.textContent = "Removing..."; 477 + try { 478 + await navigator.atproto.revokeOrigin(origin); 479 + await refreshOrigins(); 480 + } catch (e) { 481 + console.error("Failed to revoke origin:", e); 482 + btn.disabled = false; 483 + btn.textContent = "Remove"; 484 + } 485 + }); 486 + }); 487 + } catch (e) { 488 + console.error("Failed to get authorized origins:", e); 489 + } 490 + } 491 + 492 + loginBtn.addEventListener("click", async () => { 493 + const handle = loginHandle.value.trim(); 494 + const password = loginPassword.value.trim(); 495 + if (!handle || !password) { 496 + showError("Please enter both handle and password."); 497 + return; 498 + } 499 + 500 + hideError(); 501 + loginBtn.disabled = true; 502 + loginBtn.textContent = "Logging in..."; 503 + 504 + try { 505 + const session = await navigator.atproto.login(handle, password); 506 + showLoggedIn(session); 507 + loginPassword.value = ""; 508 + } catch (e) { 509 + showError(`Login failed: ${e.message || e}`); 510 + } finally { 511 + loginBtn.disabled = false; 512 + loginBtn.textContent = "Log in"; 513 + } 514 + }); 515 + 516 + logoutBtn.addEventListener("click", async () => { 517 + logoutBtn.disabled = true; 518 + logoutBtn.textContent = "Logging out..."; 519 + 520 + try { 521 + await navigator.atproto.logout(); 522 + showLoggedOut(); 523 + } catch (e) { 524 + showError(`Logout failed: ${e.message || e}`); 525 + } finally { 526 + logoutBtn.disabled = false; 527 + logoutBtn.textContent = "Log out"; 528 + } 529 + }); 530 + 531 + // Allow Enter key to submit login 532 + loginPassword.addEventListener("keydown", (e) => { 533 + if (e.key === "Enter") loginBtn.click(); 534 + }); 535 + loginHandle.addEventListener("keydown", (e) => { 536 + if (e.key === "Enter") loginPassword.focus(); 537 + }); 538 + 539 + checkSession(); 540 + } 541 + 402 542 // Initialize 403 543 setupNavigation(); 404 544 setupMobileNavigation(); 405 545 renderThemeCards(); 406 546 setupSearchEngines(); 407 547 setupP2P(); 548 + setupATProto();
-56
ui/system/atproto/browser.css
··· 1 - /* SPDX-License-Identifier: AGPL-3.0-or-later */ 2 - 3 - html { 4 - font-family: var(--font-family-base); 5 - color: var(--color-text); 6 - } 7 - 8 - img.avatar { 9 - max-width: 256px; 10 - } 11 - 12 - .hidden { 13 - display: none; 14 - } 15 - 16 - table, 17 - input, 18 - button { 19 - border-radius: var(--radius-sm); 20 - } 21 - 22 - header { 23 - display: flex; 24 - gap: var(--spacing-sm); 25 - align-items: center; 26 - border-bottom: 2px solid var(--color-border); 27 - padding-bottom: var(--spacing-sm); 28 - margin-bottom: var(--spacing-sm); 29 - } 30 - 31 - table { 32 - border: 2px solid var(--color-border); 33 - margin: var(--spacing-sm); 34 - } 35 - 36 - table td { 37 - padding-left: var(--spacing-xs); 38 - padding-right: var(--spacing-xs); 39 - } 40 - 41 - table tr:nth-child(odd) { 42 - background-color: var(--color-menu-item-hover); 43 - } 44 - 45 - details button { 46 - margin-left: 2em; 47 - } 48 - 49 - a { 50 - text-decoration: underline; 51 - cursor: pointer; 52 - } 53 - 54 - a.external::after { 55 - content: "🔗"; 56 - }
-25
ui/system/atproto/browser.html
··· 1 - <!doctype html> 2 - <!-- SPDX-License-Identifier: AGPL-3.0-or-later --> 3 - <html> 4 - <head> 5 - <title>at:// browser</title> 6 - <meta charset="utf-8" /> 7 - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 8 - <link rel="stylesheet" href="//theme.localhost:8888/index.css" /> 9 - <link rel="stylesheet" href="//shared.localhost:8888/fonts/fonts.css" /> 10 - <link rel="stylesheet" href="browser.css" /> 11 - <script src="browser.js"></script> 12 - </head> 13 - <body> 14 - <h2>at:// browser</h2> 15 - <header> 16 - User: 17 - <input 18 - id="handle" 19 - value="me.webbeef.org" 20 - placeholder="Handle or DID" 21 - /><button id="start-button">Start</button> 22 - </header> 23 - <main id="result"></main> 24 - </body> 25 - </html>
-172
ui/system/atproto/browser.js
··· 1 - // SPDX-License-Identifier: AGPL-3.0-or-later 2 - 3 - // Basic at:// data browser. 4 - 5 - document.addEventListener("DOMContentLoaded", () => { 6 - window["start-button"].onclick = () => { 7 - let handle = window.handle.value.trim(); 8 - console.log(`Handle is ${handle}`); 9 - if (handle.length > 0) { 10 - location.hash = `#${handle}`; 11 - } 12 - }; 13 - 14 - window.onhashchange = (event) => { 15 - let url = location.hash?.substring(1); 16 - window.handle.value = currentHandle(); 17 - if (url) { 18 - atFetch(`at://${url}`); 19 - } 20 - }; 21 - 22 - window.onhashchange(); 23 - }); 24 - 25 - function router(data) { 26 - window["result"].innerHTML = ""; 27 - 28 - if (data.collections && data.collections.length != 0) { 29 - return displayRoot(data); 30 - } 31 - 32 - if (data.records && data.records.length != 0) { 33 - data.records.forEach(displayRecord); 34 - return; 35 - } 36 - 37 - console.log(data); 38 - } 39 - 40 - async function atFetch(url) { 41 - console.log(`Fetching ${url}`); 42 - try { 43 - let response = await fetch(url); 44 - let data = await response.json(); 45 - router(data); 46 - } catch (e) { 47 - console.error(e); 48 - } 49 - } 50 - 51 - function currentHandle() { 52 - let end = location.hash.indexOf("/"); 53 - console.log(`==== end=${end} hash=${location.hash}`); 54 - if (end == -1) { 55 - return decodeURIComponent(location.hash.substring(1)); 56 - } 57 - return location.hash.substring(1, end); 58 - } 59 - 60 - function buildImage(record, kind) { 61 - if (!record.mimeType.startsWith("image/") || !record.ref?.$link) { 62 - return null; 63 - } 64 - 65 - let img = document.createElement("img"); 66 - img.classList.add(kind); 67 - img.src = `at://${currentHandle()}/com.atproto.sync.blob/${record.ref.$link}`; 68 - return img; 69 - } 70 - 71 - function buildSubjectLink(subject) { 72 - let handle = encodeURIComponent(subject); 73 - let anchor = document.createElement("a"); 74 - anchor.textContent = subject; 75 - anchor.onclick = () => { 76 - location.hash = `#${handle}`; 77 - }; 78 - return anchor; 79 - } 80 - 81 - // Makes sure the authority part is properly encoded. 82 - function sanitizeAtURI(uri) { 83 - if (!uri.startsWith("at://")) { 84 - return uri; 85 - } 86 - 87 - let comps = uri.substring(5).split("/"); 88 - comps[0] = encodeURIComponent(comps[0]); 89 - return "at://" + comps.join("/"); 90 - } 91 - 92 - // Displays a single record. 93 - // TODO: instanciate custom elements based on the value's type. 94 - function displayRecord(record) { 95 - let container = document.createElement("details"); 96 - let summary = document.createElement("summary"); 97 - summary.textContent = record.value.$type; 98 - let table = document.createElement("table"); 99 - 100 - // Iterate over each property. 101 - for (let prop in record.value) { 102 - if (prop == "$type") { 103 - continue; 104 - } 105 - 106 - let value = record.value[prop]; 107 - let row = document.createElement("tr"); 108 - let colProp = document.createElement("td"); 109 - let colValue = document.createElement("td"); 110 - row.append(colProp); 111 - colProp.textContent = prop; 112 - row.append(colValue); 113 - 114 - table.append(row); 115 - 116 - if (prop == "avatar" || prop == "banner") { 117 - let node = buildImage(value, prop); 118 - if (node) { 119 - colValue.append(node); 120 - } 121 - continue; 122 - } 123 - 124 - if (prop == "subject" && typeof value == "string") { 125 - colValue.append(buildSubjectLink(value)); 126 - } else if (!value.startsWith("did:") && URL.canParse(value)) { 127 - let anchor = document.createElement("a"); 128 - anchor.setAttribute("href", value); 129 - anchor.setAttribute("target", "_blank"); 130 - anchor.className = "external"; 131 - anchor.textContent = value; 132 - colValue.append(anchor); 133 - } else { 134 - colValue.textContent = value; 135 - if (colValue.textContent == "[object Object]") { 136 - colValue.textContent = JSON.stringify(value); 137 - } 138 - } 139 - } 140 - 141 - container.append(summary); 142 - container.append(table); 143 - let deleteButton = document.createElement("button"); 144 - deleteButton.onclick = async () => { 145 - try { 146 - let uri = sanitizeAtURI(record.uri); 147 - let response = await fetch(uri, { method: "DELETE" }); 148 - console.log(await response.text()); 149 - location.reload(); 150 - } catch (e) { 151 - console.error(e); 152 - } 153 - }; 154 - deleteButton.textContent = "Delete Record"; 155 - container.append(deleteButton); 156 - window["result"].append(container); 157 - } 158 - 159 - // Displays the list of collections attached to a user. 160 - function displayRoot(data) { 161 - let result = window["result"]; 162 - let list = document.createElement("ul"); 163 - data.collections.forEach((collection) => { 164 - let item = document.createElement("li"); 165 - let anchor = document.createElement("a"); 166 - anchor.setAttribute("href", `#${data.handle}/${collection}`); 167 - anchor.textContent = collection; 168 - item.append(anchor); 169 - list.append(item); 170 - }); 171 - result.append(list); 172 - }
+2 -2
ui/system/index.js
··· 818 818 break; 819 819 case "atproto": 820 820 openView( 821 - "http://system.localhost:8888/atproto/browser.html", 822 - "Settings", 821 + "http://atproto.localhost:8888/index.html", 822 + "ATProto", 823 823 ); 824 824 break; 825 825 case "reload-ui":
+5
ui/system/system_menu.css
··· 18 18 left: calc(var(--sidebar-width) / 2); 19 19 z-index: 1001; 20 20 } 21 + 22 + .atproto-handle { 23 + font-size: var(--font-size-sm); 24 + color: var(--color-text-secondary); 25 + }
+17 -1
ui/system/system_menu.js
··· 3 3 import { MenuBase, html, css } from "./menu_base.js"; 4 4 5 5 export class SystemMenu extends MenuBase { 6 + static properties = { 7 + ...MenuBase.properties, 8 + atprotoHandle: { type: String, state: true }, 9 + }; 10 + 6 11 constructor() { 7 12 super(); 13 + this.atprotoHandle = ""; 8 14 this.handleKeyDown = this.handleKeyDown.bind(this); 9 15 } 10 16 ··· 17 23 if (changedProperties.has("open")) { 18 24 if (this.open) { 19 25 document.addEventListener("keydown", this.handleKeyDown); 26 + this.refreshAtprotoStatus(); 20 27 } else { 21 28 this.removeEventListeners(); 22 29 } ··· 25 32 26 33 removeEventListeners() { 27 34 document.removeEventListener("keydown", this.handleKeyDown); 35 + } 36 + 37 + async refreshAtprotoStatus() { 38 + try { 39 + const session = await navigator.atproto.current(); 40 + this.atprotoHandle = session.handle; 41 + } catch { 42 + this.atprotoHandle = ""; 43 + } 28 44 } 29 45 30 46 handleKeyDown(e) { ··· 70 86 </li> 71 87 <li @click=${() => this.handleItemClick("atproto")}> 72 88 <lucide-icon name="at-sign"></lucide-icon> 73 - <span>ATProto</span> 89 + <span>ATProto</span><span class="atproto-handle">${this.atprotoHandle || ""}</span> 74 90 </li> 75 91 <li class="menu-separator"></li> 76 92 <li @click=${() => this.handleItemClick("reload-ui")}>
+32 -1
ui/system/web_view.js
··· 517 517 // Pairing service may not be running — skip the items. 518 518 } 519 519 520 + // Add "Authorize ATProto" if the user is logged in and the current 521 + // page's origin isn't already a privileged localhost page. 522 + try { 523 + await navigator.atproto.current(); 524 + const origin = new URL(this.currentUrl).origin; 525 + if (!origin.includes("localhost:8888")) { 526 + items.push({ 527 + id: `atproto_authorize:${origin}`, 528 + label: "Authorize ATProto access", 529 + icon: "at-sign", 530 + disabled: false, 531 + }); 532 + } 533 + } catch { 534 + // Not logged in — skip. 535 + } 536 + 520 537 // In mobile mode, show the radial menu instead of the regular context menu 521 538 if (document.body.classList.contains("mobile-mode")) { 522 539 // Extract navigation state from context menu items ··· 573 590 }; 574 591 } 575 592 576 - handleContextMenuAction(e) { 593 + async handleContextMenuAction(e) { 577 594 const { action, controlId } = e.detail; 578 595 console.log( 579 596 "[ContextMenu] Action selected:", ··· 604 621 detail: { peerId, url }, 605 622 }), 606 623 ); 624 + return; 625 + } 626 + 627 + // Handle "Authorize ATProto" action. 628 + if (action.startsWith("atproto_authorize:")) { 629 + this.iframe.respondToContextMenu(controlId, null); 630 + this.contextMenu = null; 631 + const origin = action.slice("atproto_authorize:".length); 632 + try { 633 + await navigator.atproto.authorizeOrigin(origin); 634 + console.log(`[ATProto] Authorized origin: ${origin}`); 635 + } catch (e) { 636 + console.error(`[ATProto] Failed to authorize origin:`, e); 637 + } 607 638 return; 608 639 } 609 640