@atproto/bsky 0.0.249 → 0.0.251

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/api/age-assurance/const.d.ts.map +1 -1
  3. package/dist/api/age-assurance/const.js +19 -0
  4. package/dist/api/age-assurance/const.js.map +1 -1
  5. package/dist/api/age-assurance/kws/age-verified.d.ts +20 -0
  6. package/dist/api/age-assurance/kws/age-verified.d.ts.map +1 -1
  7. package/dist/api/age-assurance/kws/age-verified.js +6 -0
  8. package/dist/api/age-assurance/kws/age-verified.js.map +1 -1
  9. package/dist/api/app/bsky/feed/getFeed.d.ts.map +1 -1
  10. package/dist/api/app/bsky/feed/getFeed.js +4 -0
  11. package/dist/api/app/bsky/feed/getFeed.js.map +1 -1
  12. package/dist/api/app/bsky/feed/searchPosts.js +5 -0
  13. package/dist/api/app/bsky/feed/searchPosts.js.map +1 -1
  14. package/dist/api/app/bsky/feed/searchPostsV2.js +5 -0
  15. package/dist/api/app/bsky/feed/searchPostsV2.js.map +1 -1
  16. package/dist/api/app/bsky/graph/muteActor.d.ts.map +1 -1
  17. package/dist/api/app/bsky/graph/muteActor.js +3 -0
  18. package/dist/api/app/bsky/graph/muteActor.js.map +1 -1
  19. package/dist/api/kws/types.d.ts +33 -0
  20. package/dist/api/kws/types.d.ts.map +1 -1
  21. package/dist/api/kws/types.js +3 -0
  22. package/dist/api/kws/types.js.map +1 -1
  23. package/dist/config.d.ts +2 -0
  24. package/dist/config.d.ts.map +1 -1
  25. package/dist/config.js +5 -0
  26. package/dist/config.js.map +1 -1
  27. package/dist/data-plane/index.d.ts +0 -1
  28. package/dist/data-plane/index.d.ts.map +1 -1
  29. package/dist/data-plane/index.js +0 -1
  30. package/dist/data-plane/index.js.map +1 -1
  31. package/dist/data-plane/server/bsync-subscription.d.ts +47 -0
  32. package/dist/data-plane/server/bsync-subscription.d.ts.map +1 -0
  33. package/dist/data-plane/server/bsync-subscription.js +461 -0
  34. package/dist/data-plane/server/bsync-subscription.js.map +1 -0
  35. package/dist/data-plane/server/index.d.ts +1 -0
  36. package/dist/data-plane/server/index.d.ts.map +1 -1
  37. package/dist/data-plane/server/index.js +1 -0
  38. package/dist/data-plane/server/index.js.map +1 -1
  39. package/dist/hydration/feed.d.ts +1 -2
  40. package/dist/hydration/feed.d.ts.map +1 -1
  41. package/dist/hydration/feed.js.map +1 -1
  42. package/dist/lexicons/app/bsky/ageassurance/defs.defs.d.ts +4 -0
  43. package/dist/lexicons/app/bsky/ageassurance/defs.defs.d.ts.map +1 -1
  44. package/dist/lexicons/app/bsky/ageassurance/defs.defs.js +3 -0
  45. package/dist/lexicons/app/bsky/ageassurance/defs.defs.js.map +1 -1
  46. package/dist/lexicons/chat/bsky/convo/defs.defs.js +1 -1
  47. package/dist/lexicons/chat/bsky/convo/defs.defs.js.map +1 -1
  48. package/dist/lexicons/chat/bsky/group/editGroup.defs.d.ts +4 -4
  49. package/dist/lexicons/chat/bsky/group/editGroup.defs.js +2 -2
  50. package/dist/lexicons/chat/bsky/group/editGroup.defs.js.map +1 -1
  51. package/dist/lexicons/chat/bsky/moderation/defs.defs.js +1 -1
  52. package/dist/lexicons/chat/bsky/moderation/defs.defs.js.map +1 -1
  53. package/package.json +10 -10
  54. package/src/api/age-assurance/const.ts +19 -0
  55. package/src/api/age-assurance/kws/age-verified.test.ts +98 -0
  56. package/src/api/age-assurance/kws/age-verified.ts +6 -0
  57. package/src/api/app/bsky/feed/getFeed.ts +5 -0
  58. package/src/api/app/bsky/feed/searchPosts.ts +7 -0
  59. package/src/api/app/bsky/feed/searchPostsV2.ts +7 -0
  60. package/src/api/app/bsky/graph/muteActor.ts +3 -0
  61. package/src/api/kws/types.ts +11 -0
  62. package/src/api/kws/util.test.ts +50 -0
  63. package/src/config.ts +9 -0
  64. package/src/data-plane/index.ts +0 -1
  65. package/src/data-plane/{bsync/index.ts → server/bsync-subscription.ts} +245 -121
  66. package/src/data-plane/server/index.ts +1 -0
  67. package/src/hydration/feed.ts +1 -2
  68. package/tests/__snapshots__/feed-generation.test.ts.snap +62 -15
  69. package/tests/data-plane/thread-mutes.test.ts +2 -0
  70. package/tests/feed-generation.test.ts +55 -9
  71. package/tests/stash.test.ts +6 -1
  72. package/tests/views/age-assurance-v2.test.ts +3 -0
  73. package/tests/views/age-assurance.test.ts +8 -0
  74. package/tests/views/bookmarks.test.ts +19 -0
  75. package/tests/views/drafts.test.ts +25 -2
  76. package/tests/views/mute-lists.test.ts +2 -0
  77. package/tests/views/mutes.test.ts +1 -0
  78. package/tests/views/notifications.test.ts +16 -0
  79. package/tests/views/post-search.test.ts +81 -0
  80. package/tsconfig.build.tsbuildinfo +1 -1
  81. package/dist/data-plane/bsync/index.d.ts +0 -11
  82. package/dist/data-plane/bsync/index.d.ts.map +0 -1
  83. package/dist/data-plane/bsync/index.js +0 -376
  84. package/dist/data-plane/bsync/index.js.map +0 -1
