From 1aec1a0ac30cb3873aef831e6366d16b9ee55c2e Mon Sep 17 00:00:00 2001 From: dholms Date: Mon, 4 Mar 2024 19:19:21 -0600 Subject: [PATCH 01/15] action takedown labels in hydrator --- packages/bsky/src/hydration/hydrator.ts | 67 ++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 8 deletions(-) diff --git a/packages/bsky/src/hydration/hydrator.ts b/packages/bsky/src/hydration/hydrator.ts index c79df963a7b..ecd1aa4f854 100644 --- a/packages/bsky/src/hydration/hydrator.ts +++ b/packages/bsky/src/hydration/hydrator.ts @@ -7,6 +7,7 @@ import { ids } from '../lexicon/lexicons' import { isMain as isEmbedRecord } from '../lexicon/types/app/bsky/embed/record' import { isMain as isEmbedRecordWithMedia } from '../lexicon/types/app/bsky/embed/recordWithMedia' import { isListRule } from '../lexicon/types/app/bsky/feed/threadgate' +import type { Label } from '../lexicon/types/com/atproto/label/defs' import { ActorHydrator, ProfileAggs, @@ -126,6 +127,9 @@ export class Hydrator { this.label.getLabelsForSubjects(labelSubjectsForDid(dids)), viewer ? this.hydrateProfileViewers(dids, viewer) : undefined, ]) + if (!includeTakedowns) { + actionTakedownLabels(dids, actors, labels) + } return mergeStates(profileViewersState ?? {}, { actors, labels, @@ -170,10 +174,15 @@ export class Hydrator { async hydrateLists( uris: string[], viewer: string | null, + includeTakedowns = false, ): Promise { const [listsState, profilesState] = await Promise.all([ - await this.hydrateListsBasic(uris, viewer), - await this.hydrateProfilesBasic(uris.map(didFromUri), viewer), + await this.hydrateListsBasic(uris, viewer, includeTakedowns), + await this.hydrateProfilesBasic( + uris.map(didFromUri), + viewer, + includeTakedowns, + ), ]) return mergeStates(listsState, profilesState) } @@ -183,12 +192,19 @@ export class Hydrator { async hydrateListsBasic( uris: string[], viewer: string | null, + includeTakedowns = false, ): Promise { - const [lists, listViewers] = await Promise.all([ + const [lists, listViewers, labels] = await Promise.all([ this.graph.getLists(uris), viewer ? this.graph.getListViewerStates(uris, viewer) : undefined, + this.label.getLabelsForSubjects(uris), ]) - return { lists, listViewers, viewer } + + if (!includeTakedowns) { + actionTakedownLabels(uris, lists, labels) + } + + return { lists, listViewers, labels, viewer } } // app.bsky.graph.defs#listItemView @@ -286,9 +302,16 @@ export class Hydrator { viewer, includeTakedowns, ), - this.hydrateLists([...nestedListUris, ...gateListUris], viewer), - this.hydrateFeedGens(nestedFeedGenUris, viewer), + this.hydrateLists( + [...nestedListUris, ...gateListUris], + viewer, + includeTakedowns, + ), + this.hydrateFeedGens(nestedFeedGenUris, viewer, includeTakedowns), ]) + if (!includeTakedowns) { + actionTakedownLabels(allPostUris, posts, labels) + } // combine all hydration state return mergeManyStates(profileState, listState, feedGenState, { posts, @@ -401,8 +424,9 @@ export class Hydrator { async hydrateThreadPosts( refs: ItemRef[], viewer: string | null, + includeTakedowns = false, ): Promise { - return this.hydratePosts(refs, viewer) + return this.hydratePosts(refs, viewer, includeTakedowns) } // app.bsky.feed.defs#generatorView @@ -412,18 +436,24 @@ export class Hydrator { async hydrateFeedGens( uris: string[], // @TODO any way to get refs here? viewer: string | null, + includeTakedowns = false, ): Promise { - const [feedgens, feedgenAggs, feedgenViewers, profileState] = + const [feedgens, feedgenAggs, feedgenViewers, profileState, labels] = await Promise.all([ this.feed.getFeedGens(uris), this.feed.getFeedGenAggregates(uris.map((uri) => ({ uri }))), viewer ? this.feed.getFeedGenViewerStates(uris, viewer) : undefined, this.hydrateProfiles(uris.map(didFromUri), viewer), + this.label.getLabelsForSubjects(uris), ]) + if (!includeTakedowns) { + actionTakedownLabels(uris, feedgens, labels) + } return mergeStates(profileState, { feedgens, feedgenAggs, feedgenViewers, + labels, viewer, }) } @@ -478,6 +508,7 @@ export class Hydrator { this.label.getLabelsForSubjects(uris), this.hydrateProfiles(uris.map(didFromUri), viewer), ]) + actionTakedownLabels(postUris, posts, labels) return mergeStates(profileState, { posts, likes, @@ -724,3 +755,23 @@ const mergeManyStates = (...states: HydrationState[]) => { const mergeManyMaps = (...maps: HydrationMap[]) => { return maps.reduce(mergeMaps, undefined as HydrationMap | undefined) } + +const actionTakedownLabels = ( + keys: string[], + hydrationMap: HydrationMap, + labels: Labels, +) => { + for (const key of keys) { + const subjectLabels = labels.get(key) + if (!subjectLabels) continue + if (includesTakedownLabel(subjectLabels)) { + hydrationMap.set(key, null) + } + } +} + +const includesTakedownLabel = (labels: Label[]) => { + return labels.some((l) => l.val === TAKEDOWN_LABEL) +} + +const TAKEDOWN_LABEL = '!takedown' From 91efd643617ec58962236e7f16b9d85a179c65b6 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 5 Mar 2024 13:16:51 -0600 Subject: [PATCH 02/15] wip tests --- .../bsky/tests/views/takedown-labels.test.ts | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 packages/bsky/tests/views/takedown-labels.test.ts diff --git a/packages/bsky/tests/views/takedown-labels.test.ts b/packages/bsky/tests/views/takedown-labels.test.ts new file mode 100644 index 00000000000..cebe943513d --- /dev/null +++ b/packages/bsky/tests/views/takedown-labels.test.ts @@ -0,0 +1,75 @@ +import AtpAgent from '@atproto/api' +import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env' + +describe('bsky takedown labels', () => { + let network: TestNetwork + let agent: AtpAgent + let sc: SeedClient + + beforeAll(async () => { + network = await TestNetwork.create({ + dbPostgresSchema: 'bsky_views_takedown_labels', + }) + agent = network.bsky.getClient() + sc = network.getSeedClient() + await basicSeed(sc) + await network.processAll() + }) + + afterAll(async () => { + await network.close() + }) + + const addTakedownLabels = async (subjects: string[]) => { + if (subjects.length === 0) return + const src = network.bsky.ctx.cfg.modServiceDid + const cts = new Date().toISOString() + const labels = subjects.map((uri) => ({ + src, + uri, + cid: '', + val: '!takedown', + neg: false, + cts, + })) + + await network.bsky.db.db.insertInto('label').values(labels).execute() + } + const clearTakedownLabels = async () => { + await network.bsky.db.db + .deleteFrom('label') + .where('val', '=', '!takedown') + .execute() + } + + it('takesdown posts', async () => { + await addTakedownLabels([ + sc.posts[sc.dids.alice][0].ref.uriStr, + sc.dids.carol, + ]) + const uris = [ + sc.posts[sc.dids.alice][0].ref.uriStr, + sc.posts[sc.dids.alice][1].ref.uriStr, + sc.posts[sc.dids.bob][0].ref.uriStr, + sc.posts[sc.dids.carol][0].ref.uriStr, + sc.posts[sc.dids.dan][1].ref.uriStr, + sc.replies[sc.dids.alice][0].ref.uriStr, + ] + const posts = await agent.api.app.bsky.feed.getPosts( + { uris }, + { headers: await network.serviceHeaders(sc.dids.alice) }, + ) + + expect(posts.data.posts.length).toBe(4) + expect(posts.data.posts.some((p) => p.author.did === sc.dids.carol)).toBe( + false, + ) + expect( + posts.data.posts.some( + (p) => p.uri === sc.posts[sc.dids.alice][0].ref.uriStr, + ), + ).toBe(false) + + await clearTakedownLabels() + }) +}) From 2121225045751d14992d90eb30532c697e013536 Mon Sep 17 00:00:00 2001 From: dholms Date: Wed, 6 Mar 2024 11:26:34 -0600 Subject: [PATCH 03/15] fix tests & better hydration --- packages/bsky/src/hydration/hydrator.ts | 11 +------ packages/bsky/src/hydration/label.ts | 30 ++++++++++++++----- .../bsky/tests/views/takedown-labels.test.ts | 2 +- packages/dev-env/src/network.ts | 1 + 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/packages/bsky/src/hydration/hydrator.ts b/packages/bsky/src/hydration/hydrator.ts index ecd1aa4f854..534594208f4 100644 --- a/packages/bsky/src/hydration/hydrator.ts +++ b/packages/bsky/src/hydration/hydrator.ts @@ -7,7 +7,6 @@ import { ids } from '../lexicon/lexicons' import { isMain as isEmbedRecord } from '../lexicon/types/app/bsky/embed/record' import { isMain as isEmbedRecordWithMedia } from '../lexicon/types/app/bsky/embed/recordWithMedia' import { isListRule } from '../lexicon/types/app/bsky/feed/threadgate' -import type { Label } from '../lexicon/types/com/atproto/label/defs' import { ActorHydrator, ProfileAggs, @@ -762,16 +761,8 @@ const actionTakedownLabels = ( labels: Labels, ) => { for (const key of keys) { - const subjectLabels = labels.get(key) - if (!subjectLabels) continue - if (includesTakedownLabel(subjectLabels)) { + if (labels.get(key)?.isTakendown) { hydrationMap.set(key, null) } } } - -const includesTakedownLabel = (labels: Label[]) => { - return labels.some((l) => l.val === TAKEDOWN_LABEL) -} - -const TAKEDOWN_LABEL = '!takedown' diff --git a/packages/bsky/src/hydration/label.ts b/packages/bsky/src/hydration/label.ts index 352c9ed4059..7b2a3bd741e 100644 --- a/packages/bsky/src/hydration/label.ts +++ b/packages/bsky/src/hydration/label.ts @@ -4,7 +4,12 @@ import { HydrationMap, parseJsonBytes } from './util' export type { Label } from '../lexicon/types/com/atproto/label/defs' -export type Labels = HydrationMap +export type SubjectLabels = { + isTakendown: boolean + labels: Label[] +} + +export type Labels = HydrationMap export class LabelHydrator { constructor( @@ -19,18 +24,27 @@ export class LabelHydrator { issuers = ([] as string[]) .concat(issuers ?? []) .concat(this.opts?.labelsFromIssuerDids ?? []) - if (!subjects.length || !issuers.length) return new HydrationMap() + if (!subjects.length || !issuers.length) + return new HydrationMap() const res = await this.dataplane.getLabels({ subjects, issuers }) return res.labels.reduce((acc, cur) => { const label = parseJsonBytes(cur) as Label | undefined if (!label || label.neg) return acc - const entry = acc.get(label.uri) - if (entry) { - entry.push(label) - } else { - acc.set(label.uri, [label]) + let entry = acc.get(label.uri) + if (!entry) { + entry = { + isTakendown: false, + labels: [], + } + acc.set(label.uri, entry) + } + entry.labels.push(label) + if (TAKEDOWN_LABELS.includes(label.val) && !label.neg) { + entry.isTakendown = true } return acc - }, new HydrationMap()) + }, new HydrationMap()) } } + +const TAKEDOWN_LABELS = ['!takedown', '!suspend'] diff --git a/packages/bsky/tests/views/takedown-labels.test.ts b/packages/bsky/tests/views/takedown-labels.test.ts index cebe943513d..dc7f8243a58 100644 --- a/packages/bsky/tests/views/takedown-labels.test.ts +++ b/packages/bsky/tests/views/takedown-labels.test.ts @@ -22,7 +22,7 @@ describe('bsky takedown labels', () => { const addTakedownLabels = async (subjects: string[]) => { if (subjects.length === 0) return - const src = network.bsky.ctx.cfg.modServiceDid + const src = network.ozone.ctx.cfg.service.did const cts = new Date().toISOString() const labels = subjects.map((uri) => ({ src, diff --git a/packages/dev-env/src/network.ts b/packages/dev-env/src/network.ts index c90e2c181f5..04e60907a0b 100644 --- a/packages/dev-env/src/network.ts +++ b/packages/dev-env/src/network.ts @@ -60,6 +60,7 @@ export class TestNetwork extends TestNetworkNoAppView { dbPostgresUrl, redisHost, modServiceDid: ozoneDid, + labelsFromIssuerDids: [ozoneDid], ...params.bsky, }) From 3060b43ec31c2de91612a420149cf8aa96439763 Mon Sep 17 00:00:00 2001 From: dholms Date: Fri, 8 Mar 2024 13:28:06 -0600 Subject: [PATCH 04/15] tidy up includeTakedowns --- .../bsky/src/api/app/bsky/actor/getProfile.ts | 18 ++--- .../src/api/app/bsky/actor/getSuggestions.ts | 6 +- .../src/api/app/bsky/feed/getAuthorFeed.ts | 18 ++--- .../src/api/app/bsky/graph/getFollowers.ts | 18 ++--- .../bsky/src/api/app/bsky/graph/getFollows.ts | 18 ++--- packages/bsky/src/auth-verifier.ts | 4 +- packages/bsky/src/hydration/hydrator.ts | 77 +++++++------------ packages/bsky/src/views/index.ts | 10 +-- 8 files changed, 60 insertions(+), 109 deletions(-) diff --git a/packages/bsky/src/api/app/bsky/actor/getProfile.ts b/packages/bsky/src/api/app/bsky/actor/getProfile.ts index b7860791507..a14718e394e 100644 --- a/packages/bsky/src/api/app/bsky/actor/getProfile.ts +++ b/packages/bsky/src/api/app/bsky/actor/getProfile.ts @@ -16,14 +16,11 @@ export default function (server: Server, ctx: AppContext) { server.app.bsky.actor.getProfile({ auth: ctx.authVerifier.optionalStandardOrRole, handler: async ({ auth, params, req, res }) => { - const { viewer, canViewTakedowns } = ctx.authVerifier.parseCreds(auth) + const { viewer, includeTakedowns } = ctx.authVerifier.parseCreds(auth) const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } + const hydrateCtx = { labelers, viewer, includeTakedowns } - const result = await getProfile( - { ...params, hydrateCtx, canViewTakedowns }, - ctx, - ) + const result = await getProfile({ ...params, hydrateCtx }, ctx) const repoRev = await ctx.hydrator.actor.getRepoRevSafe(viewer) setRepoRev(res, repoRev) @@ -54,11 +51,7 @@ const hydration = async (input: { skeleton: SkeletonState }) => { const { ctx, params, skeleton } = input - return ctx.hydrator.hydrateProfilesDetailed( - [skeleton.did], - params.hydrateCtx, - true, - ) + return ctx.hydrator.hydrateProfilesDetailed([skeleton.did], params.hydrateCtx) } const presentation = (input: { @@ -72,7 +65,7 @@ const presentation = (input: { if (!profile) { throw new InvalidRequestError('Profile not found') } else if ( - !params.canViewTakedowns && + !params.hydrateCtx.includeTakedowns && ctx.views.actorIsTakendown(skeleton.did, hydration) ) { throw new InvalidRequestError( @@ -90,7 +83,6 @@ type Context = { type Params = QueryParams & { hydrateCtx: HydrateCtx - canViewTakedowns: boolean } type SkeletonState = { did: string } diff --git a/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts b/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts index db312373ea0..fa2edd2d09e 100644 --- a/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts +++ b/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts @@ -64,11 +64,7 @@ const hydration = async (input: { skeleton: Skeleton }) => { const { ctx, params, skeleton } = input - return ctx.hydrator.hydrateProfilesDetailed( - skeleton.dids, - params.hydrateCtx, - true, - ) + return ctx.hydrator.hydrateProfilesDetailed(skeleton.dids, params.hydrateCtx) } const noBlocksOrMutes = (input: { diff --git a/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts b/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts index a8174b164fc..01fd33272c7 100644 --- a/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts +++ b/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts @@ -28,14 +28,11 @@ export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getAuthorFeed({ auth: ctx.authVerifier.optionalStandardOrRole, handler: async ({ params, auth, req, res }) => { - const { viewer, canViewTakedowns } = ctx.authVerifier.parseCreds(auth) + const { viewer, includeTakedowns } = ctx.authVerifier.parseCreds(auth) const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } + const hydrateCtx = { labelers, viewer, includeTakedowns } - const result = await getAuthorFeed( - { ...params, hydrateCtx, includeTakedowns: canViewTakedowns }, - ctx, - ) + const result = await getAuthorFeed({ ...params, hydrateCtx }, ctx) const repoRev = await ctx.hydrator.actor.getRepoRevSafe(viewer) setRepoRev(res, repoRev) @@ -66,7 +63,7 @@ export const skeleton = async (inputs: { } const actors = await ctx.hydrator.actor.getActors( [did], - params.includeTakedowns, + params.hydrateCtx.includeTakedowns, ) const actor = actors.get(did) if (!actor) { @@ -100,11 +97,7 @@ const hydration = async (inputs: { }): Promise => { const { ctx, params, skeleton } = inputs const [feedPostState, profileViewerState] = await Promise.all([ - ctx.hydrator.hydrateFeedItems( - skeleton.items, - params.hydrateCtx, - params.includeTakedowns, - ), + ctx.hydrator.hydrateFeedItems(skeleton.items, params.hydrateCtx), ctx.hydrator.hydrateProfileViewers([skeleton.actor.did], params.hydrateCtx), ]) return mergeStates(feedPostState, profileViewerState) @@ -160,7 +153,6 @@ type Context = { type Params = QueryParams & { hydrateCtx: HydrateCtx - includeTakedowns: boolean } type Skeleton = { diff --git a/packages/bsky/src/api/app/bsky/graph/getFollowers.ts b/packages/bsky/src/api/app/bsky/graph/getFollowers.ts index b2d3288b4bd..267aad6ec2a 100644 --- a/packages/bsky/src/api/app/bsky/graph/getFollowers.ts +++ b/packages/bsky/src/api/app/bsky/graph/getFollowers.ts @@ -29,14 +29,11 @@ export default function (server: Server, ctx: AppContext) { server.app.bsky.graph.getFollowers({ auth: ctx.authVerifier.optionalStandardOrRole, handler: async ({ params, auth, req }) => { - const { viewer, canViewTakedowns } = ctx.authVerifier.parseCreds(auth) + const { viewer, includeTakedowns } = ctx.authVerifier.parseCreds(auth) const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } + const hydrateCtx = { labelers, viewer, includeTakedowns } - const result = await getFollowers( - { ...params, hydrateCtx, canViewTakedowns }, - ctx, - ) + const result = await getFollowers({ ...params, hydrateCtx }, ctx) return { encoding: 'application/json', @@ -84,7 +81,6 @@ const hydration = async ( const profileState = await ctx.hydrator.hydrateProfiles( dids, params.hydrateCtx, - params.canViewTakedowns, ) return mergeStates(followState, profileState) } @@ -111,13 +107,16 @@ const presentation = ( ctx.views.actorIsTakendown(did, hydration) const subject = ctx.views.profile(subjectDid, hydration) - if (!subject || (!params.canViewTakedowns && isTakendown(subjectDid))) { + if ( + !subject || + (!params.hydrateCtx.includeTakedowns && isTakendown(subjectDid)) + ) { throw new InvalidRequestError(`Actor not found: ${params.actor}`) } const followers = mapDefined(followUris, (followUri) => { const followerDid = didFromUri(followUri) - if (!params.canViewTakedowns && isTakendown(followerDid)) { + if (!params.hydrateCtx.includeTakedowns && isTakendown(followerDid)) { return } return ctx.views.profile(didFromUri(followUri), hydration) @@ -133,7 +132,6 @@ type Context = { type Params = QueryParams & { hydrateCtx: HydrateCtx - canViewTakedowns: boolean } type SkeletonState = { diff --git a/packages/bsky/src/api/app/bsky/graph/getFollows.ts b/packages/bsky/src/api/app/bsky/graph/getFollows.ts index d1840f9a19a..f7c35b710ce 100644 --- a/packages/bsky/src/api/app/bsky/graph/getFollows.ts +++ b/packages/bsky/src/api/app/bsky/graph/getFollows.ts @@ -23,15 +23,12 @@ export default function (server: Server, ctx: AppContext) { server.app.bsky.graph.getFollows({ auth: ctx.authVerifier.optionalStandardOrRole, handler: async ({ params, auth, req }) => { - const { viewer, canViewTakedowns } = ctx.authVerifier.parseCreds(auth) + const { viewer, includeTakedowns } = ctx.authVerifier.parseCreds(auth) const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } + const hydrateCtx = { labelers, viewer, includeTakedowns } // @TODO ensure canViewTakedowns gets threaded through and applied properly - const result = await getFollows( - { ...params, hydrateCtx, canViewTakedowns }, - ctx, - ) + const result = await getFollows({ ...params, hydrateCtx }, ctx) return { encoding: 'application/json', @@ -79,7 +76,6 @@ const hydration = async ( const profileState = await ctx.hydrator.hydrateProfiles( dids, params.hydrateCtx, - params.canViewTakedowns, ) return mergeStates(followState, profileState) } @@ -108,14 +104,17 @@ const presentation = ( ctx.views.actorIsTakendown(did, hydration) const subject = ctx.views.profile(subjectDid, hydration) - if (!subject || (!params.canViewTakedowns && isTakendown(subjectDid))) { + if ( + !subject || + (!params.hydrateCtx.includeTakedowns && isTakendown(subjectDid)) + ) { throw new InvalidRequestError(`Actor not found: ${params.actor}`) } const follows = mapDefined(followUris, (followUri) => { const followDid = hydration.follows?.get(followUri)?.record.subject if (!followDid) return - if (!params.canViewTakedowns && isTakendown(followDid)) { + if (!params.hydrateCtx.includeTakedowns && isTakendown(followDid)) { return } return ctx.views.profile(followDid, hydration) @@ -131,7 +130,6 @@ type Context = { type Params = QueryParams & { hydrateCtx: HydrateCtx - canViewTakedowns: boolean } type SkeletonState = { diff --git a/packages/bsky/src/auth-verifier.ts b/packages/bsky/src/auth-verifier.ts index 2936b1cd28b..44bde797932 100644 --- a/packages/bsky/src/auth-verifier.ts +++ b/packages/bsky/src/auth-verifier.ts @@ -258,7 +258,7 @@ export class AuthVerifier { ) { const viewer = creds.credentials.type === 'standard' ? creds.credentials.iss : null - const canViewTakedowns = + const includeTakedowns = (creds.credentials.type === 'role' && creds.credentials.admin) || creds.credentials.type === 'mod_service' || (creds.credentials.type === 'standard' && @@ -269,7 +269,7 @@ export class AuthVerifier { return { viewer, - canViewTakedowns, + includeTakedowns, canPerformTakedown, } } diff --git a/packages/bsky/src/hydration/hydrator.ts b/packages/bsky/src/hydration/hydrator.ts index d3012814eb1..f7b68ef17ea 100644 --- a/packages/bsky/src/hydration/hydrator.ts +++ b/packages/bsky/src/hydration/hydrator.ts @@ -49,6 +49,7 @@ import { export type HydrateCtx = { labelers: string[] viewer: string | null + includeTakedowns?: boolean } export type HydrationState = { @@ -132,14 +133,13 @@ export class Hydrator { async hydrateProfiles( dids: string[], ctx: HydrateCtx, - includeTakedowns = false, ): Promise { const [actors, labels, profileViewersState] = await Promise.all([ - this.actor.getActors(dids, includeTakedowns), + this.actor.getActors(dids, ctx.includeTakedowns), this.label.getLabelsForSubjects(labelSubjectsForDid(dids), ctx.labelers), this.hydrateProfileViewers(dids, ctx), ]) - if (!includeTakedowns) { + if (!ctx.includeTakedowns) { actionTakedownLabels(dids, actors, labels) } return mergeStates(profileViewersState ?? {}, { @@ -156,9 +156,8 @@ export class Hydrator { async hydrateProfilesBasic( dids: string[], ctx: HydrateCtx, - includeTakedowns = false, ): Promise { - return this.hydrateProfiles(dids, ctx, includeTakedowns) + return this.hydrateProfiles(dids, ctx) } // app.bsky.actor.defs#profileViewDetailed @@ -168,10 +167,9 @@ export class Hydrator { async hydrateProfilesDetailed( dids: string[], ctx: HydrateCtx, - includeTakedowns = false, ): Promise { const [state, profileAggs] = await Promise.all([ - this.hydrateProfiles(dids, ctx, includeTakedowns), + this.hydrateProfiles(dids, ctx), this.actor.getProfileAggregates(dids), ]) return { @@ -183,18 +181,10 @@ export class Hydrator { // app.bsky.graph.defs#listView // - list // - profile basic - async hydrateLists( - uris: string[], - ctx: HydrateCtx, - includeTakedowns = false, - ): Promise { + async hydrateLists(uris: string[], ctx: HydrateCtx): Promise { const [listsState, profilesState] = await Promise.all([ - await this.hydrateListsBasic(uris, ctx, includeTakedowns), - await this.hydrateProfilesBasic( - uris.map(didFromUri), - ctx, - includeTakedowns, - ), + await this.hydrateListsBasic(uris, ctx), + await this.hydrateProfilesBasic(uris.map(didFromUri), ctx), ]) return mergeStates(listsState, profilesState) @@ -205,7 +195,6 @@ export class Hydrator { async hydrateListsBasic( uris: string[], ctx: HydrateCtx, - includeTakedowns = false, ): Promise { const [lists, listViewers, labels] = await Promise.all([ this.graph.getLists(uris), @@ -213,7 +202,7 @@ export class Hydrator { this.label.getLabelsForSubjects(uris, ctx.labelers), ]) - if (!includeTakedowns) { + if (!ctx.includeTakedowns) { actionTakedownLabels(uris, lists, labels) } @@ -255,13 +244,12 @@ export class Hydrator { async hydratePosts( refs: ItemRef[], ctx: HydrateCtx, - includeTakedowns = false, state: HydrationState = {}, ): Promise { const uris = refs.map((ref) => ref.uri) const postsLayer0 = await this.feed.getPosts( uris, - includeTakedowns, + ctx.includeTakedowns, state.posts, ) // first level embeds plus thread roots we haven't fetched yet @@ -271,7 +259,7 @@ export class Hydrator { const postUrisLayer1 = urisLayer1ByCollection.get(ids.AppBskyFeedPost) ?? [] const postsLayer1 = await this.feed.getPosts( [...postUrisLayer1, ...additionalRootUris], - includeTakedowns, + ctx.includeTakedowns, ) // second level embeds, ignoring any additional root uris we mixed-in to the previous layer const urisLayer2 = nestedRecordUrisFromPosts(postsLayer1, postUrisLayer1) @@ -284,7 +272,7 @@ export class Hydrator { } } const [postsLayer2, threadgates] = await Promise.all([ - this.feed.getPosts(postUrisLayer2, includeTakedowns), + this.feed.getPosts(postUrisLayer2, ctx.includeTakedowns), this.feed.getThreadgatesForPosts([...threadRootUris.values()]), ]) // collect list/feedgen embeds, lists in threadgates, post record hydration @@ -318,16 +306,12 @@ export class Hydrator { ctx.viewer ? this.feed.getPostViewerStates(refs, ctx.viewer) : undefined, this.label.getLabelsForSubjects(allPostUris, ctx.labelers), this.hydratePostBlocks(posts), - this.hydrateProfiles(allPostUris.map(didFromUri), ctx, includeTakedowns), - this.hydrateLists( - [...nestedListUris, ...gateListUris], - ctx, - includeTakedowns, - ), - this.hydrateFeedGens(nestedFeedGenUris, ctx, includeTakedowns), - this.hydrateLabelers(nestedLabelerDids, ctx, includeTakedowns), + this.hydrateProfiles(allPostUris.map(didFromUri), ctx), + this.hydrateLists([...nestedListUris, ...gateListUris], ctx), + this.hydrateFeedGens(nestedFeedGenUris, ctx), + this.hydrateLabelers(nestedLabelerDids, ctx), ]) - if (!includeTakedowns) { + if (!ctx.includeTakedowns) { actionTakedownLabels(allPostUris, posts, labels) } // combine all hydration state @@ -402,14 +386,13 @@ export class Hydrator { async hydrateFeedItems( items: FeedItem[], ctx: HydrateCtx, - includeTakedowns = false, ): Promise { const postUris = items.map((item) => item.post.uri) const repostUris = mapDefined(items, (item) => item.repost?.uri) const [posts, reposts, repostProfileState] = await Promise.all([ - this.feed.getPosts(postUris, includeTakedowns), - this.feed.getReposts(repostUris, includeTakedowns), - this.hydrateProfiles(repostUris.map(didFromUri), ctx, includeTakedowns), + this.feed.getPosts(postUris, ctx.includeTakedowns), + this.feed.getReposts(repostUris, ctx.includeTakedowns), + this.hydrateProfiles(repostUris.map(didFromUri), ctx), ]) const postAndReplyRefs: ItemRef[] = [] posts.forEach((post, uri) => { @@ -419,12 +402,7 @@ export class Hydrator { postAndReplyRefs.push(post.record.reply.root, post.record.reply.parent) } }) - const postState = await this.hydratePosts( - postAndReplyRefs, - ctx, - includeTakedowns, - { posts }, - ) + const postState = await this.hydratePosts(postAndReplyRefs, ctx, { posts }) return mergeManyStates(postState, repostProfileState, { reposts, ctx, @@ -444,9 +422,8 @@ export class Hydrator { async hydrateThreadPosts( refs: ItemRef[], ctx: HydrateCtx, - includeTakedowns = false, ): Promise { - return this.hydratePosts(refs, ctx, includeTakedowns) + return this.hydratePosts(refs, ctx) } // app.bsky.feed.defs#generatorView @@ -456,11 +433,10 @@ export class Hydrator { async hydrateFeedGens( uris: string[], // @TODO any way to get refs here? ctx: HydrateCtx, - includeTakedowns = false, ): Promise { const [feedgens, feedgenAggs, feedgenViewers, profileState, labels] = await Promise.all([ - this.feed.getFeedGens(uris), + this.feed.getFeedGens(uris, ctx.includeTakedowns), this.feed.getFeedGenAggregates(uris.map((uri) => ({ uri }))), ctx.viewer ? this.feed.getFeedGenViewerStates(uris, ctx.viewer) @@ -468,7 +444,7 @@ export class Hydrator { this.hydrateProfiles(uris.map(didFromUri), ctx), this.label.getLabelsForSubjects(uris, ctx.labelers), ]) - if (!includeTakedowns) { + if (!ctx.includeTakedowns) { actionTakedownLabels(uris, feedgens, labels) } return mergeStates(profileState, { @@ -569,16 +545,15 @@ export class Hydrator { async hydrateLabelers( dids: string[], ctx: HydrateCtx, - includeTakedowns = false, ): Promise { const [labelers, labelerAggs, labelerViewers, profileState] = await Promise.all([ - this.label.getLabelers(dids, includeTakedowns), + this.label.getLabelers(dids), this.label.getLabelerAggregates(dids), ctx.viewer ? this.label.getLabelerViewerStates(dids, ctx.viewer) : undefined, - this.hydrateProfiles(dids.map(didFromUri), ctx, includeTakedowns), + this.hydrateProfiles(dids.map(didFromUri), ctx), ]) return mergeStates(profileState, { labelers, diff --git a/packages/bsky/src/views/index.ts b/packages/bsky/src/views/index.ts index 4c573d6ace6..67a9bee51ef 100644 --- a/packages/bsky/src/views/index.ts +++ b/packages/bsky/src/views/index.ts @@ -134,8 +134,8 @@ export class Views { 'self', ).toString() const labels = [ - ...(state.labels?.get(did) ?? []), - ...(state.labels?.get(profileUri) ?? []), + ...(state.labels?.get(did)?.labels ?? []), + ...(state.labels?.get(profileUri)?.labels ?? []), ...this.selfLabels({ uri: profileUri, cid: actor.profileCid?.toString(), @@ -277,7 +277,7 @@ export class Views { const uri = AtUri.make(did, ids.AppBskyLabelerService, 'self').toString() const labels = [ - ...(state.labels?.get(uri) ?? []), + ...(state.labels?.get(uri)?.labels ?? []), ...this.selfLabels({ uri, cid: labeler.cid.toString(), @@ -402,7 +402,7 @@ export class Views { parsedUri.rkey, ).toString() const labels = [ - ...(state.labels?.get(uri) ?? []), + ...(state.labels?.get(uri)?.labels ?? []), ...this.selfLabels({ uri, cid: post.cid, @@ -880,7 +880,7 @@ export class Views { recordInfo = state.follows?.get(notif.uri) } if (!recordInfo) return - const labels = state.labels?.get(notif.uri) ?? [] + const labels = state.labels?.get(notif.uri)?.labels ?? [] const selfLabels = this.selfLabels({ uri: notif.uri, cid: recordInfo.cid, From 499b3afe93faea078e0c9ffba1f18b6d45b2c06c Mon Sep 17 00:00:00 2001 From: dholms Date: Mon, 11 Mar 2024 15:32:15 -0500 Subject: [PATCH 05/15] update labelers header semantics --- packages/bsky/src/context.ts | 19 +++++++------- packages/bsky/src/hydration/hydrator.ts | 12 ++++++--- packages/bsky/src/util.ts | 33 +++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 14 deletions(-) create mode 100644 packages/bsky/src/util.ts diff --git a/packages/bsky/src/context.ts b/packages/bsky/src/context.ts index b166368cdf8..9194bb41781 100644 --- a/packages/bsky/src/context.ts +++ b/packages/bsky/src/context.ts @@ -9,9 +9,13 @@ import { DataPlaneClient } from './data-plane/client' import { Hydrator } from './hydration/hydrator' import { Views } from './views' import { AuthVerifier } from './auth-verifier' -import { dedupeStrs } from '@atproto/common' import { BsyncClient } from './bsync' import { CourierClient } from './courier' +import { + ParsedLabelers, + defaultLabelerHeader, + parseLabelerHeader, +} from './util' export class AppContext { constructor( @@ -82,15 +86,10 @@ export class AppContext { }) } - reqLabelers(req: express.Request): string[] { - const val = req.header('atproto-labelers') - if (!val) return this.cfg.labelsFromIssuerDids - return dedupeStrs( - val - .split(',') - .map((did) => did.trim()) - .slice(0, 10), - ) + reqLabelers(req: express.Request): ParsedLabelers { + const val = req.header('atproto-accept-labelers') + if (!val) return defaultLabelerHeader(this.cfg.labelsFromIssuerDids) + return parseLabelerHeader(val) } } diff --git a/packages/bsky/src/hydration/hydrator.ts b/packages/bsky/src/hydration/hydrator.ts index 402e4985b5e..edd026af05b 100644 --- a/packages/bsky/src/hydration/hydrator.ts +++ b/packages/bsky/src/hydration/hydrator.ts @@ -45,9 +45,10 @@ import { FeedItem, ItemRef, } from './feed' +import { ParsedLabelers } from '../util' export type HydrateCtx = { - labelers: string[] + labelers: ParsedLabelers viewer: string | null } @@ -136,7 +137,10 @@ export class Hydrator { ): Promise { const [actors, labels, profileViewersState] = await Promise.all([ this.actor.getActors(dids, includeTakedowns), - this.label.getLabelsForSubjects(labelSubjectsForDid(dids), ctx.labelers), + this.label.getLabelsForSubjects( + labelSubjectsForDid(dids), + ctx.labelers.dids, + ), this.hydrateProfileViewers(dids, ctx), ]) return mergeStates(profileViewersState ?? {}, { @@ -297,7 +301,7 @@ export class Hydrator { ] = await Promise.all([ this.feed.getPostAggregates(refs), ctx.viewer ? this.feed.getPostViewerStates(refs, ctx.viewer) : undefined, - this.label.getLabelsForSubjects(allPostUris, ctx.labelers), + this.label.getLabelsForSubjects(allPostUris, ctx.labelers.dids), this.hydratePostBlocks(posts), this.hydrateProfiles(allPostUris.map(didFromUri), ctx, includeTakedowns), this.hydrateLists([...nestedListUris, ...gateListUris], ctx), @@ -491,7 +495,7 @@ export class Hydrator { this.feed.getLikes(likeUris), // reason: like this.feed.getReposts(repostUris), // reason: repost this.graph.getFollows(followUris), // reason: follow - this.label.getLabelsForSubjects(uris, ctx.labelers), + this.label.getLabelsForSubjects(uris, ctx.labelers.dids), this.hydrateProfiles(uris.map(didFromUri), ctx), ]) return mergeStates(profileState, { diff --git a/packages/bsky/src/util.ts b/packages/bsky/src/util.ts new file mode 100644 index 00000000000..f95a4e5a2f2 --- /dev/null +++ b/packages/bsky/src/util.ts @@ -0,0 +1,33 @@ +export type ParsedLabelers = { + dids: string[] + redact: Set +} + +export const parseLabelerHeader = (header: string): ParsedLabelers => { + const labelers = header.split(',').map((part) => part.trim()) + const labelerDids = new Set() + const redactDids = new Set() + for (const labeler of labelers) { + if (labeler.length === 0) { + continue + } + const parts = labeler.split(';') + const did = parts[0].trim() + labelerDids.add(did) + const rest = parts.slice(1).map((part) => part.trim()) + if (rest.includes('redact')) { + redactDids.add(did) + } + } + return { + dids: [...labelerDids], + redact: redactDids, + } +} + +export const defaultLabelerHeader = (dids: string[]): ParsedLabelers => { + return { + dids, + redact: new Set(dids), + } +} From cfaffeefce1837bcb1f23ba98118e3304888a572 Mon Sep 17 00:00:00 2001 From: dholms Date: Mon, 11 Mar 2024 15:49:19 -0500 Subject: [PATCH 06/15] add response header --- .../bsky/src/api/app/bsky/actor/getProfile.ts | 9 +++++--- .../src/api/app/bsky/actor/getProfiles.ts | 9 +++++--- .../src/api/app/bsky/actor/getSuggestions.ts | 2 ++ .../src/api/app/bsky/actor/searchActors.ts | 2 ++ .../app/bsky/actor/searchActorsTypeahead.ts | 2 ++ .../src/api/app/bsky/feed/getActorFeeds.ts | 3 ++- .../src/api/app/bsky/feed/getActorLikes.ts | 9 +++++--- .../src/api/app/bsky/feed/getAuthorFeed.ts | 9 +++++--- .../bsky/src/api/app/bsky/feed/getFeed.ts | 14 +++++++---- .../src/api/app/bsky/feed/getFeedGenerator.ts | 2 ++ .../api/app/bsky/feed/getFeedGenerators.ts | 2 ++ .../bsky/src/api/app/bsky/feed/getLikes.ts | 3 ++- .../bsky/src/api/app/bsky/feed/getListFeed.ts | 6 ++--- .../src/api/app/bsky/feed/getPostThread.ts | 11 ++++++--- .../bsky/src/api/app/bsky/feed/getPosts.ts | 2 ++ .../src/api/app/bsky/feed/getRepostedBy.ts | 3 ++- .../api/app/bsky/feed/getSuggestedFeeds.ts | 2 ++ .../bsky/src/api/app/bsky/feed/getTimeline.ts | 6 ++--- .../bsky/src/api/app/bsky/feed/searchPosts.ts | 2 ++ .../bsky/src/api/app/bsky/graph/getBlocks.ts | 3 ++- .../src/api/app/bsky/graph/getFollowers.ts | 3 ++- .../bsky/src/api/app/bsky/graph/getFollows.ts | 3 ++- .../bsky/src/api/app/bsky/graph/getList.ts | 3 ++- .../src/api/app/bsky/graph/getListBlocks.ts | 3 ++- .../src/api/app/bsky/graph/getListMutes.ts | 3 ++- .../bsky/src/api/app/bsky/graph/getLists.ts | 3 ++- .../bsky/src/api/app/bsky/graph/getMutes.ts | 3 ++- .../bsky/graph/getSuggestedFollowsByActor.ts | 2 ++ .../src/api/app/bsky/labeler/getServices.ts | 2 ++ .../bsky/notification/listNotifications.ts | 3 ++- .../unspecced/getPopularFeedGenerators.ts | 3 ++- packages/bsky/src/api/util.ts | 23 +++++++++++++++---- packages/bsky/src/util.ts | 7 ++++++ 33 files changed, 119 insertions(+), 43 deletions(-) diff --git a/packages/bsky/src/api/app/bsky/actor/getProfile.ts b/packages/bsky/src/api/app/bsky/actor/getProfile.ts index b7860791507..f58ecc7dbee 100644 --- a/packages/bsky/src/api/app/bsky/actor/getProfile.ts +++ b/packages/bsky/src/api/app/bsky/actor/getProfile.ts @@ -2,7 +2,7 @@ import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import { QueryParams } from '../../../../lexicon/types/app/bsky/actor/getProfile' import AppContext from '../../../../context' -import { setRepoRev } from '../../../util' +import { resHeaders } from '../../../util' import { createPipeline, noRules } from '../../../../pipeline' import { HydrateCtx, @@ -15,7 +15,7 @@ export default function (server: Server, ctx: AppContext) { const getProfile = createPipeline(skeleton, hydration, noRules, presentation) server.app.bsky.actor.getProfile({ auth: ctx.authVerifier.optionalStandardOrRole, - handler: async ({ auth, params, req, res }) => { + handler: async ({ auth, params, req }) => { const { viewer, canViewTakedowns } = ctx.authVerifier.parseCreds(auth) const labelers = ctx.reqLabelers(req) const hydrateCtx = { labelers, viewer } @@ -26,11 +26,14 @@ export default function (server: Server, ctx: AppContext) { ) const repoRev = await ctx.hydrator.actor.getRepoRevSafe(viewer) - setRepoRev(res, repoRev) return { encoding: 'application/json', body: result, + headers: resHeaders({ + repoRev, + labelers, + }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/actor/getProfiles.ts b/packages/bsky/src/api/app/bsky/actor/getProfiles.ts index 862ac239c7f..014f9339fe8 100644 --- a/packages/bsky/src/api/app/bsky/actor/getProfiles.ts +++ b/packages/bsky/src/api/app/bsky/actor/getProfiles.ts @@ -2,7 +2,7 @@ import { mapDefined } from '@atproto/common' import { Server } from '../../../../lexicon' import { QueryParams } from '../../../../lexicon/types/app/bsky/actor/getProfiles' import AppContext from '../../../../context' -import { setRepoRev } from '../../../util' +import { resHeaders } from '../../../util' import { createPipeline, noRules } from '../../../../pipeline' import { HydrateCtx, @@ -15,7 +15,7 @@ export default function (server: Server, ctx: AppContext) { const getProfile = createPipeline(skeleton, hydration, noRules, presentation) server.app.bsky.actor.getProfiles({ auth: ctx.authVerifier.standardOptional, - handler: async ({ auth, params, req, res }) => { + handler: async ({ auth, params, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) const hydrateCtx = { viewer, labelers } @@ -23,11 +23,14 @@ export default function (server: Server, ctx: AppContext) { const result = await getProfile({ ...params, hydrateCtx }, ctx) const repoRev = await ctx.hydrator.actor.getRepoRevSafe(viewer) - setRepoRev(res, repoRev) return { encoding: 'application/json', body: result, + headers: resHeaders({ + repoRev, + labelers, + }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts b/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts index db312373ea0..451fcedcb5e 100644 --- a/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts +++ b/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts @@ -11,6 +11,7 @@ import { import { Views } from '../../../../views' import { DataPlaneClient } from '../../../../data-plane' import { parseString } from '../../../../hydration/util' +import { resHeaders } from '../../../util' export default function (server: Server, ctx: AppContext) { const getSuggestions = createPipeline( @@ -30,6 +31,7 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: result, + headers: resHeaders({ labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/actor/searchActors.ts b/packages/bsky/src/api/app/bsky/actor/searchActors.ts index b7bf881ab3a..adb3b6704ca 100644 --- a/packages/bsky/src/api/app/bsky/actor/searchActors.ts +++ b/packages/bsky/src/api/app/bsky/actor/searchActors.ts @@ -14,6 +14,7 @@ import { HydrateCtx, Hydrator } from '../../../../hydration/hydrator' import { Views } from '../../../../views' import { DataPlaneClient } from '../../../../data-plane' import { parseString } from '../../../../hydration/util' +import { resHeaders } from '../../../util' export default function (server: Server, ctx: AppContext) { const searchActors = createPipeline( @@ -32,6 +33,7 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: results, + headers: resHeaders({ labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts b/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts index 0ac1506f86d..7517c58ab9d 100644 --- a/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts +++ b/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts @@ -14,6 +14,7 @@ import { HydrateCtx, Hydrator } from '../../../../hydration/hydrator' import { Views } from '../../../../views' import { DataPlaneClient } from '../../../../data-plane' import { parseString } from '../../../../hydration/util' +import { resHeaders } from '../../../util' export default function (server: Server, ctx: AppContext) { const searchActorsTypeahead = createPipeline( @@ -35,6 +36,7 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: results, + headers: resHeaders({ labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/feed/getActorFeeds.ts b/packages/bsky/src/api/app/bsky/feed/getActorFeeds.ts index b94486940fa..0ba5c15409a 100644 --- a/packages/bsky/src/api/app/bsky/feed/getActorFeeds.ts +++ b/packages/bsky/src/api/app/bsky/feed/getActorFeeds.ts @@ -12,7 +12,7 @@ import { import { Views } from '../../../../views' import { DataPlaneClient } from '../../../../data-plane' import { parseString } from '../../../../hydration/util' -import { clearlyBadCursor } from '../../../util' +import { clearlyBadCursor, resHeaders } from '../../../util' export default function (server: Server, ctx: AppContext) { const getActorFeeds = createPipeline( @@ -31,6 +31,7 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: result, + headers: resHeaders({ labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/feed/getActorLikes.ts b/packages/bsky/src/api/app/bsky/feed/getActorLikes.ts index a8b85a3c155..4411f7295ea 100644 --- a/packages/bsky/src/api/app/bsky/feed/getActorLikes.ts +++ b/packages/bsky/src/api/app/bsky/feed/getActorLikes.ts @@ -3,7 +3,7 @@ import { mapDefined } from '@atproto/common' import { Server } from '../../../../lexicon' import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getActorLikes' import AppContext from '../../../../context' -import { clearlyBadCursor, setRepoRev } from '../../../util' +import { clearlyBadCursor, resHeaders } from '../../../util' import { createPipeline } from '../../../../pipeline' import { HydrateCtx, @@ -25,7 +25,7 @@ export default function (server: Server, ctx: AppContext) { ) server.app.bsky.feed.getActorLikes({ auth: ctx.authVerifier.standardOptional, - handler: async ({ params, auth, req, res }) => { + handler: async ({ params, auth, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) const hydrateCtx = { labelers, viewer } @@ -33,11 +33,14 @@ export default function (server: Server, ctx: AppContext) { const result = await getActorLikes({ ...params, hydrateCtx }, ctx) const repoRev = await ctx.hydrator.actor.getRepoRevSafe(viewer) - setRepoRev(res, repoRev) return { encoding: 'application/json', body: result, + headers: resHeaders({ + repoRev, + labelers, + }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts b/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts index a8174b164fc..eb686b80910 100644 --- a/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts +++ b/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts @@ -3,7 +3,7 @@ import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getAuthorFeed' import AppContext from '../../../../context' -import { clearlyBadCursor, setRepoRev } from '../../../util' +import { clearlyBadCursor, resHeaders } from '../../../util' import { createPipeline } from '../../../../pipeline' import { HydrateCtx, @@ -27,7 +27,7 @@ export default function (server: Server, ctx: AppContext) { ) server.app.bsky.feed.getAuthorFeed({ auth: ctx.authVerifier.optionalStandardOrRole, - handler: async ({ params, auth, req, res }) => { + handler: async ({ params, auth, req }) => { const { viewer, canViewTakedowns } = ctx.authVerifier.parseCreds(auth) const labelers = ctx.reqLabelers(req) const hydrateCtx = { labelers, viewer } @@ -38,11 +38,14 @@ export default function (server: Server, ctx: AppContext) { ) const repoRev = await ctx.hydrator.actor.getRepoRevSafe(viewer) - setRepoRev(res, repoRev) return { encoding: 'application/json', body: result, + headers: resHeaders({ + repoRev, + labelers, + }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/feed/getFeed.ts b/packages/bsky/src/api/app/bsky/feed/getFeed.ts index 465348f0522..26e17ea3e2d 100644 --- a/packages/bsky/src/api/app/bsky/feed/getFeed.ts +++ b/packages/bsky/src/api/app/bsky/feed/getFeed.ts @@ -28,6 +28,7 @@ import { isDataplaneError, unpackIdentityServices, } from '../../../../data-plane' +import { resHeaders } from '../../../util' export default function (server: Server, ctx: AppContext) { const getFeed = createPipeline( @@ -47,16 +48,19 @@ export default function (server: Server, ctx: AppContext) { 'accept-language': req.headers['accept-language'], }) // @NOTE feed cursors should not be affected by appview swap - const { timerSkele, timerHydr, resHeaders, ...result } = await getFeed( - { ...params, hydrateCtx, headers }, - ctx, - ) + const { + timerSkele, + timerHydr, + resHeaders: feedResHeaders, + ...result + } = await getFeed({ ...params, hydrateCtx, headers }, ctx) return { encoding: 'application/json', body: result, headers: { - ...(resHeaders ?? {}), + ...(feedResHeaders ?? {}), + ...resHeaders({ labelers }), 'server-timing': serverTimingHeader([timerSkele, timerHydr]), }, } diff --git a/packages/bsky/src/api/app/bsky/feed/getFeedGenerator.ts b/packages/bsky/src/api/app/bsky/feed/getFeedGenerator.ts index 171c497e2ae..9c94fb2b213 100644 --- a/packages/bsky/src/api/app/bsky/feed/getFeedGenerator.ts +++ b/packages/bsky/src/api/app/bsky/feed/getFeedGenerator.ts @@ -8,6 +8,7 @@ import { isDataplaneError, unpackIdentityServices, } from '../../../../data-plane' +import { resHeaders } from '../../../util' export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getFeedGenerator({ @@ -63,6 +64,7 @@ export default function (server: Server, ctx: AppContext) { isOnline: true, isValid: true, }, + headers: resHeaders({ labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/feed/getFeedGenerators.ts b/packages/bsky/src/api/app/bsky/feed/getFeedGenerators.ts index d8225384c97..65d5a5dd316 100644 --- a/packages/bsky/src/api/app/bsky/feed/getFeedGenerators.ts +++ b/packages/bsky/src/api/app/bsky/feed/getFeedGenerators.ts @@ -9,6 +9,7 @@ import { Hydrator, } from '../../../../hydration/hydrator' import { Views } from '../../../../views' +import { resHeaders } from '../../../util' export default function (server: Server, ctx: AppContext) { const getFeedGenerators = createPipeline( @@ -27,6 +28,7 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: view, + headers: resHeaders({ labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/feed/getLikes.ts b/packages/bsky/src/api/app/bsky/feed/getLikes.ts index 5e9ebf7d70c..402a6c44a8a 100644 --- a/packages/bsky/src/api/app/bsky/feed/getLikes.ts +++ b/packages/bsky/src/api/app/bsky/feed/getLikes.ts @@ -12,7 +12,7 @@ import { import { Views } from '../../../../views' import { parseString } from '../../../../hydration/util' import { creatorFromUri } from '../../../../views/util' -import { clearlyBadCursor } from '../../../util' +import { clearlyBadCursor, resHeaders } from '../../../util' export default function (server: Server, ctx: AppContext) { const getLikes = createPipeline(skeleton, hydration, noBlocks, presentation) @@ -27,6 +27,7 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: result, + headers: resHeaders({ labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/feed/getListFeed.ts b/packages/bsky/src/api/app/bsky/feed/getListFeed.ts index daed2a99025..75ae9edf815 100644 --- a/packages/bsky/src/api/app/bsky/feed/getListFeed.ts +++ b/packages/bsky/src/api/app/bsky/feed/getListFeed.ts @@ -1,7 +1,7 @@ import { Server } from '../../../../lexicon' import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getListFeed' import AppContext from '../../../../context' -import { clearlyBadCursor, setRepoRev } from '../../../util' +import { clearlyBadCursor, resHeaders } from '../../../util' import { createPipeline } from '../../../../pipeline' import { HydrateCtx, @@ -23,7 +23,7 @@ export default function (server: Server, ctx: AppContext) { ) server.app.bsky.feed.getListFeed({ auth: ctx.authVerifier.standardOptional, - handler: async ({ params, auth, req, res }) => { + handler: async ({ params, auth, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) const hydrateCtx = { labelers, viewer } @@ -31,11 +31,11 @@ export default function (server: Server, ctx: AppContext) { const result = await getListFeed({ ...params, hydrateCtx }, ctx) const repoRev = await ctx.hydrator.actor.getRepoRevSafe(viewer) - setRepoRev(res, repoRev) return { encoding: 'application/json', body: result, + headers: resHeaders({ labelers, repoRev }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/feed/getPostThread.ts b/packages/bsky/src/api/app/bsky/feed/getPostThread.ts index d7c4009bdaa..3cfd69f47f8 100644 --- a/packages/bsky/src/api/app/bsky/feed/getPostThread.ts +++ b/packages/bsky/src/api/app/bsky/feed/getPostThread.ts @@ -6,7 +6,7 @@ import { OutputSchema, } from '../../../../lexicon/types/app/bsky/feed/getPostThread' import AppContext from '../../../../context' -import { setRepoRev } from '../../../util' +import { ATPROTO_REPO_REV, resHeaders } from '../../../util' import { HydrationFnInput, PresentationFnInput, @@ -37,16 +37,21 @@ export default function (server: Server, ctx: AppContext) { result = await getPostThread({ ...params, hydrateCtx }, ctx) } catch (err) { const repoRev = await ctx.hydrator.actor.getRepoRevSafe(viewer) - setRepoRev(res, repoRev) + if (repoRev) { + res.setHeader(ATPROTO_REPO_REV, repoRev) + } throw err } const repoRev = await ctx.hydrator.actor.getRepoRevSafe(viewer) - setRepoRev(res, repoRev) return { encoding: 'application/json', body: result, + headers: resHeaders({ + repoRev, + labelers, + }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/feed/getPosts.ts b/packages/bsky/src/api/app/bsky/feed/getPosts.ts index fd5b89f96e0..322197c7e3f 100644 --- a/packages/bsky/src/api/app/bsky/feed/getPosts.ts +++ b/packages/bsky/src/api/app/bsky/feed/getPosts.ts @@ -10,6 +10,7 @@ import { } from '../../../../hydration/hydrator' import { Views } from '../../../../views' import { creatorFromUri } from '../../../../views/util' +import { resHeaders } from '../../../util' export default function (server: Server, ctx: AppContext) { const getPosts = createPipeline(skeleton, hydration, noBlocks, presentation) @@ -25,6 +26,7 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: results, + headers: resHeaders({ labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/feed/getRepostedBy.ts b/packages/bsky/src/api/app/bsky/feed/getRepostedBy.ts index 887200476b7..eee518961b2 100644 --- a/packages/bsky/src/api/app/bsky/feed/getRepostedBy.ts +++ b/packages/bsky/src/api/app/bsky/feed/getRepostedBy.ts @@ -11,7 +11,7 @@ import { import { Views } from '../../../../views' import { parseString } from '../../../../hydration/util' import { creatorFromUri } from '../../../../views/util' -import { clearlyBadCursor } from '../../../util' +import { clearlyBadCursor, resHeaders } from '../../../util' export default function (server: Server, ctx: AppContext) { const getRepostedBy = createPipeline( @@ -31,6 +31,7 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: result, + headers: resHeaders({ labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/feed/getSuggestedFeeds.ts b/packages/bsky/src/api/app/bsky/feed/getSuggestedFeeds.ts index ee42ea74dd3..e848eddd634 100644 --- a/packages/bsky/src/api/app/bsky/feed/getSuggestedFeeds.ts +++ b/packages/bsky/src/api/app/bsky/feed/getSuggestedFeeds.ts @@ -2,6 +2,7 @@ import { mapDefined } from '@atproto/common' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' import { parseString } from '../../../../hydration/util' +import { resHeaders } from '../../../util' export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getSuggestedFeeds({ @@ -31,6 +32,7 @@ export default function (server: Server, ctx: AppContext) { feeds: feedViews, cursor: parseString(suggestedRes.cursor), }, + headers: resHeaders({ labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/feed/getTimeline.ts b/packages/bsky/src/api/app/bsky/feed/getTimeline.ts index e9e31bcaceb..c58199d195b 100644 --- a/packages/bsky/src/api/app/bsky/feed/getTimeline.ts +++ b/packages/bsky/src/api/app/bsky/feed/getTimeline.ts @@ -1,7 +1,7 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getTimeline' -import { clearlyBadCursor, setRepoRev } from '../../../util' +import { clearlyBadCursor, resHeaders } from '../../../util' import { createPipeline } from '../../../../pipeline' import { HydrateCtx, @@ -23,7 +23,7 @@ export default function (server: Server, ctx: AppContext) { ) server.app.bsky.feed.getTimeline({ auth: ctx.authVerifier.standard, - handler: async ({ params, auth, req, res }) => { + handler: async ({ params, auth, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) const hydrateCtx = { labelers, viewer } @@ -31,11 +31,11 @@ export default function (server: Server, ctx: AppContext) { const result = await getTimeline({ ...params, hydrateCtx }, ctx) const repoRev = await ctx.hydrator.actor.getRepoRevSafe(viewer) - setRepoRev(res, repoRev) return { encoding: 'application/json', body: result, + headers: resHeaders({ labelers, repoRev }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/feed/searchPosts.ts b/packages/bsky/src/api/app/bsky/feed/searchPosts.ts index c511883fbde..0ab4fb8ba3a 100644 --- a/packages/bsky/src/api/app/bsky/feed/searchPosts.ts +++ b/packages/bsky/src/api/app/bsky/feed/searchPosts.ts @@ -15,6 +15,7 @@ import { Views } from '../../../../views' import { DataPlaneClient } from '../../../../data-plane' import { parseString } from '../../../../hydration/util' import { creatorFromUri } from '../../../../views/util' +import { resHeaders } from '../../../util' export default function (server: Server, ctx: AppContext) { const searchPosts = createPipeline( @@ -33,6 +34,7 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: results, + headers: resHeaders({ labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/graph/getBlocks.ts b/packages/bsky/src/api/app/bsky/graph/getBlocks.ts index e7a9df2030e..b6723cb3c45 100644 --- a/packages/bsky/src/api/app/bsky/graph/getBlocks.ts +++ b/packages/bsky/src/api/app/bsky/graph/getBlocks.ts @@ -11,7 +11,7 @@ import { } from '../../../../pipeline' import { HydrateCtx, Hydrator } from '../../../../hydration/hydrator' import { Views } from '../../../../views' -import { clearlyBadCursor } from '../../../util' +import { clearlyBadCursor, resHeaders } from '../../../util' export default function (server: Server, ctx: AppContext) { const getBlocks = createPipeline(skeleton, hydration, noRules, presentation) @@ -25,6 +25,7 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: result, + headers: resHeaders({ labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/graph/getFollowers.ts b/packages/bsky/src/api/app/bsky/graph/getFollowers.ts index b2d3288b4bd..42e31f7288f 100644 --- a/packages/bsky/src/api/app/bsky/graph/getFollowers.ts +++ b/packages/bsky/src/api/app/bsky/graph/getFollowers.ts @@ -17,7 +17,7 @@ import { mergeStates, } from '../../../../hydration/hydrator' import { Views } from '../../../../views' -import { clearlyBadCursor } from '../../../util' +import { clearlyBadCursor, resHeaders } from '../../../util' export default function (server: Server, ctx: AppContext) { const getFollowers = createPipeline( @@ -41,6 +41,7 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: result, + headers: resHeaders({ labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/graph/getFollows.ts b/packages/bsky/src/api/app/bsky/graph/getFollows.ts index d1840f9a19a..cfe8e220cc7 100644 --- a/packages/bsky/src/api/app/bsky/graph/getFollows.ts +++ b/packages/bsky/src/api/app/bsky/graph/getFollows.ts @@ -16,7 +16,7 @@ import { mergeStates, } from '../../../../hydration/hydrator' import { Views } from '../../../../views' -import { clearlyBadCursor } from '../../../util' +import { clearlyBadCursor, resHeaders } from '../../../util' export default function (server: Server, ctx: AppContext) { const getFollows = createPipeline(skeleton, hydration, noBlocks, presentation) @@ -36,6 +36,7 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: result, + headers: resHeaders({ labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/graph/getList.ts b/packages/bsky/src/api/app/bsky/graph/getList.ts index 42c298ecbe2..92ea332d0ed 100644 --- a/packages/bsky/src/api/app/bsky/graph/getList.ts +++ b/packages/bsky/src/api/app/bsky/graph/getList.ts @@ -16,7 +16,7 @@ import { mergeStates, } from '../../../../hydration/hydrator' import { Views } from '../../../../views' -import { clearlyBadCursor } from '../../../util' +import { clearlyBadCursor, resHeaders } from '../../../util' import { ListItemInfo } from '../../../../proto/bsky_pb' export default function (server: Server, ctx: AppContext) { @@ -31,6 +31,7 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: result, + headers: resHeaders({ labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/graph/getListBlocks.ts b/packages/bsky/src/api/app/bsky/graph/getListBlocks.ts index 9b7e74f69b6..2360781a6ef 100644 --- a/packages/bsky/src/api/app/bsky/graph/getListBlocks.ts +++ b/packages/bsky/src/api/app/bsky/graph/getListBlocks.ts @@ -11,7 +11,7 @@ import { } from '../../../../pipeline' import { HydrateCtx, Hydrator } from '../../../../hydration/hydrator' import { Views } from '../../../../views' -import { clearlyBadCursor } from '../../../util' +import { clearlyBadCursor, resHeaders } from '../../../util' export default function (server: Server, ctx: AppContext) { const getListBlocks = createPipeline( @@ -30,6 +30,7 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: result, + headers: resHeaders({ labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/graph/getListMutes.ts b/packages/bsky/src/api/app/bsky/graph/getListMutes.ts index 7004e43249f..cc4f5a64942 100644 --- a/packages/bsky/src/api/app/bsky/graph/getListMutes.ts +++ b/packages/bsky/src/api/app/bsky/graph/getListMutes.ts @@ -11,7 +11,7 @@ import { } from '../../../../pipeline' import { HydrateCtx, Hydrator } from '../../../../hydration/hydrator' import { Views } from '../../../../views' -import { clearlyBadCursor } from '../../../util' +import { clearlyBadCursor, resHeaders } from '../../../util' export default function (server: Server, ctx: AppContext) { const getListMutes = createPipeline( @@ -30,6 +30,7 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: result, + headers: resHeaders({ labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/graph/getLists.ts b/packages/bsky/src/api/app/bsky/graph/getLists.ts index c53b7e14482..3ebc7fbb628 100644 --- a/packages/bsky/src/api/app/bsky/graph/getLists.ts +++ b/packages/bsky/src/api/app/bsky/graph/getLists.ts @@ -11,7 +11,7 @@ import { } from '../../../../pipeline' import { HydrateCtx, Hydrator } from '../../../../hydration/hydrator' import { Views } from '../../../../views' -import { clearlyBadCursor } from '../../../util' +import { clearlyBadCursor, resHeaders } from '../../../util' export default function (server: Server, ctx: AppContext) { const getLists = createPipeline(skeleton, hydration, noRules, presentation) @@ -26,6 +26,7 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: result, + headers: resHeaders({ labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/graph/getMutes.ts b/packages/bsky/src/api/app/bsky/graph/getMutes.ts index db81c46ab1b..6890087afa9 100644 --- a/packages/bsky/src/api/app/bsky/graph/getMutes.ts +++ b/packages/bsky/src/api/app/bsky/graph/getMutes.ts @@ -11,7 +11,7 @@ import { createPipeline, noRules, } from '../../../../pipeline' -import { clearlyBadCursor } from '../../../util' +import { clearlyBadCursor, resHeaders } from '../../../util' export default function (server: Server, ctx: AppContext) { const getMutes = createPipeline(skeleton, hydration, noRules, presentation) @@ -25,6 +25,7 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: result, + headers: resHeaders({ labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts b/packages/bsky/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts index 579a9ab329b..356b33aa7fe 100644 --- a/packages/bsky/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts +++ b/packages/bsky/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts @@ -12,6 +12,7 @@ import { } from '../../../../pipeline' import { HydrateCtx, Hydrator } from '../../../../hydration/hydrator' import { Views } from '../../../../views' +import { resHeaders } from '../../../util' export default function (server: Server, ctx: AppContext) { const getSuggestedFollowsByActor = createPipeline( @@ -33,6 +34,7 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: result, + headers: resHeaders({ labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/labeler/getServices.ts b/packages/bsky/src/api/app/bsky/labeler/getServices.ts index 9f151757361..aeca1a10834 100644 --- a/packages/bsky/src/api/app/bsky/labeler/getServices.ts +++ b/packages/bsky/src/api/app/bsky/labeler/getServices.ts @@ -1,6 +1,7 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' import { mapDefined } from '@atproto/common' +import { resHeaders } from '../../../util' export default function (server: Server, ctx: AppContext) { server.app.bsky.labeler.getServices({ @@ -38,6 +39,7 @@ export default function (server: Server, ctx: AppContext) { body: { views, }, + headers: resHeaders({ labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/notification/listNotifications.ts b/packages/bsky/src/api/app/bsky/notification/listNotifications.ts index 6ee8951fc66..8eb3996b2a2 100644 --- a/packages/bsky/src/api/app/bsky/notification/listNotifications.ts +++ b/packages/bsky/src/api/app/bsky/notification/listNotifications.ts @@ -14,7 +14,7 @@ import { HydrateCtx, Hydrator } from '../../../../hydration/hydrator' import { Views } from '../../../../views' import { Notification } from '../../../../proto/bsky_pb' import { didFromUri } from '../../../../hydration/util' -import { clearlyBadCursor } from '../../../util' +import { clearlyBadCursor, resHeaders } from '../../../util' export default function (server: Server, ctx: AppContext) { const listNotifications = createPipeline( @@ -33,6 +33,7 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: result, + headers: resHeaders({ labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts b/packages/bsky/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts index c676e115908..883a451c8ac 100644 --- a/packages/bsky/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts +++ b/packages/bsky/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts @@ -2,7 +2,7 @@ import { mapDefined } from '@atproto/common' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' import { parseString } from '../../../../hydration/util' -import { clearlyBadCursor } from '../../../util' +import { clearlyBadCursor, resHeaders } from '../../../util' // THIS IS A TEMPORARY UNSPECCED ROUTE // @TODO currently mirrors getSuggestedFeeds and ignores the "query" param. @@ -53,6 +53,7 @@ export default function (server: Server, ctx: AppContext) { feeds: feedViews, cursor, }, + headers: resHeaders({ labelers }), } }, }) diff --git a/packages/bsky/src/api/util.ts b/packages/bsky/src/api/util.ts index 2fe54a8a7be..3ee0ea2c59b 100644 --- a/packages/bsky/src/api/util.ts +++ b/packages/bsky/src/api/util.ts @@ -1,9 +1,24 @@ -import express from 'express' +import { ParsedLabelers, formatLabelerHeader } from '../util' -export const setRepoRev = (res: express.Response, rev: string | null) => { - if (rev !== null) { - res.setHeader('Atproto-Repo-Rev', rev) +export const ATPROTO_CONTENT_LABELERS = 'Atproto-Content-Labelers' +export const ATPROTO_REPO_REV = 'Atproto-Repo-Rev' + +type ResHeaderOpts = { + labelers: ParsedLabelers + repoRev: string | null +} + +export const resHeaders = ( + opts: Partial, +): Record => { + const headers = {} + if (opts.labelers) { + headers[ATPROTO_CONTENT_LABELERS] = formatLabelerHeader(opts.labelers) + } + if (opts.repoRev) { + headers[ATPROTO_REPO_REV] = opts.repoRev } + return headers } export const clearlyBadCursor = (cursor?: string) => { diff --git a/packages/bsky/src/util.ts b/packages/bsky/src/util.ts index f95a4e5a2f2..0c0b172f1b5 100644 --- a/packages/bsky/src/util.ts +++ b/packages/bsky/src/util.ts @@ -31,3 +31,10 @@ export const defaultLabelerHeader = (dids: string[]): ParsedLabelers => { redact: new Set(dids), } } + +export const formatLabelerHeader = (parsed: ParsedLabelers): string => { + const parts = parsed.dids.map((did) => + parsed.redact.has(did) ? `${did};redact` : did, + ) + return parts.join(',') +} From 9c98940121483cd0ef6fd40eeb9a70f586053884 Mon Sep 17 00:00:00 2001 From: dholms Date: Mon, 11 Mar 2024 15:54:53 -0500 Subject: [PATCH 07/15] pds: pipe through res headers --- packages/pds/src/pipethrough.ts | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/pds/src/pipethrough.ts b/packages/pds/src/pipethrough.ts index 0d9c00737b5..8f633a7448f 100644 --- a/packages/pds/src/pipethrough.ts +++ b/packages/pds/src/pipethrough.ts @@ -95,10 +95,10 @@ export const parseProxyHeader = async ( return { did, serviceUrl } } -const HEADERS_TO_FORWARD = [ +const REQ_HEADERS_TO_FORWARD = [ 'accept-language', 'content-type', - 'atproto-labelers', + 'atproto-accept-labelers', ] export const createUrlAndHeaders = async ( @@ -122,7 +122,7 @@ export const createUrlAndHeaders = async ( ? (await ctx.serviceAuthHeaders(requester, aud)).headers : {} // forward select headers to upstream services - for (const header of HEADERS_TO_FORWARD) { + for (const header of REQ_HEADERS_TO_FORWARD) { const val = req.headers[header] if (val) { headers[header] = val @@ -152,15 +152,24 @@ export const doProxy = async (url: URL, reqInit: RequestInit) => { ) } const encoding = res.headers.get('content-type') ?? 'application/json' - const repoRevHeader = res.headers.get('atproto-repo-rev') - const contentLanguage = res.headers.get('content-language') - const resHeaders = noUndefinedVals({ - ['atproto-repo-rev']: repoRevHeader ?? undefined, - ['content-language']: contentLanguage ?? undefined, - }) + const resHeaders = makeResHeaders(res) return { encoding, buffer, headers: resHeaders } } +const RES_HEADERS_TO_FORWARD = [ + 'atproto-repo-rev', + 'content-language', + 'atproto-content-labelers', +] + +const makeResHeaders = (res: Response): Record => { + const headers = RES_HEADERS_TO_FORWARD.reduce((acc, cur) => { + acc[cur] = res.headers.get(cur) ?? undefined + return acc + }, {} as Record) + return noUndefinedVals(headers) +} + const isSafeUrl = (url: URL) => { if (url.protocol !== 'https:') return false if (!url.hostname || url.hostname === 'localhost') return false From 877906f6f0edc49c0da08fa05f96c1fc89c9a97e Mon Sep 17 00:00:00 2001 From: dholms Date: Mon, 11 Mar 2024 16:00:43 -0500 Subject: [PATCH 08/15] fix up tests --- packages/bsky/tests/label-hydration.test.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/bsky/tests/label-hydration.test.ts b/packages/bsky/tests/label-hydration.test.ts index ab672619f86..c07fb916eab 100644 --- a/packages/bsky/tests/label-hydration.test.ts +++ b/packages/bsky/tests/label-hydration.test.ts @@ -61,11 +61,17 @@ describe('label hydration', () => { it('hydrates labels based on a supplied labeler header', async () => { const res = await pdsAgent.api.app.bsky.actor.getProfile( { actor: carol }, - { headers: { ...sc.getHeaders(bob), 'atproto-labelers': alice } }, + { + headers: { + ...sc.getHeaders(bob), + 'atproto-accept-labelers': `${alice};redact`, + }, + }, ) expect(res.data.labels?.length).toBe(1) expect(res.data.labels?.[0].src).toBe(alice) expect(res.data.labels?.[0].val).toBe('spam') + expect(res.headers['atproto-content-labelers']).toEqual(`${alice};redact`) }) it('hydrates labels based on multiple a supplied labelers', async () => { @@ -74,7 +80,7 @@ describe('label hydration', () => { { headers: { ...sc.getHeaders(bob), - 'atproto-labelers': `${alice},${bob}, ${labelerDid}`, + 'atproto-accept-labelers': `${alice},${bob};redact, ${labelerDid}`, }, }, ) @@ -86,6 +92,10 @@ describe('label hydration', () => { expect(res.data.labels?.find((l) => l.src === labelerDid)?.val).toEqual( 'misleading', ) + const labelerHeaderDids = res.headers['atproto-content-labelers'].split(',') + expect(labelerHeaderDids.sort()).toEqual( + [alice, `${bob};redact`, labelerDid].sort(), + ) }) it('defaults to service labels when no labeler header is provided', async () => { @@ -96,5 +106,8 @@ describe('label hydration', () => { expect(res.data.labels?.length).toBe(1) expect(res.data.labels?.[0].src).toBe(labelerDid) expect(res.data.labels?.[0].val).toBe('misleading') + expect(res.headers['atproto-content-labelers']).toEqual( + `${labelerDid};redact`, + ) }) }) From e1edd8936542f9064cc5b29804fb4c69dc7298b9 Mon Sep 17 00:00:00 2001 From: dholms Date: Mon, 11 Mar 2024 18:15:45 -0500 Subject: [PATCH 09/15] tidy tests --- .../bsky/src/api/app/bsky/actor/getProfile.ts | 5 +- packages/bsky/src/views/index.ts | 4 +- .../bsky/tests/views/takedown-labels.test.ts | 115 +++++++++++++----- packages/dev-env/src/seed/client.ts | 24 ++++ 4 files changed, 118 insertions(+), 30 deletions(-) diff --git a/packages/bsky/src/api/app/bsky/actor/getProfile.ts b/packages/bsky/src/api/app/bsky/actor/getProfile.ts index b008077e2b8..fa2226fb0ba 100644 --- a/packages/bsky/src/api/app/bsky/actor/getProfile.ts +++ b/packages/bsky/src/api/app/bsky/actor/getProfile.ts @@ -54,7 +54,10 @@ const hydration = async (input: { skeleton: SkeletonState }) => { const { ctx, params, skeleton } = input - return ctx.hydrator.hydrateProfilesDetailed([skeleton.did], params.hydrateCtx) + return ctx.hydrator.hydrateProfilesDetailed([skeleton.did], { + ...params.hydrateCtx, + includeTakedowns: true, + }) } const presentation = (input: { diff --git a/packages/bsky/src/views/index.ts b/packages/bsky/src/views/index.ts index 67a9bee51ef..52422d8dde1 100644 --- a/packages/bsky/src/views/index.ts +++ b/packages/bsky/src/views/index.ts @@ -61,7 +61,9 @@ export class Views { // ------------ actorIsTakendown(did: string, state: HydrationState): boolean { - return !!state.actors?.get(did)?.takedownRef + if (state.actors?.get(did)?.takedownRef) return true + if (state.labels?.get(did)?.isTakendown) return true + return false } viewerBlockExists(did: string, state: HydrationState): boolean { diff --git a/packages/bsky/tests/views/takedown-labels.test.ts b/packages/bsky/tests/views/takedown-labels.test.ts index dc7f8243a58..b7118c75a34 100644 --- a/packages/bsky/tests/views/takedown-labels.test.ts +++ b/packages/bsky/tests/views/takedown-labels.test.ts @@ -1,11 +1,19 @@ import AtpAgent from '@atproto/api' -import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env' +import { TestNetwork, SeedClient, basicSeed, RecordRef } from '@atproto/dev-env' describe('bsky takedown labels', () => { let network: TestNetwork let agent: AtpAgent let sc: SeedClient + let takendownSubjects: string[] + + let aliceListRef: RecordRef + let carolListRef: RecordRef + let aliceGenRef: RecordRef + let bobGenRef: RecordRef + let carolGenRef: RecordRef + beforeAll(async () => { network = await TestNetwork.create({ dbPostgresSchema: 'bsky_views_takedown_labels', @@ -13,18 +21,36 @@ describe('bsky takedown labels', () => { agent = network.bsky.getClient() sc = network.getSeedClient() await basicSeed(sc) - await network.processAll() - }) - afterAll(async () => { - await network.close() - }) + aliceListRef = await sc.createList(sc.dids.alice, 'alice list', 'mod') + carolListRef = await sc.createList(sc.dids.carol, 'carol list', 'mod') + aliceGenRef = await sc.createFeedGen( + sc.dids.alice, + 'did:web:example.com', + 'alice generator', + ) + bobGenRef = await sc.createFeedGen( + sc.dids.bob, + 'did:web:example.com', + 'bob generator', + ) + carolGenRef = await sc.createFeedGen( + sc.dids.carol, + 'did:web:example.com', + 'carol generator', + ) + + await network.processAll() - const addTakedownLabels = async (subjects: string[]) => { - if (subjects.length === 0) return + takendownSubjects = [ + sc.posts[sc.dids.alice][0].ref.uriStr, + sc.dids.carol, + aliceListRef.uriStr, + aliceGenRef.uriStr, + ] const src = network.ozone.ctx.cfg.service.did const cts = new Date().toISOString() - const labels = subjects.map((uri) => ({ + const labels = takendownSubjects.map((uri) => ({ src, uri, cid: '', @@ -34,19 +60,25 @@ describe('bsky takedown labels', () => { })) await network.bsky.db.db.insertInto('label').values(labels).execute() - } - const clearTakedownLabels = async () => { - await network.bsky.db.db - .deleteFrom('label') - .where('val', '=', '!takedown') - .execute() - } + }) + + afterAll(async () => { + await network.close() + }) + + it('takesdown profiles', async () => { + const attempt = agent.api.app.bsky.actor.getProfile({ + actor: sc.dids.carol, + }) + await expect(attempt).rejects.toThrow('Account has been suspended') + const res = await agent.api.app.bsky.actor.getProfiles({ + actors: [sc.dids.alice, sc.dids.bob, sc.dids.carol], + }) + expect(res.data.profiles.length).toBe(2) + expect(res.data.profiles.some((p) => p.did === sc.dids.carol)).toBe(false) + }) it('takesdown posts', async () => { - await addTakedownLabels([ - sc.posts[sc.dids.alice][0].ref.uriStr, - sc.dids.carol, - ]) const uris = [ sc.posts[sc.dids.alice][0].ref.uriStr, sc.posts[sc.dids.alice][1].ref.uriStr, @@ -55,21 +87,48 @@ describe('bsky takedown labels', () => { sc.posts[sc.dids.dan][1].ref.uriStr, sc.replies[sc.dids.alice][0].ref.uriStr, ] - const posts = await agent.api.app.bsky.feed.getPosts( - { uris }, - { headers: await network.serviceHeaders(sc.dids.alice) }, - ) + const res = await agent.api.app.bsky.feed.getPosts({ uris }) - expect(posts.data.posts.length).toBe(4) - expect(posts.data.posts.some((p) => p.author.did === sc.dids.carol)).toBe( + expect(res.data.posts.length).toBe(4) + expect(res.data.posts.some((p) => p.author.did === sc.dids.carol)).toBe( false, ) expect( - posts.data.posts.some( + res.data.posts.some( (p) => p.uri === sc.posts[sc.dids.alice][0].ref.uriStr, ), ).toBe(false) + }) - await clearTakedownLabels() + it('takesdown lists', async () => { + // record takedown + const attempt1 = agent.api.app.bsky.graph.getList({ + list: aliceListRef.uriStr, + }) + await expect(attempt1).rejects.toThrow('List not found') + + // actor takedown + const attempt2 = agent.api.app.bsky.graph.getList({ + list: carolListRef.uriStr, + }) + await expect(attempt2).rejects.toThrow('List not found') + }) + + it('takesdown feed generators', async () => { + const res = await agent.api.app.bsky.feed.getFeedGenerators({ + feeds: [aliceGenRef.uriStr, bobGenRef.uriStr, carolGenRef.uriStr], + }) + expect(res.data.feeds.length).toBe(1) + expect(res.data.feeds.at(0)?.uri).toEqual(bobGenRef.uriStr) + }) + + it('only applies if the relevant labeler is configured', async () => { + const res = await agent.api.app.bsky.actor.getProfile( + { + actor: sc.dids.carol, + }, + { headers: { 'atproto-accept-labelers': 'did:web:example.com' } }, + ) + expect(res.data.did).toEqual(sc.dids.carol) }) }) diff --git a/packages/dev-env/src/seed/client.ts b/packages/dev-env/src/seed/client.ts index 1e5a9ae10d4..f4c2283543d 100644 --- a/packages/dev-env/src/seed/client.ts +++ b/packages/dev-env/src/seed/client.ts @@ -81,6 +81,10 @@ export class SeedClient< string, Record }> > + feedgens: Record< + string, + Record }> + > dids: Record constructor(public network: Network, public agent: AtpAgent) { @@ -93,6 +97,7 @@ export class SeedClient< this.replies = {} this.reposts = {} this.lists = {} + this.feedgens = {} this.dids = {} } @@ -396,6 +401,25 @@ export class SeedClient< return ref } + async createFeedGen(by: string, feedDid: string, name: string) { + const res = await this.agent.api.app.bsky.feed.generator.create( + { repo: by }, + { + did: feedDid, + displayName: name, + createdAt: new Date().toISOString(), + }, + this.getHeaders(by), + ) + this.feedgens[by] ??= {} + const ref = new RecordRef(res.uri, res.cid) + this.feedgens[by][ref.uriStr] = { + ref: ref, + items: {}, + } + return ref + } + async addToList(by: string, subject: string, list: RecordRef) { const res = await this.agent.api.app.bsky.graph.listitem.create( { repo: by }, From a7fc93d1ada2f4392e5e46a6461c7bc6bec36eee Mon Sep 17 00:00:00 2001 From: dholms Date: Mon, 11 Mar 2024 18:21:44 -0500 Subject: [PATCH 10/15] re-include example in dev-env --- packages/dev-env/src/network.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev-env/src/network.ts b/packages/dev-env/src/network.ts index 52bc6470d1f..d7da4df79ab 100644 --- a/packages/dev-env/src/network.ts +++ b/packages/dev-env/src/network.ts @@ -53,7 +53,7 @@ export class TestNetwork extends TestNetworkNoAppView { dbPostgresUrl, redisHost, modServiceDid: ozoneDid, - labelsFromIssuerDids: [ozoneDid], + labelsFromIssuerDids: [ozoneDid, 'did:example:labeler'], // this did is also used as the labeler in seeds ...params.bsky, }) From 30a4c02c84e60faace421a35352ae14521d43fda Mon Sep 17 00:00:00 2001 From: dholms Date: Mon, 11 Mar 2024 18:34:52 -0500 Subject: [PATCH 11/15] fix test --- packages/bsky/tests/label-hydration.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/bsky/tests/label-hydration.test.ts b/packages/bsky/tests/label-hydration.test.ts index c07fb916eab..2b514634587 100644 --- a/packages/bsky/tests/label-hydration.test.ts +++ b/packages/bsky/tests/label-hydration.test.ts @@ -106,8 +106,11 @@ describe('label hydration', () => { expect(res.data.labels?.length).toBe(1) expect(res.data.labels?.[0].src).toBe(labelerDid) expect(res.data.labels?.[0].val).toBe('misleading') + expect(res.headers['atproto-content-labelers']).toEqual( - `${labelerDid};redact`, + network.bsky.ctx.cfg.labelsFromIssuerDids + .map((did) => `${did};redact`) + .join(','), ) }) }) From aa1ce7c9588d90fe78ffdd1f8d49620cf6eb55e9 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 12 Mar 2024 15:52:51 -0500 Subject: [PATCH 12/15] revamp parsing --- packages/bsky/package.json | 1 + packages/bsky/src/context.ts | 12 ++++++++++-- packages/bsky/src/util.ts | 22 +++++++++++++--------- pnpm-lock.yaml | 8 ++++++++ 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/packages/bsky/package.json b/packages/bsky/package.json index ff1725ca980..9138c16d0be 100644 --- a/packages/bsky/package.json +++ b/packages/bsky/package.json @@ -63,6 +63,7 @@ "pino": "^8.15.0", "pino-http": "^8.2.1", "sharp": "^0.32.6", + "structured-headers": "^1.0.1", "typed-emitter": "^2.1.0", "uint8arrays": "3.0.0" }, diff --git a/packages/bsky/src/context.ts b/packages/bsky/src/context.ts index 9194bb41781..e8a15f5197a 100644 --- a/packages/bsky/src/context.ts +++ b/packages/bsky/src/context.ts @@ -16,6 +16,7 @@ import { defaultLabelerHeader, parseLabelerHeader, } from './util' +import { httpLogger as log } from './logger' export class AppContext { constructor( @@ -88,8 +89,15 @@ export class AppContext { reqLabelers(req: express.Request): ParsedLabelers { const val = req.header('atproto-accept-labelers') - if (!val) return defaultLabelerHeader(this.cfg.labelsFromIssuerDids) - return parseLabelerHeader(val) + let parsed: ParsedLabelers | null + try { + parsed = parseLabelerHeader(val) + } catch (err) { + parsed = null + log.info({ err, val }, 'failed to parse labeler header') + } + if (!parsed) return defaultLabelerHeader(this.cfg.labelsFromIssuerDids) + return parsed } } diff --git a/packages/bsky/src/util.ts b/packages/bsky/src/util.ts index 0c0b172f1b5..50c0c1fa565 100644 --- a/packages/bsky/src/util.ts +++ b/packages/bsky/src/util.ts @@ -1,21 +1,25 @@ +import { parseList } from 'structured-headers' + export type ParsedLabelers = { dids: string[] redact: Set } -export const parseLabelerHeader = (header: string): ParsedLabelers => { - const labelers = header.split(',').map((part) => part.trim()) +export const parseLabelerHeader = ( + header: string | undefined, +): ParsedLabelers | null => { + if (!header) return null const labelerDids = new Set() const redactDids = new Set() - for (const labeler of labelers) { - if (labeler.length === 0) { - continue + const parsed = parseList(header) + for (const item of parsed) { + const did = item[0].toString() + if (!did) { + return null } - const parts = labeler.split(';') - const did = parts[0].trim() labelerDids.add(did) - const rest = parts.slice(1).map((part) => part.trim()) - if (rest.includes('redact')) { + const redact = item[1].get('redact')?.valueOf() + if (redact === true) { redactDids.add(did) } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 47fabe9593b..0e235e55695 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -258,6 +258,9 @@ importers: sharp: specifier: ^0.32.6 version: 0.32.6 + structured-headers: + specifier: ^1.0.1 + version: 1.0.1 typed-emitter: specifier: ^2.1.0 version: 2.1.0 @@ -11321,6 +11324,11 @@ packages: '@tokenizer/token': 0.3.0 peek-readable: 4.1.0 + /structured-headers@1.0.1: + resolution: {integrity: sha512-QYBxdBtA4Tl5rFPuqmbmdrS9kbtren74RTJTcs0VSQNVV5iRhJD4QlYTLD0+81SBwUQctjEQzjTRI3WG4DzICA==} + engines: {node: '>= 14', npm: '>=6'} + dev: false + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} From 77a2f18a37351e26afbb528fb9aec4e016c67093 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 12 Mar 2024 16:10:40 -0500 Subject: [PATCH 13/15] fix build err in hydrator --- packages/bsky/src/hydration/hydrator.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bsky/src/hydration/hydrator.ts b/packages/bsky/src/hydration/hydrator.ts index f032feb7383..d72c6477bad 100644 --- a/packages/bsky/src/hydration/hydrator.ts +++ b/packages/bsky/src/hydration/hydrator.ts @@ -305,7 +305,7 @@ export class Hydrator { ] = await Promise.all([ this.feed.getPostAggregates(refs), ctx.viewer ? this.feed.getPostViewerStates(refs, ctx.viewer) : undefined, - this.label.getLabelsForSubjects(allPostUris, ctx.labelers.dids), + this.label.getLabelsForSubjects(allPostUris, ctx.labelers), this.hydratePostBlocks(posts), this.hydrateProfiles(allPostUris.map(didFromUri), ctx), this.hydrateLists([...nestedListUris, ...gateListUris], ctx), @@ -501,7 +501,7 @@ export class Hydrator { this.feed.getLikes(likeUris), // reason: like this.feed.getReposts(repostUris), // reason: repost this.graph.getFollows(followUris), // reason: follow - this.label.getLabelsForSubjects(uris, ctx.labelers.dids), + this.label.getLabelsForSubjects(uris, ctx.labelers), this.hydrateProfiles(uris.map(didFromUri), ctx), ]) actionTakedownLabels(postUris, posts, labels) From 8ef4c3f5465e2388136e26af980e5ec78be862d3 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 12 Mar 2024 16:11:29 -0500 Subject: [PATCH 14/15] fix more build errs --- packages/bsky/src/api/com/atproto/admin/getAccountInfos.ts | 6 +++--- packages/bsky/src/views/index.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/bsky/src/api/com/atproto/admin/getAccountInfos.ts b/packages/bsky/src/api/com/atproto/admin/getAccountInfos.ts index 971839d5afd..c10b55d1048 100644 --- a/packages/bsky/src/api/com/atproto/admin/getAccountInfos.ts +++ b/packages/bsky/src/api/com/atproto/admin/getAccountInfos.ts @@ -8,16 +8,16 @@ export default function (server: Server, ctx: AppContext) { auth: ctx.authVerifier.optionalStandardOrRole, handler: async ({ params, auth }) => { const { dids } = params - const { canViewTakedowns } = ctx.authVerifier.parseCreds(auth) + const { includeTakedowns } = ctx.authVerifier.parseCreds(auth) const actors = await ctx.hydrator.actor.getActors(dids, true) const infos = mapDefined(dids, (did) => { const info = actors.get(did) if (!info) return - if (info.takedownRef && !canViewTakedowns) return + if (info.takedownRef && !includeTakedowns) return const profileRecord = - !info.profileTakedownRef || canViewTakedowns + !info.profileTakedownRef || includeTakedowns ? info.profile : undefined return { diff --git a/packages/bsky/src/views/index.ts b/packages/bsky/src/views/index.ts index c38c99c4d55..424fc810aea 100644 --- a/packages/bsky/src/views/index.ts +++ b/packages/bsky/src/views/index.ts @@ -225,7 +225,7 @@ export class Views { return undefined } const listViewer = state.listViewers?.get(uri) - const labels = state.labels?.get(uri) ?? [] + const labels = state.labels?.get(uri)?.labels ?? [] const creator = new AtUri(uri).hostname return { uri, @@ -351,7 +351,7 @@ export class Views { if (!creator) return const viewer = state.feedgenViewers?.get(uri) const aggs = state.feedgenAggs?.get(uri) - const labels = state.labels?.get(uri) ?? [] + const labels = state.labels?.get(uri)?.labels ?? [] return { uri, From e849278006c9686667b1d866470824548b08405b Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 12 Mar 2024 16:16:24 -0500 Subject: [PATCH 15/15] fix test --- packages/bsky/tests/label-hydration.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bsky/tests/label-hydration.test.ts b/packages/bsky/tests/label-hydration.test.ts index 58b5e192261..ec1fcb92c07 100644 --- a/packages/bsky/tests/label-hydration.test.ts +++ b/packages/bsky/tests/label-hydration.test.ts @@ -152,7 +152,7 @@ describe('label hydration', () => { val: opts.val, cts: new Date().toISOString(), neg: false, - src: opts.src ?? 'did:example:labeler', + src: opts.src ?? labelerDid, }) .execute() }