A decentralized music tracking and discovery platform built on AT Protocol 🎵 rocksky.app
spotify atproto lastfm musicbrainz scrobbling listenbrainz
98
fork

Configure Feed

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

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