@@ -0,0 +1,98 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ parseKWSAgeVerifiedStatus,
4
+ serializeKWSAgeVerifiedStatus,
5
+ } from './age-verified.js'
6
+
7
+ describe('parseKWSAgeVerifiedStatus', () => {
8
+ it('parses a minimal status object', () => {
9
+ const raw = JSON.stringify({ verified: true, verifiedMinimumAge: 18 })
10
+ expect(parseKWSAgeVerifiedStatus(raw)).toEqual({
11
+ verified: true,
12
+ verifiedMinimumAge: 18,
13
+ })
14
+ })
15
+
16
+ it('parses the optional transactionId and timestamp fields', () => {
17
+ const raw = JSON.stringify({
18
+ verified: true,
19
+ verifiedMinimumAge: 18,
20
+ transactionId: 'txn-123',
21
+ timestamp: 1750680000,
22
+ })
23
+ expect(parseKWSAgeVerifiedStatus(raw)).toEqual({
24
+ verified: true,
25
+ verifiedMinimumAge: 18,
26
+ transactionId: 'txn-123',
27
+ timestamp: 1750680000,
28
+ })
29
+ })
30
+
31
+ it('tolerates the new timestamp field without transactionId', () => {
32
+ const raw = JSON.stringify({
33
+ verified: true,
34
+ verifiedMinimumAge: 18,
35
+ timestamp: 1750680000,
36
+ })
37
+ expect(parseKWSAgeVerifiedStatus(raw)).toEqual({
38
+ verified: true,
39
+ verifiedMinimumAge: 18,
40
+ timestamp: 1750680000,
41
+ })
42
+ })
43
+
44
+ it('silently strips unknown fields KWS may add', () => {
45
+ const raw = JSON.stringify({
46
+ verified: true,
47
+ verifiedMinimumAge: 18,
48
+ timestamp: 1750680000,
49
+ errorCode: null,
50
+ somethingNew: 'ignored',
51
+ })
52
+ expect(parseKWSAgeVerifiedStatus(raw)).toEqual({
53
+ verified: true,
54
+ verifiedMinimumAge: 18,
55
+ timestamp: 1750680000,
56
+ errorCode: null,
57
+ })
58
+ })
59
+
60
+ it('strips a truly unknown field', () => {
61
+ const raw = JSON.stringify({
62
+ verified: true,
63
+ verifiedMinimumAge: 18,
64
+ somethingNew: 'ignored',
65
+ })
66
+ expect(parseKWSAgeVerifiedStatus(raw)).toEqual({
67
+ verified: true,
68
+ verifiedMinimumAge: 18,
69
+ })
70
+ })
71
+
72
+ it('throws on malformed JSON', () => {
73
+ expect(() => parseKWSAgeVerifiedStatus('not json')).toThrow(
74
+ /Invalid KWS age-verified status/,
75
+ )
76
+ })
77
+
78
+ it('throws when a required field is missing', () => {
79
+ const raw = JSON.stringify({ verified: true })
80
+ expect(() => parseKWSAgeVerifiedStatus(raw)).toThrow(
81
+ /Invalid KWS age-verified status/,
82
+ )
83
+ })
84
+ })
85
+
86
+ describe('serializeKWSAgeVerifiedStatus', () => {
87
+ it('round-trips a status object including timestamp', () => {
88
+ const status = {
89
+ verified: true,
90
+ verifiedMinimumAge: 18,
91
+ transactionId: 'txn-123',
92
+ timestamp: 1750680000,
93
+ }
94
+ expect(
95
+ parseKWSAgeVerifiedStatus(serializeKWSAgeVerifiedStatus(status)),
96
+ ).toEqual(status)
97
+ })
98
+ })
@@ -7,6 +7,12 @@ export const KWSAgeVerifiedStatusSchema = z.object({
7
7
  verified: z.boolean(),
8
8
  verifiedMinimumAge: z.number(),
9
9
  transactionId: z.string().optional(),
10
+ // A code indicating the reason for failure, or null when verification
11
+ // succeeded.
12
+ errorCode: z.string().nullable().optional(),
13
+ // Epoch seconds indicating when KWS generated the payload. Optional so we
14
+ // remain compatible with payloads sent before this field was introduced.
15
+ timestamp: z.number().optional(),
10
16
  })
