Skip to content

Commit 5e3bd2e

Browse files
committed
feat: show bluesky avatar in header
1 parent dcd81b0 commit 5e3bd2e

File tree

4 files changed

+60
-4
lines changed

4 files changed

+60
-4
lines changed

app/components/Header/AccountMenu.client.vue

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,17 @@ function openAuthModal() {
8787
</span>
8888

8989
<!-- Atmosphere avatar (second/front, overlapping) -->
90+
<img
91+
v-if="atprotoUser?.avatar"
92+
:src="atprotoUser.avatar"
93+
:alt="atprotoUser.handle"
94+
width="24"
95+
height="24"
96+
class="w-6 h-6 rounded-full ring-2 ring-bg"
97+
:class="hasBothConnections ? 'relative z-10' : ''"
98+
/>
9099
<span
91-
v-if="atprotoUser"
100+
v-else-if="atprotoUser"
92101
class="w-6 h-6 rounded-full bg-bg-muted ring-2 ring-bg flex items-center justify-center"
93102
:class="hasBothConnections ? 'relative z-10' : ''"
94103
>
@@ -181,7 +190,18 @@ function openAuthModal() {
181190
class="w-full px-3 py-2.5 flex items-center gap-3 hover:bg-bg-subtle transition-colors text-start"
182191
@click="openAuthModal"
183192
>
184-
<span class="w-8 h-8 rounded-full bg-bg-muted flex items-center justify-center">
193+
<img
194+
v-if="atprotoUser.avatar"
195+
:src="atprotoUser.avatar"
196+
:alt="atprotoUser.handle"
197+
width="32"
198+
height="32"
199+
class="w-8 h-8 rounded-full"
200+
/>
201+
<span
202+
v-else
203+
class="w-8 h-8 rounded-full bg-bg-muted flex items-center justify-center"
204+
>
185205
<span class="i-carbon-cloud w-4 h-4 text-fg-muted" aria-hidden="true" />
186206
</span>
187207
<div class="flex-1 min-w-0">

server/api/auth/atproto.get.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ import { SLINGSHOT_HOST } from '#shared/utils/constants'
77
import { useServerSession } from '#server/utils/server-session'
88
import type { PublicUserSession } from '#shared/schemas/publicUserSession'
99

10+
interface ProfileRecord {
11+
avatar?: {
12+
$type: 'blob'
13+
ref: { $link: string }
14+
mimeType: string
15+
size: number
16+
}
17+
}
18+
1019
export default defineEventHandler(async event => {
1120
const config = useRuntimeConfig(event)
1221
if (!config.sessionPassword) {
@@ -58,8 +67,33 @@ export default defineEventHandler(async event => {
5867
)
5968
if (response.ok) {
6069
const miniDoc: PublicUserSession = await response.json()
70+
71+
// Fetch the user's profile record to get their avatar blob reference
72+
// We use com.atproto.repo.getRecord to fetch directly from the user's PDS
73+
// This works with any PDS, not just Bluesky
74+
let avatar: string | undefined
75+
try {
76+
const profileResponse = await fetch(
77+
`${miniDoc.pds}/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent(agent.did!)}&collection=app.bsky.actor.profile&rkey=self`,
78+
{ headers: { 'User-Agent': 'npmx' } },
79+
)
80+
if (profileResponse.ok) {
81+
const record = (await profileResponse.json()) as { value: ProfileRecord }
82+
const avatarBlob = record.value.avatar
83+
if (avatarBlob?.ref?.$link) {
84+
// Construct the blob URL from the user's PDS
85+
avatar = `${miniDoc.pds}/xrpc/com.atproto.sync.getBlob?did=${encodeURIComponent(agent.did!)}&cid=${encodeURIComponent(avatarBlob.ref.$link)}`
86+
}
87+
}
88+
} catch {
89+
// Avatar fetch failed, continue without it
90+
}
91+
6192
await session.update({
62-
public: miniDoc,
93+
public: {
94+
...miniDoc,
95+
avatar,
96+
},
6397
})
6498
}
6599

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { object, string, pipe, url } from 'valibot'
1+
import { object, string, pipe, url, optional } from 'valibot'
22
import type { InferOutput } from 'valibot'
33

44
export const PublicUserSessionSchema = object({
55
// Safe to pass to the frontend
66
did: string(),
77
handle: string(),
88
pds: pipe(string(), url()),
9+
avatar: optional(pipe(string(), url())),
910
})
1011

1112
export type PublicUserSession = InferOutput<typeof PublicUserSessionSchema>

shared/types/userSession.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export interface UserServerSession {
55
did: string
66
handle: string
77
pds: string
8+
avatar?: string
89
}
910
// Only to be used in the atproto session and state stores
1011
// Will need to change to Record<string, T> and add a current logged in user if we ever want to support

0 commit comments

Comments
 (0)