simple atproto oauth for static svelte apps flo-bit.dev/svelte-atproto-client-oauth/

add dark mode, small fixes, add comments

Florian 9615a0c1 42f02ab8

+243 -67
+1
package.json
··· 20 "@atcute/identity-resolver": "^1.2.2", 21 "@atcute/lexicons": "^1.2.6", 22 "@atcute/oauth-browser-client": "^2.0.3", 23 "@eslint/compat": "^2.0.1", 24 "@eslint/js": "^9.39.2", 25 "@sveltejs/adapter-auto": "^7.0.0",
··· 20 "@atcute/identity-resolver": "^1.2.2", 21 "@atcute/lexicons": "^1.2.6", 22 "@atcute/oauth-browser-client": "^2.0.3", 23 + "@atcute/tid": "^1.1.1", 24 "@eslint/compat": "^2.0.1", 25 "@eslint/js": "^9.39.2", 26 "@sveltejs/adapter-auto": "^7.0.0",
+84 -33
pnpm-lock.yaml
··· 26 '@atcute/oauth-browser-client': 27 specifier: ^2.0.3 28 version: 2.0.3(@atcute/identity@1.1.3) 29 '@eslint/compat': 30 specifier: ^2.0.1 31 version: 2.0.1(eslint@9.39.2(jiti@2.6.1)) ··· 34 version: 9.39.2 35 '@sveltejs/adapter-auto': 36 specifier: ^7.0.0 37 - version: 7.0.0(@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2))) 38 '@sveltejs/adapter-static': 39 specifier: ^3.0.10 40 - version: 3.0.10(@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2))) 41 '@sveltejs/kit': 42 specifier: ^2.50.0 43 - version: 2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)) 44 '@sveltejs/vite-plugin-svelte': 45 specifier: ^6.2.4 46 - version: 6.2.4(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)) 47 '@tailwindcss/forms': 48 specifier: ^0.5.11 49 version: 0.5.11(tailwindcss@4.1.18) 50 '@tailwindcss/vite': 51 specifier: ^4.1.18 52 - version: 4.1.18(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)) 53 bits-ui: 54 specifier: ^2.15.4 55 - version: 2.15.4(@internationalized/date@3.10.1)(@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0) 56 eslint: 57 specifier: ^9.39.2 58 version: 9.39.2(jiti@2.6.1) ··· 91 version: 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 92 vite: 93 specifier: ^7.3.1 94 - version: 7.3.1(jiti@2.6.1)(lightningcss@1.30.2) 95 96 packages: 97 ··· 120 121 '@atcute/oauth-browser-client@2.0.3': 122 resolution: {integrity: sha512-rzUjwhjE4LRRKdQnCFQag/zXRZMEAB1hhBoLfnoQuHwWbmDUCL7fzwC3jRhDPp3om8XaYNDj8a/iqRip0wRqoQ==, tarball: https://registry.npmjs.org/@atcute/oauth-browser-client/-/oauth-browser-client-2.0.3.tgz} 123 124 '@atcute/uint8array@1.0.6': 125 resolution: {integrity: sha512-ucfRBQc7BFT8n9eCyGOzDHEMKF/nZwhS2pPao4Xtab1ML3HdFYcX2DM1tadCzas85QTGxHe5urnUAAcNKGRi9A==, tarball: https://registry.npmjs.org/@atcute/uint8array/-/uint8array-1.0.6.tgz} ··· 660 peerDependencies: 661 vite: ^5.2.0 || ^6 || ^7 662 663 '@types/cookie@0.6.0': 664 resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==, tarball: https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz} 665 ··· 668 669 '@types/json-schema@7.0.15': 670 resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==, tarball: https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz} 671 672 '@typescript-eslint/eslint-plugin@8.53.1': 673 resolution: {integrity: sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==, tarball: https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz} ··· 772 brace-expansion@2.0.2: 773 resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==, tarball: https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz} 774 775 callsites@3.1.0: 776 resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, tarball: https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz} 777 engines: {node: '>=6'} ··· 1166 natural-compare@1.4.0: 1167 resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, tarball: https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz} 1168 1169 obug@2.1.1: 1170 resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==, tarball: https://registry.npmjs.org/obug/-/obug-2.1.1.tgz} 1171 ··· 1437 engines: {node: '>=14.17'} 1438 hasBin: true 1439 1440 unicode-segmenter@0.14.5: 1441 resolution: {integrity: sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g==, tarball: https://registry.npmjs.org/unicode-segmenter/-/unicode-segmenter-0.14.5.tgz} 1442 ··· 1564 transitivePeerDependencies: 1565 - '@atcute/identity' 1566 1567 '@atcute/uint8array@1.0.6': {} 1568 1569 '@atcute/util-fetch@1.0.5': ··· 1838 dependencies: 1839 acorn: 8.15.0 1840 1841 - '@sveltejs/adapter-auto@7.0.0(@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))': 1842 dependencies: 1843 - '@sveltejs/kit': 2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)) 1844 1845 - '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))': 1846 dependencies: 1847 - '@sveltejs/kit': 2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)) 1848 1849 - '@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2))': 1850 dependencies: 1851 '@standard-schema/spec': 1.1.0 1852 '@sveltejs/acorn-typescript': 1.0.8(acorn@8.15.0) 1853 - '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)) 1854 '@types/cookie': 0.6.0 1855 acorn: 8.15.0 1856 cookie: 0.6.0 ··· 1863 set-cookie-parser: 2.7.2 1864 sirv: 3.0.2 1865 svelte: 5.48.0 1866 - vite: 7.3.1(jiti@2.6.1)(lightningcss@1.30.2) 1867 optionalDependencies: 1868 typescript: 5.9.3 1869 1870 - '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2))': 1871 dependencies: 1872 - '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)) 1873 obug: 2.1.1 1874 svelte: 5.48.0 1875 - vite: 7.3.1(jiti@2.6.1)(lightningcss@1.30.2) 1876 1877 - '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2))': 1878 dependencies: 1879 - '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)) 1880 deepmerge: 4.3.1 1881 magic-string: 0.30.21 1882 obug: 2.1.1 1883 svelte: 5.48.0 1884 - vite: 7.3.1(jiti@2.6.1)(lightningcss@1.30.2) 1885 - vitefu: 1.1.1(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)) 1886 1887 '@swc/helpers@0.5.18': 1888 dependencies: ··· 1954 '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 1955 '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 1956 1957 - '@tailwindcss/vite@4.1.18(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2))': 1958 dependencies: 1959 '@tailwindcss/node': 4.1.18 1960 '@tailwindcss/oxide': 4.1.18 1961 tailwindcss: 4.1.18 1962 - vite: 7.3.1(jiti@2.6.1)(lightningcss@1.30.2) 1963 1964 '@types/cookie@0.6.0': {} 1965 1966 '@types/estree@1.0.8': {} 1967 1968 '@types/json-schema@7.0.15': {} 1969 1970 '@typescript-eslint/eslint-plugin@8.53.1(@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': 1971 dependencies: ··· 2083 2084 balanced-match@1.0.2: {} 2085 2086 - bits-ui@2.15.4(@internationalized/date@3.10.1)(@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0): 2087 dependencies: 2088 '@floating-ui/core': 1.7.3 2089 '@floating-ui/dom': 1.7.4 2090 '@internationalized/date': 3.10.1 2091 esm-env: 1.2.2 2092 - runed: 0.35.1(@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0) 2093 svelte: 5.48.0 2094 - svelte-toolbelt: 0.10.6(@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0) 2095 tabbable: 6.4.0 2096 transitivePeerDependencies: 2097 - '@sveltejs/kit' ··· 2104 brace-expansion@2.0.2: 2105 dependencies: 2106 balanced-match: 1.0.2 2107 2108 callsites@3.1.0: {} 2109 ··· 2462 2463 natural-compare@1.4.0: {} 2464 2465 obug@2.1.1: {} 2466 2467 optionator@0.9.4: ··· 2571 '@rollup/rollup-win32-x64-msvc': 4.56.0 2572 fsevents: 2.3.3 2573 2574 - runed@0.35.1(@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0): 2575 dependencies: 2576 dequal: 2.0.3 2577 esm-env: 1.2.2 2578 lz-string: 1.5.0 2579 svelte: 5.48.0 2580 optionalDependencies: 2581 - '@sveltejs/kit': 2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)) 2582 2583 sade@1.8.1: 2584 dependencies: ··· 2635 optionalDependencies: 2636 svelte: 5.48.0 2637 2638 - svelte-toolbelt@0.10.6(@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0): 2639 dependencies: 2640 clsx: 2.1.1 2641 - runed: 0.35.1(@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0) 2642 style-to-object: 1.0.14 2643 svelte: 5.48.0 2644 transitivePeerDependencies: ··· 2698 2699 typescript@5.9.3: {} 2700 2701 unicode-segmenter@0.14.5: {} 2702 2703 uri-js@4.4.1: ··· 2706 2707 util-deprecate@1.0.2: {} 2708 2709 - vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2): 2710 dependencies: 2711 esbuild: 0.27.2 2712 fdir: 6.5.0(picomatch@4.0.3) ··· 2715 rollup: 4.56.0 2716 tinyglobby: 0.2.15 2717 optionalDependencies: 2718 fsevents: 2.3.3 2719 jiti: 2.6.1 2720 lightningcss: 1.30.2 2721 2722 - vitefu@1.1.1(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)): 2723 optionalDependencies: 2724 - vite: 7.3.1(jiti@2.6.1)(lightningcss@1.30.2) 2725 2726 which@2.0.2: 2727 dependencies:
··· 26 '@atcute/oauth-browser-client': 27 specifier: ^2.0.3 28 version: 2.0.3(@atcute/identity@1.1.3) 29 + '@atcute/tid': 30 + specifier: ^1.1.1 31 + version: 1.1.1 32 '@eslint/compat': 33 specifier: ^2.0.1 34 version: 2.0.1(eslint@9.39.2(jiti@2.6.1)) ··· 37 version: 9.39.2 38 '@sveltejs/adapter-auto': 39 specifier: ^7.0.0 40 + version: 7.0.0(@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2))) 41 '@sveltejs/adapter-static': 42 specifier: ^3.0.10 43 + version: 3.0.10(@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2))) 44 '@sveltejs/kit': 45 specifier: ^2.50.0 46 + version: 2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)) 47 '@sveltejs/vite-plugin-svelte': 48 specifier: ^6.2.4 49 + version: 6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)) 50 '@tailwindcss/forms': 51 specifier: ^0.5.11 52 version: 0.5.11(tailwindcss@4.1.18) 53 '@tailwindcss/vite': 54 specifier: ^4.1.18 55 + version: 4.1.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)) 56 bits-ui: 57 specifier: ^2.15.4 58 + version: 2.15.4(@internationalized/date@3.10.1)(@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0) 59 eslint: 60 specifier: ^9.39.2 61 version: 9.39.2(jiti@2.6.1) ··· 94 version: 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 95 vite: 96 specifier: ^7.3.1 97 + version: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2) 98 99 packages: 100 ··· 123 124 '@atcute/oauth-browser-client@2.0.3': 125 resolution: {integrity: sha512-rzUjwhjE4LRRKdQnCFQag/zXRZMEAB1hhBoLfnoQuHwWbmDUCL7fzwC3jRhDPp3om8XaYNDj8a/iqRip0wRqoQ==, tarball: https://registry.npmjs.org/@atcute/oauth-browser-client/-/oauth-browser-client-2.0.3.tgz} 126 + 127 + '@atcute/tid@1.1.1': 128 + resolution: {integrity: sha512-djJ8UGhLkTU5V51yCnBEruMg35qETjWzWy5sJG/2gEOl2Gd7rQWHSaf+yrO6vMS5EFA38U2xOWE3EDUPzvc2ZQ==, tarball: https://registry.npmjs.org/@atcute/tid/-/tid-1.1.1.tgz} 129 + 130 + '@atcute/time-ms@1.2.0': 131 + resolution: {integrity: sha512-dtNKebVIbr1+yu3a6vgtL4sfkNgxkL3aA+ohHsjtW83WWMjjGvX8GVTVmYCJ2dYSxIoxK0q1yWs11PmlqzmQ/A==, tarball: https://registry.npmjs.org/@atcute/time-ms/-/time-ms-1.2.0.tgz} 132 133 '@atcute/uint8array@1.0.6': 134 resolution: {integrity: sha512-ucfRBQc7BFT8n9eCyGOzDHEMKF/nZwhS2pPao4Xtab1ML3HdFYcX2DM1tadCzas85QTGxHe5urnUAAcNKGRi9A==, tarball: https://registry.npmjs.org/@atcute/uint8array/-/uint8array-1.0.6.tgz} ··· 669 peerDependencies: 670 vite: ^5.2.0 || ^6 || ^7 671 672 + '@types/bun@1.3.6': 673 + resolution: {integrity: sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA==, tarball: https://registry.npmjs.org/@types/bun/-/bun-1.3.6.tgz} 674 + 675 '@types/cookie@0.6.0': 676 resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==, tarball: https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz} 677 ··· 680 681 '@types/json-schema@7.0.15': 682 resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==, tarball: https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz} 683 + 684 + '@types/node@25.0.10': 685 + resolution: {integrity: sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==, tarball: https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz} 686 687 '@typescript-eslint/eslint-plugin@8.53.1': 688 resolution: {integrity: sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==, tarball: https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz} ··· 787 brace-expansion@2.0.2: 788 resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==, tarball: https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz} 789 790 + bun-types@1.3.6: 791 + resolution: {integrity: sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ==, tarball: https://registry.npmjs.org/bun-types/-/bun-types-1.3.6.tgz} 792 + 793 callsites@3.1.0: 794 resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, tarball: https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz} 795 engines: {node: '>=6'} ··· 1184 natural-compare@1.4.0: 1185 resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, tarball: https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz} 1186 1187 + node-gyp-build@4.8.4: 1188 + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==, tarball: https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz} 1189 + hasBin: true 1190 + 1191 obug@2.1.1: 1192 resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==, tarball: https://registry.npmjs.org/obug/-/obug-2.1.1.tgz} 1193 ··· 1459 engines: {node: '>=14.17'} 1460 hasBin: true 1461 1462 + undici-types@7.16.0: 1463 + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==, tarball: https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz} 1464 + 1465 unicode-segmenter@0.14.5: 1466 resolution: {integrity: sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g==, tarball: https://registry.npmjs.org/unicode-segmenter/-/unicode-segmenter-0.14.5.tgz} 1467 ··· 1589 transitivePeerDependencies: 1590 - '@atcute/identity' 1591 1592 + '@atcute/tid@1.1.1': 1593 + dependencies: 1594 + '@atcute/time-ms': 1.2.0 1595 + 1596 + '@atcute/time-ms@1.2.0': 1597 + dependencies: 1598 + '@types/bun': 1.3.6 1599 + node-gyp-build: 4.8.4 1600 + 1601 '@atcute/uint8array@1.0.6': {} 1602 1603 '@atcute/util-fetch@1.0.5': ··· 1872 dependencies: 1873 acorn: 8.15.0 1874 1875 + '@sveltejs/adapter-auto@7.0.0(@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))': 1876 dependencies: 1877 + '@sveltejs/kit': 2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)) 1878 1879 + '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))': 1880 dependencies: 1881 + '@sveltejs/kit': 2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)) 1882 1883 + '@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2))': 1884 dependencies: 1885 '@standard-schema/spec': 1.1.0 1886 '@sveltejs/acorn-typescript': 1.0.8(acorn@8.15.0) 1887 + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)) 1888 '@types/cookie': 0.6.0 1889 acorn: 8.15.0 1890 cookie: 0.6.0 ··· 1897 set-cookie-parser: 2.7.2 1898 sirv: 3.0.2 1899 svelte: 5.48.0 1900 + vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2) 1901 optionalDependencies: 1902 typescript: 5.9.3 1903 1904 + '@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2))': 1905 dependencies: 1906 + '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)) 1907 obug: 2.1.1 1908 svelte: 5.48.0 1909 + vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2) 1910 1911 + '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2))': 1912 dependencies: 1913 + '@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)) 1914 deepmerge: 4.3.1 1915 magic-string: 0.30.21 1916 obug: 2.1.1 1917 svelte: 5.48.0 1918 + vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2) 1919 + vitefu: 1.1.1(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)) 1920 1921 '@swc/helpers@0.5.18': 1922 dependencies: ··· 1988 '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 1989 '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 1990 1991 + '@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2))': 1992 dependencies: 1993 '@tailwindcss/node': 4.1.18 1994 '@tailwindcss/oxide': 4.1.18 1995 tailwindcss: 4.1.18 1996 + vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2) 1997 + 1998 + '@types/bun@1.3.6': 1999 + dependencies: 2000 + bun-types: 1.3.6 2001 2002 '@types/cookie@0.6.0': {} 2003 2004 '@types/estree@1.0.8': {} 2005 2006 '@types/json-schema@7.0.15': {} 2007 + 2008 + '@types/node@25.0.10': 2009 + dependencies: 2010 + undici-types: 7.16.0 2011 2012 '@typescript-eslint/eslint-plugin@8.53.1(@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': 2013 dependencies: ··· 2125 2126 balanced-match@1.0.2: {} 2127 2128 + bits-ui@2.15.4(@internationalized/date@3.10.1)(@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0): 2129 dependencies: 2130 '@floating-ui/core': 1.7.3 2131 '@floating-ui/dom': 1.7.4 2132 '@internationalized/date': 3.10.1 2133 esm-env: 1.2.2 2134 + runed: 0.35.1(@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0) 2135 svelte: 5.48.0 2136 + svelte-toolbelt: 0.10.6(@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0) 2137 tabbable: 6.4.0 2138 transitivePeerDependencies: 2139 - '@sveltejs/kit' ··· 2146 brace-expansion@2.0.2: 2147 dependencies: 2148 balanced-match: 1.0.2 2149 + 2150 + bun-types@1.3.6: 2151 + dependencies: 2152 + '@types/node': 25.0.10 2153 2154 callsites@3.1.0: {} 2155 ··· 2508 2509 natural-compare@1.4.0: {} 2510 2511 + node-gyp-build@4.8.4: {} 2512 + 2513 obug@2.1.1: {} 2514 2515 optionator@0.9.4: ··· 2619 '@rollup/rollup-win32-x64-msvc': 4.56.0 2620 fsevents: 2.3.3 2621 2622 + runed@0.35.1(@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0): 2623 dependencies: 2624 dequal: 2.0.3 2625 esm-env: 1.2.2 2626 lz-string: 1.5.0 2627 svelte: 5.48.0 2628 optionalDependencies: 2629 + '@sveltejs/kit': 2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)) 2630 2631 sade@1.8.1: 2632 dependencies: ··· 2683 optionalDependencies: 2684 svelte: 5.48.0 2685 2686 + svelte-toolbelt@0.10.6(@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0): 2687 dependencies: 2688 clsx: 2.1.1 2689 + runed: 0.35.1(@sveltejs/kit@2.50.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)))(svelte@5.48.0) 2690 style-to-object: 1.0.14 2691 svelte: 5.48.0 2692 transitivePeerDependencies: ··· 2746 2747 typescript@5.9.3: {} 2748 2749 + undici-types@7.16.0: {} 2750 + 2751 unicode-segmenter@0.14.5: {} 2752 2753 uri-js@4.4.1: ··· 2756 2757 util-deprecate@1.0.2: {} 2758 2759 + vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2): 2760 dependencies: 2761 esbuild: 0.27.2 2762 fdir: 6.5.0(picomatch@4.0.3) ··· 2765 rollup: 4.56.0 2766 tinyglobby: 0.2.15 2767 optionalDependencies: 2768 + '@types/node': 25.0.10 2769 fsevents: 2.3.3 2770 jiti: 2.6.1 2771 lightningcss: 1.30.2 2772 2773 + vitefu@1.1.1(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)): 2774 optionalDependencies: 2775 + vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2) 2776 2777 which@2.0.2: 2778 dependencies:
+1 -1
src/app.css
··· 2 3 @plugin '@tailwindcss/forms'; 4 5 - @custom-variant dark (&:where(.dark, .dark *)); 6 7 @theme inline { 8 --color-base-50: var(--base-50);
··· 2 3 @plugin '@tailwindcss/forms'; 4 5 + /* @custom-variant dark (&:where(.dark, .dark *)); */ 6 7 @theme inline { 8 --color-base-50: var(--base-50);
+1 -1
src/app.html
··· 6 <meta name="viewport" content="width=device-width, initial-scale=1" /> 7 %sveltekit.head% 8 </head> 9 - <body data-sveltekit-preload-data="hover"> 10 <div style="display: contents">%sveltekit.body%</div> 11 </body> 12 </html>
··· 6 <meta name="viewport" content="width=device-width, initial-scale=1" /> 7 %sveltekit.head% 8 </head> 9 + <body data-sveltekit-preload-data="hover" class="bg-base-50 dark:bg-base-900 text-base-900 dark:text-base-50"> 10 <div style="display: contents">%sveltekit.body%</div> 11 </body> 12 </html>
+5 -2
src/lib/UI/Button.svelte
··· 3 4 type Props = HTMLButtonAttributes & { 5 children: () => any; 6 }; 7 8 - let { children, class: className, ...props }: Props = $props(); 9 </script> 10 11 <button 12 class={[ 13 - 'bg-accent-600 text-white hover:bg-accent-500 focus-visible:outline-accent-600', 14 'inline-flex cursor-pointer justify-center rounded-full px-3 py-2 text-sm font-semibold shadow-sm focus-visible:outline focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:opacity-50', 15 className 16 ]}
··· 3 4 type Props = HTMLButtonAttributes & { 5 children: () => any; 6 + ref?: HTMLButtonElement | null; 7 + 8 }; 9 10 + let { children, ref = $bindable(), class: className, ...props }: Props = $props(); 11 </script> 12 13 <button 14 + bind:this={ref} 15 class={[ 16 + 'bg-accent-600 hover:bg-accent-500 focus-visible:outline-accent-600 text-white', 17 'inline-flex cursor-pointer justify-center rounded-full px-3 py-2 text-sm font-semibold shadow-sm focus-visible:outline focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:opacity-50', 18 className 19 ]}
+3 -3
src/lib/UI/HandleInput.svelte
··· 57 value = e.currentTarget.value; 58 search(e.currentTarget.value); 59 }} 60 - class="w-full touch-none rounded-full border-0 bg-white ring-0 outline-1 -outline-offset-1 outline-gray-300 focus-within:outline-2 focus-within:-outline-offset-2 focus-within:outline-accent-600 dark:bg-white/5 dark:outline-white/10 dark:focus-within:outline-accent-500" 61 placeholder="handle" 62 id="" 63 aria-label="enter your handle" 64 /> 65 <Combobox.Content 66 - class="z-100 max-h-[30dvh] w-full rounded-2xl border border-base-300 bg-base-50 shadow-lg" 67 sideOffset={10} 68 align="start" 69 side="top" ··· 71 <Combobox.Viewport class="w-full p-1"> 72 {#each results as actor (actor.did)} 73 <Combobox.Item 74 - class="rounded-button my-0.5 flex w-full cursor-pointer items-center gap-2 rounded-xl p-2 px-2 data-highlighted:bg-accent-100" 75 value={actor.handle} 76 label={actor.handle} 77 >
··· 57 value = e.currentTarget.value; 58 search(e.currentTarget.value); 59 }} 60 + class="w-full touch-none rounded-full border-0 bg-white ring-0 outline-1 -outline-offset-1 outline-gray-300 focus-within:outline-2 focus-within:-outline-offset-2 focus-within:outline-accent-600 dark:bg-white/5 dark:outline-white/10 dark:focus-within:outline-accent-500 dark:placeholder:text-base-400" 61 placeholder="handle" 62 id="" 63 aria-label="enter your handle" 64 /> 65 <Combobox.Content 66 + class="z-100 max-h-[30dvh] w-full rounded-2xl border border-base-300 bg-base-50 dark:bg-base-900 dark:border-base-800 shadow-lg" 67 sideOffset={10} 68 align="start" 69 side="top" ··· 71 <Combobox.Viewport class="w-full p-1"> 72 {#each results as actor (actor.did)} 73 <Combobox.Item 74 + class="rounded-button my-0.5 flex w-full cursor-pointer items-center gap-2 rounded-xl p-2 px-2 data-highlighted:bg-accent-100 dark:data-highlighted:bg-accent-600/30" 75 value={actor.handle} 76 label={actor.handle} 77 >
+29 -12
src/lib/UI/LoginModal.svelte
··· 16 import { AppBskyActorDefs } from '@atcute/bluesky'; 17 import Avatar from './Avatar.svelte'; 18 19 - let { signUp = true, loginOnSelect = true }: { signUp?: boolean; loginOnSelect?: boolean } = 20 $props(); 21 22 let value = $state(''); ··· 41 } 42 43 let input: HTMLInputElement | null = $state(null); 44 45 $effect(() => { 46 if (!loginModalState.visible) { ··· 49 loadingLogin = false; 50 selectedActor = undefined; 51 } else { 52 - tick().then(() => { 53 - input?.focus(); 54 - }); 55 } 56 }); 57 58 let selectedActor: AppBskyActorDefs.ProfileViewBasic | undefined = $state(); 59 60 let recentLogins: Record<Did, AppBskyActorDefs.ProfileViewBasic> = $state({}); ··· 104 Login with your internet handle 105 </h3> 106 107 - <div class="text-base-800 mt-2 mb-2 text-xs font-light">e.g. your bluesky account</div> 108 109 <form onsubmit={onSubmit} class="mt-2 flex w-full flex-col gap-2"> 110 {#if showRecentLogins} ··· 113 {#each Object.values(recentLogins).slice(0, 4) as recentLogin} 114 <div class="group"> 115 <div 116 - class="group-hover:bg-base-300 bg-base-200 border-base-300 relative flex h-10 w-full items-center justify-between gap-2 rounded-full border px-2 font-semibold transition-colors duration-100" 117 > 118 <div class="flex items-center gap-2"> 119 - <Avatar src={recentLogin.avatar} /> 120 {recentLogin.handle} 121 </div> 122 <button ··· 124 onclick={() => { 125 value = recentLogin.handle; 126 selectedActor = recentLogin; 127 - onSubmit(); 128 }} 129 > 130 <div class="absolute inset-0 h-full w-full"></div> ··· 165 selectedActor = a; 166 value = a.handle; 167 if (loginOnSelect) onSubmit(); 168 }} 169 bind:ref={input} 170 /> 171 </div> 172 {:else} 173 <div 174 - class="bg-base-200 border-base-300 mt-4 flex h-10 w-full items-center justify-between gap-2 rounded-full border px-2 font-semibold" 175 > 176 <div class="flex items-center gap-2"> 177 - <Avatar src={selectedActor.avatar} /> 178 {selectedActor.handle} 179 </div> 180 ··· 211 <Button 212 onclick={() => { 213 recentLoginsView = false; 214 }} 215 class="w-full">Login with new handle</Button 216 > 217 {:else} 218 - <Button type="submit" disabled={loadingLogin} class="w-full" 219 >{loadingLogin ? 'Loading...' : 'Login'}</Button 220 > 221 {/if} 222 </div> 223 224 {#if signUp} 225 - <div class="border-base-200 text-base-800 mt-4 border-t pt-4 text-sm leading-7"> 226 Don't have an account? 227 <div class="mt-3"> 228 <SecondaryButton
··· 16 import { AppBskyActorDefs } from '@atcute/bluesky'; 17 import Avatar from './Avatar.svelte'; 18 19 + let { signUp = true, loginOnSelect = false }: { signUp?: boolean; loginOnSelect?: boolean } = 20 $props(); 21 22 let value = $state(''); ··· 41 } 42 43 let input: HTMLInputElement | null = $state(null); 44 + let submitButton: HTMLButtonElement | null = $state(null); 45 46 $effect(() => { 47 if (!loginModalState.visible) { ··· 50 loadingLogin = false; 51 selectedActor = undefined; 52 } else { 53 + focusInput(); 54 } 55 }); 56 57 + function focusInput() { 58 + tick().then(() => { 59 + input?.focus(); 60 + }); 61 + } 62 + function focusSubmit() { 63 + tick().then(() => { 64 + submitButton?.focus(); 65 + }); 66 + } 67 + 68 let selectedActor: AppBskyActorDefs.ProfileViewBasic | undefined = $state(); 69 70 let recentLogins: Record<Did, AppBskyActorDefs.ProfileViewBasic> = $state({}); ··· 114 Login with your internet handle 115 </h3> 116 117 + <div class="text-base-800 dark:text-base-200 mt-2 mb-2 text-xs font-light"> 118 + e.g. your bluesky account 119 + </div> 120 121 <form onsubmit={onSubmit} class="mt-2 flex w-full flex-col gap-2"> 122 {#if showRecentLogins} ··· 125 {#each Object.values(recentLogins).slice(0, 4) as recentLogin} 126 <div class="group"> 127 <div 128 + class="group-hover:bg-base-300 bg-base-200 dark:bg-base-700 dark:hover:bg-base-600 dark:border-base-500/50 border-base-300 relative flex h-10 w-full items-center justify-between gap-2 rounded-full border px-2 font-semibold transition-colors duration-100" 129 > 130 <div class="flex items-center gap-2"> 131 + <Avatar class="size-6" src={recentLogin.avatar} /> 132 {recentLogin.handle} 133 </div> 134 <button ··· 136 onclick={() => { 137 value = recentLogin.handle; 138 selectedActor = recentLogin; 139 + if (loginOnSelect) onSubmit(); 140 + else focusSubmit(); 141 }} 142 > 143 <div class="absolute inset-0 h-full w-full"></div> ··· 178 selectedActor = a; 179 value = a.handle; 180 if (loginOnSelect) onSubmit(); 181 + else focusSubmit(); 182 }} 183 bind:ref={input} 184 /> 185 </div> 186 {:else} 187 <div 188 + class="bg-base-200 dark:bg-base-700 border-base-300 dark:border-base-600 mt-4 flex h-10 w-full items-center justify-between gap-2 rounded-full border px-2 font-semibold" 189 > 190 <div class="flex items-center gap-2"> 191 + <Avatar class="size-6" src={selectedActor.avatar} /> 192 {selectedActor.handle} 193 </div> 194 ··· 225 <Button 226 onclick={() => { 227 recentLoginsView = false; 228 + focusInput(); 229 }} 230 class="w-full">Login with new handle</Button 231 > 232 {:else} 233 + <Button bind:ref={submitButton} type="submit" disabled={loadingLogin} class="w-full" 234 >{loadingLogin ? 'Loading...' : 'Login'}</Button 235 > 236 {/if} 237 </div> 238 239 {#if signUp} 240 + <div 241 + class="border-base-200 dark:border-base-700 text-base-800 dark:text-base-200 mt-4 border-t pt-4 text-sm leading-7" 242 + > 243 Don't have an account? 244 <div class="mt-3"> 245 <SecondaryButton
+1 -1
src/lib/UI/SecondaryButton.svelte
··· 10 11 <button 12 class={[ 13 - 'bg-base-300 text-black transition-colors duration-100 hover:bg-base-200 focus-visible:outline-base-600', 14 'inline-flex cursor-pointer justify-center rounded-full px-3 py-2 text-sm font-semibold shadow-sm focus-visible:outline focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:opacity-50', 15 className 16 ]}
··· 10 11 <button 12 class={[ 13 + 'bg-base-300 dark:bg-base-700 dark:text-base-50 dark:hover:bg-base-600 text-black transition-colors duration-100 hover:bg-base-200 focus-visible:outline-base-600', 14 'inline-flex cursor-pointer justify-center rounded-full px-3 py-2 text-sm font-semibold shadow-sm focus-visible:outline focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:opacity-50', 15 className 16 ]}
+3 -2
src/lib/atproto/auth.svelte.ts
··· 24 import { metadata } from './metadata'; 25 import { getDetailedProfile } from './methods'; 26 import { signUpPDS } from './settings'; 27 28 import type { ActorIdentifier, Did } from '@atcute/lexicons'; 29 ··· 68 }) 69 }); 70 71 - const params = new URLSearchParams(location.hash.slice(1)); 72 73 const did = (localStorage.getItem('current-login') as Did) ?? undefined; 74 ··· 151 } 152 } 153 154 - async function finalizeLogin(params: URLSearchParams, did?: Did) { 155 try { 156 const { session } = await finalizeAuthorization(params); 157 replaceState(location.pathname + location.search, {});
··· 24 import { metadata } from './metadata'; 25 import { getDetailedProfile } from './methods'; 26 import { signUpPDS } from './settings'; 27 + import { SvelteURLSearchParams } from 'svelte/reactivity'; 28 29 import type { ActorIdentifier, Did } from '@atcute/lexicons'; 30 ··· 69 }) 70 }); 71 72 + const params = new SvelteURLSearchParams(location.hash.slice(1)); 73 74 const did = (localStorage.getItem('current-login') as Did) ?? undefined; 75 ··· 152 } 153 } 154 155 + async function finalizeLogin(params: SvelteURLSearchParams, did?: Did) { 156 try { 157 const { session } = await finalizeAuthorization(params); 158 replaceState(location.pathname + location.search, {});
+1 -1
src/lib/atproto/index.ts
··· 14 uploadBlob, 15 describeRepo, 16 getBlobURL, 17 - getCDNImageBlobUrl as getImageBlobUrl, 18 searchActorsTypeahead 19 } from './methods';
··· 14 uploadBlob, 15 describeRepo, 16 getBlobURL, 17 + getCDNImageBlobUrl, 18 searchActorsTypeahead 19 } from './methods';
+113 -10
src/lib/atproto/methods.ts
··· 1 - import type { Did, Handle } from '@atcute/lexicons'; 2 import { user } from './auth.svelte'; 3 import type { AllowedCollection } from './settings'; 4 import { ··· 10 WellKnownHandleResolver 11 } from '@atcute/identity-resolver'; 12 import { Client, simpleFetchHandler } from '@atcute/client'; 13 - import type { AppBskyActorDefs } from '@atcute/bluesky'; 14 15 export type Collection = `${string}.${string}.${string}`; 16 17 export function parseUri(uri: string) { 18 - const [did, collection, rkey] = uri.replace('at://', '').split('/'); 19 - return { did, collection, rkey } as { 20 - collection: Collection; 21 - rkey: string; 22 - did: string; 23 - }; 24 } 25 26 export async function resolveHandle({ handle }: { handle: Handle }) { 27 const handleResolver = new CompositeHandleResolver({ 28 methods: { ··· 42 } 43 }); 44 45 export async function getPDS(did: Did) { 46 - const doc = await didResolver.resolve(did as `did:plc:${string}` | `did:web:${string}`); 47 if (!doc.service) throw new Error('No PDS found'); 48 for (const service of doc.service) { 49 if (service.id === '#atproto_pds') { ··· 52 } 53 } 54 55 export async function getDetailedProfile(data?: { did?: Did; client?: Client }) { 56 data ??= {}; 57 data.did ??= user.did; ··· 71 return response.data; 72 } 73 74 export async function getClient({ did }: { did: Did }) { 75 const pds = await getPDS(did); 76 if (!pds) throw new Error('PDS not found'); ··· 82 return client; 83 } 84 85 export async function listRecords({ 86 did, 87 collection, ··· 129 return allRecords; 130 } 131 132 export async function getRecord({ 133 did, 134 collection, ··· 162 return JSON.parse(JSON.stringify(record.data)); 163 } 164 165 export async function putRecord({ 166 collection, 167 rkey = 'self', ··· 187 return response; 188 } 189 190 export async function deleteRecord({ 191 collection, 192 rkey = 'self' ··· 207 return response.ok; 208 } 209 210 export async function uploadBlob({ blob }: { blob: Blob }) { 211 if (!user.did || !user.client) throw new Error("Can't upload blob: Not logged in"); 212 ··· 231 return blobInfo; 232 } 233 234 export async function describeRepo({ client, did }: { client: Client; did?: Did }) { 235 did ??= user.did; 236 if (!did) { ··· 248 return repo.data; 249 } 250 251 export async function getBlobURL({ 252 did, 253 blob ··· 264 return `${pds}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${blob.ref.$link}`; 265 } 266 267 export function getCDNImageBlobUrl({ 268 did, 269 blob 270 }: { 271 - did: string; 272 blob: { 273 $type: 'blob'; 274 ref: { ··· 276 }; 277 }; 278 }) { 279 return `https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${blob.ref.$link}@webp`; 280 } 281 282 export async function searchActorsTypeahead( 283 q: string, 284 limit: number = 10, ··· 301 302 return { actors: response.data.actors, q }; 303 }
··· 1 + import { parseResourceUri, type Did, type Handle } from '@atcute/lexicons'; 2 import { user } from './auth.svelte'; 3 import type { AllowedCollection } from './settings'; 4 import { ··· 10 WellKnownHandleResolver 11 } from '@atcute/identity-resolver'; 12 import { Client, simpleFetchHandler } from '@atcute/client'; 13 + import { type AppBskyActorDefs } from '@atcute/bluesky'; 14 15 export type Collection = `${string}.${string}.${string}`; 16 + import * as TID from '@atcute/tid'; 17 18 + /** 19 + * Parses an AT Protocol URI into its components. 20 + * @param uri - The AT URI to parse (e.g., "at://did:plc:xyz/app.bsky.feed.post/abc123") 21 + * @returns An object containing the repo, collection, and rkey or undefined if not an AT uri 22 + */ 23 export function parseUri(uri: string) { 24 + const parts = parseResourceUri(uri); 25 + if (!parts.ok) return; 26 + return parts.value; 27 } 28 29 + /** 30 + * Resolves a handle to a DID using DNS and HTTP methods. 31 + * @param handle - The handle to resolve (e.g., "alice.bsky.social") 32 + * @returns The DID associated with the handle 33 + */ 34 export async function resolveHandle({ handle }: { handle: Handle }) { 35 const handleResolver = new CompositeHandleResolver({ 36 methods: { ··· 50 } 51 }); 52 53 + /** 54 + * Gets the PDS (Personal Data Server) URL for a given DID. 55 + * @param did - The DID to look up 56 + * @returns The PDS service endpoint URL 57 + * @throws If no PDS is found in the DID document 58 + */ 59 export async function getPDS(did: Did) { 60 + const doc = await didResolver.resolve(did as Did<'plc'> | Did<'web'>); 61 if (!doc.service) throw new Error('No PDS found'); 62 for (const service of doc.service) { 63 if (service.id === '#atproto_pds') { ··· 66 } 67 } 68 69 + /** 70 + * Fetches a detailed Bluesky profile for a user. 71 + * @param data - Optional object with did and client 72 + * @param data.did - The DID to fetch the profile for (defaults to current user) 73 + * @param data.client - The client to use (defaults to public Bluesky API) 74 + * @returns The profile data or undefined if not found 75 + */ 76 export async function getDetailedProfile(data?: { did?: Did; client?: Client }) { 77 data ??= {}; 78 data.did ??= user.did; ··· 92 return response.data; 93 } 94 95 + /** 96 + * Creates an AT Protocol client for a user's PDS. 97 + * @param did - The DID of the user 98 + * @returns A client configured for the user's PDS 99 + * @throws If the PDS cannot be found 100 + */ 101 export async function getClient({ did }: { did: Did }) { 102 const pds = await getPDS(did); 103 if (!pds) throw new Error('PDS not found'); ··· 109 return client; 110 } 111 112 + /** 113 + * Lists records from a repository collection with pagination support. 114 + * @param did - The DID of the repository (defaults to current user) 115 + * @param collection - The collection to list records from 116 + * @param cursor - Pagination cursor for continuing from a previous request 117 + * @param limit - Maximum number of records to return (default 100, set to 0 for all records) 118 + * @param client - The client to use (defaults to user's PDS client) 119 + * @returns An array of records from the collection 120 + */ 121 export async function listRecords({ 122 did, 123 collection, ··· 165 return allRecords; 166 } 167 168 + /** 169 + * Fetches a single record from a repository. 170 + * @param did - The DID of the repository (defaults to current user) 171 + * @param collection - The collection the record belongs to 172 + * @param rkey - The record key (defaults to "self") 173 + * @param client - The client to use (defaults to user's PDS client) 174 + * @returns The record data 175 + */ 176 export async function getRecord({ 177 did, 178 collection, ··· 206 return JSON.parse(JSON.stringify(record.data)); 207 } 208 209 + /** 210 + * Creates or updates a record in the current user's repository. 211 + * Only accepts collections that are configured in permissions. 212 + * @param collection - The collection to write to (must be in permissions.collections) 213 + * @param rkey - The record key (defaults to "self") 214 + * @param record - The record data to write 215 + * @returns The response from the PDS 216 + * @throws If the user is not logged in 217 + */ 218 export async function putRecord({ 219 collection, 220 rkey = 'self', ··· 240 return response; 241 } 242 243 + /** 244 + * Deletes a record from the current user's repository. 245 + * Only accepts collections that are configured in permissions. 246 + * @param collection - The collection the record belongs to (must be in permissions.collections) 247 + * @param rkey - The record key (defaults to "self") 248 + * @returns True if the deletion was successful 249 + * @throws If the user is not logged in 250 + */ 251 export async function deleteRecord({ 252 collection, 253 rkey = 'self' ··· 268 return response.ok; 269 } 270 271 + /** 272 + * Uploads a blob to the current user's PDS. 273 + * @param blob - The blob data to upload 274 + * @returns The blob metadata including ref, mimeType, and size, or undefined on failure 275 + * @throws If the user is not logged in 276 + */ 277 export async function uploadBlob({ blob }: { blob: Blob }) { 278 if (!user.did || !user.client) throw new Error("Can't upload blob: Not logged in"); 279 ··· 298 return blobInfo; 299 } 300 301 + /** 302 + * Gets metadata about a repository. 303 + * @param client - The client to use 304 + * @param did - The DID of the repository (defaults to current user) 305 + * @returns Repository metadata or undefined on failure 306 + */ 307 export async function describeRepo({ client, did }: { client: Client; did?: Did }) { 308 did ??= user.did; 309 if (!did) { ··· 321 return repo.data; 322 } 323 324 + /** 325 + * Constructs a URL to fetch a blob directly from a user's PDS. 326 + * @param did - The DID of the user who owns the blob 327 + * @param blob - The blob reference object 328 + * @returns The URL to fetch the blob 329 + */ 330 export async function getBlobURL({ 331 did, 332 blob ··· 343 return `${pds}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${blob.ref.$link}`; 344 } 345 346 + /** 347 + * Constructs a Bluesky CDN URL for an image blob. 348 + * @param did - The DID of the user who owns the blob (defaults to current user) 349 + * @param blob - The blob reference object 350 + * @returns The CDN URL for the image in webp format 351 + */ 352 export function getCDNImageBlobUrl({ 353 did, 354 blob 355 }: { 356 + did?: string; 357 blob: { 358 $type: 'blob'; 359 ref: { ··· 361 }; 362 }; 363 }) { 364 + did ??= user.did; 365 + 366 return `https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${blob.ref.$link}@webp`; 367 } 368 369 + /** 370 + * Searches for actors with typeahead/autocomplete functionality. 371 + * @param q - The search query 372 + * @param limit - Maximum number of results (default 10) 373 + * @param host - The API host to use (defaults to public Bluesky API) 374 + * @returns An object containing matching actors and the original query 375 + */ 376 export async function searchActorsTypeahead( 377 q: string, 378 limit: number = 10, ··· 395 396 return { actors: response.data.actors, q }; 397 } 398 + 399 + /** 400 + * Return a TID based on current time 401 + * 402 + * @returns TID for current time 403 + */ 404 + export function createTID() { 405 + return TID.now(); 406 + }
+1 -1
src/routes/+page.svelte
··· 16 <a 17 href="https://github.com/flo-bit/svelte-atproto-client-oauth" 18 target="_blank" 19 - class="mt-2 text-sm text-rose-600">source code</a 20 > 21 22 {#if user.isInitializing}
··· 16 <a 17 href="https://github.com/flo-bit/svelte-atproto-client-oauth" 18 target="_blank" 19 + class="mt-2 text-sm text-rose-600 dark:text-accent-500">source code</a 20 > 21 22 {#if user.isInitializing}