the statusphere demo reworked into a vite/react app in a monorepo

Read profile record directly

+200 -11
+49
lexicons/profile.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.actor.profile", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "A declaration of a Bluesky account profile.", 8 + "key": "literal:self", 9 + "record": { 10 + "type": "object", 11 + "properties": { 12 + "displayName": { 13 + "type": "string", 14 + "maxGraphemes": 64, 15 + "maxLength": 640 16 + }, 17 + "description": { 18 + "type": "string", 19 + "description": "Free-form profile description text.", 20 + "maxGraphemes": 256, 21 + "maxLength": 2560 22 + }, 23 + "avatar": { 24 + "type": "blob", 25 + "description": "Small image to be displayed next to posts from account. AKA, 'profile picture'", 26 + "accept": ["image/png", "image/jpeg"], 27 + "maxSize": 1000000 28 + }, 29 + "banner": { 30 + "type": "blob", 31 + "description": "Larger horizontal image to display behind profile view.", 32 + "accept": ["image/png", "image/jpeg"], 33 + "maxSize": 1000000 34 + }, 35 + "labels": { 36 + "type": "union", 37 + "description": "Self-label values, specific to the Bluesky application, on the overall account.", 38 + "refs": ["com.atproto.label.defs#selfLabels"] 39 + }, 40 + "joinedViaStarterPack": { 41 + "type": "ref", 42 + "ref": "com.atproto.repo.strongRef" 43 + }, 44 + "createdAt": { "type": "string", "format": "datetime" } 45 + } 46 + } 47 + } 48 + } 49 + }
+30
src/lexicon/index.ts
··· 16 16 17 17 export class Server { 18 18 xrpc: XrpcServer 19 + app: AppNS 19 20 com: ComNS 20 21 21 22 constructor(options?: XrpcOptions) { 22 23 this.xrpc = createXrpcServer(schemas, options) 24 + this.app = new AppNS(this) 23 25 this.com = new ComNS(this) 26 + } 27 + } 28 + 29 + export class AppNS { 30 + _server: Server 31 + bsky: AppBskyNS 32 + 33 + constructor(server: Server) { 34 + this._server = server 35 + this.bsky = new AppBskyNS(server) 36 + } 37 + } 38 + 39 + export class AppBskyNS { 40 + _server: Server 41 + actor: AppBskyActorNS 42 + 43 + constructor(server: Server) { 44 + this._server = server 45 + this.actor = new AppBskyActorNS(server) 46 + } 47 + } 48 + 49 + export class AppBskyActorNS { 50 + _server: Server 51 + 52 + constructor(server: Server) { 53 + this._server = server 24 54 } 25 55 } 26 56
+59 -1
src/lexicon/lexicons.ts
··· 4 4 import { LexiconDoc, Lexicons } from '@atproto/lexicon' 5 5 6 6 export const schemaDict = { 7 + AppBskyActorProfile: { 8 + lexicon: 1, 9 + id: 'app.bsky.actor.profile', 10 + defs: { 11 + main: { 12 + type: 'record', 13 + description: 'A declaration of a Bluesky account profile.', 14 + key: 'literal:self', 15 + record: { 16 + type: 'object', 17 + properties: { 18 + displayName: { 19 + type: 'string', 20 + maxGraphemes: 64, 21 + maxLength: 640, 22 + }, 23 + description: { 24 + type: 'string', 25 + description: 'Free-form profile description text.', 26 + maxGraphemes: 256, 27 + maxLength: 2560, 28 + }, 29 + avatar: { 30 + type: 'blob', 31 + description: 32 + "Small image to be displayed next to posts from account. AKA, 'profile picture'", 33 + accept: ['image/png', 'image/jpeg'], 34 + maxSize: 1000000, 35 + }, 36 + banner: { 37 + type: 'blob', 38 + description: 39 + 'Larger horizontal image to display behind profile view.', 40 + accept: ['image/png', 'image/jpeg'], 41 + maxSize: 1000000, 42 + }, 43 + labels: { 44 + type: 'union', 45 + description: 46 + 'Self-label values, specific to the Bluesky application, on the overall account.', 47 + refs: ['lex:com.atproto.label.defs#selfLabels'], 48 + }, 49 + joinedViaStarterPack: { 50 + type: 'ref', 51 + ref: 'lex:com.atproto.repo.strongRef', 52 + }, 53 + createdAt: { 54 + type: 'string', 55 + format: 'datetime', 56 + }, 57 + }, 58 + }, 59 + }, 60 + }, 61 + }, 7 62 ComExampleStatus: { 8 63 lexicon: 1, 9 64 id: 'com.example.status', ··· 33 88 } 34 89 export const schemas: LexiconDoc[] = Object.values(schemaDict) as LexiconDoc[] 35 90 export const lexicons: Lexicons = new Lexicons(schemas) 36 - export const ids = { ComExampleStatus: 'com.example.status' } 91 + export const ids = { 92 + AppBskyActorProfile: 'app.bsky.actor.profile', 93 + ComExampleStatus: 'com.example.status', 94 + }
+38
src/lexicon/types/app/bsky/actor/profile.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { ValidationResult, BlobRef } from '@atproto/lexicon' 5 + import { lexicons } from '../../../../lexicons' 6 + import { isObj, hasProp } from '../../../../util' 7 + import { CID } from 'multiformats/cid' 8 + import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' 9 + import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef' 10 + 11 + export interface Record { 12 + displayName?: string 13 + /** Free-form profile description text. */ 14 + description?: string 15 + /** Small image to be displayed next to posts from account. AKA, 'profile picture' */ 16 + avatar?: BlobRef 17 + /** Larger horizontal image to display behind profile view. */ 18 + banner?: BlobRef 19 + labels?: 20 + | ComAtprotoLabelDefs.SelfLabels 21 + | { $type: string; [k: string]: unknown } 22 + joinedViaStarterPack?: ComAtprotoRepoStrongRef.Main 23 + createdAt?: string 24 + [k: string]: unknown 25 + } 26 + 27 + export function isRecord(v: unknown): v is Record { 28 + return ( 29 + isObj(v) && 30 + hasProp(v, '$type') && 31 + (v.$type === 'app.bsky.actor.profile#main' || 32 + v.$type === 'app.bsky.actor.profile') 33 + ) 34 + } 35 + 36 + export function validateRecord(v: unknown): ValidationResult { 37 + return lexicons.validate('app.bsky.actor.profile#main', v) 38 + }
+4 -4
src/pages/home.ts
··· 1 - import type { Status } from '#/db/schema' 1 + import type { Status } from '#/db' 2 2 import { html } from '../lib/view' 3 3 import { shell } from './shell' 4 4 ··· 37 37 type Props = { 38 38 statuses: Status[] 39 39 didHandleMap: Record<string, string> 40 - profile?: { displayName?: string; handle: string } 40 + profile?: { displayName?: string } 41 41 myStatus?: Status 42 42 } 43 43 ··· 60 60 ${profile 61 61 ? html`<form action="/logout" method="post" class="session-form"> 62 62 <div> 63 - Hi, <strong>${profile.displayName || profile.handle}</strong>. 64 - what's your status today? 63 + Hi, <strong>${profile.displayName || 'friend'}</strong>. What's 64 + your status today? 65 65 </div> 66 66 <div> 67 67 <button type="submit">Log out</button>
+20 -6
src/routes.ts
··· 11 11 import { env } from '#/lib/env' 12 12 import { page } from '#/lib/view' 13 13 import * as Status from '#/lexicon/types/com/example/status' 14 + import * as Profile from '#/lexicon/types/app/bsky/actor/profile' 14 15 15 16 type Session = { did: string } 16 17 ··· 167 168 } 168 169 169 170 // Fetch additional information about the logged-in user 170 - const { data: profile } = await agent.getProfile({ 171 - actor: agent.accountDid, 171 + const { data: profileRecord } = await agent.com.atproto.repo.getRecord({ 172 + repo: agent.accountDid, 173 + collection: 'app.bsky.actor.profile', 174 + rkey: 'self', 172 175 }) 173 - didHandleMap[profile.handle] = agent.accountDid 176 + const profile = 177 + Profile.isRecord(profileRecord.value) && 178 + Profile.validateRecord(profileRecord.value).success 179 + ? profileRecord.value 180 + : {} 174 181 175 182 // Serve the logged-in view 176 - return res 177 - .type('html') 178 - .send(page(home({ statuses, didHandleMap, profile, myStatus }))) 183 + return res.type('html').send( 184 + page( 185 + home({ 186 + statuses, 187 + didHandleMap, 188 + profile, 189 + myStatus, 190 + }) 191 + ) 192 + ) 179 193 }) 180 194 ) 181 195