replies timeline only, appview-less bluesky client

handle repost backlinks from spacedust

ptr.pet 4434b1e7 a006a151

verified
Changed files
+73 -63
src
+1 -3
src/components/FollowingView.svelte
··· 42 42 } 43 43 44 44 // schedule spinner to appear only if calculation takes > 200ms 45 - calculationTimer = setTimeout(() => { 46 - isLongCalculation = true; 47 - }, 200); 45 + calculationTimer = setTimeout(() => (isLongCalculation = true), 200); 48 46 // yield to main thread to allow UI to show spinner/update 49 47 await new Promise((resolve) => setTimeout(resolve, 0)); 50 48
+3 -8
src/lib/cache.ts
··· 29 29 private writeFlushScheduled = false; 30 30 31 31 constructor() { 32 - if (typeof indexedDB === 'undefined') { 33 - return; 34 - } 32 + if (typeof indexedDB === 'undefined') return; 35 33 36 34 this.dbPromise = new Promise((resolve, reject) => { 37 35 const request = indexedDB.open(DB_NAME, DB_VERSION); ··· 165 163 batch.forEach((op) => { 166 164 try { 167 165 let request: IDBRequest; 168 - if (op.type === 'put') { 169 - request = store.put(op.value, op.key); 170 - } else { 171 - request = store.delete(op.key); 172 - } 166 + if (op.type === 'put') request = store.put(op.value, op.key); 167 + else request = store.delete(op.key); 173 168 174 169 request.onsuccess = () => op.resolve(); 175 170 request.onerror = () => op.reject(request.error);
+65 -50
src/lib/state.svelte.ts
··· 326 326 if (event.kind !== 'commit') return; 327 327 328 328 const { did, commit } = event; 329 - if (commit.collection !== 'app.bsky.feed.post') return; 330 - 331 329 const uri: ResourceUri = `at://${did}/${commit.collection}/${commit.rkey}`; 332 - 333 - if (commit.operation === 'create') { 334 - const { cid, record } = commit; 335 - const post: PostWithUri = { 336 - uri, 337 - cid, 338 - // assume record is valid, we trust the jetstream 339 - record: record as AppBskyFeedPost.Main 340 - }; 341 - addPosts([[uri, post]]); 342 - addTimeline(did, [uri]); 343 - } else if (commit.operation === 'delete') { 344 - allPosts.get(did)?.delete(uri); 330 + if (commit.collection === 'app.bsky.feed.post') { 331 + if (commit.operation === 'create') { 332 + const { cid, record } = commit; 333 + const post: PostWithUri = { 334 + uri, 335 + cid, 336 + // assume record is valid, we trust the jetstream 337 + record: record as AppBskyFeedPost.Main 338 + }; 339 + addPosts([[uri, post]]); 340 + addTimeline(did, [uri]); 341 + } else if (commit.operation === 'delete') { 342 + allPosts.get(did)?.delete(uri); 343 + } 345 344 } 346 345 }; 347 346 348 - export const handleNotification = async (event: NotificationsStreamEvent) => { 349 - if (event.type === 'message') { 350 - const parsedSubjectUri = expect(parseCanonicalResourceUri(event.data.link.subject)); 351 - const did = parsedSubjectUri.repo as AtprotoDid; 352 - const client = await getClient(did); 353 - const subjectPost = await client.getRecord( 354 - AppBskyFeedPost.mainSchema, 355 - did, 356 - parsedSubjectUri.rkey 357 - ); 358 - if (!subjectPost.ok) return; 347 + const handlePostNotification = async (event: NotificationsStreamEvent & { type: 'message' }) => { 348 + const parsedSubjectUri = expect(parseCanonicalResourceUri(event.data.link.subject)); 349 + const did = parsedSubjectUri.repo as AtprotoDid; 350 + const client = await getClient(did); 351 + const subjectPost = await client.getRecord( 352 + AppBskyFeedPost.mainSchema, 353 + did, 354 + parsedSubjectUri.rkey 355 + ); 356 + if (!subjectPost.ok) return; 359 357 360 - const parsedSourceUri = expect(parseCanonicalResourceUri(event.data.link.source_record)); 361 - const hydrated = await hydratePosts(client, did, [ 362 - { 363 - record: subjectPost.value.record, 364 - uri: event.data.link.subject, 365 - cid: subjectPost.value.cid, 366 - replies: { 367 - cursor: null, 368 - total: 1, 369 - records: [ 370 - { 371 - did: parsedSourceUri.repo, 372 - collection: parsedSourceUri.collection, 373 - rkey: parsedSourceUri.rkey 374 - } 375 - ] 376 - } 358 + const parsedSourceUri = expect(parseCanonicalResourceUri(event.data.link.source_record)); 359 + const hydrated = await hydratePosts(client, did, [ 360 + { 361 + record: subjectPost.value.record, 362 + uri: event.data.link.subject, 363 + cid: subjectPost.value.cid, 364 + replies: { 365 + cursor: null, 366 + total: 1, 367 + records: [ 368 + { 369 + did: parsedSourceUri.repo, 370 + collection: parsedSourceUri.collection, 371 + rkey: parsedSourceUri.rkey 372 + } 373 + ] 377 374 } 378 - ]); 379 - if (!hydrated.ok) { 380 - console.error(`cant hydrate posts ${did}: ${hydrated.error}`); 381 - return; 375 + } 376 + ]); 377 + if (!hydrated.ok) { 378 + console.error(`cant hydrate posts ${did}: ${hydrated.error}`); 379 + return; 380 + } 381 + 382 + // console.log(hydrated); 383 + addPosts(hydrated.value); 384 + addTimeline(did, hydrated.value.keys()); 385 + }; 386 + 387 + const handleBacklink = (event: NotificationsStreamEvent & { type: 'message' }) => { 388 + const parsedSource = expect(parseCanonicalResourceUri(event.data.link.source_record)); 389 + addBacklinks(event.data.link.subject, event.data.link.source, [ 390 + { 391 + did: parsedSource.repo, 392 + collection: parsedSource.collection, 393 + rkey: parsedSource.rkey 382 394 } 395 + ]); 396 + }; 383 397 384 - // console.log(hydrated); 385 - addPosts(hydrated.value); 386 - addTimeline(did, hydrated.value.keys()); 398 + export const handleNotification = async (event: NotificationsStreamEvent) => { 399 + if (event.type === 'message') { 400 + if (event.data.link.source.startsWith('app.bsky.feed.post')) handlePostNotification(event); 401 + else handleBacklink(event); 387 402 } 388 403 }; 389 404
+4 -2
src/routes/+page.svelte
··· 114 114 newAccounts.map((account) => account.did), 115 115 'app.bsky.feed.post:reply.parent.uri', 116 116 'app.bsky.feed.post:embed.record.record.uri', 117 - 'app.bsky.feed.post:embed.record.uri' 117 + 'app.bsky.feed.post:embed.record.uri', 118 + 'app.bsky.feed.repost:subject.uri' 118 119 ) 119 120 ); 120 121 }); ··· 127 128 const jetstreamSub = new JetstreamSubscription({ 128 129 url: $settings.endpoints.jetstream, 129 130 wantedCollections: ['app.bsky.feed.post'], 130 - wantedDids: ['did:web:guestbook.gaze.systems'] // initially contain sentinel 131 + // this is here because if wantedDids is zero jetstream will send all events 132 + wantedDids: ['did:web:guestbook.gaze.systems'] 131 133 }); 132 134 jetstream.set(jetstreamSub); 133 135