@atproto/bsky 0.0.103 → 0.0.104

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 (65) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/api/blob-dispatcher.d.ts +4 -0
  3. package/dist/api/blob-dispatcher.d.ts.map +1 -0
  4. package/dist/api/blob-dispatcher.js +37 -0
  5. package/dist/api/blob-dispatcher.js.map +1 -0
  6. package/dist/api/blob-resolver.d.ts +17 -8
  7. package/dist/api/blob-resolver.d.ts.map +1 -1
  8. package/dist/api/blob-resolver.js +246 -99
  9. package/dist/api/blob-resolver.js.map +1 -1
  10. package/dist/api/well-known.d.ts.map +1 -1
  11. package/dist/api/well-known.js +30 -24
  12. package/dist/api/well-known.js.map +1 -1
  13. package/dist/config.d.ts +14 -1
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/config.js +40 -5
  16. package/dist/config.js.map +1 -1
  17. package/dist/context.d.ts +3 -0
  18. package/dist/context.d.ts.map +1 -1
  19. package/dist/context.js +3 -0
  20. package/dist/context.js.map +1 -1
  21. package/dist/data-plane/server/indexing/index.js +2 -2
  22. package/dist/image/server.d.ts +7 -13
  23. package/dist/image/server.d.ts.map +1 -1
  24. package/dist/image/server.js +119 -115
  25. package/dist/image/server.js.map +1 -1
  26. package/dist/image/sharp.d.ts +11 -2
  27. package/dist/image/sharp.d.ts.map +1 -1
  28. package/dist/image/sharp.js +35 -38
  29. package/dist/image/sharp.js.map +1 -1
  30. package/dist/image/util.d.ts +6 -4
  31. package/dist/image/util.d.ts.map +1 -1
  32. package/dist/image/util.js +14 -10
  33. package/dist/image/util.js.map +1 -1
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +6 -10
  37. package/dist/index.js.map +1 -1
  38. package/dist/util/http.d.ts +12 -0
  39. package/dist/util/http.d.ts.map +1 -0
  40. package/dist/util/http.js +36 -0
  41. package/dist/util/http.js.map +1 -0
  42. package/dist/util/retry.d.ts +2 -5
  43. package/dist/util/retry.d.ts.map +1 -1
  44. package/dist/util/retry.js +8 -27
  45. package/dist/util/retry.js.map +1 -1
  46. package/package.json +17 -13
  47. package/src/api/blob-dispatcher.ts +38 -0
  48. package/src/api/blob-resolver.ts +341 -106
  49. package/src/api/well-known.ts +31 -24
  50. package/src/config.ts +63 -6
  51. package/src/context.ts +6 -0
  52. package/src/data-plane/server/indexing/index.ts +3 -3
  53. package/src/image/server.ts +131 -107
  54. package/src/image/sharp.ts +48 -52
  55. package/src/image/util.ts +20 -12
  56. package/src/index.ts +8 -15
  57. package/src/util/http.ts +41 -0
  58. package/src/util/retry.ts +8 -32
  59. package/tests/_util.ts +50 -3
  60. package/tests/blob-resolver.test.ts +62 -36
  61. package/tests/image/server.test.ts +40 -32
  62. package/tests/image/sharp.test.ts +17 -4
  63. package/tests/label-hydration.test.ts +6 -6
  64. package/tests/server.test.ts +41 -56
  65. package/tsconfig.build.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @atproto/bsky
2
2
 