11
17
 
12
18
  /**
@@ -251,6 +251,11 @@ const skeletonFromFeedGen = async (
251
251
  app.bsky.feed.defs.skeletonReasonRepost.$isTypeOf(item.reason)
252
252
  ? { uri: item.reason.repost }
253
253
  : undefined,
254
+ authorPinned:
255
+ item.reason != null &&
256
+ app.bsky.feed.defs.skeletonReasonPin.$isTypeOf(item.reason)
257
+ ? true
258
+ : undefined,
254
259
  feedContext: item.feedContext,
255
260
  }))
256
261
 
@@ -196,6 +196,13 @@ const noBlocksOrTagged = (inputs: RulesFnInput<Context, Params, Skeleton>) => {
196
196
  // Cases to never show.
197
197
  if (ctx.views.viewerBlockExists(creator, hydration)) return false
198
198
 
199
+ // Tags that are hidden from all search surfaces (Top and Latest),
200
+ // regardless of curation or author filtering.
201
+ const alwaysHidden = [...ctx.cfg.searchTagsHideAll].some((t) =>
202
+ post.tags.has(t),
203
+ )
204
+ if (alwaysHidden) return false
205
+
199
206
  let tagged = false
200
207
  if (
201
208
  params.hydrateCtx.features?.checkGate(
@@ -164,6 +164,13 @@ const noBlocksOrTagged = (inputs: RulesFnInput<Context, Params, Skeleton>) => {
164
164
 
165
165
  if (ctx.views.viewerBlockExists(creator, hydration)) return false
166
166
 
167
+ // Tags that are hidden from all search surfaces (Top and Latest),
168
+ // regardless of curation or author filtering.
169
+ const alwaysHidden = [...ctx.cfg.searchTagsHideAll].some((t) =>
170
+ post.tags.has(t),
171
+ )
172
+ if (alwaysHidden) return false
173
+
167
174
  const tagged = [...ctx.cfg.searchTagsHide].some((t) => post.tags.has(t))
168
175
 
169
176
  if (isCuratedSearch && tagged) return false
@@ -11,6 +11,9 @@ export default function (server: Server, ctx: AppContext) {
11
11
  const requester = auth.credentials.iss
12
12
  const [did] = await ctx.hydrator.actor.getDids([actor])
13
13
  if (!did) throw new InvalidRequestError('Actor not found')
14
+ if (requester === did) {
15
+ throw new InvalidRequestError('Actor cannot mute themselves')
16
+ }
14
17
  await ctx.bsyncClient.addMuteOperation({
15
18
  type: MuteOperation_Type.ADD,
16
19
  actorDid: requester,
@@ -25,6 +25,14 @@ export const externalPayloadSchema = z
25
25
 
26
26
  export type KwsStatus = {
27
27
  verified: boolean
28
+ // A unique reference ID for the verification transaction.
29
+ transactionId?: string
30
+ // A code indicating the reason for failure, or null when verification
31
+ // succeeded.
32
+ errorCode?: string | null
33
+ // Epoch seconds indicating when KWS generated the payload. Optional so we
34
+ // remain compatible with payloads sent before this field was introduced.
35
+ timestamp?: number
28
36
  }
29
37
 
30
38
  export type KwsVerificationIntermediateQuery = {
@@ -56,6 +64,9 @@ export type KwsWebhookBody = {
56
64
  // Not `.strict()` to avoid breaking if KWS adds fields.
57
65
  export const statusSchema = z.object({
58
66
  verified: z.boolean(),
67
+ transactionId: z.string().optional(),
68
+ errorCode: z.string().nullable().optional(),
69
+ timestamp: z.number().optional(),
59
70
  })
60
71
 
61
72
  // Not `.strict()` to avoid breaking if KWS adds fields.
@@ -0,0 +1,50 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { parseStatus } from './util.js'
3
+
4
+ describe('parseStatus', () => {
5
+ it('parses a minimal status object', () => {
6
+ const raw = JSON.stringify({ verified: true })
7
+ expect(parseStatus(raw)).toEqual({ verified: true })
8
+ })
9
+
10
+ it('parses the optional transactionId, errorCode, and timestamp fields', () => {
11
+ const raw = JSON.stringify({
12
+ verified: true,
13
+ transactionId: 'txn-123',
14
+ errorCode: null,
15
+ timestamp: 1750680000,
16
+ })
17
+ expect(parseStatus(raw)).toEqual({
18
+ verified: true,
19
+ transactionId: 'txn-123',
20
+ errorCode: null,
21
+ timestamp: 1750680000,
22
+ })
23
+ })
24
+
25
+ it('parses a non-null errorCode', () => {
26
+ const raw = JSON.stringify({ verified: false, errorCode: 'some-failure' })
27
+ expect(parseStatus(raw)).toEqual({
28
+ verified: false,
29
+ errorCode: 'some-failure',
30
+ })
31
+ })
32
+
33
+ it('silently strips unknown fields KWS may add', () => {
34
+ const raw = JSON.stringify({
35
+ verified: true,
36
+ timestamp: 1750680000,
37
+ somethingNew: 'ignored',
38
+ })
39
+ expect(parseStatus(raw)).toEqual({ verified: true, timestamp: 1750680000 })
40
+ })
41
+
42
+ it('throws on malformed JSON', () => {
43
+ expect(() => parseStatus('not json')).toThrow(/Invalid status/)
44
+ })
45
+
46
+ it('throws when verified is missing', () => {
47
+ const raw = JSON.stringify({ timestamp: 1750680000 })
48
+ expect(() => parseStatus(raw)).toThrow(/Invalid status/)
49
+ })
50
+ })
package/src/config.ts CHANGED
@@ -63,6 +63,7 @@ export interface ServerConfigValues {
63
63
  rolodexIgnoreBadTls?: boolean
64
64
  searchUrl?: string
65
65
  searchTagsHide: Set<string>
66
+ searchTagsHideAll: Set<string>
66
67
  suggestionsUrl?: string
67
68
  suggestionsApiKey?: string
68
69
  topicsUrl?: string
@@ -163,6 +164,9 @@ export class ServerConfig {
163
164
  process.env.BSKY_SEARCH_ENDPOINT ||
164
165
  undefined
165
166
  const searchTagsHide = new Set(envList(process.env.BSKY_SEARCH_TAGS_HIDE))
167
+ const searchTagsHideAll = new Set(
168
+ envList(process.env.BSKY_SEARCH_TAGS_HIDE_ALL),
169
+ )
166
170
  const suggestionsUrl = process.env.BSKY_SUGGESTIONS_URL || undefined
167
171
  const suggestionsApiKey = process.env.BSKY_SUGGESTIONS_API_KEY || undefined
168
172
  const topicsUrl = process.env.BSKY_TOPICS_URL || undefined
@@ -354,6 +358,7 @@ export class ServerConfig {
354
358
  dataplaneIgnoreBadTls,
355
359
  searchUrl,
356
360
  searchTagsHide,
361
+ searchTagsHideAll,
357
362
  suggestionsUrl,
358
363
  suggestionsApiKey,
359
364
  topicsUrl,
@@ -524,6 +529,10 @@ export class ServerConfig {
524
529
  return this.cfg.searchTagsHide
525
530
  }
526
531
 
532
+ get searchTagsHideAll() {
533
+ return this.cfg.searchTagsHideAll
534
+ }
535
+
527
536
  get suggestionsUrl() {
528
537
  return this.cfg.suggestionsUrl
529
538
  }
@@ -1,3 +1,2 @@
1
1
  export * from './server/index.js'
2
2
  export * from './client/index.js'
3
- export * from './bsync/index.js'