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 20 "@atcute/identity-resolver": "^1.2.2", 21 21 "@atcute/lexicons": "^1.2.6", 22 22 "@atcute/oauth-browser-client": "^2.0.3", 23 + "@atcute/tid": "^1.1.1", 23 24 "@eslint/compat": "^2.0.1", 24 25 "@eslint/js": "^9.39.2", 25 26 "@sveltejs/adapter-auto": "^7.0.0",
+84 -33
pnpm-lock.yaml
··· 26 26 '@atcute/oauth-browser-client': 27 27 specifier: ^2.0.3 28 28 version: 2.0.3(@atcute/identity@1.1.3) 29 + '@atcute/tid': 30 + specifier: ^1.1.1 31 + version: 1.1.1 29 32 '@eslint/compat': 30 33 specifier: ^2.0.1 31 34 version: 2.0.1(eslint@9.39.2(jiti@2.6.1)) ··· 34 37 version: 9.39.2 35 38 '@sveltejs/adapter-auto': 36 39 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))) 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))) 38 41 '@sveltejs/adapter-static': 39 42 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))) 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))) 41 44 '@sveltejs/kit': 42 45 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)) 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)) 44 47 '@sveltejs/vite-plugin-svelte': 45 48 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)) 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)) 47 50 '@tailwindcss/forms': 48 51 specifier: ^0.5.11 49 52 version: 0.5.11(tailwindcss@4.1.18) 50 53 '@tailwindcss/vite': 51 54 specifier: ^4.1.18 52 - version: 4.1.18(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)) 55 + version: 4.1.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)) 53 56 bits-ui: 54 57 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) 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) 56 59 eslint: 57 60 specifier: ^9.39.2 58 61 version: 9.39.2(jiti@2.6.1) ··· 91 94 version: 8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 92 95 vite: 93 96 specifier: ^7.3.1 94 - version: 7.3.1(jiti@2.6.1)(lightningcss@1.30.2) 97 + version: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2) 95 98 96 99 packages: 97 100 ··· 120 123 121 124 '@atcute/oauth-browser-client@2.0.3': 122 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} 123 132 124 133 '@atcute/uint8array@1.0.6': 125 134 resolution: {integrity: sha512-ucfRBQc7BFT8n9eCyGOzDHEMKF/nZwhS2pPao4Xtab1ML3HdFYcX2DM1tadCzas85QTGxHe5urnUAAcNKGRi9A==, tarball: https://registry.npmjs.org/@atcute/uint8array/-/uint8array-1.0.6.tgz} ··· 660 669 peerDependencies: 661 670 vite: ^5.2.0 || ^6 || ^7 662 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 + 663 675 '@types/cookie@0.6.0': 664 676 resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==, tarball: https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz} 665 677 ··· 668 680 669 681 '@types/json-schema@7.0.15': 670 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} 671 686 672 687 '@typescript-eslint/eslint-plugin@8.53.1': 673 688 resolution: {integrity: sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==, tarball: https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz} ··· 772 787 brace-expansion@2.0.2: 773 788 resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==, tarball: https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz} 774 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 + 775 793 callsites@3.1.0: 776 794 resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, tarball: https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz} 777 795 engines: {node: '>=6'} ··· 1166 1184 natural-compare@1.4.0: 1167 1185 resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, tarball: https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz} 1168 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 + 1169 1191 obug@2.1.1: 1170 1192 resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==, tarball: https://registry.npmjs.org/obug/-/obug-2.1.1.tgz} 1171 1193 ··· 1437 1459 engines: {node: '>=14.17'} 1438 1460 hasBin: true 1439 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 + 1440 1465 unicode-segmenter@0.14.5: 1441 1466 resolution: {integrity: sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g==, tarball: https://registry.npmjs.org/unicode-segmenter/-/unicode-segmenter-0.14.5.tgz} 1442 1467 ··· 1564 1589 transitivePeerDependencies: 1565 1590 - '@atcute/identity' 1566 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 + 1567 1601 '@atcute/uint8array@1.0.6': {} 1568 1602 1569 1603 '@atcute/util-fetch@1.0.5': ··· 1838 1872 dependencies: 1839 1873 acorn: 8.15.0 1840 1874 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)))': 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)))': 1842 1876 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)) 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)) 1844 1878 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)))': 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)))': 1846 1880 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)) 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)) 1848 1882 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))': 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))': 1850 1884 dependencies: 1851 1885 '@standard-schema/spec': 1.1.0 1852 1886 '@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)) 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)) 1854 1888 '@types/cookie': 0.6.0 1855 1889 acorn: 8.15.0 1856 1890 cookie: 0.6.0 ··· 1863 1897 set-cookie-parser: 2.7.2 1864 1898 sirv: 3.0.2 1865 1899 svelte: 5.48.0 1866 - vite: 7.3.1(jiti@2.6.1)(lightningcss@1.30.2) 1900 + vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2) 1867 1901 optionalDependencies: 1868 1902 typescript: 5.9.3 1869 1903 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))': 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))': 1871 1905 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)) 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)) 1873 1907 obug: 2.1.1 1874 1908 svelte: 5.48.0 1875 - vite: 7.3.1(jiti@2.6.1)(lightningcss@1.30.2) 1909 + vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2) 1876 1910 1877 - '@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.48.0)(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2))': 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))': 1878 1912 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)) 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)) 1880 1914 deepmerge: 4.3.1 1881 1915 magic-string: 0.30.21 1882 1916 obug: 2.1.1 1883 1917 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)) 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)) 1886 1920 1887 1921 '@swc/helpers@0.5.18': 1888 1922 dependencies: ··· 1954 1988 '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 1955 1989 '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 1956 1990 1957 - '@tailwindcss/vite@4.1.18(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2))': 1991 + '@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2))': 1958 1992 dependencies: 1959 1993 '@tailwindcss/node': 4.1.18 1960 1994 '@tailwindcss/oxide': 4.1.18 1961 1995 tailwindcss: 4.1.18 1962 - vite: 7.3.1(jiti@2.6.1)(lightningcss@1.30.2) 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 1963 2001 1964 2002 '@types/cookie@0.6.0': {} 1965 2003 1966 2004 '@types/estree@1.0.8': {} 1967 2005 1968 2006 '@types/json-schema@7.0.15': {} 2007 + 2008 + '@types/node@25.0.10': 2009 + dependencies: 2010 + undici-types: 7.16.0 1969 2011 1970 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)': 1971 2013 dependencies: ··· 2083 2125 2084 2126 balanced-match@1.0.2: {} 2085 2127 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): 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): 2087 2129 dependencies: 2088 2130 '@floating-ui/core': 1.7.3 2089 2131 '@floating-ui/dom': 1.7.4 2090 2132 '@internationalized/date': 3.10.1 2091 2133 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) 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) 2093 2135 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) 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) 2095 2137 tabbable: 6.4.0 2096 2138 transitivePeerDependencies: 2097 2139 - '@sveltejs/kit' ··· 2104 2146 brace-expansion@2.0.2: 2105 2147 dependencies: 2106 2148 balanced-match: 1.0.2 2149 + 2150 + bun-types@1.3.6: 2151 + dependencies: 2152 + '@types/node': 25.0.10 2107 2153 2108 2154 callsites@3.1.0: {} 2109 2155 ··· 2462 2508 2463 2509 natural-compare@1.4.0: {} 2464 2510 2511 + node-gyp-build@4.8.4: {} 2512 + 2465 2513 obug@2.1.1: {} 2466 2514 2467 2515 optionator@0.9.4: ··· 2571 2619 '@rollup/rollup-win32-x64-msvc': 4.56.0 2572 2620 fsevents: 2.3.3 2573 2621 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): 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): 2575 2623 dependencies: 2576 2624 dequal: 2.0.3 2577 2625 esm-env: 1.2.2 2578 2626 lz-string: 1.5.0 2579 2627 svelte: 5.48.0 2580 2628 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)) 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)) 2582 2630 2583 2631 sade@1.8.1: 2584 2632 dependencies: ··· 2635 2683 optionalDependencies: 2636 2684 svelte: 5.48.0 2637 2685 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): 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): 2639 2687 dependencies: 2640 2688 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) 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) 2642 2690 style-to-object: 1.0.14 2643 2691 svelte: 5.48.0 2644 2692 transitivePeerDependencies: ··· 2698 2746 2699 2747 typescript@5.9.3: {} 2700 2748 2749 + undici-types@7.16.0: {} 2750 + 2701 2751 unicode-segmenter@0.14.5: {} 2702 2752 2703 2753 uri-js@4.4.1: ··· 2706 2756 2707 2757 util-deprecate@1.0.2: {} 2708 2758 2709 - vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2): 2759 + vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2): 2710 2760 dependencies: 2711 2761 esbuild: 0.27.2 2712 2762 fdir: 6.5.0(picomatch@4.0.3) ··· 2715 2765 rollup: 4.56.0 2716 2766 tinyglobby: 0.2.15 2717 2767 optionalDependencies: 2768 + '@types/node': 25.0.10 2718 2769 fsevents: 2.3.3 2719 2770 jiti: 2.6.1 2720 2771 lightningcss: 1.30.2 2721 2772 2722 - vitefu@1.1.1(vite@7.3.1(jiti@2.6.1)(lightningcss@1.30.2)): 2773 + vitefu@1.1.1(vite@7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)): 2723 2774 optionalDependencies: 2724 - vite: 7.3.1(jiti@2.6.1)(lightningcss@1.30.2) 2775 + vite: 7.3.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2) 2725 2776 2726 2777 which@2.0.2: 2727 2778 dependencies:
+1 -1
src/app.css
··· 2 2 3 3 @plugin '@tailwindcss/forms'; 4 4 5 - @custom-variant dark (&:where(.dark, .dark *)); 5 + /* @custom-variant dark (&:where(.dark, .dark *)); */ 6 6 7 7 @theme inline { 8 8 --color-base-50: var(--base-50);
+1 -1
src/app.html
··· 6 6 <meta name="viewport" content="width=device-width, initial-scale=1" /> 7 7 %sveltekit.head% 8 8 </head> 9 - <body data-sveltekit-preload-data="hover"> 9 + <body data-sveltekit-preload-data="hover" class="bg-base-50 dark:bg-base-900 text-base-900 dark:text-base-50"> 10 10 <div style="display: contents">%sveltekit.body%</div> 11 11 </body> 12 12 </html>
+5 -2
src/lib/UI/Button.svelte
··· 3 3 4 4 type Props = HTMLButtonAttributes & { 5 5 children: () => any; 6 + ref?: HTMLButtonElement | null; 7 + 6 8 }; 7 9 8 - let { children, class: className, ...props }: Props = $props(); 10 + let { children, ref = $bindable(), class: className, ...props }: Props = $props(); 9 11 </script> 10 12 11 13 <button 14 + bind:this={ref} 12 15 class={[ 13 - 'bg-accent-600 text-white hover:bg-accent-500 focus-visible:outline-accent-600', 16 + 'bg-accent-600 hover:bg-accent-500 focus-visible:outline-accent-600 text-white', 14 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', 15 18 className 16 19 ]}
+3 -3
src/lib/UI/HandleInput.svelte
··· 57 57 value = e.currentTarget.value; 58 58 search(e.currentTarget.value); 59 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" 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 61 placeholder="handle" 62 62 id="" 63 63 aria-label="enter your handle" 64 64 /> 65 65 <Combobox.Content 66 - class="z-100 max-h-[30dvh] w-full rounded-2xl border border-base-300 bg-base-50 shadow-lg" 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 67 sideOffset={10} 68 68 align="start" 69 69 side="top" ··· 71 71 <Combobox.Viewport class="w-full p-1"> 72 72 {#each results as actor (actor.did)} 73 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" 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 75 value={actor.handle} 76 76 label={actor.handle} 77 77 >
+29 -12
src/lib/UI/LoginModal.svelte
··· 16 16 import { AppBskyActorDefs } from '@atcute/bluesky'; 17 17 import Avatar from './Avatar.svelte'; 18 18 19 - let { signUp = true, loginOnSelect = true }: { signUp?: boolean; loginOnSelect?: boolean } = 19 + let { signUp = true, loginOnSelect = false }: { signUp?: boolean; loginOnSelect?: boolean } = 20 20 $props(); 21 21 22 22 let value = $state(''); ··· 41 41 } 42 42 43 43 let input: HTMLInputElement | null = $state(null); 44 + let submitButton: HTMLButtonElement | null = $state(null); 44 45 45 46 $effect(() => { 46 47 if (!loginModalState.visible) { ··· 49 50 loadingLogin = false; 50 51 selectedActor = undefined; 51 52 } else { 52 - tick().then(() => { 53 - input?.focus(); 54 - }); 53 + focusInput(); 55 54 } 56 55 }); 57 56 57 + function focusInput() { 58 + tick().then(() => { 59 + input?.focus(); 60 + }); 61 + } 62 + function focusSubmit() { 63 + tick().then(() => { 64 + submitButton?.focus(); 65 + }); 66 + } 67 + 58 68 let selectedActor: AppBskyActorDefs.ProfileViewBasic | undefined = $state(); 59 69 60 70 let recentLogins: Record<Did, AppBskyActorDefs.ProfileViewBasic> = $state({}); ··· 104 114 Login with your internet handle 105 115 </h3> 106 116 107 - <div class="text-base-800 mt-2 mb-2 text-xs font-light">e.g. your bluesky account</div> 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> 108 120 109 121 <form onsubmit={onSubmit} class="mt-2 flex w-full flex-col gap-2"> 110 122 {#if showRecentLogins} ··· 113 125 {#each Object.values(recentLogins).slice(0, 4) as recentLogin} 114 126 <div class="group"> 115 127 <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" 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" 117 129 > 118 130 <div class="flex items-center gap-2"> 119 - <Avatar src={recentLogin.avatar} /> 131 + <Avatar class="size-6" src={recentLogin.avatar} /> 120 132 {recentLogin.handle} 121 133 </div> 122 134 <button ··· 124 136 onclick={() => { 125 137 value = recentLogin.handle; 126 138 selectedActor = recentLogin; 127 - onSubmit(); 139 + if (loginOnSelect) onSubmit(); 140 + else focusSubmit(); 128 141 }} 129 142 > 130 143 <div class="absolute inset-0 h-full w-full"></div> ··· 165 178 selectedActor = a; 166 179 value = a.handle; 167 180 if (loginOnSelect) onSubmit(); 181 + else focusSubmit(); 168 182 }} 169 183 bind:ref={input} 170 184 /> 171 185 </div> 172 186 {:else} 173 187 <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" 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" 175 189 > 176 190 <div class="flex items-center gap-2"> 177 - <Avatar src={selectedActor.avatar} /> 191 + <Avatar class="size-6" src={selectedActor.avatar} /> 178 192 {selectedActor.handle} 179 193 </div> 180 194 ··· 211 225 <Button 212 226 onclick={() => { 213 227 recentLoginsView = false; 228 + focusInput(); 214 229 }} 215 230 class="w-full">Login with new handle</Button 216 231 > 217 232 {:else} 218 - <Button type="submit" disabled={loadingLogin} class="w-full" 233 + <Button bind:ref={submitButton} type="submit" disabled={loadingLogin} class="w-full" 219 234 >{loadingLogin ? 'Loading...' : 'Login'}</Button 220 235 > 221 236 {/if} 222 237 </div> 223 238 224 239 {#if signUp} 225 - <div class="border-base-200 text-base-800 mt-4 border-t pt-4 text-sm leading-7"> 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 + > 226 243 Don't have an account? 227 244 <div class="mt-3"> 228 245 <SecondaryButton
+1 -1
src/lib/UI/SecondaryButton.svelte
··· 10 10 11 11 <button 12 12 class={[ 13 - 'bg-base-300 text-black transition-colors duration-100 hover:bg-base-200 focus-visible:outline-base-600', 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 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 15 className 16 16 ]}
+3 -2
src/lib/atproto/auth.svelte.ts
··· 24 24 import { metadata } from './metadata'; 25 25 import { getDetailedProfile } from './methods'; 26 26 import { signUpPDS } from './settings'; 27 + import { SvelteURLSearchParams } from 'svelte/reactivity'; 27 28 28 29 import type { ActorIdentifier, Did } from '@atcute/lexicons'; 29 30 ··· 68 69 }) 69 70 }); 70 71 71 - const params = new URLSearchParams(location.hash.slice(1)); 72 + const params = new SvelteURLSearchParams(location.hash.slice(1)); 72 73 73 74 const did = (localStorage.getItem('current-login') as Did) ?? undefined; 74 75 ··· 151 152 } 152 153 } 153 154 154 - async function finalizeLogin(params: URLSearchParams, did?: Did) { 155 + async function finalizeLogin(params: SvelteURLSearchParams, did?: Did) { 155 156 try { 156 157 const { session } = await finalizeAuthorization(params); 157 158 replaceState(location.pathname + location.search, {});
+1 -1
src/lib/atproto/index.ts
··· 14 14 uploadBlob, 15 15 describeRepo, 16 16 getBlobURL, 17 - getCDNImageBlobUrl as getImageBlobUrl, 17 + getCDNImageBlobUrl, 18 18 searchActorsTypeahead 19 19 } from './methods';
+113 -10
src/lib/atproto/methods.ts
··· 1 - import type { Did, Handle } from '@atcute/lexicons'; 1 + import { parseResourceUri, type Did, type Handle } from '@atcute/lexicons'; 2 2 import { user } from './auth.svelte'; 3 3 import type { AllowedCollection } from './settings'; 4 4 import { ··· 10 10 WellKnownHandleResolver 11 11 } from '@atcute/identity-resolver'; 12 12 import { Client, simpleFetchHandler } from '@atcute/client'; 13 - import type { AppBskyActorDefs } from '@atcute/bluesky'; 13 + import { type AppBskyActorDefs } from '@atcute/bluesky'; 14 14 15 15 export type Collection = `${string}.${string}.${string}`; 16 + import * as TID from '@atcute/tid'; 16 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 + */ 17 23 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 + const parts = parseResourceUri(uri); 25 + if (!parts.ok) return; 26 + return parts.value; 24 27 } 25 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 + */ 26 34 export async function resolveHandle({ handle }: { handle: Handle }) { 27 35 const handleResolver = new CompositeHandleResolver({ 28 36 methods: { ··· 42 50 } 43 51 }); 44 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 + */ 45 59 export async function getPDS(did: Did) { 46 - const doc = await didResolver.resolve(did as `did:plc:${string}` | `did:web:${string}`); 60 + const doc = await didResolver.resolve(did as Did<'plc'> | Did<'web'>); 47 61 if (!doc.service) throw new Error('No PDS found'); 48 62 for (const service of doc.service) { 49 63 if (service.id === '#atproto_pds') { ··· 52 66 } 53 67 } 54 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 + */ 55 76 export async function getDetailedProfile(data?: { did?: Did; client?: Client }) { 56 77 data ??= {}; 57 78 data.did ??= user.did; ··· 71 92 return response.data; 72 93 } 73 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 + */ 74 101 export async function getClient({ did }: { did: Did }) { 75 102 const pds = await getPDS(did); 76 103 if (!pds) throw new Error('PDS not found'); ··· 82 109 return client; 83 110 } 84 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 + */ 85 121 export async function listRecords({ 86 122 did, 87 123 collection, ··· 129 165 return allRecords; 130 166 } 131 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 + */ 132 176 export async function getRecord({ 133 177 did, 134 178 collection, ··· 162 206 return JSON.parse(JSON.stringify(record.data)); 163 207 } 164 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 + */ 165 218 export async function putRecord({ 166 219 collection, 167 220 rkey = 'self', ··· 187 240 return response; 188 241 } 189 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 + */ 190 251 export async function deleteRecord({ 191 252 collection, 192 253 rkey = 'self' ··· 207 268 return response.ok; 208 269 } 209 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 + */ 210 277 export async function uploadBlob({ blob }: { blob: Blob }) { 211 278 if (!user.did || !user.client) throw new Error("Can't upload blob: Not logged in"); 212 279 ··· 231 298 return blobInfo; 232 299 } 233 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 + */ 234 307 export async function describeRepo({ client, did }: { client: Client; did?: Did }) { 235 308 did ??= user.did; 236 309 if (!did) { ··· 248 321 return repo.data; 249 322 } 250 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 + */ 251 330 export async function getBlobURL({ 252 331 did, 253 332 blob ··· 264 343 return `${pds}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${blob.ref.$link}`; 265 344 } 266 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 + */ 267 352 export function getCDNImageBlobUrl({ 268 353 did, 269 354 blob 270 355 }: { 271 - did: string; 356 + did?: string; 272 357 blob: { 273 358 $type: 'blob'; 274 359 ref: { ··· 276 361 }; 277 362 }; 278 363 }) { 364 + did ??= user.did; 365 + 279 366 return `https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${blob.ref.$link}@webp`; 280 367 } 281 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 + */ 282 376 export async function searchActorsTypeahead( 283 377 q: string, 284 378 limit: number = 10, ··· 301 395 302 396 return { actors: response.data.actors, q }; 303 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 16 <a 17 17 href="https://github.com/flo-bit/svelte-atproto-client-oauth" 18 18 target="_blank" 19 - class="mt-2 text-sm text-rose-600">source code</a 19 + class="mt-2 text-sm text-rose-600 dark:text-accent-500">source code</a 20 20 > 21 21 22 22 {#if user.isInitializing}