3
+ ## 0.0.104
4
+
5
+ ### Patch Changes
6
+
7
+ - [#3177](https://github.com/bluesky-social/atproto/pull/3177) [`72eba67af`](https://github.com/bluesky-social/atproto/commit/72eba67af1af8320b5400bcb9319d5c3c8407d99) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Improve performance when serving blobs
8
+
9
+ - [#3177](https://github.com/bluesky-social/atproto/pull/3177) [`72eba67af`](https://github.com/bluesky-social/atproto/commit/72eba67af1af8320b5400bcb9319d5c3c8407d99) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Remove dependency on Axios
10
+
11
+ - Updated dependencies [[`72eba67af`](https://github.com/bluesky-social/atproto/commit/72eba67af1af8320b5400bcb9319d5c3c8407d99), [`72eba67af`](https://github.com/bluesky-social/atproto/commit/72eba67af1af8320b5400bcb9319d5c3c8407d99)]:
12
+ - @atproto-labs/xrpc-utils@0.0.1
13
+ - @atproto/identity@0.4.4
14
+ - @atproto/api@0.13.26
15
+ - @atproto/common@0.4.6
16
+ - @atproto/lexicon@0.4.5
17
+ - @atproto/repo@0.6.1
18
+ - @atproto/sync@0.1.8
19
+ - @atproto/crypto@0.4.2
20
+ - @atproto/xrpc-server@0.7.5
21
+
3
22
  ## 0.0.103
4
23
 
5
24
  ### Patch Changes
@@ -0,0 +1,4 @@
1
+ import { Dispatcher } from 'undici';
2
+ import { ServerConfig } from '../config';
3
+ export declare function createBlobDispatcher(cfg: ServerConfig): Dispatcher;
4
+ //# sourceMappingURL=blob-dispatcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blob-dispatcher.d.ts","sourceRoot":"","sources":["../../src/api/blob-dispatcher.ts"],"names":[],"mappings":"AACA,OAAO,EAAS,UAAU,EAAoB,MAAM,QAAQ,CAAA;AAE5D,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAGxC,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,YAAY,GAAG,UAAU,CA+BlE"}
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createBlobDispatcher = createBlobDispatcher;
4
+ const fetch_node_1 = require("@atproto-labs/fetch-node");
5
+ const undici_1 = require("undici");
6
+ const retry_1 = require("../util/retry");
7
+ function createBlobDispatcher(cfg) {
8
+ const baseDispatcher = new undici_1.Agent({
9
+ allowH2: cfg.proxyAllowHTTP2, // This is experimental
10
+ headersTimeout: cfg.proxyHeadersTimeout,
11
+ maxResponseSize: cfg.proxyMaxResponseSize,
12
+ bodyTimeout: cfg.proxyBodyTimeout,
13
+ factory: cfg.disableSsrfProtection
14
+ ? undefined
15
+ : (origin, opts) => {
16
+ const { protocol, hostname } = origin instanceof URL ? origin : new URL(origin);
17
+ if (protocol !== 'https:') {
18
+ throw new Error(`Forbidden protocol "${protocol}"`);
19
+ }
20
+ if ((0, fetch_node_1.isUnicastIp)(hostname) === false) {
21
+ throw new Error('Hostname resolved to non-unicast address');
22
+ }
23
+ return new undici_1.Pool(origin, opts);
24
+ },
25
+ connect: {
26
+ lookup: cfg.disableSsrfProtection ? undefined : fetch_node_1.unicastLookup,
27
+ },
28
+ });
29
+ return cfg.proxyMaxRetries > 0
30
+ ? new undici_1.RetryAgent(baseDispatcher, {
31
+ statusCodes: [...retry_1.RETRYABLE_HTTP_STATUS_CODES],
32
+ methods: ['GET', 'HEAD'],
33
+ maxRetries: cfg.proxyMaxRetries,
34
+ })
35
+ : baseDispatcher;
36
+ }
37
+ //# sourceMappingURL=blob-dispatcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blob-dispatcher.js","sourceRoot":"","sources":["../../src/api/blob-dispatcher.ts"],"names":[],"mappings":";;AAMA,oDA+BC;AArCD,yDAAqE;AACrE,mCAA4D;AAG5D,yCAA2D;AAE3D,SAAgB,oBAAoB,CAAC,GAAiB;IACpD,MAAM,cAAc,GAAG,IAAI,cAAK,CAAC;QAC/B,OAAO,EAAE,GAAG,CAAC,eAAe,EAAE,uBAAuB;QACrD,cAAc,EAAE,GAAG,CAAC,mBAAmB;QACvC,eAAe,EAAE,GAAG,CAAC,oBAAoB;QACzC,WAAW,EAAE,GAAG,CAAC,gBAAgB;QACjC,OAAO,EAAE,GAAG,CAAC,qBAAqB;YAChC,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE;gBACf,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAC1B,MAAM,YAAY,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,CAAA;gBAClD,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;oBAC1B,MAAM,IAAI,KAAK,CAAC,uBAAuB,QAAQ,GAAG,CAAC,CAAA;gBACrD,CAAC;gBACD,IAAI,IAAA,wBAAW,EAAC,QAAQ,CAAC,KAAK,KAAK,EAAE,CAAC;oBACpC,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAA;gBAC7D,CAAC;gBACD,OAAO,IAAI,aAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;YAC/B,CAAC;QACL,OAAO,EAAE;YACP,MAAM,EAAE,GAAG,CAAC,qBAAqB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,0BAAa;SAC9D;KACF,CAAC,CAAA;IAEF,OAAO,GAAG,CAAC,eAAe,GAAG,CAAC;QAC5B,CAAC,CAAC,IAAI,mBAAU,CAAC,cAAc,EAAE;YAC7B,WAAW,EAAE,CAAC,GAAG,mCAA2B,CAAC;YAC7C,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC;YACxB,UAAU,EAAE,GAAG,CAAC,eAAe;SAChC,CAAC;QACJ,CAAC,CAAC,cAAc,CAAA;AACpB,CAAC"}
@@ -1,11 +1,20 @@
1
- import express from 'express';
1
+ import { AtprotoDid } from '@atproto/did';
2
2
  import { CID } from 'multiformats/cid';
3
- import { VerifyCidTransform } from '@atproto/common';
3
+ import { Writable } from 'node:stream';
4
+ import { Dispatcher } from 'undici';
4
5
  import AppContext from '../context';
5
- export declare const createRouter: (ctx: AppContext) => express.Router;
6
- export declare function resolveBlob(ctx: AppContext, did: string, cid: CID): Promise<{
7
- pds: string;
8
- contentType: string;
9
- stream: VerifyCidTransform;
10
- }>;
6
+ import { Middleware } from '../util/http';
7
+ export declare function createMiddleware(ctx: AppContext): Middleware;
8
+ export type StreamBlobOptions = {
9
+ cid: string;
10
+ did: string;
11
+ acceptEncoding?: string;
12
+ signal?: AbortSignal;
13
+ };
14
+ export type StreamBlobFactory = (data: Dispatcher.StreamFactoryData, info: {
15
+ url: URL;
16
+ did: AtprotoDid;
17
+ cid: CID;
18
+ }) => Writable;
19
+ export declare function streamBlob(ctx: AppContext, options: StreamBlobOptions, factory: StreamBlobFactory): Promise<Dispatcher.StreamData>;
11
20
  //# sourceMappingURL=blob-resolver.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"blob-resolver.d.ts","sourceRoot":"","sources":["../../src/api/blob-resolver.ts"],"names":[],"mappings":"AACA,OAAO,OAAO,MAAM,SAAS,CAAA;AAG7B,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AAEtC,OAAO,EAAuB,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAEzE,OAAO,UAAU,MAAM,YAAY,CAAA;AAYnC,eAAO,MAAM,YAAY,QAAS,UAAU,KAAG,OAAO,CAAC,MA4DtD,CAAA;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG;;;;GAuCvE"}
1
+ {"version":3,"file":"blob-resolver.d.ts","sourceRoot":"","sources":["../../src/api/blob-resolver.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,UAAU,EAAgB,MAAM,cAAc,CAAA;AAEvD,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AACtC,OAAO,EAAqB,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEzD,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AAGnC,OAAO,UAAU,MAAM,YAAY,CAAA;AAUnC,OAAO,EAAE,UAAU,EAAwC,MAAM,cAAc,CAAA;AAE/E,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,kCAgE3B"}
@@ -22,87 +22,190 @@ var __importStar = (this && this.__importStar) || function (mod) {
22
22
  __setModuleDefault(result, mod);
23
23
  return result;
24
24
  };
25
- var __importDefault = (this && this.__importDefault) || function (mod) {
26
- return (mod && mod.__esModule) ? mod : { "default": mod };
27
- };
28
25
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.createRouter = void 0;
30
- exports.resolveBlob = resolveBlob;
31
- const stream_1 = require("stream");
32
- const express_1 = __importDefault(require("express"));
33
- const http_errors_1 = __importDefault(require("http-errors"));
34
- const axios_1 = __importStar(require("axios"));
35
- const cid_1 = require("multiformats/cid");
36
- const syntax_1 = require("@atproto/syntax");
26
+ exports.createMiddleware = createMiddleware;
27
+ exports.streamBlob = streamBlob;
28
+ const xrpc_utils_1 = require("@atproto-labs/xrpc-utils");
37
29
  const common_1 = require("@atproto/common");
38
- const identity_1 = require("@atproto/identity");
39
- const logger_1 = require("../logger");
40
- const retry_1 = require("../util/retry");
30
+ const did_1 = require("@atproto/did");
31
+ const http_errors_1 = __importStar(require("http-errors"));
32
+ const node_stream_1 = require("node:stream");
33
+ const promises_1 = require("node:stream/promises");
41
34
  const data_plane_1 = require("../data-plane");
42
- // Resolve and verify blob from its origin host
43
- const createRouter = (ctx) => {
44
- const router = express_1.default.Router();
45
- router.get('/blob/:did/:cid', async function (req, res, next) {
35
+ const util_1 = require("../hydration/util");
36
+ const logger_1 = require("../logger");
37
+ const http_1 = require("../util/http");
38
+ function createMiddleware(ctx) {
39
+ return async (req, res, next) => {
40
+ if (req.method !== 'GET' && req.method !== 'HEAD')
41
+ return next();
42
+ if (!req.url?.startsWith('/blob/'))
43
+ return next();
44
+ const { length, 2: didParam, 3: cidParam } = req.url.split('/');
45
+ if (length !== 4 || !didParam || !cidParam)
46
+ return next();
47
+ // @TODO Check sec-fetch-* headers (e.g. to prevent files from being
48
+ // displayed as a web page) ?
46
49
  try {
47
- const { did, cid: cidStr } = req.params;
48
- try {
49
- (0, syntax_1.ensureValidDid)(did);
50
- }
51
- catch (err) {
52
- return next((0, http_errors_1.default)(400, 'Invalid did'));
53
- }
54
- let cid;
55
- try {
56
- cid = cid_1.CID.parse(cidStr);
57
- }
58
- catch (err) {
59
- return next((0, http_errors_1.default)(400, 'Invalid cid'));
60
- }
61
- const verifiedImage = await resolveBlob(ctx, did, cid);
62
- // Send chunked response, destroying stream early (before
63
- // closing chunk) if the bytes don't match the expected cid.
64
- res.statusCode = 200;
65
- res.setHeader('content-type', verifiedImage.contentType);
66
- res.setHeader('x-content-type-options', 'nosniff');
67
- res.setHeader('content-security-policy', `default-src 'none'; sandbox`);
68
- (0, stream_1.pipeline)(verifiedImage.stream, res, (err) => {
69
- if (err) {
70
- logger_1.httpLogger.warn({ err, did, cid: cidStr, pds: verifiedImage.pds }, 'blob resolution failed during transmission');
71
- }
50
+ const streamOptions = {
51
+ did: didParam,
52
+ cid: cidParam,
53
+ signal: (0, http_1.responseSignal)(res),
54
+ // Because we will be verifying the CID, we need to ensure that the
55
+ // upstream response can be de-compressed. We do this by negotiating the
56
+ // "accept-encoding" header based on the downstream client's capabilities.
57
+ acceptEncoding: (0, xrpc_utils_1.buildProxiedContentEncoding)(req.headers['accept-encoding'], ctx.cfg.proxyPreferCompressed),
58
+ };
59
+ await streamBlob(ctx, streamOptions, (upstream, { cid, did, url }) => {
60
+ const encoding = upstream.headers['content-encoding'];
61
+ const verifier = createCidVerifier(cid, encoding);
62
+ const logError = (err) => {
63
+ logger_1.httpLogger.warn({ err, did, cid: cid.toString(), pds: url.origin }, 'blob resolution failed during transmission');
64
+ };
65
+ const onError = (err) => {
66
+ // No need to pipe the data (verifier) into the response, as it is
67
+ // "errored". The response processing will continue in the "catch"
68
+ // block below (because streamBlob() will reject the promise in case
69
+ // of "error" event on the writable stream returned by the factory).
70
+ clearTimeout(graceTimer);
71
+ logError(err);
72
+ };
73
+ // Catch any error that occurs before the timer bellow is triggered.
74
+ // The promise returned by streamBlob() will be rejected as soon as
75
+ // the verifier errors.
76
+ verifier.on('error', onError);
77
+ // The way I/O work, it is likely that, in case of small payloads, the
78
+ // full upstream response is already buffered at this point. In order to
79
+ // return a 404 instead of a broken response stream, we allow the event
80
+ // loop to to process any pending I/O events before we start piping the
81
+ // bytes to the response. For larger payloads, the response will look
82
+ // like a 200 with a broken chunked response stream. The only way around
83
+ // that would be to buffer the entire response before piping it to the
84
+ // response, which will hurt latency (need the full payload) and memory
85
+ // usage (either RAM or DISK). Since this is more of an edge case, we
86
+ // allow the broken response stream to be sent.
87
+ const graceTimer = setTimeout(() => {
88
+ verifier.off('error', onError);
89
+ // Make sure that the content served from the bsky api domain cannot
90
+ // be used to perform XSS attacks (by serving HTML pages)
91
+ res.setHeader('Content-Security-Policy', `default-src 'none'; sandbox`);
92
+ res.setHeader('X-Content-Type-Options', 'nosniff');
93
+ res.setHeader('X-Frame-Options', 'DENY');
94
+ res.setHeader('X-XSS-Protection', '0');
95
+ // @TODO Add a cache-control header ?
96
+ // @TODO Add content-disposition header (to force download) ?
97
+ (0, http_1.proxyResponseHeaders)(upstream, res);
98
+ // Force chunked encoding. This is required because the verifier will
99
+ // trigger an error *after* the last chunk has been passed through.
100
+ // Because the number of bytes sent will match the content-length, the
101
+ // HTTP response will be considered "complete" by the HTTP server. At
102
+ // this point, only trailers headers could indicate that an error
103
+ // occurred, but that is not the behavior we expect.
104
+ res.removeHeader('content-length');
105
+ // From this point on, triggering the next middleware (including any
106
+ // error handler) can be problematic because content-type,
107
+ // content-enconding, etc. headers have already been set. Because of
108
+ // this, we make sure that res.headersSent is set to true, preventing
109
+ // another error handler middleware from being called (from the catch
110
+ // block bellow). Not flushing the headers here would require to
111
+ // revert the headers set from this middleware (which we don't do for
112
+ // now).
113
+ res.flushHeaders();
114
+ // Pipe the verifier output into the HTTP response
115
+ void (0, promises_1.pipeline)([verifier, res]).catch(logError);
116
+ }, 10); // 0 works too. Allow for additional data to come in for 10ms.
117
+ // Write the upstream response into the verifier.
118
+ return verifier;
72
119
  });
73
120
  }
74
121
  catch (err) {
75
- if (err instanceof axios_1.AxiosError) {
76
- if (err.code === axios_1.AxiosError.ETIMEDOUT) {
77
- logger_1.httpLogger.warn({ host: err.request?.host, path: err.request?.path }, 'blob resolution timeout');
78
- return next((0, http_errors_1.default)(504)); // Gateway timeout
79
- }
80
- if (!err.response || err.response.status >= 500) {
81
- logger_1.httpLogger.warn({ host: err.request?.host, path: err.request?.path }, 'blob resolution failed upstream');
82
- return next((0, http_errors_1.default)(502));
83
- }
84
- return next((0, http_errors_1.default)(404, 'Blob not found'));
122
+ if (res.headersSent || res.destroyed) {
123
+ res.destroy();
124
+ }
125
+ else if (err instanceof common_1.VerifyCidError) {
126
+ // @NOTE This only works because of the graceTimer above. It will also
127
+ // only be triggered for small payloads.
128
+ next((0, http_errors_1.default)(404, err.message));
85
129
  }
86
- if (err instanceof identity_1.DidNotFoundError) {
87
- return next((0, http_errors_1.default)(404, 'Blob not found'));
130
+ else if ((0, http_errors_1.isHttpError)(err)) {
131
+ next(err);
88
132
  }
89
- return next(err);
133
+ else {
134
+ next((0, http_errors_1.default)(502, 'Upstream Error', { cause: err }));
135
+ }
136
+ }
137
+ };
138
+ }
139
+ async function streamBlob(ctx, options, factory) {
140
+ const { did, cid } = parseBlobParams(options);
141
+ const url = await getBlobUrl(ctx.dataplane, did, cid);
142
+ const headers = getBlobHeaders(ctx.cfg, url);
143
+ headers.set('accept-encoding', options.acceptEncoding ||
144
+ (0, xrpc_utils_1.formatAcceptHeader)(ctx.cfg.proxyPreferCompressed
145
+ ? xrpc_utils_1.ACCEPT_ENCODING_COMPRESSED
146
+ : xrpc_utils_1.ACCEPT_ENCODING_UNCOMPRESSED));
147
+ let headersReceived = false;
148
+ return ctx.blobDispatcher
149
+ .stream({
150
+ method: 'GET',
151
+ origin: url.origin,
152
+ path: url.pathname + url.search,
153
+ headers,
154
+ signal: options.signal,
155
+ }, (upstream) => {
156
+ headersReceived = true;
157
+ if (upstream.statusCode !== 200) {
158
+ logger_1.httpLogger.warn({
159
+ did,
160
+ cid: cid.toString(),
161
+ pds: url.origin,
162
+ status: upstream.statusCode,
163
+ }, `blob resolution failed upstream`);
164
+ throw upstream.statusCode >= 400 && upstream.statusCode < 500
165
+ ? (0, http_errors_1.default)(404, 'Blob not found', { cause: upstream }) // 4xx => 404
166
+ : (0, http_errors_1.default)(502, 'Upstream Error', { cause: upstream }); // !200 && !4xx => 502
90
167
  }
168
+ return factory(upstream, { url, did, cid });
169
+ })
170
+ .catch((err) => {
171
+ // Is this a connection error, or a stream error ?
172
+ if (!headersReceived) {
173
+ // connection error, dns error, headers timeout, ...
174
+ logger_1.httpLogger.warn({ err, did, cid: cid.toString(), pds: url.origin }, 'blob resolution failed during connection');
175
+ throw (0, http_errors_1.default)(502, 'Upstream Error', { cause: err });
176
+ }
177
+ throw err;
91
178
  });
92
- return router;
93
- };
94
- exports.createRouter = createRouter;
95
- async function resolveBlob(ctx, did, cid) {
96
- const cidStr = cid.toString();
179
+ }
180
+ function parseBlobParams(params) {
181
+ const { cid, did } = params;
182
+ if (!(0, did_1.isAtprotoDid)(did))
183
+ throw (0, http_errors_1.default)(400, 'Invalid did');
184
+ const cidObj = (0, util_1.parseCid)(cid);
185
+ if (!cidObj)
186
+ throw (0, http_errors_1.default)(400, 'Invalid cid');
187
+ return { cid: cidObj, did };
188
+ }
189
+ async function getBlobUrl(dataplane, did, cid) {
190
+ const pds = await getBlobPds(dataplane, did, cid);
191
+ const url = new URL(`/xrpc/com.atproto.sync.getBlob`, pds);
192
+ url.searchParams.set('did', did);
193
+ url.searchParams.set('cid', cid.toString());
194
+ return url;
195
+ }
196
+ async function getBlobPds(dataplane, did, cid) {
97
197
  const [identity, { takenDown }] = await Promise.all([
98
- ctx.dataplane.getIdentityByDid({ did }).catch((err) => {
198
+ dataplane.getIdentityByDid({ did }).catch((err) => {
99
199
  if ((0, data_plane_1.isDataplaneError)(err, data_plane_1.Code.NotFound)) {
100
200
  return undefined;
101
201
  }
102
202
  throw err;
103
203
  }),
104
- ctx.dataplane.getBlobTakedown({ did, cid: cid.toString() }),
204
+ dataplane.getBlobTakedown({ did, cid: cid.toString() }),
105
205
  ]);
206
+ if (takenDown) {
207
+ throw (0, http_errors_1.default)(404, 'Blob not found');
208
+ }
106
209
  const services = identity && (0, data_plane_1.unpackIdentityServices)(identity.services);
107
210
  const pds = services &&
108
211
  (0, data_plane_1.getServiceEndpoint)(services, {
@@ -112,45 +215,89 @@ async function resolveBlob(ctx, did, cid) {
112
215
  if (!pds) {
113
216
  throw (0, http_errors_1.default)(404, 'Origin not found');
114
217
  }
115
- if (takenDown) {
116
- throw (0, http_errors_1.default)(404, 'Blob not found');
218
+ return pds;
219
+ }
220
+ function getBlobHeaders({ blobRateLimitBypassKey: bypassKey, blobRateLimitBypassHostname: bypassHostname, }, url) {
221
+ const headers = new Map();
222
+ if (bypassKey && bypassHostname) {
223
+ const matchesUrl = bypassHostname.startsWith('.')
224
+ ? url.hostname.endsWith(bypassHostname)
225
+ : url.hostname === bypassHostname;
226
+ if (matchesUrl) {
227
+ headers.set('x-ratelimit-bypass', bypassKey);
228
+ }
117
229
  }
118
- const blobResult = await (0, retry_1.retryHttp)(() => getBlob(ctx, { pds, did, cid: cidStr }));
119
- const imageStream = blobResult.data;
120
- const verifyCid = new common_1.VerifyCidTransform(cid);
121
- (0, common_1.forwardStreamErrors)(imageStream, verifyCid);
122
- return {
123
- pds,
124
- contentType: blobResult.headers['content-type'] || 'application/octet-stream',
125
- stream: imageStream.pipe(verifyCid),
126
- };
230
+ return headers;
127
231
  }
128
- async function getBlob(ctx, opts) {
129
- const { pds, did, cid } = opts;
130
- return axios_1.default.get(`${pds}/xrpc/com.atproto.sync.getBlob`, {
131
- params: { did, cid },
132
- decompress: true,
133
- responseType: 'stream',
134
- timeout: 5000, // 5sec of inactivity on the connection
135
- headers: getRateLimitBypassHeaders(ctx, pds),
232
+ /**
233
+ * This function creates a passthrough stream that will decompress (if needed)
234
+ * and verify the CID of the input stream. The output data will be identical to
235
+ * the input data.
236
+ *
237
+ * If you need the un-compressed data, you should use a decompress + verify
238
+ * pipeline instead.
239
+ */
240
+ function createCidVerifier(cid, encoding) {
241
+ // If the upstream content is compressed, we do not want to return a
242
+ // de-compressed stream here. Indeed, the "compression" middleware will
243
+ // compress the response before it is sent downstream, if it is not already
244
+ // compressed. Because of this, it is preferable to return the content as-is
245
+ // to avoid re-compressing it.
246
+ //
247
+ // We do still want to be able to verify the CID, which requires decompressing
248
+ // the input bytes.
249
+ //
250
+ // To that end, we create a passthrough in order to "tee" the stream into two
251
+ // streams: one that will be sent, unaltered, downstream, and a pipeline that
252
+ // will be used to decompress & verify the CID (discarding de-compressed
253
+ // data).
254
+ const decoders = (0, common_1.createDecoders)(encoding);
255
+ const verifier = new common_1.VerifyCidTransform(cid);
256
+ // Optimization: If the content is not compressed, we don't need to "tee" the
257
+ // stream, we can use the verifier as simple passthrough.
258
+ if (!decoders.length)
259
+ return verifier;
260
+ const pipelineController = new AbortController();
261
+ const pipelineStreams = [...decoders, verifier];
262
+ const pipelineInput = pipelineStreams[0];
263
+ // Create a promise that will resolve if, and only if, the decoding and
264
+ // verification succeed.
265
+ const pipelinePromise = (0, promises_1.pipeline)(pipelineStreams, {
266
+ signal: pipelineController.signal,
267
+ }).then(() => null, (err) => {
268
+ const error = asError(err);
269
+ // the data being processed by the pipeline is invalid (e.g. invalid
270
+ // compressed content, non-matching the CID, ...). If that occurs, we can
271
+ // destroy the passthrough (this allows not to wait for the "flush" event
272
+ // to propagate the error).
273
+ passthrough.destroy(error);
274
+ return error;
275
+ });
276
+ // We don't care about the un-compressed data, we only use the verifier to
277
+ // detect any error through the pipelinePromise. We still need to pass the
278
+ // verifier into flowing mode to ensure that the pipelinePromise resolves.
279
+ verifier.resume();
280
+ const passthrough = new node_stream_1.Transform({
281
+ transform(chunk, encoding, callback) {
282
+ pipelineInput.write(chunk, encoding);
283
+ callback(null, chunk);
284
+ },
285
+ flush(callback) {
286
+ // End the input stream, which will resolve the pipeline promise
287
+ pipelineInput.end();
288
+ // End the pass-through stream according to the result of the pipeline
289
+ pipelinePromise.then(callback);
290
+ },
291
+ destroy(err, callback) {
292
+ pipelineController.abort(); // Causes pipeline() to destroy all streams
293
+ callback(err);
294
+ },
136
295
  });
296
+ return passthrough;
137
297
  }
138
- function getRateLimitBypassHeaders(ctx, pds) {
139
- const { blobRateLimitBypassKey: bypassKey, blobRateLimitBypassHostname: bypassHostname, } = ctx.cfg;
140
- if (!bypassKey || !bypassHostname) {
141
- return {};
142
- }
143
- const url = new URL(pds);
144
- if (bypassHostname.startsWith('.')) {
145
- if (url.hostname.endsWith(bypassHostname)) {
146
- return { 'x-ratelimit-bypass': bypassKey };
147
- }
148
- }
149
- else {
150
- if (url.hostname === bypassHostname) {
151
- return { 'x-ratelimit-bypass': bypassKey };
152
- }
153
- }
154
- return {};
298
+ function asError(err) {
299
+ return err instanceof Error
300
+ ? err
301
+ : new Error('Processing failed', { cause: err });
155
302
  }
156
303
  //# sourceMappingURL=blob-resolver.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"blob-resolver.js","sourceRoot":"","sources":["../../src/api/blob-resolver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"blob-resolver.js","sourceRoot":"","sources":["../../src/api/blob-resolver.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"well-known.d.ts","sourceRoot":"","sources":["../../src/api/well-known.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAA;AAC7B,OAAO,UAAU,MAAM,YAAY,CAAA;AAEnC,eAAO,MAAM,YAAY,QAAS,UAAU,KAAG,OAAO,CAAC,MA8BtD,CAAA"}
1
+ {"version":3,"file":"well-known.d.ts","sourceRoot":"","sources":["../../src/api/well-known.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAA;AAC7B,OAAO,UAAU,MAAM,YAAY,CAAA;AAEnC,eAAO,MAAM,YAAY,QAAS,UAAU,KAAG,OAAO,CAAC,MAqCtD,CAAA"}
@@ -7,31 +7,37 @@ exports.createRouter = void 0;
7
7
  const express_1 = __importDefault(require("express"));
8
8
  const createRouter = (ctx) => {
9
9
  const router = express_1.default.Router();
10
- router.get('/.well-known/did.json', (_req, res) => {
11
- const hostname = ctx.cfg.publicUrl && new URL(ctx.cfg.publicUrl).hostname;
12
- if (!hostname || ctx.cfg.serverDid !== `did:web:${hostname}`) {
13
- return res.sendStatus(404);
14
- }
15
- res.json({
16
- '@context': ['https://www.w3.org/ns/did/v1'],
17
- id: ctx.cfg.serverDid,
18
- verificationMethod: [
19
- {
20
- id: `${ctx.cfg.serverDid}#atproto`,
21
- type: 'Multikey',
22
- controller: ctx.cfg.serverDid,
23
- publicKeyMultibase: ctx.signingKey.did().replace('did:key:', ''),
24
- },
25
- ],
26
- service: [
27
- {
28
- id: '#bsky_notif',
29
- type: 'BskyNotificationService',
30
- serviceEndpoint: `https://${hostname}`,
31
- },
32
- ],
10
+ const did = ctx.cfg.serverDid;
11
+ if (did.startsWith('did:web:')) {
12
+ const hostname = did.slice('did:web:'.length);
13
+ const serviceEndpoint = `https://${hostname}`;
14
+ router.get('/.well-known/did.json', (_req, res) => {
15
+ res.json({
16
+ '@context': ['https://www.w3.org/ns/did/v1'],
17
+ id: did,
18
+ verificationMethod: [
19
+ {
20
+ id: `${did}#atproto`,
21
+ type: 'Multikey',
22
+ controller: did,
23
+ publicKeyMultibase: ctx.signingKey.did().replace('did:key:', ''),
24
+ },
25
+ ],
26
+ service: [
27
+ {
28
+ id: '#bsky_notif',
29
+ type: 'BskyNotificationService',
30
+ serviceEndpoint,
31
+ },
32
+ {
33
+ id: '#bsky_appview',
34
+ type: 'BskyAppView',
35
+ serviceEndpoint,
36
+ },
37
+ ],
38
+ });
33
39
  });
34
- });
40
+ }
35
41
  return router;
36
42
  };
37
43
  exports.createRouter = createRouter;
@@ -1 +1 @@
1
- {"version":3,"file":"well-known.js","sourceRoot":"","sources":["../../src/api/well-known.ts"],"names":[],"mappings":";;;;;;AAAA,sDAA6B;AAGtB,MAAM,YAAY,GAAG,CAAC,GAAe,EAAkB,EAAE;IAC9D,MAAM,MAAM,GAAG,iBAAO,CAAC,MAAM,EAAE,CAAA;IAE/B,MAAM,CAAC,GAAG,CAAC,uBAAuB,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;QAChD,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,SAAS,IAAI,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAA;QACzE,IAAI,CAAC,QAAQ,IAAI,GAAG,CAAC,GAAG,CAAC,SAAS,KAAK,WAAW,QAAQ,EAAE,EAAE,CAAC;YAC7D,OAAO,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAA;QAC5B,CAAC;QACD,GAAG,CAAC,IAAI,CAAC;YACP,UAAU,EAAE,CAAC,8BAA8B,CAAC;YAC5C,EAAE,EAAE,GAAG,CAAC,GAAG,CAAC,SAAS;YACrB,kBAAkB,EAAE;gBAClB;oBACE,EAAE,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC,SAAS,UAAU;oBAClC,IAAI,EAAE,UAAU;oBAChB,UAAU,EAAE,GAAG,CAAC,GAAG,CAAC,SAAS;oBAC7B,kBAAkB,EAAE,GAAG,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;iBACjE;aACF;YACD,OAAO,EAAE;gBACP;oBACE,EAAE,EAAE,aAAa;oBACjB,IAAI,EAAE,yBAAyB;oBAC/B,eAAe,EAAE,WAAW,QAAQ,EAAE;iBACvC;aACF;SACF,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,OAAO,MAAM,CAAA;AACf,CAAC,CAAA;AA9BY,QAAA,YAAY,gBA8BxB"}
1
+ {"version":3,"file":"well-known.js","sourceRoot":"","sources":["../../src/api/well-known.ts"],"names":[],"mappings":";;;;;;AAAA,sDAA6B;AAGtB,MAAM,YAAY,GAAG,CAAC,GAAe,EAAkB,EAAE;IAC9D,MAAM,MAAM,GAAG,iBAAO,CAAC,MAAM,EAAE,CAAA;IAE/B,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,SAAS,CAAA;IAC7B,IAAI,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;QAC7C,MAAM,eAAe,GAAG,WAAW,QAAQ,EAAE,CAAA;QAE7C,MAAM,CAAC,GAAG,CAAC,uBAAuB,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;YAChD,GAAG,CAAC,IAAI,CAAC;gBACP,UAAU,EAAE,CAAC,8BAA8B,CAAC;gBAC5C,EAAE,EAAE,GAAG;gBACP,kBAAkB,EAAE;oBAClB;wBACE,EAAE,EAAE,GAAG,GAAG,UAAU;wBACpB,IAAI,EAAE,UAAU;wBAChB,UAAU,EAAE,GAAG;wBACf,kBAAkB,EAAE,GAAG,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;qBACjE;iBACF;gBACD,OAAO,EAAE;oBACP;wBACE,EAAE,EAAE,aAAa;wBACjB,IAAI,EAAE,yBAAyB;wBAC/B,eAAe;qBAChB;oBACD;wBACE,EAAE,EAAE,eAAe;wBACnB,IAAI,EAAE,aAAa;wBACnB,eAAe;qBAChB;iBACF;aACF,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC,CAAA;AArCY,QAAA,YAAY,gBAqCxB"}