A decentralized music tracking and discovery platform built on AT Protocol 🎵

Handle missing profile record and add OAuth resolver

Catch failures when retrieving app.bsky.actor.profile and return an
empty profileRecord so the actor flow doesn't throw. Use null/undefined
avatar values when no profile record exists. Add @atcute oauth and
identity-resolver dependencies and configure OAuth/identity resolver
(with DOH and DID resolvers) in the Main layout; update bun.lock.

+128 -16
+24 -15
apps/api/src/xrpc/app/rocksky/actor/getProfile.ts
··· 165 165 user, 166 166 }: WithUser): Effect.Effect< 167 167 [ 168 - Profile, 168 + Profile | {}, 169 169 string, 170 170 SelectSpotifyAccount, 171 171 SelectSpotifyToken, ··· 176 176 > => { 177 177 return Effect.tryPromise({ 178 178 try: async () => { 179 + let record = {}; 180 + try { 181 + const { data } = await agent.com.atproto.repo.getRecord({ 182 + repo: did, 183 + collection: "app.bsky.actor.profile", 184 + rkey: "self", 185 + }); 186 + record = data; 187 + } catch (error) { 188 + consola.error("Failed to retrieve profile record:", error); 189 + } 179 190 return Promise.all([ 180 - agent.com.atproto.repo 181 - .getRecord({ 182 - repo: did, 183 - collection: "app.bsky.actor.profile", 184 - rkey: "self", 185 - }) 186 - .then(({ data }) => ({ 187 - profileRecord: data, 188 - ctx, 189 - did, 190 - user, 191 - })), 191 + Promise.resolve({ 192 + profileRecord: record, 193 + ctx, 194 + did, 195 + user, 196 + }), 192 197 ctx.resolver.resolveDidToHandle(did), 193 198 ctx.db 194 199 .select() ··· 297 302 .update(tables.users) 298 303 .set({ 299 304 handle, 300 - avatar: `https://cdn.bsky.app/img/avatar/plain/${profile.did}/${_.get(profile, "profileRecord.value.avatar.ref", "").toString()}@jpeg`, 305 + avatar: _.get(profile, "profileRecord") 306 + ? `https://cdn.bsky.app/img/avatar/plain/${profile.did}/${_.get(profile, "profileRecord.value.avatar.ref", "").toString()}@jpeg` 307 + : null, 301 308 displayName: _.get(profile, "profileRecord.value.displayName"), 302 309 updatedAt: new Date(), 303 310 }) ··· 311 318 did: profile.user.did, 312 319 handle, 313 320 display_name: _.get(profile, "profileRecord.value.displayName"), 314 - avatar: `https://cdn.bsky.app/img/avatar/plain/${profile.did}/${_.get(profile, "profileRecord.value.avatar.ref", "").toString()}@jpeg`, 321 + avatar: _.get(profile, "profileRecord") 322 + ? `https://cdn.bsky.app/img/avatar/plain/${profile.did}/${_.get(profile, "profileRecord.value.avatar.ref", "").toString()}@jpeg` 323 + : undefined, 315 324 xata_createdat: profile.user.createdAt.toISOString(), 316 325 xata_updatedat: new Date().toISOString(), 317 326 xata_version: (profile.user.xataVersion || 1) + 1,
+2
apps/web/package.json
··· 16 16 "format": "biome format src" 17 17 }, 18 18 "dependencies": { 19 + "@atcute/identity-resolver": "^1.2.2", 20 + "@atcute/oauth-browser-client": "^2.0.3", 19 21 "@emotion/react": "^11.14.0", 20 22 "@emotion/styled": "^11.14.0", 21 23 "@hookform/resolvers": "^4.0.0",
+69
apps/web/src/layouts/Main.tsx
··· 18 18 import SpotifyLogin from "./SpotifyLogin"; 19 19 import { IconEye, IconEyeOff, IconLock } from "@tabler/icons-react"; 20 20 import { consola } from "consola"; 21 + import { 22 + CompositeDidDocumentResolver, 23 + CompositeHandleResolver, 24 + DohJsonHandleResolver, 25 + LocalActorResolver, 26 + PlcDidDocumentResolver, 27 + WebDidDocumentResolver, 28 + WellKnownHandleResolver, 29 + } from "@atcute/identity-resolver"; 30 + import { 31 + configureOAuth, 32 + // createAuthorizationUrl, 33 + } from "@atcute/oauth-browser-client"; 34 + 35 + const DOH_RESOLVER = "https://mozilla.cloudflare-dns.com/dns-query"; 36 + const PUBLIC_URL: string = 37 + import.meta.env.VITE_PUBLIC_URL || "http://localhost:8000"; 38 + const REDIRECT_URI = `${PUBLIC_URL}/oauth/callback`; 39 + const scope = "atproto transition:generic"; 21 40 22 41 const Container = styled.div` 23 42 display: flex; ··· 69 88 const [passwordLogin, setPasswordLogin] = useState(false); 70 89 71 90 useEffect(() => { 91 + const clientId = PUBLIC_URL.startsWith("http://localhost") 92 + ? `http://localhost` + 93 + `?redirect_uri=${encodeURIComponent(REDIRECT_URI)}` + 94 + `&scope=${encodeURIComponent(scope)}` 95 + : `${PUBLIC_URL}/oauth-client-metadata.json`; 96 + 97 + const handleResolver = new CompositeHandleResolver({ 98 + methods: { 99 + dns: new DohJsonHandleResolver({ dohUrl: DOH_RESOLVER }), 100 + http: new WellKnownHandleResolver(), 101 + }, 102 + }); 103 + 104 + configureOAuth({ 105 + metadata: { 106 + client_id: clientId, 107 + redirect_uri: REDIRECT_URI, 108 + }, 109 + identityResolver: new LocalActorResolver({ 110 + handleResolver: handleResolver, 111 + didDocumentResolver: new CompositeDidDocumentResolver({ 112 + methods: { 113 + plc: new PlcDidDocumentResolver(), 114 + web: new WebDidDocumentResolver(), 115 + }, 116 + }), 117 + }), 118 + }); 119 + }, []); 120 + 121 + useEffect(() => { 72 122 if (did && did !== "null") { 73 123 localStorage.setItem("did", did); 74 124 ··· 161 211 162 212 window.location.href = `https://rocksky.pages.dev/loading?handle=${handle}`; 163 213 }; 214 + 215 + /*const onCreateAccount = async () => { 216 + const authUrl = await createAuthorizationUrl({ 217 + target: { type: "pds", serviceUrl: "https://selfhosted.social" }, 218 + // @ts-expect-error - new stuff 219 + prompt: "create", 220 + scope, 221 + }); 222 + window.location.assign(authUrl); 223 + };*/ 164 224 165 225 return ( 166 226 <Container ··· 325 385 </LabelMedium> 326 386 <div className="text-center text-[var(--color-text-muted)] "> 327 387 You can create one at{" "} 388 + {/* 389 + <span 390 + onClick={onCreateAccount} 391 + className="no-underline cursor-pointer !text-[var(--color-primary)]" 392 + > 393 + selfhosted.social 394 + </span> 395 + ,{" "} 396 + */} 328 397 <a 329 398 href="https://bsky.app" 330 399 className="no-underline cursor-pointer !text-[var(--color-primary)]"
+33 -1
bun.lock
··· 196 196 "name": "@rocksky/web", 197 197 "version": "0.0.0", 198 198 "dependencies": { 199 + "@atcute/identity-resolver": "^1.2.2", 200 + "@atcute/oauth-browser-client": "^2.0.3", 199 201 "@emotion/react": "^11.14.0", 200 202 "@emotion/styled": "^11.14.0", 201 203 "@hookform/resolvers": "^4.0.0", ··· 372 374 373 375 "@asteasolutions/zod-to-openapi": ["@asteasolutions/zod-to-openapi@7.3.4", "", { "dependencies": { "openapi3-ts": "^4.1.2" }, "peerDependencies": { "zod": "^3.20.2" } }, "sha512-/2rThQ5zPi9OzVwes6U7lK1+Yvug0iXu25olp7S0XsYmOqnyMfxH7gdSQjn/+DSOHRg7wnotwGJSyL+fBKdnEA=="], 374 376 377 + "@atcute/client": ["@atcute/client@4.2.1", "", { "dependencies": { "@atcute/identity": "^1.1.3", "@atcute/lexicons": "^1.2.6" } }, "sha512-ZBFM2pW075JtgGFu5g7HHZBecrClhlcNH8GVP9Zz1aViWR+cjjBsTpeE63rJs+FCOHFYlirUyo5L8SGZ4kMINw=="], 378 + 379 + "@atcute/identity": ["@atcute/identity@1.1.3", "", { "dependencies": { "@atcute/lexicons": "^1.2.4", "@badrap/valita": "^0.4.6" } }, "sha512-oIqPoI8TwWeQxvcLmFEZLdN2XdWcaLVtlm8pNk0E72As9HNzzD9pwKPrLr3rmTLRIoULPPFmq9iFNsTeCIU9ng=="], 380 + 381 + "@atcute/identity-resolver": ["@atcute/identity-resolver@1.2.2", "", { "dependencies": { "@atcute/lexicons": "^1.2.6", "@atcute/util-fetch": "^1.0.5", "@badrap/valita": "^0.4.6" }, "peerDependencies": { "@atcute/identity": "^1.0.0" } }, "sha512-eUh/UH4bFvuXS0X7epYCeJC/kj4rbBXfSRumLEH4smMVwNOgTo7cL/0Srty+P/qVPoZEyXdfEbS0PHJyzoXmHw=="], 382 + 383 + "@atcute/lexicons": ["@atcute/lexicons@1.2.6", "", { "dependencies": { "@atcute/uint8array": "^1.0.6", "@atcute/util-text": "^0.0.1", "@standard-schema/spec": "^1.1.0", "esm-env": "^1.2.2" } }, "sha512-s76UQd8D+XmHIzrjD9CJ9SOOeeLPHc+sMmcj7UFakAW/dDFXc579fcRdRfuUKvXBL5v1Gs2VgDdlh/IvvQZAwA=="], 384 + 385 + "@atcute/multibase": ["@atcute/multibase@1.1.6", "", { "dependencies": { "@atcute/uint8array": "^1.0.5" } }, "sha512-HBxuCgYLKPPxETV0Rot4VP9e24vKl8JdzGCZOVsDaOXJgbRZoRIF67Lp0H/OgnJeH/Xpva8Z5ReoTNJE5dn3kg=="], 386 + 387 + "@atcute/oauth-browser-client": ["@atcute/oauth-browser-client@2.0.3", "", { "dependencies": { "@atcute/client": "^4.1.1", "@atcute/identity-resolver": "^1.2.0", "@atcute/lexicons": "^1.2.5", "@atcute/multibase": "^1.1.6", "@atcute/uint8array": "^1.0.6", "nanoid": "^5.1.6" } }, "sha512-rzUjwhjE4LRRKdQnCFQag/zXRZMEAB1hhBoLfnoQuHwWbmDUCL7fzwC3jRhDPp3om8XaYNDj8a/iqRip0wRqoQ=="], 388 + 389 + "@atcute/uint8array": ["@atcute/uint8array@1.0.6", "", {}, "sha512-ucfRBQc7BFT8n9eCyGOzDHEMKF/nZwhS2pPao4Xtab1ML3HdFYcX2DM1tadCzas85QTGxHe5urnUAAcNKGRi9A=="], 390 + 391 + "@atcute/util-fetch": ["@atcute/util-fetch@1.0.5", "", { "dependencies": { "@badrap/valita": "^0.4.6" } }, "sha512-qjHj01BGxjSjIFdPiAjSARnodJIIyKxnCMMEcXMESo9TAyND6XZQqrie5fia+LlYWVXdpsTds8uFQwc9jdKTig=="], 392 + 393 + "@atcute/util-text": ["@atcute/util-text@0.0.1", "", { "dependencies": { "unicode-segmenter": "^0.14.4" } }, "sha512-t1KZqvn0AYy+h2KcJyHnKF9aEqfRfMUmyY8j1ELtAEIgqN9CxINAjxnoRCJIFUlvWzb+oY3uElQL/Vyk3yss0g=="], 394 + 375 395 "@atproto-labs/did-resolver": ["@atproto-labs/did-resolver@0.1.11", "", { "dependencies": { "@atproto-labs/fetch": "0.2.2", "@atproto-labs/pipe": "0.1.0", "@atproto-labs/simple-store": "0.1.2", "@atproto-labs/simple-store-memory": "0.1.2", "@atproto/did": "0.1.5", "zod": "^3.23.8" } }, "sha512-qXNzIX2GPQnxT1gl35nv/8ErDdc4Fj/+RlJE7oyE7JGkFAPUyuY03TvKJ79SmWFsWE8wyTXEpLuphr9Da1Vhkw=="], 376 396 377 397 "@atproto-labs/fetch": ["@atproto-labs/fetch@0.2.2", "", { "dependencies": { "@atproto-labs/pipe": "0.1.0" } }, "sha512-QyafkedbFeVaN20DYUpnY2hcArYxjdThPXbYMqOSoZhcvkrUqaw4xDND4wZB5TBD9cq2yqe9V6mcw9P4XQKQuQ=="], ··· 485 505 "@babel/traverse": ["@babel/traverse@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/types": "^7.28.4", "debug": "^4.3.1" } }, "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ=="], 486 506 487 507 "@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], 508 + 509 + "@badrap/valita": ["@badrap/valita@0.4.6", "", {}, "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg=="], 488 510 489 511 "@biomejs/biome": ["@biomejs/biome@2.2.5", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.2.5", "@biomejs/cli-darwin-x64": "2.2.5", "@biomejs/cli-linux-arm64": "2.2.5", "@biomejs/cli-linux-arm64-musl": "2.2.5", "@biomejs/cli-linux-x64": "2.2.5", "@biomejs/cli-linux-x64-musl": "2.2.5", "@biomejs/cli-win32-arm64": "2.2.5", "@biomejs/cli-win32-x64": "2.2.5" }, "bin": { "biome": "bin/biome" } }, "sha512-zcIi+163Rc3HtyHbEO7CjeHq8DjQRs40HsGbW6vx2WI0tg8mYQOPouhvHSyEnCBAorfYNnKdR64/IxO7xQ5faw=="], 490 512 ··· 1824 1846 1825 1847 "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], 1826 1848 1849 + "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="], 1850 + 1827 1851 "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], 1828 1852 1829 1853 "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], ··· 2280 2304 2281 2305 "mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="], 2282 2306 2283 - "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], 2307 + "nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], 2284 2308 2285 2309 "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], 2286 2310 ··· 2834 2858 2835 2859 "unenv": ["unenv@2.0.0-rc.14", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.1", "ohash": "^2.0.10", "pathe": "^2.0.3", "ufo": "^1.5.4" } }, "sha512-od496pShMen7nOy5VmVJCnq8rptd45vh6Nx/r2iPbrba6pa6p+tS2ywuIHRZ/OBvSbQZB0kWvpO9XBNVFXHD3Q=="], 2836 2860 2861 + "unicode-segmenter": ["unicode-segmenter@0.14.5", "", {}, "sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g=="], 2862 + 2837 2863 "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], 2838 2864 2839 2865 "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], ··· 2939 2965 "zod-to-json-schema": ["zod-to-json-schema@3.24.6", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg=="], 2940 2966 2941 2967 "zx": ["zx@8.8.4", "", { "bin": { "zx": "build/cli.js" } }, "sha512-44GcD+ZlM/v1OQtbwnSxLPcoE1ZEUICmR+RSbJZLAqfIixNLuMjLyh0DcS75OyfJ/sWYAwCWDmDvJ4hdnANAPQ=="], 2968 + 2969 + "@atcute/lexicons/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], 2942 2970 2943 2971 "@atproto-labs/fetch-node/ipaddr.js": ["ipaddr.js@2.2.0", "", {}, "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA=="], 2944 2972 ··· 3256 3284 3257 3285 "pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], 3258 3286 3287 + "postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], 3288 + 3259 3289 "posthog-js/fflate": ["fflate@0.4.8", "", {}, "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="], 3260 3290 3261 3291 "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], ··· 3663 3693 "send/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], 3664 3694 3665 3695 "styled-components/@emotion/is-prop-valid/@emotion/memoize": ["@emotion/memoize@0.8.1", "", {}, "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="], 3696 + 3697 + "styled-components/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], 3666 3698 3667 3699 "table/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], 3668 3700