Skip to content

Commit

Permalink
Appview v1 handling clearly bad cursors (#2088)
Browse files Browse the repository at this point in the history
* fail open on clearly bad appview cursors

* tidy tests
  • Loading branch information
devinivy authored Jan 26, 2024
1 parent 61bf9fb commit d03ddd0
Show file tree
Hide file tree
Showing 26 changed files with 110 additions and 3 deletions.
2 changes: 1 addition & 1 deletion packages/bsky/src/api/app/bsky/actor/getSuggestions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const skeleton = async (
): Promise<SkeletonState> => {
const { db } = ctx
const { viewer } = params
const alreadyIncluded = parseCursor(params.cursor)
const alreadyIncluded = parseCursor(params.cursor) // @NOTE handles bad cursor e.g. on appview swap
const { ref } = db.db.dynamic
const suggestions = await db.db
.selectFrom('suggested_follow')
Expand Down
1 change: 1 addition & 0 deletions packages/bsky/src/api/app/bsky/actor/searchActors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default function (server: Server, ctx: AppContext) {
let results: string[]
let resCursor: string | undefined
if (ctx.searchAgent) {
// @NOTE cursors wont change on appview swap
const res =
await ctx.searchAgent.api.app.bsky.unspecced.searchActorsSkeleton({
q: query,
Expand Down
6 changes: 6 additions & 0 deletions packages/bsky/src/api/app/bsky/feed/getActorFeeds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ export default function (server: Server, ctx: AppContext) {
handler: async ({ auth, params }) => {
const { actor, limit, cursor } = params
const viewer = auth.credentials.iss
if (TimeCidKeyset.clearlyBad(cursor)) {
return {
encoding: 'application/json',
body: { feeds: [] },
}
}

const db = ctx.db.getReplica()
const actorService = ctx.services.actor(db)
Expand Down
4 changes: 4 additions & 0 deletions packages/bsky/src/api/app/bsky/feed/getActorLikes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ const skeleton = async (
throw new InvalidRequestError('Profile not found')
}

if (FeedKeyset.clearlyBad(cursor)) {
return { params, feedItems: [] }
}

let feedItemsQb = feedService
.selectFeedItemQb()
.innerJoin('like', 'like.subject', 'feed_item.uri')
Expand Down
4 changes: 4 additions & 0 deletions packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ export const skeleton = async (
}
}

if (FeedKeyset.clearlyBad(cursor)) {
return { params, feedItems: [] }
}

// defaults to posts, reposts, and replies
let feedItemsQb = feedService
.selectFeedItemQb()
Expand Down
1 change: 1 addition & 0 deletions packages/bsky/src/api/app/bsky/feed/getFeed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export default function (server: Server, ctx: AppContext) {
authorization: req.headers['authorization'],
'accept-language': req.headers['accept-language'],
})
// @NOTE feed cursors should not be affected by appview swap
const { timerSkele, timerHydr, resHeaders, ...result } = await getFeed(
{ ...params, viewer },
{
Expand Down
4 changes: 4 additions & 0 deletions packages/bsky/src/api/app/bsky/feed/getLikes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ const skeleton = async (
const { uri, cid, limit, cursor } = params
const { ref } = db.db.dynamic

if (TimeCidKeyset.clearlyBad(cursor)) {
return { params, likes: [] }
}

let builder = db.db
.selectFrom('like')
.where('like.subject', '=', uri)
Expand Down
4 changes: 4 additions & 0 deletions packages/bsky/src/api/app/bsky/feed/getListFeed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ export const skeleton = async (
const { db } = ctx
const { ref } = db.db.dynamic

if (FeedKeyset.clearlyBad(cursor)) {
return { params, feedItems: [] }
}

const keyset = new FeedKeyset(ref('post.sortAt'), ref('post.cid'))
const sortFrom = keyset.unpack(cursor)?.primary

Expand Down
4 changes: 4 additions & 0 deletions packages/bsky/src/api/app/bsky/feed/getRepostedBy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ const skeleton = async (
const { limit, cursor, uri, cid } = params
const { ref } = db.db.dynamic

if (TimeCidKeyset.clearlyBad(cursor)) {
return { params, repostedBy: [] }
}

let builder = db.db
.selectFrom('repost')
.where('repost.subject', '=', uri)
Expand Down
2 changes: 1 addition & 1 deletion packages/bsky/src/api/app/bsky/feed/getSuggestedFeeds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ export default function (server: Server, ctx: AppContext) {
server.app.bsky.feed.getSuggestedFeeds({
auth: ctx.authVerifier.standardOptional,
handler: async ({ auth }) => {
// @NOTE ignores cursor, doesn't matter for appview swap
const viewer = auth.credentials.iss

const db = ctx.db.getReplica()
const feedService = ctx.services.feed(db)
const actorService = ctx.services.actor(db)
Expand Down
4 changes: 4 additions & 0 deletions packages/bsky/src/api/app/bsky/feed/getTimeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ export const skeleton = async (
return skeletonLimit1(params, ctx)
}

if (FeedKeyset.clearlyBad(cursor)) {
return { params, feedItems: [] }
}

const keyset = new FeedKeyset(ref('feed_item.sortAt'), ref('feed_item.cid'))
const sortFrom = keyset.unpack(cursor)?.primary

Expand Down
1 change: 1 addition & 0 deletions packages/bsky/src/api/app/bsky/feed/searchPosts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const skeleton = async (
params: Params,
ctx: Context,
): Promise<SkeletonState> => {
// @NOTE cursors wont change on appview swap
const res = await ctx.searchAgent.api.app.bsky.unspecced.searchPostsSkeleton({
q: params.q,
cursor: params.cursor,
Expand Down
7 changes: 7 additions & 0 deletions packages/bsky/src/api/app/bsky/graph/getBlocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ export default function (server: Server, ctx: AppContext) {
handler: async ({ params, auth }) => {
const { limit, cursor } = params
const requester = auth.credentials.iss
if (TimeCidKeyset.clearlyBad(cursor)) {
return {
encoding: 'application/json',
body: { blocks: [] },
}
}

const db = ctx.db.getReplica()
const { ref } = db.db.dynamic

Expand Down
4 changes: 4 additions & 0 deletions packages/bsky/src/api/app/bsky/graph/getFollowers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ const skeleton = async (
throw new InvalidRequestError(`Actor not found: ${actor}`)
}

if (TimeCidKeyset.clearlyBad(cursor)) {
return { params, followers: [], subject }
}

let followersReq = db.db
.selectFrom('follow')
.where('follow.subjectDid', '=', subject.did)
Expand Down
4 changes: 4 additions & 0 deletions packages/bsky/src/api/app/bsky/graph/getFollows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ const skeleton = async (
throw new InvalidRequestError(`Actor not found: ${actor}`)
}

if (TimeCidKeyset.clearlyBad(cursor)) {
return { params, follows: [], creator }
}

let followsReq = db.db
.selectFrom('follow')
.where('follow.creator', '=', creator.did)
Expand Down
4 changes: 4 additions & 0 deletions packages/bsky/src/api/app/bsky/graph/getList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ const skeleton = async (
throw new InvalidRequestError(`List not found: ${list}`)
}

if (TimeCidKeyset.clearlyBad(cursor)) {
return { params, list: listRes, listItems: [] }
}

let itemsReq = graphService
.getListItemsQb()
.where('list_item.listUri', '=', list)
Expand Down
4 changes: 4 additions & 0 deletions packages/bsky/src/api/app/bsky/graph/getListBlocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ const skeleton = async (
const { limit, cursor, viewer } = params
const { ref } = db.db.dynamic

if (TimeCidKeyset.clearlyBad(cursor)) {
return { params, listInfos: [] }
}

let listsReq = graphService
.getListsQb(viewer)
.whereExists(
Expand Down
7 changes: 7 additions & 0 deletions packages/bsky/src/api/app/bsky/graph/getListMutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ export default function (server: Server, ctx: AppContext) {
handler: async ({ params, auth }) => {
const { limit, cursor } = params
const requester = auth.credentials.iss
if (TimeCidKeyset.clearlyBad(cursor)) {
return {
encoding: 'application/json',
body: { lists: [] },
}
}

const db = ctx.db.getReplica()
const { ref } = db.db.dynamic

Expand Down
7 changes: 7 additions & 0 deletions packages/bsky/src/api/app/bsky/graph/getLists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ export default function (server: Server, ctx: AppContext) {
handler: async ({ params, auth }) => {
const { actor, limit, cursor } = params
const requester = auth.credentials.iss
if (TimeCidKeyset.clearlyBad(cursor)) {
return {
encoding: 'application/json',
body: { lists: [] },
}
}

const db = ctx.db.getReplica()
const { ref } = db.db.dynamic

Expand Down
7 changes: 7 additions & 0 deletions packages/bsky/src/api/app/bsky/graph/getMutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ export default function (server: Server, ctx: AppContext) {
handler: async ({ params, auth }) => {
const { limit, cursor } = params
const requester = auth.credentials.iss
if (TimeCidKeyset.clearlyBad(cursor)) {
return {
encoding: 'application/json',
body: { mutes: [] },
}
}

const db = ctx.db.getReplica()
const { ref } = db.db.dynamic

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ const skeleton = async (
if (params.seenAt) {
throw new InvalidRequestError('The seenAt parameter is unsupported')
}
if (NotifsKeyset.clearlyBad(cursor)) {
return { params, notifs: [] }
}
let notifBuilder = db.db
.selectFrom('notification as notif')
.where('notif.did', '=', viewer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,17 @@ export default function (server: Server, ctx: AppContext) {
handler: async ({ auth, params }) => {
const { limit, cursor, query } = params
const requester = auth.credentials.iss
if (LikeCountKeyset.clearlyBad(cursor)) {
return {
encoding: 'application/json',
body: { feeds: [] },
}
}

const db = ctx.db.getReplica()
const { ref } = db.db.dynamic
const feedService = ctx.services.feed(db)
const actorService = ctx.services.actor(db)

let inner = db.db
.selectFrom('feed_generator')
.select([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default function (server: Server, ctx: AppContext) {
const feedService = ctx.services.feed(db)
const viewer = auth.credentials.iss

// @NOTE bad cursor during appview swap handled within skeleton()
const result = await skeleton({ ...params, viewer }, { db, feedService })

return {
Expand Down
3 changes: 3 additions & 0 deletions packages/bsky/src/db/pagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export abstract class GenericKeyset<R, LR extends LabeledResult> {
abstract labelResult(result: R): LR
abstract labeledResultToCursor(labeled: LR): Cursor
abstract cursorToLabeledResult(cursor: Cursor): LR
static clearlyBad(cursor?: string) {
return cursor !== undefined && !cursor.includes('::')
}
packFromResult(results: R | R[]): string | undefined {
const result = Array.isArray(results) ? results.at(-1) : results
if (!result) return
Expand Down
9 changes: 9 additions & 0 deletions packages/bsky/tests/views/notifications.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,4 +296,13 @@ describe('notification views', () => {
),
)
})

it('fails open on clearly bad cursor.', async () => {
const { data: notifs } =
await agent.api.app.bsky.notification.listNotifications(
{ cursor: 'bad' },
{ headers: await network.serviceHeaders(alice) },
)
expect(notifs).toEqual({ notifications: [] })
})
})
8 changes: 8 additions & 0 deletions packages/bsky/tests/views/timeline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,4 +298,12 @@ describe('timeline views', () => {
),
)
})

it('fails open on clearly bad cursor.', async () => {
const { data: timeline } = await agent.api.app.bsky.feed.getTimeline(
{ cursor: 'bad' },
{ headers: await network.serviceHeaders(alice) },
)
expect(timeline).toEqual({ feed: [] })
})
})

0 comments on commit d03ddd0

Please sign in to comment.