Graphical PDS migrator for AT Protocol

feat: plc wip

Turtlepaw c3dfe73f e85145a9

Changed files
+693 -9
islands
routes
api
ticket-booth
+32 -8
deno.json
··· 8 8 }, 9 9 "lint": { 10 10 "rules": { 11 - "tags": ["fresh", "recommended"] 11 + "tags": [ 12 + "fresh", 13 + "recommended" 14 + ] 12 15 } 13 16 }, 14 - "exclude": ["**/_fresh/*"], 17 + "exclude": [ 18 + "**/_fresh/*" 19 + ], 15 20 "imports": { 16 21 "@atproto/api": "npm:@atproto/api@^0.15.6", 17 22 "@bigmoves/atproto-oauth-client": "jsr:@bigmoves/atproto-oauth-client@^0.2.0", ··· 21 26 "posthog-js": "npm:posthog-js@1.120.0", 22 27 "preact": "npm:preact@^10.26.6", 23 28 "@preact/signals": "npm:@preact/signals@^2.0.4", 24 - "tailwindcss": "npm:tailwindcss@^3.4.3" 29 + "tailwindcss": "npm:tailwindcss@^3.4.3", 30 + "@atproto/crypto": "npm:@atproto/crypto@^0.4.4", 31 + "@did-plc/lib": "npm:@did-plc/lib@^0.0.4" 25 32 }, 26 33 "compilerOptions": { 27 - "lib": ["dom", "dom.asynciterable", "dom.iterable", "deno.ns"], 34 + "lib": [ 35 + "dom", 36 + "dom.asynciterable", 37 + "dom.iterable", 38 + "deno.ns" 39 + ], 28 40 "jsx": "precompile", 29 41 "jsxImportSource": "preact", 30 - "jsxPrecompileSkipElements": ["a", "img", "source", "body", "html", "head"], 31 - "types": ["node"] 42 + "jsxPrecompileSkipElements": [ 43 + "a", 44 + "img", 45 + "source", 46 + "body", 47 + "html", 48 + "head" 49 + ], 50 + "types": [ 51 + "node" 52 + ] 32 53 }, 33 - "unstable": ["kv", "otel"] 34 - } 54 + "unstable": [ 55 + "kv", 56 + "otel" 57 + ] 58 + }
+284 -1
deno.lock
··· 33 33 "npm:@atproto/api@*": "0.15.6", 34 34 "npm:@atproto/api@~0.15.6": "0.15.6", 35 35 "npm:@atproto/crypto@*": "0.4.4", 36 + "npm:@atproto/crypto@~0.4.4": "0.4.4", 36 37 "npm:@atproto/identity@*": "0.4.8", 37 38 "npm:@atproto/jwk@0.1.4": "0.1.4", 38 39 "npm:@atproto/oauth-client@~0.3.13": "0.3.16", 39 40 "npm:@atproto/oauth-types@~0.2.4": "0.2.7", 40 41 "npm:@atproto/syntax@*": "0.4.0", 41 42 "npm:@atproto/xrpc@*": "0.7.0", 43 + "npm:@did-plc/lib@^0.0.4": "0.0.4", 42 44 "npm:@lucide/lab@*": "0.1.2", 43 45 "npm:@opentelemetry/api@^1.9.0": "1.9.0", 44 46 "npm:@preact/signals@^1.2.3": "1.3.2_preact@10.26.6", ··· 293 295 "zod" 294 296 ] 295 297 }, 298 + "@atproto/common@0.1.1": { 299 + "integrity": "sha512-GYwot5wF/z8iYGSPjrLHuratLc0CVgovmwfJss7+BUOB6y2/Vw8+1Vw0n9DDI0gb5vmx3UI8z0uJgC8aa8yuJg==", 300 + "dependencies": [ 301 + "@ipld/dag-cbor", 302 + "multiformats@9.9.0", 303 + "pino", 304 + "zod" 305 + ] 306 + }, 307 + "@atproto/crypto@0.1.0": { 308 + "integrity": "sha512-9xgFEPtsCiJEPt9o3HtJT30IdFTGw5cQRSJVIy5CFhqBA4vDLcdXiRDLCjkzHEVbtNCsHUW6CrlfOgbeLPcmcg==", 309 + "dependencies": [ 310 + "@noble/secp256k1", 311 + "big-integer", 312 + "multiformats@9.9.0", 313 + "one-webcrypto", 314 + "uint8arrays@3.0.0" 315 + ] 316 + }, 296 317 "@atproto/crypto@0.4.4": { 297 318 "integrity": "sha512-Yq9+crJ7WQl7sxStVpHgie5Z51R05etaK9DLWYG/7bR5T4bhdcIgF6IfklLShtZwLYdVVj+K15s0BqW9a8PSDA==", 298 319 "dependencies": [ ··· 311 332 "integrity": "sha512-Z0sLnJ87SeNdAifT+rqpgE1Rc3layMMW25gfWNo4u40RGuRODbdfAZlTwBSU2r+Vk45hU+iE+xeQspfednCEnA==", 312 333 "dependencies": [ 313 334 "@atproto/common-web", 314 - "@atproto/crypto" 335 + "@atproto/crypto@0.4.4" 315 336 ] 316 337 }, 317 338 "@atproto/jwk@0.1.4": { ··· 369 390 "integrity": "sha512-SfhP9dGx2qclaScFDb58Jnrmim5nk4geZXCqg6sB0I/KZhZEkr9iIx1hLCp+sxkIfEsmEJjeWO4B0rjUIJW5cw==", 370 391 "dependencies": [ 371 392 "@atproto/lexicon", 393 + "zod" 394 + ] 395 + }, 396 + "@did-plc/lib@0.0.4": { 397 + "integrity": "sha512-Omeawq3b8G/c/5CtkTtzovSOnWuvIuCI4GTJNrt1AmCskwEQV7zbX5d6km1mjJNbE0gHuQPTVqZxLVqetNbfwA==", 398 + "dependencies": [ 399 + "@atproto/common", 400 + "@atproto/crypto@0.1.0", 401 + "@ipld/dag-cbor", 402 + "axios", 403 + "multiformats@9.9.0", 404 + "uint8arrays@3.0.0", 372 405 "zod" 373 406 ] 374 407 }, ··· 492 525 "os": ["win32"], 493 526 "cpu": ["x64"] 494 527 }, 528 + "@ipld/dag-cbor@7.0.3": { 529 + "integrity": "sha512-1VVh2huHsuohdXC1bGJNE8WR72slZ9XE2T3wbBBq31dm7ZBatmKLLxrB+XAqafxfRFjv08RZmj/W/ZqaM13AuA==", 530 + "dependencies": [ 531 + "cborg", 532 + "multiformats@9.9.0" 533 + ] 534 + }, 495 535 "@isaacs/cliui@8.0.2": { 496 536 "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", 497 537 "dependencies": [ ··· 538 578 }, 539 579 "@noble/hashes@1.8.0": { 540 580 "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==" 581 + }, 582 + "@noble/secp256k1@1.7.2": { 583 + "integrity": "sha512-/qzwYl5eFLH8OWIecQWM31qld2g1NfjgylK+TNhqtaUKP37Nm+Y+z30Fjhw0Ct8p9yCQEm2N3W/AckdIb3SMcQ==" 541 584 }, 542 585 "@nodelib/fs.scandir@2.1.5": { 543 586 "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", ··· 588 631 "undici-types" 589 632 ] 590 633 }, 634 + "abort-controller@3.0.0": { 635 + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", 636 + "dependencies": [ 637 + "event-target-shim" 638 + ] 639 + }, 591 640 "ansi-regex@5.0.1": { 592 641 "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" 593 642 }, ··· 616 665 "arg@5.0.2": { 617 666 "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" 618 667 }, 668 + "asynckit@0.4.0": { 669 + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 670 + }, 671 + "atomic-sleep@1.0.0": { 672 + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" 673 + }, 619 674 "autoprefixer@10.4.17_postcss@8.4.35": { 620 675 "integrity": "sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==", 621 676 "dependencies": [ ··· 632 687 "await-lock@2.2.2": { 633 688 "integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==" 634 689 }, 690 + "axios@1.10.0": { 691 + "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", 692 + "dependencies": [ 693 + "follow-redirects", 694 + "form-data", 695 + "proxy-from-env" 696 + ] 697 + }, 635 698 "balanced-match@1.0.2": { 636 699 "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 700 + }, 701 + "base64-js@1.5.1": { 702 + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" 703 + }, 704 + "big-integer@1.6.52": { 705 + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==" 637 706 }, 638 707 "binary-extensions@2.3.0": { 639 708 "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==" ··· 663 732 ], 664 733 "bin": true 665 734 }, 735 + "buffer@6.0.3": { 736 + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", 737 + "dependencies": [ 738 + "base64-js", 739 + "ieee754" 740 + ] 741 + }, 742 + "call-bind-apply-helpers@1.0.2": { 743 + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", 744 + "dependencies": [ 745 + "es-errors", 746 + "function-bind" 747 + ] 748 + }, 666 749 "camelcase-css@2.0.1": { 667 750 "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" 668 751 }, ··· 677 760 }, 678 761 "caniuse-lite@1.0.30001717": { 679 762 "integrity": "sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==" 763 + }, 764 + "cborg@1.10.2": { 765 + "integrity": "sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug==", 766 + "bin": true 680 767 }, 681 768 "chokidar@3.6.0": { 682 769 "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", ··· 705 792 "colord@2.9.3": { 706 793 "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" 707 794 }, 795 + "combined-stream@1.0.8": { 796 + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 797 + "dependencies": [ 798 + "delayed-stream" 799 + ] 800 + }, 708 801 "commander@4.1.1": { 709 802 "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" 710 803 }, ··· 819 912 "css-tree@2.2.1" 820 913 ] 821 914 }, 915 + "delayed-stream@1.0.0": { 916 + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" 917 + }, 822 918 "didyoumean@1.2.2": { 823 919 "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" 824 920 }, ··· 850 946 "domhandler" 851 947 ] 852 948 }, 949 + "dunder-proto@1.0.1": { 950 + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 951 + "dependencies": [ 952 + "call-bind-apply-helpers", 953 + "es-errors", 954 + "gopd" 955 + ] 956 + }, 853 957 "eastasianwidth@0.2.0": { 854 958 "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" 855 959 }, ··· 865 969 "entities@4.5.0": { 866 970 "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" 867 971 }, 972 + "es-define-property@1.0.1": { 973 + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" 974 + }, 975 + "es-errors@1.3.0": { 976 + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" 977 + }, 978 + "es-object-atoms@1.1.1": { 979 + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 980 + "dependencies": [ 981 + "es-errors" 982 + ] 983 + }, 984 + "es-set-tostringtag@2.1.0": { 985 + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", 986 + "dependencies": [ 987 + "es-errors", 988 + "get-intrinsic", 989 + "has-tostringtag", 990 + "hasown" 991 + ] 992 + }, 868 993 "esbuild-wasm@0.23.1": { 869 994 "integrity": "sha512-L3vn7ctvBrtScRfoB0zG1eOCiV4xYvpLYWfe6PDZuV+iDFDm4Mt3xeLIDllG8cDHQ8clUouK3XekulE+cxgkgw==", 870 995 "bin": true ··· 903 1028 "escalade@3.2.0": { 904 1029 "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" 905 1030 }, 1031 + "event-target-shim@5.0.1": { 1032 + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" 1033 + }, 1034 + "events@3.3.0": { 1035 + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" 1036 + }, 906 1037 "fast-glob@3.3.3": { 907 1038 "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", 908 1039 "dependencies": [ ··· 913 1044 "micromatch" 914 1045 ] 915 1046 }, 1047 + "fast-redact@3.5.0": { 1048 + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==" 1049 + }, 916 1050 "fastq@1.19.1": { 917 1051 "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", 918 1052 "dependencies": [ ··· 928 1062 "to-regex-range" 929 1063 ] 930 1064 }, 1065 + "follow-redirects@1.15.9": { 1066 + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==" 1067 + }, 931 1068 "foreground-child@3.3.1": { 932 1069 "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", 933 1070 "dependencies": [ ··· 935 1072 "signal-exit" 936 1073 ] 937 1074 }, 1075 + "form-data@4.0.3": { 1076 + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", 1077 + "dependencies": [ 1078 + "asynckit", 1079 + "combined-stream", 1080 + "es-set-tostringtag", 1081 + "hasown", 1082 + "mime-types" 1083 + ] 1084 + }, 938 1085 "fraction.js@4.3.7": { 939 1086 "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==" 940 1087 }, ··· 946 1093 "function-bind@1.1.2": { 947 1094 "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" 948 1095 }, 1096 + "get-intrinsic@1.3.0": { 1097 + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", 1098 + "dependencies": [ 1099 + "call-bind-apply-helpers", 1100 + "es-define-property", 1101 + "es-errors", 1102 + "es-object-atoms", 1103 + "function-bind", 1104 + "get-proto", 1105 + "gopd", 1106 + "has-symbols", 1107 + "hasown", 1108 + "math-intrinsics" 1109 + ] 1110 + }, 1111 + "get-proto@1.0.1": { 1112 + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 1113 + "dependencies": [ 1114 + "dunder-proto", 1115 + "es-object-atoms" 1116 + ] 1117 + }, 949 1118 "glob-parent@5.1.2": { 950 1119 "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 951 1120 "dependencies": [ ··· 970 1139 ], 971 1140 "bin": true 972 1141 }, 1142 + "gopd@1.2.0": { 1143 + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" 1144 + }, 973 1145 "graphemer@1.4.0": { 974 1146 "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" 975 1147 }, 1148 + "has-symbols@1.1.0": { 1149 + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" 1150 + }, 1151 + "has-tostringtag@1.0.2": { 1152 + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", 1153 + "dependencies": [ 1154 + "has-symbols" 1155 + ] 1156 + }, 976 1157 "hasown@2.0.2": { 977 1158 "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 978 1159 "dependencies": [ 979 1160 "function-bind" 980 1161 ] 1162 + }, 1163 + "ieee754@1.2.1": { 1164 + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" 981 1165 }, 982 1166 "ipaddr.js@2.2.0": { 983 1167 "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==" ··· 1063 1247 "preact@10.26.6" 1064 1248 ] 1065 1249 }, 1250 + "math-intrinsics@1.1.0": { 1251 + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" 1252 + }, 1066 1253 "mdn-data@2.0.28": { 1067 1254 "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==" 1068 1255 }, ··· 1079 1266 "picomatch" 1080 1267 ] 1081 1268 }, 1269 + "mime-db@1.52.0": { 1270 + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" 1271 + }, 1272 + "mime-types@2.1.35": { 1273 + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 1274 + "dependencies": [ 1275 + "mime-db" 1276 + ] 1277 + }, 1082 1278 "minimatch@9.0.5": { 1083 1279 "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", 1084 1280 "dependencies": [ ··· 1127 1323 "object-hash@3.0.0": { 1128 1324 "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==" 1129 1325 }, 1326 + "on-exit-leak-free@2.1.2": { 1327 + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==" 1328 + }, 1329 + "one-webcrypto@1.0.3": { 1330 + "integrity": "sha512-fu9ywBVBPx0gS9K0etIROTiCkvI5S1TDjFsYFb3rC1ewFxeOqsbzq7aIMBHsYfrTHBcGXJaONXXjTl8B01cW1Q==" 1331 + }, 1130 1332 "package-json-from-dist@1.0.1": { 1131 1333 "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" 1132 1334 }, ··· 1152 1354 "pify@2.3.0": { 1153 1355 "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==" 1154 1356 }, 1357 + "pino-abstract-transport@1.2.0": { 1358 + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", 1359 + "dependencies": [ 1360 + "readable-stream", 1361 + "split2" 1362 + ] 1363 + }, 1364 + "pino-std-serializers@6.2.2": { 1365 + "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==" 1366 + }, 1367 + "pino@8.21.0": { 1368 + "integrity": "sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==", 1369 + "dependencies": [ 1370 + "atomic-sleep", 1371 + "fast-redact", 1372 + "on-exit-leak-free", 1373 + "pino-abstract-transport", 1374 + "pino-std-serializers", 1375 + "process-warning", 1376 + "quick-format-unescaped", 1377 + "real-require", 1378 + "safe-stable-stringify", 1379 + "sonic-boom", 1380 + "thread-stream" 1381 + ], 1382 + "bin": true 1383 + }, 1155 1384 "pirates@4.0.7": { 1156 1385 "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==" 1157 1386 }, ··· 1451 1680 "preact@10.26.7": { 1452 1681 "integrity": "sha512-43xS+QYc1X1IPbw03faSgY6I6OYWcLrJRv3hU0+qMOfh/XCHcP0MX2CVjNARYR2cC/guu975sta4OcjlczxD7g==" 1453 1682 }, 1683 + "process-warning@3.0.0": { 1684 + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==" 1685 + }, 1686 + "process@0.11.10": { 1687 + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" 1688 + }, 1689 + "proxy-from-env@1.1.0": { 1690 + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 1691 + }, 1454 1692 "psl@1.15.0": { 1455 1693 "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", 1456 1694 "dependencies": [ ··· 1463 1701 "queue-microtask@1.2.3": { 1464 1702 "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" 1465 1703 }, 1704 + "quick-format-unescaped@4.0.4": { 1705 + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" 1706 + }, 1466 1707 "read-cache@1.0.0": { 1467 1708 "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", 1468 1709 "dependencies": [ 1469 1710 "pify" 1470 1711 ] 1471 1712 }, 1713 + "readable-stream@4.7.0": { 1714 + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", 1715 + "dependencies": [ 1716 + "abort-controller", 1717 + "buffer", 1718 + "events", 1719 + "process", 1720 + "string_decoder" 1721 + ] 1722 + }, 1472 1723 "readdirp@3.6.0": { 1473 1724 "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 1474 1725 "dependencies": [ 1475 1726 "picomatch" 1476 1727 ] 1728 + }, 1729 + "real-require@0.2.0": { 1730 + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==" 1477 1731 }, 1478 1732 "resolve@1.22.10": { 1479 1733 "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", ··· 1493 1747 "queue-microtask" 1494 1748 ] 1495 1749 }, 1750 + "safe-buffer@5.2.1": { 1751 + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 1752 + }, 1753 + "safe-stable-stringify@2.5.0": { 1754 + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==" 1755 + }, 1496 1756 "shebang-command@2.0.0": { 1497 1757 "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1498 1758 "dependencies": [ ··· 1504 1764 }, 1505 1765 "signal-exit@4.1.0": { 1506 1766 "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" 1767 + }, 1768 + "sonic-boom@3.8.1": { 1769 + "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", 1770 + "dependencies": [ 1771 + "atomic-sleep" 1772 + ] 1507 1773 }, 1508 1774 "source-map-js@1.2.1": { 1509 1775 "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" 1510 1776 }, 1777 + "split2@4.2.0": { 1778 + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==" 1779 + }, 1511 1780 "string-width@4.2.3": { 1512 1781 "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1513 1782 "dependencies": [ ··· 1522 1791 "eastasianwidth", 1523 1792 "emoji-regex@9.2.2", 1524 1793 "strip-ansi@7.1.0" 1794 + ] 1795 + }, 1796 + "string_decoder@1.3.0": { 1797 + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 1798 + "dependencies": [ 1799 + "safe-buffer" 1525 1800 ] 1526 1801 }, 1527 1802 "strip-ansi@6.0.1": { ··· 1613 1888 "any-promise" 1614 1889 ] 1615 1890 }, 1891 + "thread-stream@2.7.0": { 1892 + "integrity": "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==", 1893 + "dependencies": [ 1894 + "real-require" 1895 + ] 1896 + }, 1616 1897 "tlds@1.258.0": { 1617 1898 "integrity": "sha512-XGhStWuOlBA5D8QnyN2xtgB2cUOdJ3ztisne1DYVWMcVH29qh8eQIpRmP3HnuJLdgyzG0HpdGzRMu1lm/Oictw==", 1618 1899 "bin": true ··· 1709 1990 "jsr:@fresh/plugin-tailwind@^0.0.1-alpha.7", 1710 1991 "jsr:@knotbin/posthog-fresh@~0.1.3", 1711 1992 "npm:@atproto/api@~0.15.6", 1993 + "npm:@atproto/crypto@~0.4.4", 1994 + "npm:@did-plc/lib@^0.0.4", 1712 1995 "npm:@preact/signals@^2.0.4", 1713 1996 "npm:posthog-js@1.120.0", 1714 1997 "npm:preact@^10.26.6",
+201
islands/DidPlcProgress.tsx
··· 1 + import { useState } from "preact/hooks"; 2 + 3 + interface PlcUpdateStep { 4 + name: string; 5 + status: "pending" | "in-progress" | "completed" | "error"; 6 + error?: string; 7 + } 8 + 9 + export default function PlcUpdateProgress() { 10 + const [hasStarted, setHasStarted] = useState(false); 11 + const [steps, setSteps] = useState<PlcUpdateStep[]>([ 12 + { name: "Generate PLC key", status: "pending" }, 13 + { name: "Update PLC key", status: "pending" }, 14 + ]); 15 + const [generatedKey, setGeneratedKey] = useState<string>(""); 16 + const [updateKey, setUpdateKey] = useState<string>(""); 17 + const [updateResult, setUpdateResult] = useState<string>(""); 18 + 19 + const updateStepStatus = ( 20 + index: number, 21 + status: PlcUpdateStep["status"], 22 + error?: string 23 + ) => { 24 + setSteps((prevSteps) => 25 + prevSteps.map((step, i) => 26 + i === index ? { ...step, status, error } : step 27 + ) 28 + ); 29 + }; 30 + 31 + const handleStart = () => { 32 + setHasStarted(true); 33 + }; 34 + 35 + const handleGenerateKey = async () => { 36 + updateStepStatus(0, "in-progress"); 37 + try { 38 + const res = await fetch("/api/plc/keys"); 39 + const text = await res.text(); 40 + if (!res.ok) { 41 + try { 42 + const json = JSON.parse(text); 43 + throw new Error(json.message || "Failed to generate key"); 44 + } catch { 45 + throw new Error(text || "Failed to generate key"); 46 + } 47 + } 48 + let data; 49 + try { 50 + data = JSON.parse(text); 51 + } catch { 52 + throw new Error("Invalid response from /api/plc/keys"); 53 + } 54 + if (!data.did || !data.signature) { 55 + throw new Error("Key generation failed: missing did or signature"); 56 + } 57 + setGeneratedKey(data.did); 58 + setUpdateKey(data.did); 59 + updateStepStatus(0, "completed"); 60 + } catch (error) { 61 + updateStepStatus( 62 + 0, 63 + "error", 64 + error instanceof Error ? error.message : String(error) 65 + ); 66 + } 67 + }; 68 + 69 + const handleUpdateKey = async () => { 70 + updateStepStatus(1, "in-progress"); 71 + setUpdateResult(""); 72 + try { 73 + const res = await fetch("/api/plc/update", { 74 + method: "POST", 75 + headers: { "Content-Type": "application/json" }, 76 + body: JSON.stringify({ key: updateKey }), 77 + }); 78 + const text = await res.text(); 79 + if (!res.ok) { 80 + try { 81 + const json = JSON.parse(text); 82 + throw new Error(json.message || "Failed to update key"); 83 + } catch { 84 + throw new Error(text || "Failed to update key"); 85 + } 86 + } 87 + setUpdateResult("Key updated successfully!"); 88 + updateStepStatus(1, "completed"); 89 + } catch (error) { 90 + updateStepStatus( 91 + 1, 92 + "error", 93 + error instanceof Error ? error.message : String(error) 94 + ); 95 + setUpdateResult(error instanceof Error ? error.message : String(error)); 96 + } 97 + }; 98 + 99 + if (!hasStarted) { 100 + return ( 101 + <div class="space-y-6"> 102 + <div class="bg-blue-50 dark:bg-blue-900 p-6 rounded-lg border border-blue-200 dark:border-blue-800"> 103 + <h3 class="text-lg font-medium text-blue-900 dark:text-blue-100 mb-4"> 104 + PLC Key Management 105 + </h3> 106 + <p class="text-blue-800 dark:text-blue-200 mb-4"> 107 + This tool will help you generate and update PLC (Personal Linked 108 + Data) keys for your DID (Decentralized Identifier). 109 + </p> 110 + <div class="space-y-2 text-sm text-blue-700 dark:text-blue-300"> 111 + <p> 112 + • Generate a new PLC key with cryptographic signature verification 113 + </p> 114 + <p>• Update your existing DID with the new key</p> 115 + <p>• All operations require authentication</p> 116 + </div> 117 + <button 118 + onClick={handleStart} 119 + class="mt-4 px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors duration-200" 120 + > 121 + Start PLC Key Management 122 + </button> 123 + </div> 124 + </div> 125 + ); 126 + } 127 + 128 + return ( 129 + <div class="space-y-8"> 130 + <div class="space-y-4"> 131 + {/* Step 1: Generate PLC key */} 132 + <div 133 + class={`flex items-center space-x-3 p-4 rounded-lg ${ 134 + steps[0].status === "completed" 135 + ? "bg-green-50 dark:bg-green-900" 136 + : steps[0].status === "in-progress" 137 + ? "bg-blue-50 dark:bg-blue-900" 138 + : steps[0].status === "error" 139 + ? "bg-red-50 dark:bg-red-900" 140 + : "bg-gray-50 dark:bg-gray-800" 141 + }`} 142 + > 143 + <button 144 + class="px-4 py-2 bg-blue-600 text-white rounded-md" 145 + onClick={handleGenerateKey} 146 + disabled={steps[0].status === "in-progress"} 147 + > 148 + Generate PLC Key 149 + </button> 150 + {steps[0].status === "completed" && ( 151 + <span class="text-green-700 ml-4">Key generated!</span> 152 + )} 153 + {steps[0].status === "error" && ( 154 + <span class="text-red-700 ml-4">{steps[0].error}</span> 155 + )} 156 + </div> 157 + {generatedKey && ( 158 + <div class="p-2 bg-gray-100 dark:bg-gray-700 rounded"> 159 + <div class="text-xs text-gray-700 dark:text-gray-200 break-all"> 160 + <b>Generated DID:</b> {generatedKey} 161 + </div> 162 + </div> 163 + )} 164 + {/* Step 2: Update PLC key */} 165 + <div 166 + class={`flex flex-col space-y-2 p-4 rounded-lg ${ 167 + steps[1].status === "completed" 168 + ? "bg-green-50 dark:bg-green-900" 169 + : steps[1].status === "in-progress" 170 + ? "bg-blue-50 dark:bg-blue-900" 171 + : steps[1].status === "error" 172 + ? "bg-red-50 dark:bg-red-900" 173 + : "bg-gray-50 dark:bg-gray-800" 174 + }`} 175 + > 176 + <label class="text-sm mb-1">DID to update:</label> 177 + <input 178 + class="p-2 rounded border border-gray-300 dark:border-gray-600" 179 + type="text" 180 + value={updateKey} 181 + onInput={(e) => setUpdateKey(e.currentTarget.value)} 182 + placeholder="Paste or use generated DID" 183 + /> 184 + <button 185 + class="mt-2 px-4 py-2 bg-blue-600 text-white rounded-md" 186 + onClick={handleUpdateKey} 187 + disabled={steps[1].status === "in-progress" || !updateKey} 188 + > 189 + Update PLC Key 190 + </button> 191 + {steps[1].status === "completed" && ( 192 + <span class="text-green-700 mt-2">{updateResult}</span> 193 + )} 194 + {steps[1].status === "error" && ( 195 + <span class="text-red-700 mt-2">{steps[1].error}</span> 196 + )} 197 + </div> 198 + </div> 199 + </div> 200 + ); 201 + }
+42
routes/api/plc/keys.ts
··· 1 + import { Secp256k1Keypair } from "@atproto/crypto"; 2 + import { getSessionAgent } from "../../../lib/sessions.ts"; 3 + import { define } from "../../../utils.ts"; 4 + 5 + /** 6 + * Generate and return PLC keys for the authenticated user 7 + */ 8 + export const handler = define.handlers({ 9 + async GET(ctx) { 10 + const agent = await getSessionAgent(ctx.req); 11 + if (!agent) { 12 + return new Response("Unauthorized", { status: 401 }); 13 + } 14 + 15 + // Create a new keypair 16 + const keypair = await Secp256k1Keypair.create({ exportable: true }); 17 + 18 + // sign binary data, resulting signature bytes. 19 + // SHA-256 hash of data is what actually gets signed. 20 + // signature output is often base64-encoded. 21 + const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); 22 + const sig = await keypair.sign(data); 23 + 24 + // serialize the public key as a did:key string, which includes key type metadata 25 + const pubDidKey = keypair.did(); 26 + console.log(pubDidKey); 27 + 28 + // output would look something like: 'did:key:zQ3shVRtgqTRHC7Lj4DYScoDgReNpsDp3HBnuKBKt1FSXKQ38' 29 + 30 + // Return the key information 31 + return new Response( 32 + JSON.stringify({ 33 + did: pubDidKey, 34 + signature: btoa(String.fromCharCode(...sig)), 35 + data: Array.from(data), 36 + }), 37 + { 38 + headers: { "Content-Type": "application/json" }, 39 + } 40 + ); 41 + }, 42 + });
+115
routes/api/plc/update.ts
··· 1 + import { Agent } from "@atproto/api"; 2 + import { getSessionAgent } from "../../../lib/sessions.ts"; 3 + import { define } from "../../../utils.ts"; 4 + import * as plc from "@did-plc/lib"; 5 + 6 + /** 7 + * Update PLC rotation keys for the authenticated user 8 + */ 9 + export const handler = define.handlers({ 10 + async POST(ctx) { 11 + try { 12 + const { key: newKey } = await ctx.req.json(); 13 + 14 + if (!newKey) { 15 + return new Response( 16 + JSON.stringify({ 17 + success: false, 18 + message: "Missing key in request body", 19 + }), 20 + { 21 + status: 400, 22 + headers: { "Content-Type": "application/json" }, 23 + } 24 + ); 25 + } 26 + 27 + const agent = await getSessionAgent(ctx.req); 28 + if (!agent) { 29 + return new Response( 30 + JSON.stringify({ 31 + success: false, 32 + message: "Unauthorized", 33 + }), 34 + { 35 + status: 401, 36 + headers: { "Content-Type": "application/json" }, 37 + } 38 + ); 39 + } 40 + 41 + const did = agent.did; 42 + if (!did) { 43 + return new Response( 44 + JSON.stringify({ 45 + success: false, 46 + message: "No DID found in session", 47 + }), 48 + { 49 + status: 400, 50 + headers: { "Content-Type": "application/json" }, 51 + } 52 + ); 53 + } 54 + 55 + const client = new plc.Client("https://plc.directory"); 56 + 57 + // Fetch current DID document 58 + const didDoc = await client.getDocumentData(did); 59 + if (!didDoc) { 60 + return new Response( 61 + JSON.stringify({ 62 + success: false, 63 + message: "DID document not found", 64 + }), 65 + { 66 + status: 404, 67 + headers: { "Content-Type": "application/json" }, 68 + } 69 + ); 70 + } 71 + 72 + // Create new rotation keys array with the new key at the beginning 73 + const newKeys = [newKey, ...didDoc.rotationKeys]; 74 + 75 + // Create the update operation 76 + const updateOp = plc.updateRotationKeysOp( 77 + did, 78 + didDoc.rotationKeys, 79 + newKeys 80 + ); 81 + 82 + // Submit the operation to the PLC directory 83 + await client.sendOperation(updateOp); 84 + 85 + return new Response( 86 + JSON.stringify({ 87 + success: true, 88 + message: "PLC rotation keys updated successfully", 89 + did, 90 + newKey, 91 + totalKeys: newKeys.length, 92 + }), 93 + { 94 + status: 200, 95 + headers: { "Content-Type": "application/json" }, 96 + } 97 + ); 98 + } catch (error) { 99 + console.error("PLC update error:", error); 100 + const message = 101 + error instanceof Error ? error.message : "Unknown error occurred"; 102 + 103 + return new Response( 104 + JSON.stringify({ 105 + success: false, 106 + message: `Failed to update PLC keys: ${message}`, 107 + }), 108 + { 109 + status: 500, 110 + headers: { "Content-Type": "application/json" }, 111 + } 112 + ); 113 + } 114 + }, 115 + });
+19
routes/ticket-booth/index.tsx
··· 1 + import { PageProps } from "fresh"; 2 + import MigrationSetup from "../../islands/MigrationSetup.tsx"; 3 + import DidPlcProgress from "../../islands/DidPlcProgress.tsx"; 4 + 5 + export default function TicketBooth(props: PageProps) { 6 + const service = props.url.searchParams.get("service"); 7 + const handle = props.url.searchParams.get("handle"); 8 + 9 + return ( 10 + <div class=" bg-gray-50 dark:bg-gray-900 p-4"> 11 + <div class="max-w-2xl mx-auto"> 12 + <h1 class="font-mono text-3xl font-bold text-gray-900 dark:text-white mb-8"> 13 + Ticket Booth Self-Service Kiosk 14 + </h1> 15 + <DidPlcProgress /> 16 + </div> 17 + </div> 18 + ); 19 + }