@atproto/bsky 0.3.13 → 0.4.0

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 (45) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/api/app/bsky/notification/getGroupedNotifications.d.ts.map +1 -1
  3. package/dist/api/app/bsky/notification/getGroupedNotifications.js +14 -3
  4. package/dist/api/app/bsky/notification/getGroupedNotifications.js.map +1 -1
  5. package/dist/api/blob-resolver.d.ts.map +1 -1
  6. package/dist/api/blob-resolver.js +9 -8
  7. package/dist/api/blob-resolver.js.map +1 -1
  8. package/dist/config.d.ts +2 -2
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/config.js +7 -6
  11. package/dist/config.js.map +1 -1
  12. package/dist/hydration/external.d.ts +52 -45
  13. package/dist/hydration/external.d.ts.map +1 -1
  14. package/dist/hydration/external.js +132 -114
  15. package/dist/hydration/external.js.map +1 -1
  16. package/dist/hydration/hydrator.d.ts +10 -8
  17. package/dist/hydration/hydrator.d.ts.map +1 -1
  18. package/dist/hydration/hydrator.js +82 -189
  19. package/dist/hydration/hydrator.js.map +1 -1
  20. package/dist/image/server.d.ts.map +1 -1
  21. package/dist/image/server.js +12 -1
  22. package/dist/image/server.js.map +1 -1
  23. package/dist/lexicons/tools/ozone/moderation/emitEvent.defs.d.ts +2 -2
  24. package/dist/lexicons/tools/ozone/moderation/emitEvent.defs.js.map +1 -1
  25. package/dist/lexicons/tools/ozone/report/closeReports.defs.d.ts +1 -1
  26. package/dist/lexicons/tools/ozone/report/closeReports.defs.d.ts.map +1 -1
  27. package/dist/lexicons/tools/ozone/report/closeReports.defs.js +1 -1
  28. package/dist/lexicons/tools/ozone/report/closeReports.defs.js.map +1 -1
  29. package/dist/telemetry/events.d.ts +11 -4
  30. package/dist/telemetry/events.d.ts.map +1 -1
  31. package/dist/telemetry/events.js +29 -5
  32. package/dist/telemetry/events.js.map +1 -1
  33. package/dist/util/external/standard-site.d.ts +2 -3
  34. package/dist/util/external/standard-site.d.ts.map +1 -1
  35. package/dist/util/external/standard-site.js +2 -3
  36. package/dist/util/external/standard-site.js.map +1 -1
  37. package/dist/util/uris.d.ts +5 -2
  38. package/dist/util/uris.d.ts.map +1 -1
  39. package/dist/util/uris.js +14 -1
  40. package/dist/util/uris.js.map +1 -1
  41. package/dist/views/index.d.ts +2 -2
  42. package/dist/views/index.d.ts.map +1 -1
  43. package/dist/views/index.js +17 -11
  44. package/dist/views/index.js.map +1 -1
  45. package/package.json +11 -12
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # @atproto/bsky
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#5748](https://github.com/bluesky-social/atproto/pull/5748) [`87faedd`](https://github.com/bluesky-social/atproto/commit/87faeddd9cc708e520e3c9e1d2c0486e84a1b931) Thanks [@rafaeleyng](https://github.com/rafaeleyng)! - Group new-post notifications across accounts in All while keeping individual posts separate in Activity.
8
+
9
+ Add a notification grouping compute-time histogram with sub-millisecond buckets and an algorithm label.
10
+
11
+ Track the number of dataplane notification fetches per grouped notifications response.
12
+
13
+ Rename `notificationsV2MaxReads` to `notificationsV2MaxFetches` and `BSKY_NOTIFICATIONS_V2_MAX_READS` to `BSKY_NOTIFICATIONS_V2_MAX_FETCHES`.
14
+
15
+ ### Patch Changes
16
+
17
+ - [#5753](https://github.com/bluesky-social/atproto/pull/5753) [`3bfd00f`](https://github.com/bluesky-social/atproto/commit/3bfd00f484dc144482c82a4ed248adc49e6be769) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Bound the decoded size of upstream blobs in the blob resolver and image server
18
+
19
+ - [#5756](https://github.com/bluesky-social/atproto/pull/5756) [`54f54ea`](https://github.com/bluesky-social/atproto/commit/54f54ea831f37fe9f722844177f7e1bed8ae8bdc) Thanks [@gcwill70](https://github.com/gcwill70)! - Protect assigned and escalated reports from broad moderation-event and batch report closure, including owned subjects.
20
+
21
+ - [#5752](https://github.com/bluesky-social/atproto/pull/5752) [`78b3e20`](https://github.com/bluesky-social/atproto/commit/78b3e206286a3a02ef6693f2a66b4fa234532fbd) Thanks [@estrattonbailey](https://github.com/estrattonbailey)! - Hydrate post external embeds through the generic pipeline with one pinned root and legacy Standard.site compatibility. Bound dependency expansion and backlink-count requests; feed hydration retains content and engagement counts without fetching social previews.
22
+
23
+ - [#5746](https://github.com/bluesky-social/atproto/pull/5746) [`1c8c3a3`](https://github.com/bluesky-social/atproto/commit/1c8c3a3a8ea27a060f38cda71172339aa1728113) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Update to OpenTelemetry JS SDK 3.x (`@opentelemetry/api` 1.10), which requires Node.js `>=22.15.0`. Event log records are now emitted through `@opentelemetry/api` (`@opentelemetry/api-logs` was removed upstream). The HTTP instrumentation only reports the stable `http.server.request.duration` and `http.client.request.duration` metrics: the legacy `http.server.duration` and `http.client.duration` metrics are gone, and `OTEL_SEMCONV_STABILITY_OPT_IN=http` no longer has any effect. Instrumentations passed to `setup()` now also instrument modules loaded through ESM imports, not only `require()`d ones, when built on the same `@opentelemetry/instrumentation` as `@atproto-labs/opentelemetry-node`; otherwise `setup()` now throws an `InstrumentationMismatchError` instead of silently missing them. The new `@atproto-labs/opentelemetry-node/better-sqlite3-plugin` export, a port of `opentelemetry-plugin-better-sqlite3` producing identical spans, replaces that package in the PDS.
24
+
25
+ - [#5751](https://github.com/bluesky-social/atproto/pull/5751) [`b33e55b`](https://github.com/bluesky-social/atproto/commit/b33e55b4aa5e4b3b031a2830856300350d9ef798) Thanks [@estrattonbailey](https://github.com/estrattonbailey)! - Report external hydration traversals that inspect prehydrated roots without scheduling fetch batches.
26
+ - Updated dependencies [[`3bfd00f`](https://github.com/bluesky-social/atproto/commit/3bfd00f484dc144482c82a4ed248adc49e6be769), [`54f54ea`](https://github.com/bluesky-social/atproto/commit/54f54ea831f37fe9f722844177f7e1bed8ae8bdc), [`3bfd00f`](https://github.com/bluesky-social/atproto/commit/3bfd00f484dc144482c82a4ed248adc49e6be769), [`1c8c3a3`](https://github.com/bluesky-social/atproto/commit/1c8c3a3a8ea27a060f38cda71172339aa1728113), [`1c8c3a3`](https://github.com/bluesky-social/atproto/commit/1c8c3a3a8ea27a060f38cda71172339aa1728113), [`1c8c3a3`](https://github.com/bluesky-social/atproto/commit/1c8c3a3a8ea27a060f38cda71172339aa1728113), [`3bfd00f`](https://github.com/bluesky-social/atproto/commit/3bfd00f484dc144482c82a4ed248adc49e6be769), [`3bfd00f`](https://github.com/bluesky-social/atproto/commit/3bfd00f484dc144482c82a4ed248adc49e6be769), [`3bfd00f`](https://github.com/bluesky-social/atproto/commit/3bfd00f484dc144482c82a4ed248adc49e6be769)]:
27
+ - @atproto/common@0.8.7
28
+ - @atproto/api@0.29.6
29
+ - @atproto-labs/opentelemetry-node@0.5.0
30
+ - @atproto/xrpc-server@0.13.7
31
+ - @atproto/repo@0.11.2
32
+ - @atproto/sync@0.4.20
33
+ - @atproto-labs/xrpc-utils@0.1.28
34
+ - @atproto/lex@0.4.4
35
+
3
36
  ## 0.3.13
4
37
 
5
38
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"getGroupedNotifications.d.ts","sourceRoot":"","sources":["../../../../../src/api/app/bsky/notification/getGroupedNotifications.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAElD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAuCxD,eAAO,MAAM,eAAe,iBACZ,MAAM,eACP,MAAM,gBACL,MAAM,eACP,MAAM,eACN,MAAM,KAClB,MAoBF,CAAA;AAED,MAAM,CAAC,OAAO,WAAW,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,QAqCvD"}
1
+ {"version":3,"file":"getGroupedNotifications.d.ts","sourceRoot":"","sources":["../../../../../src/api/app/bsky/notification/getGroupedNotifications.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAElD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAuCxD,eAAO,MAAM,eAAe,iBACZ,MAAM,eACP,MAAM,gBACL,MAAM,eACP,MAAM,eACN,MAAM,KAClB,MAoBF,CAAA;AAED,MAAM,CAAC,OAAO,WAAW,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,QAqCvD"}
@@ -1,4 +1,5 @@
1
1
  import { createHash } from 'node:crypto';
2
+ import { performance } from 'node:perf_hooks';
2
3
  import { dedupeStrs, mapDefined } from '@atproto/common';
3
4
  import { asDatetimeString, } from '@atproto/lex';
4
5
  import { AtUri } from '@atproto/syntax';
@@ -73,14 +74,16 @@ const skeleton = async (input) => {
73
74
  let groups = [];
74
75
  let nextCursor;
75
76
  let itemsFetched = 0;
77
+ let dataplaneFetches = 0;
76
78
  let rawLimit = ctx.cfg.notificationsV2MinRawLimit;
77
- for (let read = 0; read < ctx.cfg.notificationsV2MaxReads; read++) {
79
+ for (let fetches = 0; fetches < ctx.cfg.notificationsV2MaxFetches; fetches++) {
78
80
  const response = await ctx.hydrator.dataplane.getNotificationsV2({
79
81
  actorDid: params.hydrateCtx.viewer,
80
82
  feed,
81
83
  cursor,
82
84
  limit: rawLimit,
83
85
  });
86
+ dataplaneFetches++;
84
87
  itemsFetched += response.notifications.length;
85
88
  for (const notification of response.notifications) {
86
89
  const notif = parseRawNotification(notification);
@@ -98,7 +101,7 @@ const skeleton = async (input) => {
98
101
  groupingKey = NOTIFICATION_REASON.FOLLOW;
99
102
  }
100
103
  if (notif.reason === NOTIFICATION_REASON.SUBSCRIBED_POST) {
101
- groupingKey = actorDid;
104
+ groupingKey = NOTIFICATION_REASON.SUBSCRIBED_POST;
102
105
  }
103
106
  items.push({
104
107
  raw: notif,
@@ -107,7 +110,13 @@ const skeleton = async (input) => {
107
110
  actorDid,
108
111
  });
109
112
  }
110
- const groupingResult = buildGroups(items, params.limit, now, seenAt, ctx.cfg.notificationsV2Algorithm, feed);
113
+ const algorithm = ctx.cfg.notificationsV2Algorithm;
114
+ const groupingStart = performance.now();
115
+ const groupingResult = buildGroups(items, params.limit, now, seenAt, algorithm, feed);
116
+ events.notificationGrouping({
117
+ algorithm,
118
+ durationMs: performance.now() - groupingStart,
119
+ });
111
120
  groups = groupingResult.groups;
112
121
  // After fetching a page from the dataplane and attempting to group, 2 situations can happen:
113
122
  // 1. The first unconsumed notification would create an additional group beyond params.limit.
@@ -128,6 +137,7 @@ const skeleton = async (input) => {
128
137
  return {
129
138
  ...buildSpotlight(items, groups, nextCursor, params.limit, seenAt),
130
139
  itemsFetched,
140
+ dataplaneFetches,
131
141
  };
132
142
  };
133
143
  const hydration = async (input) => {
@@ -348,6 +358,7 @@ const presentation = (input) => {
348
358
  events.groupedNotifications({
349
359
  itemsFetched: skeleton.itemsFetched,
350
360
  itemsReturned: groups.reduce((count, group) => count + group.count, 0),
361
+ dataplaneFetches: skeleton.dataplaneFetches,
351
362
  });
352
363
  return body;
353
364
  };
@@ -1 +1 @@
1
- {"version":3,"file":"getGroupedNotifications.js","sourceRoot":"","sources":["../../../../../src/api/app/bsky/notification/getGroupedNotifications.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto'\nimport { dedupeStrs, mapDefined } from '@atproto/common'\nimport {\n type AtUriString,\n type DatetimeString,\n type DidString,\n asDatetimeString,\n} from '@atproto/lex'\nimport { AtUri } from '@atproto/syntax'\nimport type { Server } from '@atproto/xrpc-server'\nimport type { ServerConfig } from '../../../../config.js'\nimport type { AppContext } from '../../../../context.js'\nimport type {\n HydrateCtxWithViewer,\n HydrationState,\n Hydrator,\n} from '../../../../hydration/hydrator.js'\nimport { app } from '../../../../lexicons/index.js'\nimport {\n type HydrationFnInput,\n type PresentationFnInput,\n type RulesFnInput,\n type SkeletonFnInput,\n createPipeline,\n} from '../../../../pipeline.js'\nimport { NotificationFeed } from '../../../../proto/bsky_pb.js'\nimport { events } from '../../../../telemetry/events.js'\nimport { uriToDid } from '../../../../util/uris.js'\nimport type { Views } from '../../../../views/index.js'\nimport { resHeaders } from '../../../util.js'\nimport { NOTIFICATION_REASON } from './constants.js'\nimport {\n type NotificationGroup,\n type NotificationItem,\n type RawNotification,\n buildGroups,\n parseRawNotification,\n} from './grouping/grouping.js'\nimport {\n compareNotificationGroupsNewestFirst,\n isNotificationRead,\n} from './grouping/shared.js'\nimport { buildSpotlight } from './grouping/spotlight.js'\nimport {\n delayCursor,\n shouldFilterForNeedsReview,\n shouldFilterHiddenThreadTag,\n shouldFilterReplyByThreadgate,\n} from './util.js'\n\nexport const getNextRawLimit = (\n itemsFetched: number,\n groupsBuilt: number,\n groupsNeeded: number,\n minRawLimit: number,\n maxRawItems: number,\n): number => {\n // How many raw items we still can fetch until hit the max.\n const remainingItems = maxRawItems - itemsFetched\n if (remainingItems <= 0) return 0\n\n // Defensive fallback to avoid a division by 0 later.\n // This is for the scenario where fetched items but couldn't build any groups with them\n // (let's say all notifications are missing data and are filtered out, shouldn't happen in practice).\n if (groupsBuilt === 0) return Math.min(minRawLimit, remainingItems)\n\n // How many groups still to be built - note that we might still fit items into existing groups.\n const groupsMissing = Math.max(0, groupsNeeded - groupsBuilt)\n\n const averagePerGroup = itemsFetched / groupsBuilt\n\n // Estimate additional raw notifications from the average group size.\n const estimatedRawItems = Math.ceil(averagePerGroup * groupsMissing)\n\n // Keep the estimate above the minimum batch and within the total raw-item budget.\n return Math.min(remainingItems, Math.max(minRawLimit, estimatedRawItems))\n}\n\nexport default function (server: Server, ctx: AppContext) {\n const getGroupedNotifications = createPipeline(\n skeleton,\n hydration,\n rules,\n presentation,\n )\n server.add(app.bsky.notification.getGroupedNotifications, {\n auth: ctx.authVerifier.standard,\n handler: async ({ params, auth, req }) => {\n const viewer = auth.credentials.iss\n const features = ctx.featureGatesClient.scope(\n ctx.featureGatesClient.parseUserContextFromHandler({ viewer, req }),\n )\n const labelers = ctx.reqLabelers(req)\n const hydrateCtx = await ctx.hydrator.createContext({\n labelers,\n viewer,\n features,\n })\n const lastSeenRes = await ctx.hydrator.dataplane.getNotificationSeen({\n actorDid: viewer,\n })\n const seenAt = lastSeenRes.timestamp\n ? asDatetimeString(lastSeenRes.timestamp.toJson() as string)\n : undefined\n const body = await getGroupedNotifications(\n { ...params, hydrateCtx, seenAt },\n ctx,\n )\n return {\n encoding: 'application/json',\n headers: resHeaders({ labelers: hydrateCtx.labelers }),\n body,\n }\n },\n })\n}\n\nconst skeleton = async (\n input: SkeletonFnInput<Context, Params>,\n): Promise<SkeletonState> => {\n const { ctx, params } = input\n const now = Date.now()\n const feed = normalizeFeed(params.feed)\n const seenAt = params.seenAt ? Date.parse(params.seenAt) : undefined\n const delayedCursor = delayCursor(undefined, ctx.cfg.notificationsDelayMs)\n let cursor = params.cursor\n if (!cursor || Date.parse(cursor) > Date.parse(delayedCursor)) {\n cursor = delayedCursor\n }\n\n const items: NotificationItem[] = []\n let groups: NotificationGroup[] = []\n let nextCursor: string | undefined\n let itemsFetched = 0\n let rawLimit = ctx.cfg.notificationsV2MinRawLimit\n\n for (let read = 0; read < ctx.cfg.notificationsV2MaxReads; read++) {\n const response = await ctx.hydrator.dataplane.getNotificationsV2({\n actorDid: params.hydrateCtx.viewer,\n feed,\n cursor,\n limit: rawLimit,\n })\n itemsFetched += response.notifications.length\n\n for (const notification of response.notifications) {\n const notif = parseRawNotification(notification)\n if (!notif) continue\n const uri = notif.uri\n const actorDid = uriToDid(uri)\n const indexedAt = notif.indexedAt\n // Hashing keeps IDs within the lexicon's length limit for long record URIs.\n const id = createHash('sha256')\n .update(`${indexedAt}\\0${uri}`)\n .digest('base64url')\n\n let groupingKey: string = notif.reasonSubject || uri\n if (notif.reason === NOTIFICATION_REASON.FOLLOW) {\n groupingKey = NOTIFICATION_REASON.FOLLOW\n }\n if (notif.reason === NOTIFICATION_REASON.SUBSCRIBED_POST) {\n groupingKey = actorDid\n }\n\n items.push({\n raw: notif,\n id,\n groupingKey,\n actorDid,\n })\n }\n\n const groupingResult = buildGroups(\n items,\n params.limit,\n now,\n seenAt,\n ctx.cfg.notificationsV2Algorithm,\n feed,\n )\n groups = groupingResult.groups\n\n // After fetching a page from the dataplane and attempting to group, 2 situations can happen:\n // 1. The first unconsumed notification would create an additional group beyond params.limit.\n // result.cursor points before that notification; use it so the next request processes it.\n // 2. Every fetched notification was consumed into the groups.\n // result.cursor is undefined, so use response.cursor to fetch the next dataplane page.\n nextCursor = groupingResult.cursor ?? (response.cursor || undefined)\n\n const groupingFoundUnconsumedNotification =\n groupingResult.cursor !== undefined\n const dataplaneHasMoreNotifications = !!response.cursor\n const shouldContinueBuildingPage =\n !groupingFoundUnconsumedNotification && dataplaneHasMoreNotifications\n if (!shouldContinueBuildingPage) break\n\n cursor = response.cursor\n rawLimit = getNextRawLimit(\n itemsFetched,\n groups.length,\n params.limit,\n ctx.cfg.notificationsV2MinRawLimit,\n ctx.cfg.notificationsV2MaxRawItems,\n )\n if (rawLimit === 0) break\n }\n\n return {\n ...buildSpotlight(items, groups, nextCursor, params.limit, seenAt),\n itemsFetched,\n }\n}\n\nconst hydration = async (\n input: HydrationFnInput<Context, Params, SkeletonState>,\n) => {\n const { ctx, params, skeleton } = input\n const notifs = skeleton.groups.flatMap((group) =>\n group.items.map((item) => item.raw),\n )\n\n // Optimization: instead of fetching known followers for all users in all notifications,\n // only fetch for the notifications type that want it (follow type), and only for the ones\n // that appear before truncation.\n const knownFollowersDids = dedupeStrs(\n skeleton.groups.flatMap((group) =>\n group.kind === NOTIFICATION_REASON.FOLLOW\n ? truncateRelatedViewsItems(\n group.items,\n ctx.cfg.notificationsV2MaxGroupItemsWithRelatedViews,\n ).map((item) => item.actorDid)\n : [],\n ),\n )\n\n return ctx.hydrator.hydrateGroupedNotifications(notifs, params.hydrateCtx, {\n knownFollowersDids,\n })\n}\n\nconst rules = (\n input: RulesFnInput<Context, Params, SkeletonState>,\n): SkeletonState => {\n const { ctx, params, skeleton, hydration } = input\n const seenAt = params.seenAt ? Date.parse(params.seenAt) : undefined\n const groups = mapDefined(\n skeleton.groups,\n (group): NotificationGroup | undefined => {\n const items = group.items.filter((item) => {\n const notif = item.raw\n const uri = notif.uri\n const did = item.actorDid\n if (\n ctx.views.viewerBlockExists(did, hydration) ||\n ctx.views.viewerMuteExists(did, hydration)\n ) {\n return false\n }\n if (\n shouldFilterReplyByThreadgate(\n notif.reason,\n uri,\n params.hydrateCtx.viewer,\n hydration,\n ctx.views,\n )\n ) {\n return false\n }\n if (\n shouldFilterHiddenThreadTag(\n notif.reason,\n uri,\n did,\n hydration,\n ctx.cfg.threadTagsHide,\n )\n ) {\n return false\n }\n if (\n shouldFilterForNeedsReview(\n notif.reason,\n did,\n uri,\n hydration,\n ctx.views,\n )\n ) {\n return false\n }\n\n // After hydration, notificationAvailable checks whether each notification’s required records exist.\n // If its actor, post, repost, follow, or starter pack is missing or blocked, rules removes that notification from the returned group.\n return notificationAvailable(notif, hydration, ctx.views)\n })\n\n const newest = items[0]\n const oldest = items.at(-1)\n\n // If it has no more elements, drop this group.\n if (!newest || !oldest) return\n\n // Filtering may remove notifications from this group; build a new group with only the remaining items and their updated metadata.\n return {\n ...group,\n id: newest.id,\n items,\n actorDids: items.map((item) => item.actorDid),\n itemCount: items.length,\n indexedAt: newest.raw.indexedAt,\n firstIndexedAt: oldest.raw.indexedAt,\n isRead: isNotificationRead(newest.raw.indexedAt, seenAt),\n }\n },\n )\n\n groups.sort(compareNotificationGroupsNewestFirst)\n\n return { ...skeleton, groups }\n}\n\n// Checks that the notification’s actor and required records exist and referenced posts aren’t viewer-blocked.\nconst notificationAvailable = (\n notif: RawNotification,\n state: HydrationState,\n views: Views,\n): boolean => {\n const uri = notif.uri\n const actor = state.actors?.get(uriToDid(uri))\n if (!actor) return false\n\n const postAvailable = (postUri: AtUriString) =>\n !!state.posts?.get(postUri) &&\n !!state.actors?.get(uriToDid(postUri)) &&\n !views.viewerBlockExists(uriToDid(postUri), state)\n\n const starterPackAvailable = (starterPackUri: AtUriString) =>\n !!state.starterPacks?.get(starterPackUri) &&\n !!state.actors?.get(uriToDid(starterPackUri))\n\n switch (notif.reason) {\n case NOTIFICATION_REASON.LIKE: {\n const subjectUri = notif.reasonSubject\n if (!state.likes?.get(uri)) return false\n if (new AtUri(subjectUri).collection === app.bsky.feed.generator.$type) {\n return (\n !!state.feedgens?.get(subjectUri) &&\n !!state.actors?.get(uriToDid(subjectUri))\n )\n }\n return postAvailable(subjectUri)\n }\n case NOTIFICATION_REASON.REPOST:\n return !!state.reposts?.get(uri) && postAvailable(notif.reasonSubject)\n case NOTIFICATION_REASON.LIKE_VIA_REPOST:\n case NOTIFICATION_REASON.REPOST_VIA_REPOST: {\n const recordInfo =\n notif.reason === NOTIFICATION_REASON.LIKE_VIA_REPOST\n ? state.likes?.get(uri)\n : state.reposts?.get(uri)\n const postUri = state.reposts?.get(notif.reasonSubject)?.record.subject\n .uri\n return !!recordInfo && !!postUri && postAvailable(postUri)\n }\n case NOTIFICATION_REASON.FOLLOW:\n case NOTIFICATION_REASON.FOLLOW_BACK:\n return !!state.follows?.get(uri)\n case NOTIFICATION_REASON.REPLY:\n return (\n postAvailable(uri) && !!state.posts?.get(uri)?.record.reply?.parent.uri\n )\n case NOTIFICATION_REASON.QUOTE:\n case NOTIFICATION_REASON.MENTION:\n case NOTIFICATION_REASON.SUBSCRIBED_POST:\n return postAvailable(uri)\n case NOTIFICATION_REASON.VERIFIED:\n case NOTIFICATION_REASON.UNVERIFIED:\n // Both notifications survive deletion of the verification record.\n return true\n case NOTIFICATION_REASON.STARTERPACK_JOINED:\n return (\n !!actor.profile &&\n !!actor.profileCid &&\n starterPackAvailable(notif.reasonSubject)\n )\n case NOTIFICATION_REASON.CONTACT_MATCH:\n return !!actor.profile && !!actor.profileCid\n default:\n return false\n }\n}\n\nconst presentation = (\n input: PresentationFnInput<Context, Params, SkeletonState>,\n): app.bsky.notification.getGroupedNotifications.$OutputBody => {\n const { ctx, params, skeleton, hydration } = input\n const defs = app.bsky.notification.getGroupedNotifications\n const groups = mapDefined(skeleton.groups, (group) =>\n ctx.views.notificationGroup(group, hydration),\n )\n\n const maxGroupItems = ctx.cfg.notificationsV2MaxGroupItemsWithRelatedViews\n\n // Build related profiles, records.\n const profileDids = new Set<DidString>()\n const recordUris = new Set<AtUriString>()\n for (const { kind } of groups) {\n if (\n defs.likeGroup.$isTypeOf(kind) ||\n defs.repostGroup.$isTypeOf(kind) ||\n defs.likeViaRepostGroup.$isTypeOf(kind) ||\n defs.repostViaRepostGroup.$isTypeOf(kind)\n ) {\n recordUris.add(kind.post)\n for (const item of truncateRelatedViewsItems(kind.items, maxGroupItems)) {\n profileDids.add(item.actor)\n }\n } else if (\n defs.multiPostLikeGroup.$isTypeOf(kind) ||\n defs.multiPostRepostGroup.$isTypeOf(kind) ||\n defs.multiPostLikeViaRepostGroup.$isTypeOf(kind) ||\n defs.multiPostRepostViaRepostGroup.$isTypeOf(kind)\n ) {\n profileDids.add(kind.actor)\n for (const item of truncateRelatedViewsItems(kind.items, maxGroupItems)) {\n recordUris.add(item.post)\n }\n } else if (defs.generatorLikeGroup.$isTypeOf(kind)) {\n recordUris.add(kind.generator)\n for (const item of truncateRelatedViewsItems(kind.items, maxGroupItems)) {\n profileDids.add(item.actor)\n }\n } else if (defs.followGroup.$isTypeOf(kind)) {\n for (const item of truncateRelatedViewsItems(kind.items, maxGroupItems)) {\n profileDids.add(item.actor)\n if (item.starterPack) recordUris.add(item.starterPack)\n }\n } else if (defs.subscribedPostGroup.$isTypeOf(kind)) {\n for (const item of truncateRelatedViewsItems(kind.items, maxGroupItems)) {\n profileDids.add(item.actor)\n recordUris.add(item.post)\n if (item.parent) recordUris.add(item.parent)\n }\n } else if (\n defs.replyNotification.$isTypeOf(kind) ||\n defs.quoteNotification.$isTypeOf(kind) ||\n defs.mentionNotification.$isTypeOf(kind)\n ) {\n recordUris.add(kind.post)\n if (kind.parent) recordUris.add(kind.parent)\n } else if (\n defs.followBackNotification.$isTypeOf(kind) ||\n defs.starterPackJoinedNotification.$isTypeOf(kind)\n ) {\n profileDids.add(kind.actor)\n if (kind.starterPack) recordUris.add(kind.starterPack)\n } else if (\n defs.verifiedNotification.$isTypeOf(kind) ||\n defs.unverifiedNotification.$isTypeOf(kind) ||\n defs.contactMatchNotification.$isTypeOf(kind)\n ) {\n profileDids.add(kind.actor)\n }\n }\n\n const body = {\n groups,\n relatedViews: [\n ...mapDefined([...profileDids], (did) => {\n const view = ctx.views.profileBasic(did, hydration)\n if (!view) return\n return app.bsky.actor.defs.profileViewBasic.$build(view)\n }),\n ...mapDefined([...recordUris], (uri) => {\n const collection = new AtUri(uri).collection\n if (collection === app.bsky.feed.post.$type) {\n const view = ctx.views.maybePost(uri, hydration)\n if (!view) return\n return view\n } else if (collection === app.bsky.graph.starterpack.$type) {\n const view = ctx.views.starterPackBasic(uri, hydration)\n if (!view) return\n return app.bsky.graph.defs.starterPackViewBasic.$build(view)\n } else if (collection === app.bsky.feed.generator.$type) {\n const view = ctx.views.feedGenerator(uri, hydration)\n if (!view) return\n return app.bsky.feed.defs.generatorView.$build(view)\n }\n }),\n ],\n cursor: skeleton.cursor,\n seenAt: params.seenAt,\n }\n\n events.groupedNotifications({\n itemsFetched: skeleton.itemsFetched,\n itemsReturned: groups.reduce((count, group) => count + group.count, 0),\n })\n\n return body\n}\n\nconst truncateRelatedViewsItems = <T extends unknown[]>(\n items: T,\n maxItems: number,\n): T[number][] => items.slice(0, maxItems)\n\nconst normalizeFeed = (feed: string): NotificationFeed => {\n switch (feed) {\n case 'people-i-follow':\n return NotificationFeed.PEOPLE_I_FOLLOW\n case 'conversations':\n return NotificationFeed.CONVERSATIONS\n case 'followers':\n return NotificationFeed.FOLLOWERS\n case 'activity':\n return NotificationFeed.ACTIVITY\n default:\n return NotificationFeed.ALL\n }\n}\n\ntype Context = {\n hydrator: Hydrator\n views: Views\n cfg: ServerConfig\n}\n\ntype Params = app.bsky.notification.getGroupedNotifications.$Params & {\n hydrateCtx: HydrateCtxWithViewer\n seenAt?: DatetimeString\n}\n\ntype SkeletonState = {\n groups: NotificationGroup[]\n cursor?: string\n itemsFetched: number\n}\n"]}
1
+ {"version":3,"file":"getGroupedNotifications.js","sourceRoot":"","sources":["../../../../../src/api/app/bsky/notification/getGroupedNotifications.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto'\nimport { performance } from 'node:perf_hooks'\nimport { dedupeStrs, mapDefined } from '@atproto/common'\nimport {\n type AtUriString,\n type DatetimeString,\n type DidString,\n asDatetimeString,\n} from '@atproto/lex'\nimport { AtUri } from '@atproto/syntax'\nimport type { Server } from '@atproto/xrpc-server'\nimport type { ServerConfig } from '../../../../config.js'\nimport type { AppContext } from '../../../../context.js'\nimport type {\n HydrateCtxWithViewer,\n HydrationState,\n Hydrator,\n} from '../../../../hydration/hydrator.js'\nimport { app } from '../../../../lexicons/index.js'\nimport {\n type HydrationFnInput,\n type PresentationFnInput,\n type RulesFnInput,\n type SkeletonFnInput,\n createPipeline,\n} from '../../../../pipeline.js'\nimport { NotificationFeed } from '../../../../proto/bsky_pb.js'\nimport { events } from '../../../../telemetry/events.js'\nimport { uriToDid } from '../../../../util/uris.js'\nimport type { Views } from '../../../../views/index.js'\nimport { resHeaders } from '../../../util.js'\nimport { NOTIFICATION_REASON } from './constants.js'\nimport {\n type NotificationGroup,\n type NotificationItem,\n type RawNotification,\n buildGroups,\n parseRawNotification,\n} from './grouping/grouping.js'\nimport {\n compareNotificationGroupsNewestFirst,\n isNotificationRead,\n} from './grouping/shared.js'\nimport { buildSpotlight } from './grouping/spotlight.js'\nimport {\n delayCursor,\n shouldFilterForNeedsReview,\n shouldFilterHiddenThreadTag,\n shouldFilterReplyByThreadgate,\n} from './util.js'\n\nexport const getNextRawLimit = (\n itemsFetched: number,\n groupsBuilt: number,\n groupsNeeded: number,\n minRawLimit: number,\n maxRawItems: number,\n): number => {\n // How many raw items we still can fetch until hit the max.\n const remainingItems = maxRawItems - itemsFetched\n if (remainingItems <= 0) return 0\n\n // Defensive fallback to avoid a division by 0 later.\n // This is for the scenario where fetched items but couldn't build any groups with them\n // (let's say all notifications are missing data and are filtered out, shouldn't happen in practice).\n if (groupsBuilt === 0) return Math.min(minRawLimit, remainingItems)\n\n // How many groups still to be built - note that we might still fit items into existing groups.\n const groupsMissing = Math.max(0, groupsNeeded - groupsBuilt)\n\n const averagePerGroup = itemsFetched / groupsBuilt\n\n // Estimate additional raw notifications from the average group size.\n const estimatedRawItems = Math.ceil(averagePerGroup * groupsMissing)\n\n // Keep the estimate above the minimum batch and within the total raw-item budget.\n return Math.min(remainingItems, Math.max(minRawLimit, estimatedRawItems))\n}\n\nexport default function (server: Server, ctx: AppContext) {\n const getGroupedNotifications = createPipeline(\n skeleton,\n hydration,\n rules,\n presentation,\n )\n server.add(app.bsky.notification.getGroupedNotifications, {\n auth: ctx.authVerifier.standard,\n handler: async ({ params, auth, req }) => {\n const viewer = auth.credentials.iss\n const features = ctx.featureGatesClient.scope(\n ctx.featureGatesClient.parseUserContextFromHandler({ viewer, req }),\n )\n const labelers = ctx.reqLabelers(req)\n const hydrateCtx = await ctx.hydrator.createContext({\n labelers,\n viewer,\n features,\n })\n const lastSeenRes = await ctx.hydrator.dataplane.getNotificationSeen({\n actorDid: viewer,\n })\n const seenAt = lastSeenRes.timestamp\n ? asDatetimeString(lastSeenRes.timestamp.toJson() as string)\n : undefined\n const body = await getGroupedNotifications(\n { ...params, hydrateCtx, seenAt },\n ctx,\n )\n return {\n encoding: 'application/json',\n headers: resHeaders({ labelers: hydrateCtx.labelers }),\n body,\n }\n },\n })\n}\n\nconst skeleton = async (\n input: SkeletonFnInput<Context, Params>,\n): Promise<SkeletonState> => {\n const { ctx, params } = input\n const now = Date.now()\n const feed = normalizeFeed(params.feed)\n const seenAt = params.seenAt ? Date.parse(params.seenAt) : undefined\n const delayedCursor = delayCursor(undefined, ctx.cfg.notificationsDelayMs)\n let cursor = params.cursor\n if (!cursor || Date.parse(cursor) > Date.parse(delayedCursor)) {\n cursor = delayedCursor\n }\n\n const items: NotificationItem[] = []\n let groups: NotificationGroup[] = []\n let nextCursor: string | undefined\n let itemsFetched = 0\n let dataplaneFetches = 0\n let rawLimit = ctx.cfg.notificationsV2MinRawLimit\n\n for (\n let fetches = 0;\n fetches < ctx.cfg.notificationsV2MaxFetches;\n fetches++\n ) {\n const response = await ctx.hydrator.dataplane.getNotificationsV2({\n actorDid: params.hydrateCtx.viewer,\n feed,\n cursor,\n limit: rawLimit,\n })\n dataplaneFetches++\n itemsFetched += response.notifications.length\n\n for (const notification of response.notifications) {\n const notif = parseRawNotification(notification)\n if (!notif) continue\n const uri = notif.uri\n const actorDid = uriToDid(uri)\n const indexedAt = notif.indexedAt\n // Hashing keeps IDs within the lexicon's length limit for long record URIs.\n const id = createHash('sha256')\n .update(`${indexedAt}\\0${uri}`)\n .digest('base64url')\n\n let groupingKey: string = notif.reasonSubject || uri\n if (notif.reason === NOTIFICATION_REASON.FOLLOW) {\n groupingKey = NOTIFICATION_REASON.FOLLOW\n }\n if (notif.reason === NOTIFICATION_REASON.SUBSCRIBED_POST) {\n groupingKey = NOTIFICATION_REASON.SUBSCRIBED_POST\n }\n\n items.push({\n raw: notif,\n id,\n groupingKey,\n actorDid,\n })\n }\n\n const algorithm = ctx.cfg.notificationsV2Algorithm\n const groupingStart = performance.now()\n const groupingResult = buildGroups(\n items,\n params.limit,\n now,\n seenAt,\n algorithm,\n feed,\n )\n events.notificationGrouping({\n algorithm,\n durationMs: performance.now() - groupingStart,\n })\n groups = groupingResult.groups\n\n // After fetching a page from the dataplane and attempting to group, 2 situations can happen:\n // 1. The first unconsumed notification would create an additional group beyond params.limit.\n // result.cursor points before that notification; use it so the next request processes it.\n // 2. Every fetched notification was consumed into the groups.\n // result.cursor is undefined, so use response.cursor to fetch the next dataplane page.\n nextCursor = groupingResult.cursor ?? (response.cursor || undefined)\n\n const groupingFoundUnconsumedNotification =\n groupingResult.cursor !== undefined\n const dataplaneHasMoreNotifications = !!response.cursor\n const shouldContinueBuildingPage =\n !groupingFoundUnconsumedNotification && dataplaneHasMoreNotifications\n if (!shouldContinueBuildingPage) break\n\n cursor = response.cursor\n rawLimit = getNextRawLimit(\n itemsFetched,\n groups.length,\n params.limit,\n ctx.cfg.notificationsV2MinRawLimit,\n ctx.cfg.notificationsV2MaxRawItems,\n )\n if (rawLimit === 0) break\n }\n\n return {\n ...buildSpotlight(items, groups, nextCursor, params.limit, seenAt),\n itemsFetched,\n dataplaneFetches,\n }\n}\n\nconst hydration = async (\n input: HydrationFnInput<Context, Params, SkeletonState>,\n) => {\n const { ctx, params, skeleton } = input\n const notifs = skeleton.groups.flatMap((group) =>\n group.items.map((item) => item.raw),\n )\n\n // Optimization: instead of fetching known followers for all users in all notifications,\n // only fetch for the notifications type that want it (follow type), and only for the ones\n // that appear before truncation.\n const knownFollowersDids = dedupeStrs(\n skeleton.groups.flatMap((group) =>\n group.kind === NOTIFICATION_REASON.FOLLOW\n ? truncateRelatedViewsItems(\n group.items,\n ctx.cfg.notificationsV2MaxGroupItemsWithRelatedViews,\n ).map((item) => item.actorDid)\n : [],\n ),\n )\n\n return ctx.hydrator.hydrateGroupedNotifications(notifs, params.hydrateCtx, {\n knownFollowersDids,\n })\n}\n\nconst rules = (\n input: RulesFnInput<Context, Params, SkeletonState>,\n): SkeletonState => {\n const { ctx, params, skeleton, hydration } = input\n const seenAt = params.seenAt ? Date.parse(params.seenAt) : undefined\n const groups = mapDefined(\n skeleton.groups,\n (group): NotificationGroup | undefined => {\n const items = group.items.filter((item) => {\n const notif = item.raw\n const uri = notif.uri\n const did = item.actorDid\n if (\n ctx.views.viewerBlockExists(did, hydration) ||\n ctx.views.viewerMuteExists(did, hydration)\n ) {\n return false\n }\n if (\n shouldFilterReplyByThreadgate(\n notif.reason,\n uri,\n params.hydrateCtx.viewer,\n hydration,\n ctx.views,\n )\n ) {\n return false\n }\n if (\n shouldFilterHiddenThreadTag(\n notif.reason,\n uri,\n did,\n hydration,\n ctx.cfg.threadTagsHide,\n )\n ) {\n return false\n }\n if (\n shouldFilterForNeedsReview(\n notif.reason,\n did,\n uri,\n hydration,\n ctx.views,\n )\n ) {\n return false\n }\n\n // After hydration, notificationAvailable checks whether each notification’s required records exist.\n // If its actor, post, repost, follow, or starter pack is missing or blocked, rules removes that notification from the returned group.\n return notificationAvailable(notif, hydration, ctx.views)\n })\n\n const newest = items[0]\n const oldest = items.at(-1)\n\n // If it has no more elements, drop this group.\n if (!newest || !oldest) return\n\n // Filtering may remove notifications from this group; build a new group with only the remaining items and their updated metadata.\n return {\n ...group,\n id: newest.id,\n items,\n actorDids: items.map((item) => item.actorDid),\n itemCount: items.length,\n indexedAt: newest.raw.indexedAt,\n firstIndexedAt: oldest.raw.indexedAt,\n isRead: isNotificationRead(newest.raw.indexedAt, seenAt),\n }\n },\n )\n\n groups.sort(compareNotificationGroupsNewestFirst)\n\n return { ...skeleton, groups }\n}\n\n// Checks that the notification’s actor and required records exist and referenced posts aren’t viewer-blocked.\nconst notificationAvailable = (\n notif: RawNotification,\n state: HydrationState,\n views: Views,\n): boolean => {\n const uri = notif.uri\n const actor = state.actors?.get(uriToDid(uri))\n if (!actor) return false\n\n const postAvailable = (postUri: AtUriString) =>\n !!state.posts?.get(postUri) &&\n !!state.actors?.get(uriToDid(postUri)) &&\n !views.viewerBlockExists(uriToDid(postUri), state)\n\n const starterPackAvailable = (starterPackUri: AtUriString) =>\n !!state.starterPacks?.get(starterPackUri) &&\n !!state.actors?.get(uriToDid(starterPackUri))\n\n switch (notif.reason) {\n case NOTIFICATION_REASON.LIKE: {\n const subjectUri = notif.reasonSubject\n if (!state.likes?.get(uri)) return false\n if (new AtUri(subjectUri).collection === app.bsky.feed.generator.$type) {\n return (\n !!state.feedgens?.get(subjectUri) &&\n !!state.actors?.get(uriToDid(subjectUri))\n )\n }\n return postAvailable(subjectUri)\n }\n case NOTIFICATION_REASON.REPOST:\n return !!state.reposts?.get(uri) && postAvailable(notif.reasonSubject)\n case NOTIFICATION_REASON.LIKE_VIA_REPOST:\n case NOTIFICATION_REASON.REPOST_VIA_REPOST: {\n const recordInfo =\n notif.reason === NOTIFICATION_REASON.LIKE_VIA_REPOST\n ? state.likes?.get(uri)\n : state.reposts?.get(uri)\n const postUri = state.reposts?.get(notif.reasonSubject)?.record.subject\n .uri\n return !!recordInfo && !!postUri && postAvailable(postUri)\n }\n case NOTIFICATION_REASON.FOLLOW:\n case NOTIFICATION_REASON.FOLLOW_BACK:\n return !!state.follows?.get(uri)\n case NOTIFICATION_REASON.REPLY:\n return (\n postAvailable(uri) && !!state.posts?.get(uri)?.record.reply?.parent.uri\n )\n case NOTIFICATION_REASON.QUOTE:\n case NOTIFICATION_REASON.MENTION:\n case NOTIFICATION_REASON.SUBSCRIBED_POST:\n return postAvailable(uri)\n case NOTIFICATION_REASON.VERIFIED:\n case NOTIFICATION_REASON.UNVERIFIED:\n // Both notifications survive deletion of the verification record.\n return true\n case NOTIFICATION_REASON.STARTERPACK_JOINED:\n return (\n !!actor.profile &&\n !!actor.profileCid &&\n starterPackAvailable(notif.reasonSubject)\n )\n case NOTIFICATION_REASON.CONTACT_MATCH:\n return !!actor.profile && !!actor.profileCid\n default:\n return false\n }\n}\n\nconst presentation = (\n input: PresentationFnInput<Context, Params, SkeletonState>,\n): app.bsky.notification.getGroupedNotifications.$OutputBody => {\n const { ctx, params, skeleton, hydration } = input\n const defs = app.bsky.notification.getGroupedNotifications\n const groups = mapDefined(skeleton.groups, (group) =>\n ctx.views.notificationGroup(group, hydration),\n )\n\n const maxGroupItems = ctx.cfg.notificationsV2MaxGroupItemsWithRelatedViews\n\n // Build related profiles, records.\n const profileDids = new Set<DidString>()\n const recordUris = new Set<AtUriString>()\n for (const { kind } of groups) {\n if (\n defs.likeGroup.$isTypeOf(kind) ||\n defs.repostGroup.$isTypeOf(kind) ||\n defs.likeViaRepostGroup.$isTypeOf(kind) ||\n defs.repostViaRepostGroup.$isTypeOf(kind)\n ) {\n recordUris.add(kind.post)\n for (const item of truncateRelatedViewsItems(kind.items, maxGroupItems)) {\n profileDids.add(item.actor)\n }\n } else if (\n defs.multiPostLikeGroup.$isTypeOf(kind) ||\n defs.multiPostRepostGroup.$isTypeOf(kind) ||\n defs.multiPostLikeViaRepostGroup.$isTypeOf(kind) ||\n defs.multiPostRepostViaRepostGroup.$isTypeOf(kind)\n ) {\n profileDids.add(kind.actor)\n for (const item of truncateRelatedViewsItems(kind.items, maxGroupItems)) {\n recordUris.add(item.post)\n }\n } else if (defs.generatorLikeGroup.$isTypeOf(kind)) {\n recordUris.add(kind.generator)\n for (const item of truncateRelatedViewsItems(kind.items, maxGroupItems)) {\n profileDids.add(item.actor)\n }\n } else if (defs.followGroup.$isTypeOf(kind)) {\n for (const item of truncateRelatedViewsItems(kind.items, maxGroupItems)) {\n profileDids.add(item.actor)\n if (item.starterPack) recordUris.add(item.starterPack)\n }\n } else if (defs.subscribedPostGroup.$isTypeOf(kind)) {\n for (const item of truncateRelatedViewsItems(kind.items, maxGroupItems)) {\n profileDids.add(item.actor)\n recordUris.add(item.post)\n if (item.parent) recordUris.add(item.parent)\n }\n } else if (\n defs.replyNotification.$isTypeOf(kind) ||\n defs.quoteNotification.$isTypeOf(kind) ||\n defs.mentionNotification.$isTypeOf(kind)\n ) {\n recordUris.add(kind.post)\n if (kind.parent) recordUris.add(kind.parent)\n } else if (\n defs.followBackNotification.$isTypeOf(kind) ||\n defs.starterPackJoinedNotification.$isTypeOf(kind)\n ) {\n profileDids.add(kind.actor)\n if (kind.starterPack) recordUris.add(kind.starterPack)\n } else if (\n defs.verifiedNotification.$isTypeOf(kind) ||\n defs.unverifiedNotification.$isTypeOf(kind) ||\n defs.contactMatchNotification.$isTypeOf(kind)\n ) {\n profileDids.add(kind.actor)\n }\n }\n\n const body = {\n groups,\n relatedViews: [\n ...mapDefined([...profileDids], (did) => {\n const view = ctx.views.profileBasic(did, hydration)\n if (!view) return\n return app.bsky.actor.defs.profileViewBasic.$build(view)\n }),\n ...mapDefined([...recordUris], (uri) => {\n const collection = new AtUri(uri).collection\n if (collection === app.bsky.feed.post.$type) {\n const view = ctx.views.maybePost(uri, hydration)\n if (!view) return\n return view\n } else if (collection === app.bsky.graph.starterpack.$type) {\n const view = ctx.views.starterPackBasic(uri, hydration)\n if (!view) return\n return app.bsky.graph.defs.starterPackViewBasic.$build(view)\n } else if (collection === app.bsky.feed.generator.$type) {\n const view = ctx.views.feedGenerator(uri, hydration)\n if (!view) return\n return app.bsky.feed.defs.generatorView.$build(view)\n }\n }),\n ],\n cursor: skeleton.cursor,\n seenAt: params.seenAt,\n }\n\n events.groupedNotifications({\n itemsFetched: skeleton.itemsFetched,\n itemsReturned: groups.reduce((count, group) => count + group.count, 0),\n dataplaneFetches: skeleton.dataplaneFetches,\n })\n\n return body\n}\n\nconst truncateRelatedViewsItems = <T extends unknown[]>(\n items: T,\n maxItems: number,\n): T[number][] => items.slice(0, maxItems)\n\nconst normalizeFeed = (feed: string): NotificationFeed => {\n switch (feed) {\n case 'people-i-follow':\n return NotificationFeed.PEOPLE_I_FOLLOW\n case 'conversations':\n return NotificationFeed.CONVERSATIONS\n case 'followers':\n return NotificationFeed.FOLLOWERS\n case 'activity':\n return NotificationFeed.ACTIVITY\n default:\n return NotificationFeed.ALL\n }\n}\n\ntype Context = {\n hydrator: Hydrator\n views: Views\n cfg: ServerConfig\n}\n\ntype Params = app.bsky.notification.getGroupedNotifications.$Params & {\n hydrateCtx: HydrateCtxWithViewer\n seenAt?: DatetimeString\n}\n\ntype SkeletonState = {\n groups: NotificationGroup[]\n cursor?: string\n itemsFetched: number\n dataplaneFetches: number\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"blob-resolver.d.ts","sourceRoot":"","sources":["../../src/api/blob-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAA;AAGnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AAMxC,OAAO,EAAE,KAAK,UAAU,EAAgB,MAAM,cAAc,CAAA;AAC5D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAQvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAU/C,OAAO,EACL,KAAK,UAAU,EAGhB,MAAM,iBAAiB,CAAA;AAGxB,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,UAAU,CAoH5D;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG,CAC9B,IAAI,EAAE,UAAU,CAAC,iBAAiB,EAClC,IAAI,EAAE;IACJ,GAAG,EAAE,GAAG,CAAA;IACR,GAAG,EAAE,UAAU,CAAA;IACf,GAAG,EAAE,GAAG,CAAA;CACT,KACE,QAAQ,CAAA;AAEb,wBAAsB,UAAU,CAC9B,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,iBAAiB,wCAgE3B"}
1
+ {"version":3,"file":"blob-resolver.d.ts","sourceRoot":"","sources":["../../src/api/blob-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAA;AAGnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AAMxC,OAAO,EAAE,KAAK,UAAU,EAAgB,MAAM,cAAc,CAAA;AAC5D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAQvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAU/C,OAAO,EACL,KAAK,UAAU,EAGhB,MAAM,iBAAiB,CAAA;AAGxB,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,UAAU,CAuH5D;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG,CAC9B,IAAI,EAAE,UAAU,CAAC,iBAAiB,EAClC,IAAI,EAAE;IACJ,GAAG,EAAE,GAAG,CAAA;IACR,GAAG,EAAE,UAAU,CAAA;IACf,GAAG,EAAE,GAAG,CAAA;CACT,KACE,QAAQ,CAAA;AAEb,wBAAsB,UAAU,CAC9B,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,iBAAiB,wCAgE3B"}
@@ -10,6 +10,8 @@ import { httpLogger as log } from '../logger.js';
10
10
  import { proxyResponseHeaders, responseSignal, } from '../util/http.js';
11
11
  import { BSKY_USER_AGENT } from './util.js';
12
12
  export function createMiddleware(ctx) {
13
+ // @TODO this should be from configuration
14
+ const maxSize = 100 * 1024 * 1024; // 100 MB
13
15
  return async (req, res, next) => {
14
16
  if (req.method !== 'GET' && req.method !== 'HEAD')
15
17
  return next();
@@ -32,7 +34,7 @@ export function createMiddleware(ctx) {
32
34
  };
33
35
  await streamBlob(ctx, streamOptions, (upstream, { cid, did, url }) => {
34
36
  const encoding = upstream.headers['content-encoding'];
35
- const verifier = createCidVerifier(cid, encoding);
37
+ const verifier = createCidVerifier(cid, encoding, maxSize);
36
38
  const logError = (err) => {
37
39
  log.warn({ err, did, cid: cid.toString(), pds: url.origin }, 'blob resolution failed during transmission');
38
40
  };
@@ -212,7 +214,7 @@ function getBlobHeaders({ blobRateLimitBypassKey: bypassKey, blobRateLimitBypass
212
214
  * If you need the un-compressed data, you should use a decompress + verify
213
215
  * pipeline instead.
214
216
  */
215
- function createCidVerifier(cid, encoding) {
217
+ function createCidVerifier(cid, encoding, maxSize) {
216
218
  // If the upstream content is compressed, we do not want to return a
217
219
  // de-compressed stream here. Indeed, the "compression" middleware will
218
220
  // compress the response before it is sent downstream, if it is not already
@@ -226,12 +228,11 @@ function createCidVerifier(cid, encoding) {
226
228
  // streams: one that will be sent, unaltered, downstream, and a pipeline that
227
229
  // will be used to decompress & verify the CID (discarding de-compressed
228
230
  // data).
229
- const decoders = createDecoders(encoding);
231
+ const decoders = createDecoders(encoding, {
232
+ maxSize,
233
+ maxSizeError: () => createError(400, 'Blob too large'),
234
+ });
230
235
  const verifier = new VerifyCidTransform(cid);
231
- // Optimization: If the content is not compressed, we don't need to "tee" the
232
- // stream, we can use the verifier as simple passthrough.
233
- if (!decoders.length)
234
- return verifier;
235
236
  const pipelineController = new AbortController();
236
237
  const pipelineStreams = [...decoders, verifier];
237
238
  const pipelineInput = pipelineStreams[0];
@@ -248,7 +249,7 @@ function createCidVerifier(cid, encoding) {
248
249
  passthrough.destroy(error);
249
250
  return error;
250
251
  });
251
- // We don't care about the un-compressed data, we only use the verifier to
252
+ // We don't care about the (un-compressed) data, we only use the verifier to
252
253
  // detect any error through the pipelinePromise. We still need to pass the
253
254
  // verifier into flowing mode to ensure that the pipelinePromise resolves.
254
255
  verifier.resume();
@@ -1 +1 @@
1
- {"version":3,"file":"blob-resolver.js","sourceRoot":"","sources":["../../src/api/blob-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Duplex, Transform, type Writable } from 'node:stream'\nimport { pipeline } from 'node:stream/promises'\nimport createError, { isHttpError } from 'http-errors'\nimport type { Dispatcher } from 'undici'\nimport {\n VerifyCidError,\n VerifyCidTransform,\n createDecoders,\n} from '@atproto/common'\nimport { type AtprotoDid, isAtprotoDid } from '@atproto/did'\nimport type { Cid } from '@atproto/lex'\nimport {\n ACCEPT_ENCODING_COMPRESSED,\n ACCEPT_ENCODING_UNCOMPRESSED,\n buildProxiedContentEncoding,\n formatAcceptHeader,\n} from '@atproto-labs/xrpc-utils'\nimport type { ServerConfig } from '../config.js'\nimport type { AppContext } from '../context.js'\nimport {\n Code,\n type DataPlaneClient,\n getServiceEndpoint,\n isDataplaneError,\n unpackIdentityServices,\n} from '../data-plane/index.js'\nimport { parseCid } from '../hydration/util.js'\nimport { httpLogger as log } from '../logger.js'\nimport {\n type Middleware,\n proxyResponseHeaders,\n responseSignal,\n} from '../util/http.js'\nimport { BSKY_USER_AGENT } from './util.js'\n\nexport function createMiddleware(ctx: AppContext): Middleware {\n return async (req, res, next) => {\n if (req.method !== 'GET' && req.method !== 'HEAD') return next()\n if (!req.url?.startsWith('/blob/')) return next()\n const { length, 2: didParam, 3: cidParam } = req.url.split('/')\n if (length !== 4 || !didParam || !cidParam) return next()\n\n // @TODO Check sec-fetch-* headers (e.g. to prevent files from being\n // displayed as a web page) ?\n\n try {\n const streamOptions: StreamBlobOptions = {\n did: didParam,\n cid: cidParam,\n signal: responseSignal(res),\n // Because we will be verifying the CID, we need to ensure that the\n // upstream response can be de-compressed. We do this by negotiating the\n // \"accept-encoding\" header based on the downstream client's capabilities.\n acceptEncoding: buildProxiedContentEncoding(\n req.headers['accept-encoding'],\n ctx.cfg.proxyPreferCompressed,\n ),\n }\n\n await streamBlob(ctx, streamOptions, (upstream, { cid, did, url }) => {\n const encoding = upstream.headers['content-encoding']\n const verifier = createCidVerifier(cid, encoding)\n\n const logError = (err: unknown) => {\n log.warn(\n { err, did, cid: cid.toString(), pds: url.origin },\n 'blob resolution failed during transmission',\n )\n }\n\n const onError = (err: unknown) => {\n // No need to pipe the data (verifier) into the response, as it is\n // \"errored\". The response processing will continue in the \"catch\"\n // block below (because streamBlob() will reject the promise in case\n // of \"error\" event on the writable stream returned by the factory).\n clearTimeout(graceTimer)\n logError(err)\n }\n\n // Catch any error that occurs before the timer bellow is triggered.\n // The promise returned by streamBlob() will be rejected as soon as\n // the verifier errors.\n verifier.on('error', onError)\n\n // The way I/O work, it is likely that, in case of small payloads, the\n // full upstream response is already buffered at this point. In order to\n // return a 404 instead of a broken response stream, we allow the event\n // loop to to process any pending I/O events before we start piping the\n // bytes to the response. For larger payloads, the response will look\n // like a 200 with a broken chunked response stream. The only way around\n // that would be to buffer the entire response before piping it to the\n // response, which will hurt latency (need the full payload) and memory\n // usage (either RAM or DISK). Since this is more of an edge case, we\n // allow the broken response stream to be sent.\n const graceTimer = setTimeout(() => {\n verifier.off('error', onError)\n\n // Make sure that the content served from the bsky api domain cannot\n // be used to perform XSS attacks (by serving HTML pages)\n res.setHeader(\n 'Content-Security-Policy',\n `default-src 'none'; sandbox`,\n )\n res.setHeader('X-Content-Type-Options', 'nosniff')\n res.setHeader('X-Frame-Options', 'DENY')\n res.setHeader('X-XSS-Protection', '0')\n\n // @TODO Add a cache-control header ?\n // @TODO Add content-disposition header (to force download) ?\n\n proxyResponseHeaders(upstream, res)\n\n // Force chunked encoding. This is required because the verifier will\n // trigger an error *after* the last chunk has been passed through.\n // Because the number of bytes sent will match the content-length, the\n // HTTP response will be considered \"complete\" by the HTTP server. At\n // this point, only trailers headers could indicate that an error\n // occurred, but that is not the behavior we expect.\n res.removeHeader('content-length')\n\n // From this point on, triggering the next middleware (including any\n // error handler) can be problematic because content-type,\n // content-encoding, etc. headers have already been set. Because of\n // this, we make sure that res.headersSent is set to true, preventing\n // another error handler middleware from being called (from the catch\n // block bellow). Not flushing the headers here would require to\n // revert the headers set from this middleware (which we don't do for\n // now).\n res.flushHeaders()\n\n // Pipe the verifier output into the HTTP response\n void pipeline([verifier, res]).catch(logError)\n }, 10) // 0 works too. Allow for additional data to come in for 10ms.\n\n // Write the upstream response into the verifier.\n return verifier\n })\n } catch (err) {\n if (res.headersSent || res.destroyed) {\n res.destroy()\n } else if (err instanceof VerifyCidError) {\n // @NOTE This only works because of the graceTimer above. It will also\n // only be triggered for small payloads.\n next(createError(404, err.message))\n } else if (isHttpError(err)) {\n next(err)\n } else {\n next(createError(502, 'Upstream Error', { cause: err }))\n }\n }\n }\n}\n\nexport type StreamBlobOptions = {\n cid: string\n did: string\n acceptEncoding?: string\n signal?: AbortSignal\n}\n\nexport type StreamBlobFactory = (\n data: Dispatcher.StreamFactoryData,\n info: {\n url: URL\n did: AtprotoDid\n cid: Cid\n },\n) => Writable\n\nexport async function streamBlob(\n ctx: AppContext,\n options: StreamBlobOptions,\n factory: StreamBlobFactory,\n) {\n const { did, cid } = parseBlobParams(options)\n const url = await getBlobUrl(ctx.dataplane, did, cid)\n\n const headers = getBlobHeaders(ctx.cfg, url)\n\n headers.set(\n 'accept-encoding',\n options.acceptEncoding ||\n formatAcceptHeader(\n ctx.cfg.proxyPreferCompressed\n ? ACCEPT_ENCODING_COMPRESSED\n : ACCEPT_ENCODING_UNCOMPRESSED,\n ),\n )\n\n let headersReceived = false\n\n return ctx.blobDispatcher\n .stream(\n {\n method: 'GET',\n origin: url.origin,\n path: url.pathname + url.search,\n headers,\n signal: options.signal,\n },\n (upstream) => {\n headersReceived = true\n\n if (upstream.statusCode !== 200) {\n log.warn(\n {\n did,\n cid: cid.toString(),\n pds: url.origin,\n status: upstream.statusCode,\n },\n `blob resolution failed upstream`,\n )\n\n throw upstream.statusCode >= 400 && upstream.statusCode < 500\n ? createError(404, 'Blob not found', { cause: upstream }) // 4xx => 404\n : createError(502, 'Upstream Error', { cause: upstream }) // !200 && !4xx => 502\n }\n\n return factory(upstream, { url, did, cid })\n },\n )\n .catch((err) => {\n // Is this a connection error, or a stream error ?\n if (!headersReceived) {\n // connection error, dns error, headers timeout, ...\n log.warn(\n { err, did, cid: cid.toString(), pds: url.origin },\n 'blob resolution failed during connection',\n )\n\n throw createError(502, 'Upstream Error', { cause: err })\n }\n\n throw err\n })\n}\n\nfunction parseBlobParams(params: { cid: string; did: string }) {\n const { cid, did } = params\n if (!isAtprotoDid(did)) throw createError(400, 'Invalid did')\n const cidObj = parseCid(cid)\n if (!cidObj) throw createError(400, 'Invalid cid')\n return { cid: cidObj, did }\n}\n\nasync function getBlobUrl(\n dataplane: DataPlaneClient,\n did: string,\n cid: Cid,\n): Promise<URL> {\n const pds = await getBlobPds(dataplane, did, cid)\n\n const url = new URL(`/xrpc/com.atproto.sync.getBlob`, pds)\n url.searchParams.set('did', did)\n url.searchParams.set('cid', cid.toString())\n\n return url\n}\n\nasync function getBlobPds(\n dataplane: DataPlaneClient,\n did: string,\n cid: Cid,\n): Promise<string> {\n const [identity, { takenDown }] = await Promise.all([\n dataplane.getIdentityByDid({ did }).catch((err) => {\n if (isDataplaneError(err, Code.NotFound)) {\n return undefined\n }\n throw err\n }),\n dataplane.getBlobTakedown({ did, cid: cid.toString() }),\n ])\n\n if (takenDown) {\n throw createError(404, 'Blob not found')\n }\n\n const services = identity && unpackIdentityServices(identity.services)\n const pds =\n services &&\n getServiceEndpoint(services, {\n id: 'atproto_pds',\n type: 'AtprotoPersonalDataServer',\n })\n\n if (!pds) {\n throw createError(404, 'Origin not found')\n }\n\n return pds\n}\n\nfunction getBlobHeaders(\n {\n blobRateLimitBypassKey: bypassKey,\n blobRateLimitBypassHostname: bypassHostname,\n }: ServerConfig,\n url: URL,\n): Map<string, string> {\n const headers = new Map<string, string>()\n\n headers.set('user-agent', BSKY_USER_AGENT)\n\n if (bypassKey && bypassHostname) {\n const matchesUrl = bypassHostname.startsWith('.')\n ? url.hostname.endsWith(bypassHostname)\n : url.hostname === bypassHostname\n\n if (matchesUrl) {\n headers.set('x-ratelimit-bypass', bypassKey)\n }\n }\n\n return headers\n}\n\n/**\n * This function creates a passthrough stream that will decompress (if needed)\n * and verify the CID of the input stream. The output data will be identical to\n * the input data.\n *\n * If you need the un-compressed data, you should use a decompress + verify\n * pipeline instead.\n */\nfunction createCidVerifier(cid: Cid, encoding?: string | string[]): Duplex {\n // If the upstream content is compressed, we do not want to return a\n // de-compressed stream here. Indeed, the \"compression\" middleware will\n // compress the response before it is sent downstream, if it is not already\n // compressed. Because of this, it is preferable to return the content as-is\n // to avoid re-compressing it.\n //\n // We do still want to be able to verify the CID, which requires decompressing\n // the input bytes.\n //\n // To that end, we create a passthrough in order to \"tee\" the stream into two\n // streams: one that will be sent, unaltered, downstream, and a pipeline that\n // will be used to decompress & verify the CID (discarding de-compressed\n // data).\n\n const decoders = createDecoders(encoding)\n const verifier = new VerifyCidTransform(cid)\n\n // Optimization: If the content is not compressed, we don't need to \"tee\" the\n // stream, we can use the verifier as simple passthrough.\n if (!decoders.length) return verifier\n\n const pipelineController = new AbortController()\n const pipelineStreams: Duplex[] = [...decoders, verifier]\n const pipelineInput = pipelineStreams[0]!\n\n // Create a promise that will resolve if, and only if, the decoding and\n // verification succeed.\n const pipelinePromise: Promise<null | Error> = pipeline(pipelineStreams, {\n signal: pipelineController.signal,\n }).then(\n () => null,\n (err) => {\n const error = asError(err)\n\n // the data being processed by the pipeline is invalid (e.g. invalid\n // compressed content, non-matching the CID, ...). If that occurs, we can\n // destroy the passthrough (this allows not to wait for the \"flush\" event\n // to propagate the error).\n passthrough.destroy(error)\n\n return error\n },\n )\n\n // We don't care about the un-compressed data, we only use the verifier to\n // detect any error through the pipelinePromise. We still need to pass the\n // verifier into flowing mode to ensure that the pipelinePromise resolves.\n verifier.resume()\n\n const passthrough = new Transform({\n transform(chunk, encoding, callback) {\n pipelineInput.write(chunk, encoding)\n callback(null, chunk)\n },\n flush(callback) {\n // End the input stream, which will resolve the pipeline promise\n pipelineInput.end()\n // End the pass-through stream according to the result of the pipeline\n pipelinePromise.then(callback)\n },\n destroy(err, callback) {\n pipelineController.abort() // Causes pipeline() to destroy all streams\n callback(err)\n },\n })\n\n return passthrough\n}\n\nfunction asError(err: unknown): Error {\n return err instanceof Error\n ? err\n : new Error('Processing failed', { cause: err })\n}\n"]}
1
+ {"version":3,"file":"blob-resolver.js","sourceRoot":"","sources":["../../src/api/blob-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Duplex, Transform, type Writable } from 'node:stream'\nimport { pipeline } from 'node:stream/promises'\nimport createError, { isHttpError } from 'http-errors'\nimport type { Dispatcher } from 'undici'\nimport {\n VerifyCidError,\n VerifyCidTransform,\n createDecoders,\n} from '@atproto/common'\nimport { type AtprotoDid, isAtprotoDid } from '@atproto/did'\nimport type { Cid } from '@atproto/lex'\nimport {\n ACCEPT_ENCODING_COMPRESSED,\n ACCEPT_ENCODING_UNCOMPRESSED,\n buildProxiedContentEncoding,\n formatAcceptHeader,\n} from '@atproto-labs/xrpc-utils'\nimport type { ServerConfig } from '../config.js'\nimport type { AppContext } from '../context.js'\nimport {\n Code,\n type DataPlaneClient,\n getServiceEndpoint,\n isDataplaneError,\n unpackIdentityServices,\n} from '../data-plane/index.js'\nimport { parseCid } from '../hydration/util.js'\nimport { httpLogger as log } from '../logger.js'\nimport {\n type Middleware,\n proxyResponseHeaders,\n responseSignal,\n} from '../util/http.js'\nimport { BSKY_USER_AGENT } from './util.js'\n\nexport function createMiddleware(ctx: AppContext): Middleware {\n // @TODO this should be from configuration\n const maxSize = 100 * 1024 * 1024 // 100 MB\n\n return async (req, res, next) => {\n if (req.method !== 'GET' && req.method !== 'HEAD') return next()\n if (!req.url?.startsWith('/blob/')) return next()\n const { length, 2: didParam, 3: cidParam } = req.url.split('/')\n if (length !== 4 || !didParam || !cidParam) return next()\n\n // @TODO Check sec-fetch-* headers (e.g. to prevent files from being\n // displayed as a web page) ?\n\n try {\n const streamOptions: StreamBlobOptions = {\n did: didParam,\n cid: cidParam,\n signal: responseSignal(res),\n // Because we will be verifying the CID, we need to ensure that the\n // upstream response can be de-compressed. We do this by negotiating the\n // \"accept-encoding\" header based on the downstream client's capabilities.\n acceptEncoding: buildProxiedContentEncoding(\n req.headers['accept-encoding'],\n ctx.cfg.proxyPreferCompressed,\n ),\n }\n\n await streamBlob(ctx, streamOptions, (upstream, { cid, did, url }) => {\n const encoding = upstream.headers['content-encoding']\n const verifier = createCidVerifier(cid, encoding, maxSize)\n\n const logError = (err: unknown) => {\n log.warn(\n { err, did, cid: cid.toString(), pds: url.origin },\n 'blob resolution failed during transmission',\n )\n }\n\n const onError = (err: unknown) => {\n // No need to pipe the data (verifier) into the response, as it is\n // \"errored\". The response processing will continue in the \"catch\"\n // block below (because streamBlob() will reject the promise in case\n // of \"error\" event on the writable stream returned by the factory).\n clearTimeout(graceTimer)\n logError(err)\n }\n\n // Catch any error that occurs before the timer bellow is triggered.\n // The promise returned by streamBlob() will be rejected as soon as\n // the verifier errors.\n verifier.on('error', onError)\n\n // The way I/O work, it is likely that, in case of small payloads, the\n // full upstream response is already buffered at this point. In order to\n // return a 404 instead of a broken response stream, we allow the event\n // loop to to process any pending I/O events before we start piping the\n // bytes to the response. For larger payloads, the response will look\n // like a 200 with a broken chunked response stream. The only way around\n // that would be to buffer the entire response before piping it to the\n // response, which will hurt latency (need the full payload) and memory\n // usage (either RAM or DISK). Since this is more of an edge case, we\n // allow the broken response stream to be sent.\n const graceTimer = setTimeout(() => {\n verifier.off('error', onError)\n\n // Make sure that the content served from the bsky api domain cannot\n // be used to perform XSS attacks (by serving HTML pages)\n res.setHeader(\n 'Content-Security-Policy',\n `default-src 'none'; sandbox`,\n )\n res.setHeader('X-Content-Type-Options', 'nosniff')\n res.setHeader('X-Frame-Options', 'DENY')\n res.setHeader('X-XSS-Protection', '0')\n\n // @TODO Add a cache-control header ?\n // @TODO Add content-disposition header (to force download) ?\n\n proxyResponseHeaders(upstream, res)\n\n // Force chunked encoding. This is required because the verifier will\n // trigger an error *after* the last chunk has been passed through.\n // Because the number of bytes sent will match the content-length, the\n // HTTP response will be considered \"complete\" by the HTTP server. At\n // this point, only trailers headers could indicate that an error\n // occurred, but that is not the behavior we expect.\n res.removeHeader('content-length')\n\n // From this point on, triggering the next middleware (including any\n // error handler) can be problematic because content-type,\n // content-encoding, etc. headers have already been set. Because of\n // this, we make sure that res.headersSent is set to true, preventing\n // another error handler middleware from being called (from the catch\n // block bellow). Not flushing the headers here would require to\n // revert the headers set from this middleware (which we don't do for\n // now).\n res.flushHeaders()\n\n // Pipe the verifier output into the HTTP response\n void pipeline([verifier, res]).catch(logError)\n }, 10) // 0 works too. Allow for additional data to come in for 10ms.\n\n // Write the upstream response into the verifier.\n return verifier\n })\n } catch (err) {\n if (res.headersSent || res.destroyed) {\n res.destroy()\n } else if (err instanceof VerifyCidError) {\n // @NOTE This only works because of the graceTimer above. It will also\n // only be triggered for small payloads.\n next(createError(404, err.message))\n } else if (isHttpError(err)) {\n next(err)\n } else {\n next(createError(502, 'Upstream Error', { cause: err }))\n }\n }\n }\n}\n\nexport type StreamBlobOptions = {\n cid: string\n did: string\n acceptEncoding?: string\n signal?: AbortSignal\n}\n\nexport type StreamBlobFactory = (\n data: Dispatcher.StreamFactoryData,\n info: {\n url: URL\n did: AtprotoDid\n cid: Cid\n },\n) => Writable\n\nexport async function streamBlob(\n ctx: AppContext,\n options: StreamBlobOptions,\n factory: StreamBlobFactory,\n) {\n const { did, cid } = parseBlobParams(options)\n const url = await getBlobUrl(ctx.dataplane, did, cid)\n\n const headers = getBlobHeaders(ctx.cfg, url)\n\n headers.set(\n 'accept-encoding',\n options.acceptEncoding ||\n formatAcceptHeader(\n ctx.cfg.proxyPreferCompressed\n ? ACCEPT_ENCODING_COMPRESSED\n : ACCEPT_ENCODING_UNCOMPRESSED,\n ),\n )\n\n let headersReceived = false\n\n return ctx.blobDispatcher\n .stream(\n {\n method: 'GET',\n origin: url.origin,\n path: url.pathname + url.search,\n headers,\n signal: options.signal,\n },\n (upstream) => {\n headersReceived = true\n\n if (upstream.statusCode !== 200) {\n log.warn(\n {\n did,\n cid: cid.toString(),\n pds: url.origin,\n status: upstream.statusCode,\n },\n `blob resolution failed upstream`,\n )\n\n throw upstream.statusCode >= 400 && upstream.statusCode < 500\n ? createError(404, 'Blob not found', { cause: upstream }) // 4xx => 404\n : createError(502, 'Upstream Error', { cause: upstream }) // !200 && !4xx => 502\n }\n\n return factory(upstream, { url, did, cid })\n },\n )\n .catch((err) => {\n // Is this a connection error, or a stream error ?\n if (!headersReceived) {\n // connection error, dns error, headers timeout, ...\n log.warn(\n { err, did, cid: cid.toString(), pds: url.origin },\n 'blob resolution failed during connection',\n )\n\n throw createError(502, 'Upstream Error', { cause: err })\n }\n\n throw err\n })\n}\n\nfunction parseBlobParams(params: { cid: string; did: string }) {\n const { cid, did } = params\n if (!isAtprotoDid(did)) throw createError(400, 'Invalid did')\n const cidObj = parseCid(cid)\n if (!cidObj) throw createError(400, 'Invalid cid')\n return { cid: cidObj, did }\n}\n\nasync function getBlobUrl(\n dataplane: DataPlaneClient,\n did: string,\n cid: Cid,\n): Promise<URL> {\n const pds = await getBlobPds(dataplane, did, cid)\n\n const url = new URL(`/xrpc/com.atproto.sync.getBlob`, pds)\n url.searchParams.set('did', did)\n url.searchParams.set('cid', cid.toString())\n\n return url\n}\n\nasync function getBlobPds(\n dataplane: DataPlaneClient,\n did: string,\n cid: Cid,\n): Promise<string> {\n const [identity, { takenDown }] = await Promise.all([\n dataplane.getIdentityByDid({ did }).catch((err) => {\n if (isDataplaneError(err, Code.NotFound)) {\n return undefined\n }\n throw err\n }),\n dataplane.getBlobTakedown({ did, cid: cid.toString() }),\n ])\n\n if (takenDown) {\n throw createError(404, 'Blob not found')\n }\n\n const services = identity && unpackIdentityServices(identity.services)\n const pds =\n services &&\n getServiceEndpoint(services, {\n id: 'atproto_pds',\n type: 'AtprotoPersonalDataServer',\n })\n\n if (!pds) {\n throw createError(404, 'Origin not found')\n }\n\n return pds\n}\n\nfunction getBlobHeaders(\n {\n blobRateLimitBypassKey: bypassKey,\n blobRateLimitBypassHostname: bypassHostname,\n }: ServerConfig,\n url: URL,\n): Map<string, string> {\n const headers = new Map<string, string>()\n\n headers.set('user-agent', BSKY_USER_AGENT)\n\n if (bypassKey && bypassHostname) {\n const matchesUrl = bypassHostname.startsWith('.')\n ? url.hostname.endsWith(bypassHostname)\n : url.hostname === bypassHostname\n\n if (matchesUrl) {\n headers.set('x-ratelimit-bypass', bypassKey)\n }\n }\n\n return headers\n}\n\n/**\n * This function creates a passthrough stream that will decompress (if needed)\n * and verify the CID of the input stream. The output data will be identical to\n * the input data.\n *\n * If you need the un-compressed data, you should use a decompress + verify\n * pipeline instead.\n */\nfunction createCidVerifier(\n cid: Cid,\n encoding?: string | string[],\n maxSize?: number,\n): Duplex {\n // If the upstream content is compressed, we do not want to return a\n // de-compressed stream here. Indeed, the \"compression\" middleware will\n // compress the response before it is sent downstream, if it is not already\n // compressed. Because of this, it is preferable to return the content as-is\n // to avoid re-compressing it.\n //\n // We do still want to be able to verify the CID, which requires decompressing\n // the input bytes.\n //\n // To that end, we create a passthrough in order to \"tee\" the stream into two\n // streams: one that will be sent, unaltered, downstream, and a pipeline that\n // will be used to decompress & verify the CID (discarding de-compressed\n // data).\n\n const decoders = createDecoders(encoding, {\n maxSize,\n maxSizeError: () => createError(400, 'Blob too large'),\n })\n const verifier = new VerifyCidTransform(cid)\n\n const pipelineController = new AbortController()\n const pipelineStreams: Duplex[] = [...decoders, verifier]\n const pipelineInput = pipelineStreams[0]!\n\n // Create a promise that will resolve if, and only if, the decoding and\n // verification succeed.\n const pipelinePromise: Promise<null | Error> = pipeline(pipelineStreams, {\n signal: pipelineController.signal,\n }).then(\n () => null,\n (err) => {\n const error = asError(err)\n\n // the data being processed by the pipeline is invalid (e.g. invalid\n // compressed content, non-matching the CID, ...). If that occurs, we can\n // destroy the passthrough (this allows not to wait for the \"flush\" event\n // to propagate the error).\n passthrough.destroy(error)\n\n return error\n },\n )\n\n // We don't care about the (un-compressed) data, we only use the verifier to\n // detect any error through the pipelinePromise. We still need to pass the\n // verifier into flowing mode to ensure that the pipelinePromise resolves.\n verifier.resume()\n\n const passthrough = new Transform({\n transform(chunk, encoding, callback) {\n pipelineInput.write(chunk, encoding)\n callback(null, chunk)\n },\n flush(callback) {\n // End the input stream, which will resolve the pipeline promise\n pipelineInput.end()\n // End the pass-through stream according to the result of the pipeline\n pipelinePromise.then(callback)\n },\n destroy(err, callback) {\n pipelineController.abort() // Causes pipeline() to destroy all streams\n callback(err)\n },\n })\n\n return passthrough\n}\n\nfunction asError(err: unknown): Error {\n return err instanceof Error\n ? err\n : new Error('Processing failed', { cause: err })\n}\n"]}
package/dist/config.d.ts CHANGED
@@ -102,7 +102,7 @@ export interface ServerConfigValues {
102
102
  notificationsV2Algorithm?: NotificationsV2Algorithm;
103
103
  notificationsV2MinRawLimit: number;
104
104
  notificationsV2MaxRawItems: number;
105
- notificationsV2MaxReads: number;
105
+ notificationsV2MaxFetches: number;
106
106
  notificationsV2MaxGroupItemsWithRelatedViews: number;
107
107
  clientCheckEmailConfirmed?: boolean;
108
108
  topicsEnabled?: boolean;
@@ -199,7 +199,7 @@ export declare class ServerConfig {
199
199
  get notificationsV2Algorithm(): NotificationsV2Algorithm;
200
200
  get notificationsV2MinRawLimit(): number;
201
201
  get notificationsV2MaxRawItems(): number;
202
- get notificationsV2MaxReads(): number;
202
+ get notificationsV2MaxFetches(): number;
203
203
  get notificationsV2MaxGroupItemsWithRelatedViews(): number;
204
204
  get disableSsrfProtection(): boolean;
205
205
  get proxyAllowHTTP2(): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/config.js CHANGED
@@ -151,10 +151,11 @@ export class ServerConfig {
151
151
  : 3000;
152
152
  assert(Number.isInteger(notificationsV2MaxRawItems), 'invalid BSKY_NOTIFICATIONS_V2_MAX_RAW_ITEMS');
153
153
  assert(notificationsV2MinRawLimit <= notificationsV2MaxRawItems, 'notificationsV2MinRawLimit must not exceed notificationsV2MaxRawItems');
154
- const notificationsV2MaxReads = process.env.BSKY_NOTIFICATIONS_V2_MAX_READS
155
- ? parseInt(process.env.BSKY_NOTIFICATIONS_V2_MAX_READS, 10)
154
+ const notificationsV2MaxFetches = process.env
155
+ .BSKY_NOTIFICATIONS_V2_MAX_FETCHES
156
+ ? parseInt(process.env.BSKY_NOTIFICATIONS_V2_MAX_FETCHES, 10)
156
157
  : 3;
157
- assert(Number.isInteger(notificationsV2MaxReads), 'invalid BSKY_NOTIFICATIONS_V2_MAX_READS');
158
+ assert(Number.isInteger(notificationsV2MaxFetches), 'invalid BSKY_NOTIFICATIONS_V2_MAX_FETCHES');
158
159
  const notificationsV2MaxGroupItemsWithRelatedViews = process.env
159
160
  .BSKY_NOTIFICATIONS_V2_MAX_GROUP_ITEMS_WITH_RELATED_VIEWS
160
161
  ? parseInt(process.env.BSKY_NOTIFICATIONS_V2_MAX_GROUP_ITEMS_WITH_RELATED_VIEWS, 10)
@@ -295,7 +296,7 @@ export class ServerConfig {
295
296
  notificationsV2Algorithm,
296
297
  notificationsV2MinRawLimit,
297
298
  notificationsV2MaxRawItems,
298
- notificationsV2MaxReads,
299
+ notificationsV2MaxFetches,
299
300
  notificationsV2MaxGroupItemsWithRelatedViews,
300
301
  disableSsrfProtection,
301
302
  proxyAllowHTTP2,
@@ -538,8 +539,8 @@ export class ServerConfig {
538
539
  get notificationsV2MaxRawItems() {
539
540
  return this.cfg.notificationsV2MaxRawItems;
540
541
  }
541
- get notificationsV2MaxReads() {
542
- return this.cfg.notificationsV2MaxReads;
542
+ get notificationsV2MaxFetches() {
543
+ return this.cfg.notificationsV2MaxFetches;
543
544
  }
544
545
  get notificationsV2MaxGroupItemsWithRelatedViews() {
545
546
  return this.cfg.notificationsV2MaxGroupItemsWithRelatedViews;