@atproto-labs/fetch 0.3.1 → 0.3.3
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.
- package/CHANGELOG.md +24 -0
- package/dist/fetch-request.d.ts.map +1 -1
- package/dist/fetch-request.js +8 -6
- package/dist/fetch-request.js.map +1 -1
- package/dist/fetch-response.d.ts.map +1 -1
- package/dist/fetch-response.js +15 -9
- package/dist/fetch-response.js.map +1 -1
- package/dist/fetch-wrap.js.map +1 -1
- package/package.json +9 -6
- package/src/fetch-error.ts +0 -13
- package/src/fetch-request.ts +0 -223
- package/src/fetch-response.ts +0 -312
- package/src/fetch-wrap.ts +0 -150
- package/src/fetch.ts +0 -39
- package/src/index.ts +0 -6
- package/src/transformed-response.ts +0 -36
- package/src/util.ts +0 -158
- package/tsconfig.build.json +0 -8
- package/tsconfig.build.tsbuildinfo +0 -1
- package/tsconfig.json +0 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @atproto-labs/fetch
|
|
2
2
|
|
|
3
|
+
## 0.3.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Update TypeScript build to rely on references to composite internal projects
|
|
8
|
+
|
|
9
|
+
- [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Bundle only necessary files in the NPM tarball, including the `CHANGELOG.md` and `README.md` files (if present).
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [[`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07), [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07)]:
|
|
12
|
+
- @atproto-labs/pipe@0.2.3
|
|
13
|
+
|
|
14
|
+
## 0.3.2
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- [#5148](https://github.com/bluesky-social/atproto/pull/5148) [`60e9b83`](https://github.com/bluesky-social/atproto/commit/60e9b8391f212c274b1f21991ee2a3a2d14f2f88) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Fix support of NodeJS 24 and before
|
|
19
|
+
|
|
20
|
+
- [#5148](https://github.com/bluesky-social/atproto/pull/5148) [`60e9b83`](https://github.com/bluesky-social/atproto/commit/60e9b8391f212c274b1f21991ee2a3a2d14f2f88) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Minor url parsing optimization
|
|
21
|
+
|
|
22
|
+
- [#5151](https://github.com/bluesky-social/atproto/pull/5151) [`a51c45d`](https://github.com/bluesky-social/atproto/commit/a51c45d38f6bd7b8765f640e564cf921d52162e7) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Update dependencies
|
|
23
|
+
|
|
24
|
+
- Updated dependencies [[`a51c45d`](https://github.com/bluesky-social/atproto/commit/a51c45d38f6bd7b8765f640e564cf921d52162e7)]:
|
|
25
|
+
- @atproto-labs/pipe@0.2.2
|
|
26
|
+
|
|
3
27
|
## 0.3.1
|
|
4
28
|
|
|
5
29
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-request.d.ts","sourceRoot":"","sources":["../src/fetch-request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAGtC,qBAAa,iBAAkB,SAAQ,UAAU;aAE7B,OAAO,EAAE,OAAO;IADlC,YACkB,OAAO,EAAE,OAAO,EAChC,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,YAAY,EASvB;IAED,IAAI,MAAM,YAKT;IAED,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,iBAAiB,CAG/D;CACF;AA6DD,wBAAgB,6BAA6B,CAAC,SAAS,EAAE;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,GAAG;QAAE,eAAe,EAAE,OAAO,CAAA;KAAE,CAAA;IAChD,QAAQ,CAAC,EAAE,OAAO,GAAG;QAAE,eAAe,EAAE,OAAO,CAAA;KAAE,CAAA;CAClD,WACgB,OAAO,GAAG,MAAM,GAAG,GAAG,SAAS,WAAW,
|
|
1
|
+
{"version":3,"file":"fetch-request.d.ts","sourceRoot":"","sources":["../src/fetch-request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAGtC,qBAAa,iBAAkB,SAAQ,UAAU;aAE7B,OAAO,EAAE,OAAO;IADlC,YACkB,OAAO,EAAE,OAAO,EAChC,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,YAAY,EASvB;IAED,IAAI,MAAM,YAKT;IAED,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,iBAAiB,CAG/D;CACF;AA6DD,wBAAgB,6BAA6B,CAAC,SAAS,EAAE;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,GAAG;QAAE,eAAe,EAAE,OAAO,CAAA;KAAE,CAAA;IAChD,QAAQ,CAAC,EAAE,OAAO,GAAG;QAAE,eAAe,EAAE,OAAO,CAAA;KAAE,CAAA;CAClD,WACgB,OAAO,GAAG,MAAM,GAAG,GAAG,SAAS,WAAW,aA4B1D;AAED,wBAAgB,qCAAqC,YACpC,OAAO,GAAG,MAAM,GAAG,GAAG,SAAS,WAAW,KAAG,OAAO,CAqBpE;AAED,wBAAgB,0BAA0B,YACzB,OAAO,GAAG,MAAM,GAAG,GAAG,SAAS,WAAW,aAuB1D;AAED,eAAO,MAAM,8BAA8B,UAS1C,CAAA;AAED,wBAAgB,mCAAmC,CACjD,QAAQ,GAAE,QAAQ,CAAC,MAAM,CAAkC,+BAUtC,OAAO,GAAG,MAAM,GAAG,GAAG,SAAS,WAAW,uBAsBhE"}
|
package/dist/fetch-request.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FetchError } from './fetch-error.js';
|
|
2
2
|
import { asRequest } from './fetch.js';
|
|
3
|
-
import {
|
|
3
|
+
import { isIp } from './util.js';
|
|
4
4
|
export class FetchRequestError extends FetchError {
|
|
5
5
|
constructor(request, statusCode, message, options) {
|
|
6
6
|
if (statusCode == null || !message) {
|
|
@@ -54,7 +54,7 @@ function extractInfo(err) {
|
|
|
54
54
|
return [502, err.message];
|
|
55
55
|
}
|
|
56
56
|
// NodeJS errors
|
|
57
|
-
const code =
|
|
57
|
+
const code = 'code' in err ? err.code : undefined;
|
|
58
58
|
if (typeof code === 'string') {
|
|
59
59
|
switch (true) {
|
|
60
60
|
case code === 'ENOTFOUND':
|
|
@@ -75,9 +75,11 @@ function extractInfo(err) {
|
|
|
75
75
|
}
|
|
76
76
|
export function protocolCheckRequestTransform(protocols) {
|
|
77
77
|
return (input, init) => {
|
|
78
|
-
const { protocol, port } = extractUrl(input);
|
|
79
78
|
const request = asRequest(input, init);
|
|
80
|
-
const
|
|
79
|
+
const { protocol, port } = new URL(request.url);
|
|
80
|
+
const config = Object.hasOwn(protocols, protocol)
|
|
81
|
+
? protocols[protocol]
|
|
82
|
+
: undefined;
|
|
81
83
|
if (!config) {
|
|
82
84
|
throw new FetchRequestError(request, 400, `Forbidden protocol "${protocol}"`);
|
|
83
85
|
}
|
|
@@ -111,8 +113,8 @@ export function requireHostHeaderTransform() {
|
|
|
111
113
|
return (input, init) => {
|
|
112
114
|
// Note that fetch() will automatically add the Host header from the URL and
|
|
113
115
|
// discard any Host header manually set in the request.
|
|
114
|
-
const { protocol, hostname } = extractUrl(input);
|
|
115
116
|
const request = asRequest(input, init);
|
|
117
|
+
const { protocol, hostname } = new URL(request.url);
|
|
116
118
|
// "Host" header only makes sense in the context of an HTTP request
|
|
117
119
|
if (protocol !== 'http:' && protocol !== 'https:') {
|
|
118
120
|
throw new FetchRequestError(request, 400, `"${protocol}" requests are not allowed`);
|
|
@@ -141,8 +143,8 @@ export function forbiddenDomainNameRequestTransform(denyList = DEFAULT_FORBIDDEN
|
|
|
141
143
|
return asRequest;
|
|
142
144
|
}
|
|
143
145
|
return async (input, init) => {
|
|
144
|
-
const { hostname } = extractUrl(input);
|
|
145
146
|
const request = asRequest(input, init);
|
|
147
|
+
const { hostname } = new URL(request.url);
|
|
146
148
|
// Full domain name check
|
|
147
149
|
if (denySet.has(hostname)) {
|
|
148
150
|
throw new FetchRequestError(request, 403, 'Forbidden hostname');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-request.js","sourceRoot":"","sources":["../src/fetch-request.ts"],"names":[],"mappings":"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","sourcesContent":["import { FetchError } from './fetch-error.js'\nimport { asRequest } from './fetch.js'\nimport { extractUrl, isIp } from './util.js'\n\nexport class FetchRequestError extends FetchError {\n constructor(\n public readonly request: Request,\n statusCode?: number,\n message?: string,\n options?: ErrorOptions,\n ) {\n if (statusCode == null || !message) {\n const info = extractInfo(extractRootCause(options?.cause))\n statusCode ??= info[0]\n message ||= info[1]\n }\n\n super(statusCode, message, options)\n }\n\n get expose() {\n // A 500 request error means that the request was not made due to an infra,\n // programming or server side issue. The message should no be exposed to\n // downstream clients.\n return this.statusCode !== 500\n }\n\n static from(request: Request, cause: unknown): FetchRequestError {\n if (cause instanceof FetchRequestError) return cause\n return new FetchRequestError(request, undefined, undefined, { cause })\n }\n}\n\nfunction extractRootCause(err: unknown): unknown {\n // Unwrap the Network error from undici (i.e. Node's internal fetch() implementation)\n // https://github.com/nodejs/undici/blob/3274c975947ce11a08508743df026f73598bfead/lib/web/fetch/index.js#L223-L228\n if (\n err instanceof TypeError &&\n err.message === 'fetch failed' &&\n err.cause !== undefined\n ) {\n return err.cause\n }\n\n return err\n}\n\nfunction extractInfo(err: unknown): [statusCode: number, message: string] {\n if (typeof err === 'string' && err.length > 0) {\n return [500, err]\n }\n\n if (!(err instanceof Error)) {\n return [500, 'Failed to fetch']\n }\n\n // Undici fetch() \"network\" errors\n switch (err.message) {\n case 'failed to fetch the data URL':\n return [400, err.message]\n case 'unexpected redirect':\n case 'cors failure':\n case 'blocked':\n case 'proxy authentication required':\n // These cases could be represented either as a 4xx user error (invalid\n // URL provided), or as a 5xx server error (server didn't behave as\n // expected).\n return [502, err.message]\n }\n\n // NodeJS errors\n const code = err['code']\n if (typeof code === 'string') {\n switch (true) {\n case code === 'ENOTFOUND':\n return [400, 'Invalid hostname']\n case code === 'ECONNREFUSED':\n return [502, 'Connection refused']\n case code === 'DEPTH_ZERO_SELF_SIGNED_CERT':\n return [502, 'Self-signed certificate']\n case code.startsWith('ERR_TLS'):\n return [502, 'TLS error']\n case code.startsWith('ECONN'):\n return [502, 'Connection error']\n default:\n return [500, `${code} error`]\n }\n }\n\n return [500, err.message]\n}\n\nexport function protocolCheckRequestTransform(protocols: {\n 'about:'?: boolean\n 'blob:'?: boolean\n 'data:'?: boolean\n 'file:'?: boolean\n 'http:'?: boolean | { allowCustomPort: boolean }\n 'https:'?: boolean | { allowCustomPort: boolean }\n}) {\n return (input: Request | string | URL, init?: RequestInit) => {\n const { protocol, port } = extractUrl(input)\n\n const request = asRequest(input, init)\n\n const config: undefined | boolean | { allowCustomPort?: boolean } =\n Object.hasOwn(protocols, protocol) ? protocols[protocol] : undefined\n\n if (!config) {\n throw new FetchRequestError(\n request,\n 400,\n `Forbidden protocol \"${protocol}\"`,\n )\n } else if (config === true) {\n // Safe to proceed\n } else if (!config['allowCustomPort'] && port !== '') {\n throw new FetchRequestError(\n request,\n 400,\n `Custom ${protocol} ports not allowed`,\n )\n }\n\n return request\n }\n}\n\nexport function explicitRedirectCheckRequestTransform() {\n return (input: Request | string | URL, init?: RequestInit): Request => {\n const request = asRequest(input, init)\n\n // We want to avoid the case where the user of this code forgot to explicit\n // a redirect strategy.\n if (init?.redirect != null) return request\n\n // Sadly, if the `input` is a request, and `init` was omitted, there is no\n // way to tell if the `redirect === 'follow'` value comes from the user, or\n // fetch's default. In order to prevent accidental omission, this case is\n // forbidden.\n if (request.redirect === 'follow') {\n throw new FetchRequestError(\n request,\n 500,\n 'Request redirect must be \"error\" or \"manual\"',\n )\n }\n\n return request\n }\n}\n\nexport function requireHostHeaderTransform() {\n return (input: Request | string | URL, init?: RequestInit) => {\n // Note that fetch() will automatically add the Host header from the URL and\n // discard any Host header manually set in the request.\n\n const { protocol, hostname } = extractUrl(input)\n\n const request = asRequest(input, init)\n\n // \"Host\" header only makes sense in the context of an HTTP request\n if (protocol !== 'http:' && protocol !== 'https:') {\n throw new FetchRequestError(\n request,\n 400,\n `\"${protocol}\" requests are not allowed`,\n )\n }\n\n if (!hostname || isIp(hostname)) {\n throw new FetchRequestError(request, 400, 'Invalid hostname')\n }\n\n return request\n }\n}\n\nexport const DEFAULT_FORBIDDEN_DOMAIN_NAMES = [\n 'example.com',\n '*.example.com',\n 'example.org',\n '*.example.org',\n 'example.net',\n '*.example.net',\n 'googleusercontent.com',\n '*.googleusercontent.com',\n]\n\nexport function forbiddenDomainNameRequestTransform(\n denyList: Iterable<string> = DEFAULT_FORBIDDEN_DOMAIN_NAMES,\n) {\n const denySet = new Set<string>(denyList)\n\n // Optimization: if no forbidden domain names are provided, we can skip the\n // check entirely.\n if (denySet.size === 0) {\n return asRequest\n }\n\n return async (input: Request | string | URL, init?: RequestInit) => {\n const { hostname } = extractUrl(input)\n\n const request = asRequest(input, init)\n\n // Full domain name check\n if (denySet.has(hostname)) {\n throw new FetchRequestError(request, 403, 'Forbidden hostname')\n }\n\n // Sub domain name check\n let curDot = hostname.indexOf('.')\n while (curDot !== -1) {\n const subdomain = hostname.slice(curDot + 1)\n if (denySet.has(`*.${subdomain}`)) {\n throw new FetchRequestError(request, 403, 'Forbidden hostname')\n }\n curDot = hostname.indexOf('.', curDot + 1)\n }\n\n return request\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fetch-request.js","sourceRoot":"","sources":["../src/fetch-request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,MAAM,OAAO,iBAAkB,SAAQ,UAAU;IAC/C,YACkB,OAAgB,EAChC,UAAmB,EACnB,OAAgB,EAChB,OAAsB;QAEtB,IAAI,UAAU,IAAI,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACnC,MAAM,IAAI,GAAG,WAAW,CAAC,gBAAgB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAA;YAC1D,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,CAAA;YACtB,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,CAAA;QACrB,CAAC;QAED,KAAK,CAAC,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;uBAXnB,OAAO;IAYzB,CAAC;IAED,IAAI,MAAM;QACR,2EAA2E;QAC3E,wEAAwE;QACxE,sBAAsB;QACtB,OAAO,IAAI,CAAC,UAAU,KAAK,GAAG,CAAA;IAChC,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,OAAgB,EAAE,KAAc;QAC1C,IAAI,KAAK,YAAY,iBAAiB;YAAE,OAAO,KAAK,CAAA;QACpD,OAAO,IAAI,iBAAiB,CAAC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;IACxE,CAAC;CACF;AAED,SAAS,gBAAgB,CAAC,GAAY;IACpC,qFAAqF;IACrF,kHAAkH;IAClH,IACE,GAAG,YAAY,SAAS;QACxB,GAAG,CAAC,OAAO,KAAK,cAAc;QAC9B,GAAG,CAAC,KAAK,KAAK,SAAS,EACvB,CAAC;QACD,OAAO,GAAG,CAAC,KAAK,CAAA;IAClB,CAAC;IAED,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAS,WAAW,CAAC,GAAY;IAC/B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9C,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACnB,CAAC;IAED,IAAI,CAAC,CAAC,GAAG,YAAY,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAA;IACjC,CAAC;IAED,kCAAkC;IAClC,QAAQ,GAAG,CAAC,OAAO,EAAE,CAAC;QACpB,KAAK,8BAA8B;YACjC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;QAC3B,KAAK,qBAAqB,CAAC;QAC3B,KAAK,cAAc,CAAC;QACpB,KAAK,SAAS,CAAC;QACf,KAAK,+BAA+B;YAClC,uEAAuE;YACvE,mEAAmE;YACnE,aAAa;YACb,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;IAC7B,CAAC;IAED,gBAAgB;IAChB,MAAM,IAAI,GAAG,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAA;IACjD,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,IAAI,KAAK,WAAW;gBACvB,OAAO,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAA;YAClC,KAAK,IAAI,KAAK,cAAc;gBAC1B,OAAO,CAAC,GAAG,EAAE,oBAAoB,CAAC,CAAA;YACpC,KAAK,IAAI,KAAK,6BAA6B;gBACzC,OAAO,CAAC,GAAG,EAAE,yBAAyB,CAAC,CAAA;YACzC,KAAK,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;gBAC7B,OAAO,CAAC,GAAG,EAAE,WAAW,CAAC,CAAA;YAC3B,KAAK,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;gBAC3B,OAAO,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAA;YAClC;gBACE,OAAO,CAAC,GAAG,EAAE,GAAG,IAAI,QAAQ,CAAC,CAAA;QACjC,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;AAC3B,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,SAO7C;IACC,OAAO,CAAC,KAA6B,EAAE,IAAkB,EAAE,EAAE;QAC3D,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;QAEtC,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAE/C,MAAM,MAAM,GACV,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC;YAChC,CAAC,CAAC,SAAS,CAAC,QAAkC,CAAC;YAC/C,CAAC,CAAC,SAAS,CAAA;QAEf,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,iBAAiB,CACzB,OAAO,EACP,GAAG,EACH,uBAAuB,QAAQ,GAAG,CACnC,CAAA;QACH,CAAC;aAAM,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YAC3B,kBAAkB;QACpB,CAAC;aAAM,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;YACrD,MAAM,IAAI,iBAAiB,CACzB,OAAO,EACP,GAAG,EACH,UAAU,QAAQ,oBAAoB,CACvC,CAAA;QACH,CAAC;QAED,OAAO,OAAO,CAAA;IAChB,CAAC,CAAA;AACH,CAAC;AAED,MAAM,UAAU,qCAAqC;IACnD,OAAO,CAAC,KAA6B,EAAE,IAAkB,EAAW,EAAE;QACpE,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;QAEtC,2EAA2E;QAC3E,uBAAuB;QACvB,IAAI,IAAI,EAAE,QAAQ,IAAI,IAAI;YAAE,OAAO,OAAO,CAAA;QAE1C,0EAA0E;QAC1E,2EAA2E;QAC3E,yEAAyE;QACzE,aAAa;QACb,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAClC,MAAM,IAAI,iBAAiB,CACzB,OAAO,EACP,GAAG,EACH,8CAA8C,CAC/C,CAAA;QACH,CAAC;QAED,OAAO,OAAO,CAAA;IAChB,CAAC,CAAA;AACH,CAAC;AAED,MAAM,UAAU,0BAA0B;IACxC,OAAO,CAAC,KAA6B,EAAE,IAAkB,EAAE,EAAE;QAC3D,4EAA4E;QAC5E,uDAAuD;QAEvD,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;QAEtC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAEnD,mEAAmE;QACnE,IAAI,QAAQ,KAAK,OAAO,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAClD,MAAM,IAAI,iBAAiB,CACzB,OAAO,EACP,GAAG,EACH,IAAI,QAAQ,4BAA4B,CACzC,CAAA;QACH,CAAC;QAED,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YAChC,MAAM,IAAI,iBAAiB,CAAC,OAAO,EAAE,GAAG,EAAE,kBAAkB,CAAC,CAAA;QAC/D,CAAC;QAED,OAAO,OAAO,CAAA;IAChB,CAAC,CAAA;AACH,CAAC;AAED,MAAM,CAAC,MAAM,8BAA8B,GAAG;IAC5C,aAAa;IACb,eAAe;IACf,aAAa;IACb,eAAe;IACf,aAAa;IACb,eAAe;IACf,uBAAuB;IACvB,yBAAyB;CAC1B,CAAA;AAED,MAAM,UAAU,mCAAmC,CACjD,QAAQ,GAAqB,8BAA8B;IAE3D,MAAM,OAAO,GAAG,IAAI,GAAG,CAAS,QAAQ,CAAC,CAAA;IAEzC,2EAA2E;IAC3E,kBAAkB;IAClB,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,OAAO,KAAK,EAAE,KAA6B,EAAE,IAAkB,EAAE,EAAE;QACjE,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;QAEtC,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAEzC,yBAAyB;QACzB,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,iBAAiB,CAAC,OAAO,EAAE,GAAG,EAAE,oBAAoB,CAAC,CAAA;QACjE,CAAC;QAED,wBAAwB;QACxB,IAAI,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAClC,OAAO,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC;YACrB,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;YAC5C,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS,EAAE,CAAC,EAAE,CAAC;gBAClC,MAAM,IAAI,iBAAiB,CAAC,OAAO,EAAE,GAAG,EAAE,oBAAoB,CAAC,CAAA;YACjE,CAAC;YACD,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,CAAC,CAAA;QAC5C,CAAC;QAED,OAAO,OAAO,CAAA;IAChB,CAAC,CAAA;AACH,CAAC","sourcesContent":["import { FetchError } from './fetch-error.js'\nimport { asRequest } from './fetch.js'\nimport { isIp } from './util.js'\n\nexport class FetchRequestError extends FetchError {\n constructor(\n public readonly request: Request,\n statusCode?: number,\n message?: string,\n options?: ErrorOptions,\n ) {\n if (statusCode == null || !message) {\n const info = extractInfo(extractRootCause(options?.cause))\n statusCode ??= info[0]\n message ||= info[1]\n }\n\n super(statusCode, message, options)\n }\n\n get expose() {\n // A 500 request error means that the request was not made due to an infra,\n // programming or server side issue. The message should no be exposed to\n // downstream clients.\n return this.statusCode !== 500\n }\n\n static from(request: Request, cause: unknown): FetchRequestError {\n if (cause instanceof FetchRequestError) return cause\n return new FetchRequestError(request, undefined, undefined, { cause })\n }\n}\n\nfunction extractRootCause(err: unknown): unknown {\n // Unwrap the Network error from undici (i.e. Node's internal fetch() implementation)\n // https://github.com/nodejs/undici/blob/3274c975947ce11a08508743df026f73598bfead/lib/web/fetch/index.js#L223-L228\n if (\n err instanceof TypeError &&\n err.message === 'fetch failed' &&\n err.cause !== undefined\n ) {\n return err.cause\n }\n\n return err\n}\n\nfunction extractInfo(err: unknown): [statusCode: number, message: string] {\n if (typeof err === 'string' && err.length > 0) {\n return [500, err]\n }\n\n if (!(err instanceof Error)) {\n return [500, 'Failed to fetch']\n }\n\n // Undici fetch() \"network\" errors\n switch (err.message) {\n case 'failed to fetch the data URL':\n return [400, err.message]\n case 'unexpected redirect':\n case 'cors failure':\n case 'blocked':\n case 'proxy authentication required':\n // These cases could be represented either as a 4xx user error (invalid\n // URL provided), or as a 5xx server error (server didn't behave as\n // expected).\n return [502, err.message]\n }\n\n // NodeJS errors\n const code = 'code' in err ? err.code : undefined\n if (typeof code === 'string') {\n switch (true) {\n case code === 'ENOTFOUND':\n return [400, 'Invalid hostname']\n case code === 'ECONNREFUSED':\n return [502, 'Connection refused']\n case code === 'DEPTH_ZERO_SELF_SIGNED_CERT':\n return [502, 'Self-signed certificate']\n case code.startsWith('ERR_TLS'):\n return [502, 'TLS error']\n case code.startsWith('ECONN'):\n return [502, 'Connection error']\n default:\n return [500, `${code} error`]\n }\n }\n\n return [500, err.message]\n}\n\nexport function protocolCheckRequestTransform(protocols: {\n 'about:'?: boolean\n 'blob:'?: boolean\n 'data:'?: boolean\n 'file:'?: boolean\n 'http:'?: boolean | { allowCustomPort: boolean }\n 'https:'?: boolean | { allowCustomPort: boolean }\n}) {\n return (input: Request | string | URL, init?: RequestInit) => {\n const request = asRequest(input, init)\n\n const { protocol, port } = new URL(request.url)\n\n const config: undefined | boolean | { allowCustomPort?: boolean } =\n Object.hasOwn(protocols, protocol)\n ? protocols[protocol as keyof typeof protocols]\n : undefined\n\n if (!config) {\n throw new FetchRequestError(\n request,\n 400,\n `Forbidden protocol \"${protocol}\"`,\n )\n } else if (config === true) {\n // Safe to proceed\n } else if (!config['allowCustomPort'] && port !== '') {\n throw new FetchRequestError(\n request,\n 400,\n `Custom ${protocol} ports not allowed`,\n )\n }\n\n return request\n }\n}\n\nexport function explicitRedirectCheckRequestTransform() {\n return (input: Request | string | URL, init?: RequestInit): Request => {\n const request = asRequest(input, init)\n\n // We want to avoid the case where the user of this code forgot to explicit\n // a redirect strategy.\n if (init?.redirect != null) return request\n\n // Sadly, if the `input` is a request, and `init` was omitted, there is no\n // way to tell if the `redirect === 'follow'` value comes from the user, or\n // fetch's default. In order to prevent accidental omission, this case is\n // forbidden.\n if (request.redirect === 'follow') {\n throw new FetchRequestError(\n request,\n 500,\n 'Request redirect must be \"error\" or \"manual\"',\n )\n }\n\n return request\n }\n}\n\nexport function requireHostHeaderTransform() {\n return (input: Request | string | URL, init?: RequestInit) => {\n // Note that fetch() will automatically add the Host header from the URL and\n // discard any Host header manually set in the request.\n\n const request = asRequest(input, init)\n\n const { protocol, hostname } = new URL(request.url)\n\n // \"Host\" header only makes sense in the context of an HTTP request\n if (protocol !== 'http:' && protocol !== 'https:') {\n throw new FetchRequestError(\n request,\n 400,\n `\"${protocol}\" requests are not allowed`,\n )\n }\n\n if (!hostname || isIp(hostname)) {\n throw new FetchRequestError(request, 400, 'Invalid hostname')\n }\n\n return request\n }\n}\n\nexport const DEFAULT_FORBIDDEN_DOMAIN_NAMES = [\n 'example.com',\n '*.example.com',\n 'example.org',\n '*.example.org',\n 'example.net',\n '*.example.net',\n 'googleusercontent.com',\n '*.googleusercontent.com',\n]\n\nexport function forbiddenDomainNameRequestTransform(\n denyList: Iterable<string> = DEFAULT_FORBIDDEN_DOMAIN_NAMES,\n) {\n const denySet = new Set<string>(denyList)\n\n // Optimization: if no forbidden domain names are provided, we can skip the\n // check entirely.\n if (denySet.size === 0) {\n return asRequest\n }\n\n return async (input: Request | string | URL, init?: RequestInit) => {\n const request = asRequest(input, init)\n\n const { hostname } = new URL(request.url)\n\n // Full domain name check\n if (denySet.has(hostname)) {\n throw new FetchRequestError(request, 403, 'Forbidden hostname')\n }\n\n // Sub domain name check\n let curDot = hostname.indexOf('.')\n while (curDot !== -1) {\n const subdomain = hostname.slice(curDot + 1)\n if (denySet.has(`*.${subdomain}`)) {\n throw new FetchRequestError(request, 403, 'Forbidden hostname')\n }\n curDot = hostname.indexOf('.', curDot + 1)\n }\n\n return request\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-response.d.ts","sourceRoot":"","sources":["../src/fetch-response.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAQ,MAAM,oBAAoB,CAAA;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,OAAO,EACL,IAAI,EAKL,MAAM,WAAW,CAAA;AAmBlB,MAAM,MAAM,mBAAmB,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAA;AACvD,MAAM,MAAM,qBAAqB,GAAG,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;AAE7E,qBAAa,kBAAmB,SAAQ,UAAU;aAE9B,QAAQ,EAAE,QAAQ;IADpC,YACkB,QAAQ,EAAE,QAAQ,EAClC,UAAU,GAAE,MAAwB,EACpC,OAAO,GAAE,MAA4B,EACrC,OAAO,CAAC,EAAE,YAAY,EAGvB;IAED,OAAa,IAAI,CACf,QAAQ,EAAE,QAAQ,EAClB,aAAa,GAAE,MAAM,GAAG,qBAA8C,EACtE,UAAU,SAAkB,EAC5B,OAAO,CAAC,EAAE,YAAY,+BAUvB;CACF;
|
|
1
|
+
{"version":3,"file":"fetch-response.d.ts","sourceRoot":"","sources":["../src/fetch-response.ts"],"names":[],"mappings":"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"}
|
package/dist/fetch-response.js
CHANGED
|
@@ -45,15 +45,21 @@ const extractResponseMessage = async (response) => {
|
|
|
45
45
|
if (typeof json === 'string')
|
|
46
46
|
return json;
|
|
47
47
|
if (typeof json === 'object' && json != null) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
if ('error_description' in json) {
|
|
49
|
+
const errorDescription = ifString(json.error_description);
|
|
50
|
+
if (errorDescription)
|
|
51
|
+
return errorDescription;
|
|
52
|
+
}
|
|
53
|
+
if ('error' in json) {
|
|
54
|
+
const error = ifString(json.error);
|
|
55
|
+
if (error)
|
|
56
|
+
return error;
|
|
57
|
+
}
|
|
58
|
+
if ('message' in json) {
|
|
59
|
+
const message = ifString(json.message);
|
|
60
|
+
if (message)
|
|
61
|
+
return message;
|
|
62
|
+
}
|
|
57
63
|
}
|
|
58
64
|
}
|
|
59
65
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-response.js","sourceRoot":"","sources":["../src/fetch-response.ts"],"names":[],"mappings":"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","sourcesContent":["import { Transformer, pipe } from '@atproto-labs/pipe'\nimport { FetchError } from './fetch-error.js'\nimport { TransformedResponse } from './transformed-response.js'\nimport {\n Json,\n MaxBytesTransformStream,\n cancelBody,\n ifString,\n logCancellationError,\n} from './util.js'\n\n/**\n * media-type = type \"/\" subtype *( \";\" parameter )\n * type = token\n * subtype = token\n * token = 1*<any CHAR except CTLs or separators>\n * separators = \"(\" | \")\" | \"<\" | \">\" | \"@\"\n * | \",\" | \";\" | \":\" | \"\\\" | <\">\n * | \"/\" | \"[\" | \"]\" | \"?\" | \"=\"\n * | \"{\" | \"}\" | SP | HT\n * CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>\n * SP = <US-ASCII SP, space (32)>\n * HT = <US-ASCII HT, horizontal-tab (9)>\n * @note The type, subtype, and parameter attribute names are case-insensitive.\n * @see {@link https://datatracker.ietf.org/doc/html/rfc2616#autoid-23}\n */\nconst JSON_MIME = /^application\\/(?:[^()<>@,;:/[\\]\\\\?={} \\t]+\\+)?json$/i\n\nexport type ResponseTransformer = Transformer<Response>\nexport type ResponseMessageGetter = Transformer<Response, string | undefined>\n\nexport class FetchResponseError extends FetchError {\n constructor(\n public readonly response: Response,\n statusCode: number = response.status,\n message: string = response.statusText,\n options?: ErrorOptions,\n ) {\n super(statusCode, message, options)\n }\n\n static async from(\n response: Response,\n customMessage: string | ResponseMessageGetter = extractResponseMessage,\n statusCode = response.status,\n options?: ErrorOptions,\n ) {\n const message =\n typeof customMessage === 'string'\n ? customMessage\n : typeof customMessage === 'function'\n ? await customMessage(response)\n : undefined\n\n return new FetchResponseError(response, statusCode, message, options)\n }\n}\n\nconst extractResponseMessage: ResponseMessageGetter = async (response) => {\n const mimeType = extractMime(response)\n if (!mimeType) return undefined\n\n try {\n if (mimeType === 'text/plain') {\n return await response.text()\n } else if (JSON_MIME.test(mimeType)) {\n const json: unknown = await response.json()\n\n if (typeof json === 'string') return json\n if (typeof json === 'object' && json != null) {\n const errorDescription = ifString(json['error_description'])\n if (errorDescription) return errorDescription\n\n const error = ifString(json['error'])\n if (error) return error\n\n const message = ifString(json['message'])\n if (message) return message\n }\n }\n } catch {\n // noop\n }\n\n return undefined\n}\n\nexport async function peekJson(\n response: Response,\n maxSize = Infinity,\n): Promise<undefined | Json> {\n const type = extractMime(response)\n if (type !== 'application/json') return undefined\n checkLength(response, maxSize)\n\n // 1) Clone the request so we can consume the body\n const clonedResponse = response.clone()\n\n // 2) Make sure the request's body is not too large\n const limitedResponse =\n response.body && maxSize < Infinity\n ? new TransformedResponse(\n clonedResponse,\n new MaxBytesTransformStream(maxSize),\n )\n : // Note: some runtimes (e.g. react-native) don't expose a body property\n clonedResponse\n\n // 3) Parse the JSON\n return limitedResponse.json()\n}\n\nexport function checkLength(response: Response, maxBytes: number) {\n // Note: negation accounts for invalid value types (NaN, non numbers)\n if (!(maxBytes >= 0)) {\n throw new TypeError('maxBytes must be a non-negative number')\n }\n const length = extractLength(response)\n if (length != null && length > maxBytes) {\n throw new FetchResponseError(response, 502, 'Response too large')\n }\n return length\n}\n\nexport function extractLength(response: Response) {\n const contentLength = response.headers.get('Content-Length')\n if (contentLength == null) return undefined\n if (!/^\\d+$/.test(contentLength)) {\n throw new FetchResponseError(response, 502, 'Invalid Content-Length')\n }\n const length = Number(contentLength)\n if (!Number.isSafeInteger(length)) {\n throw new FetchResponseError(response, 502, 'Content-Length too large')\n }\n return length\n}\n\nexport function extractMime(response: Response) {\n const contentType = response.headers.get('Content-Type')\n if (contentType == null) return undefined\n\n return contentType.split(';', 1)[0]!.trim()\n}\n\n/**\n * If the transformer results in an error, ensure that the response body is\n * consumed as, in some environments (Node 👀), the response will not\n * automatically be GC'd.\n *\n * @see {@link https://undici.nodejs.org/#/?id=garbage-collection}\n * @param [onCancellationError] - Callback to handle any async body cancelling\n * error. Defaults to logging the error. Do not use `null` if the request is\n * cloned.\n */\nexport function cancelBodyOnError<T>(\n transformer: Transformer<Response, T>,\n onCancellationError: null | ((err: unknown) => void) = logCancellationError,\n): (response: Response) => Promise<T> {\n return async (response) => {\n try {\n return await transformer(response)\n } catch (err) {\n await cancelBody(response, onCancellationError ?? undefined)\n throw err\n }\n }\n}\n\nexport function fetchOkProcessor(\n customMessage?: string | ResponseMessageGetter,\n): ResponseTransformer {\n return cancelBodyOnError((response) => {\n return fetchOkTransformer(response, customMessage)\n })\n}\n\nexport async function fetchOkTransformer(\n response: Response,\n customMessage?: string | ResponseMessageGetter,\n) {\n if (response.ok) return response\n throw await FetchResponseError.from(response, customMessage)\n}\n\nexport function fetchMaxSizeProcessor(maxBytes: number): ResponseTransformer {\n if (maxBytes === Infinity) return (response) => response\n if (!Number.isFinite(maxBytes) || maxBytes < 0) {\n throw new TypeError('maxBytes must be a 0, Infinity or a positive number')\n }\n return cancelBodyOnError((response) => {\n return fetchResponseMaxSizeChecker(response, maxBytes)\n })\n}\n\nexport function fetchResponseMaxSizeChecker(\n response: Response,\n maxBytes: number,\n): Response {\n if (maxBytes === Infinity) return response\n checkLength(response, maxBytes)\n\n // Some engines (react-native 👀) don't expose a body property. In that case,\n // we will only rely on the Content-Length header.\n if (!response.body) return response\n\n const transform = new MaxBytesTransformStream(maxBytes)\n return new TransformedResponse(response, transform)\n}\n\nexport type MimeTypeCheckFn = (mimeType: string) => boolean\nexport type MimeTypeCheck = string | RegExp | MimeTypeCheckFn\n\nexport function fetchTypeProcessor(\n expectedMime: MimeTypeCheck,\n contentTypeRequired = true,\n): ResponseTransformer {\n const isExpected: MimeTypeCheckFn =\n typeof expectedMime === 'string'\n ? (mimeType) => mimeType === expectedMime\n : expectedMime instanceof RegExp\n ? (mimeType) => expectedMime.test(mimeType)\n : expectedMime\n\n return cancelBodyOnError((response) => {\n return fetchResponseTypeChecker(response, isExpected, contentTypeRequired)\n })\n}\n\nexport async function fetchResponseTypeChecker(\n response: Response,\n isExpectedMime: MimeTypeCheckFn,\n contentTypeRequired = true,\n): Promise<Response> {\n const mimeType = extractMime(response)\n if (mimeType) {\n if (!isExpectedMime(mimeType.toLowerCase())) {\n throw await FetchResponseError.from(\n response,\n `Unexpected response Content-Type (${mimeType})`,\n 502,\n )\n }\n } else if (contentTypeRequired) {\n throw await FetchResponseError.from(\n response,\n 'Missing response Content-Type header',\n 502,\n )\n }\n\n return response\n}\n\nexport type ParsedJsonResponse<T = Json> = {\n response: Response\n json: T\n}\n\nexport async function fetchResponseJsonTransformer<T = Json>(\n response: Response,\n): Promise<ParsedJsonResponse<T>> {\n try {\n const json = (await response.json()) as T\n return { response, json }\n } catch (cause) {\n throw new FetchResponseError(\n response,\n 502,\n 'Unable to parse response as JSON',\n { cause },\n )\n }\n}\n\nexport function fetchJsonProcessor<T = Json>(\n expectedMime: MimeTypeCheck = JSON_MIME,\n contentTypeRequired = true,\n): Transformer<Response, ParsedJsonResponse<T>> {\n return pipe(\n fetchTypeProcessor(expectedMime, contentTypeRequired),\n cancelBodyOnError(fetchResponseJsonTransformer<T>),\n )\n}\n\nexport type SyncValidationSchema<S, P = unknown> = {\n parse(value: unknown, params?: P): S\n}\n\nexport type AsyncValidationSchema<S, P = unknown> = {\n parseAsync(value: unknown, params?: P): Promise<S>\n}\n\nexport function fetchJsonValidatorProcessor<S, P = unknown>(\n schema: SyncValidationSchema<S, P> | AsyncValidationSchema<S, P>,\n params?: P,\n): Transformer<ParsedJsonResponse, S> {\n if ('parseAsync' in schema && typeof schema.parseAsync === 'function') {\n return async (jsonResponse: ParsedJsonResponse): Promise<S> =>\n schema.parseAsync(jsonResponse.json, params)\n }\n\n if ('parse' in schema && typeof schema.parse === 'function') {\n return async (jsonResponse: ParsedJsonResponse): Promise<S> =>\n schema.parse(jsonResponse.json, params)\n }\n\n // Needed for type safety (and allows fool proofing the usage of this function)\n throw new TypeError('Invalid schema')\n}\n\n/** @note Use {@link fetchJsonValidatorProcessor} instead */\nexport const fetchJsonZodProcessor = fetchJsonValidatorProcessor\n"]}
|
|
1
|
+
{"version":3,"file":"fetch-response.js","sourceRoot":"","sources":["../src/fetch-response.ts"],"names":[],"mappings":"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","sourcesContent":["import { Transformer, pipe } from '@atproto-labs/pipe'\nimport { FetchError } from './fetch-error.js'\nimport { TransformedResponse } from './transformed-response.js'\nimport {\n Json,\n MaxBytesTransformStream,\n cancelBody,\n ifString,\n logCancellationError,\n} from './util.js'\n\n/**\n * media-type = type \"/\" subtype *( \";\" parameter )\n * type = token\n * subtype = token\n * token = 1*<any CHAR except CTLs or separators>\n * separators = \"(\" | \")\" | \"<\" | \">\" | \"@\"\n * | \",\" | \";\" | \":\" | \"\\\" | <\">\n * | \"/\" | \"[\" | \"]\" | \"?\" | \"=\"\n * | \"{\" | \"}\" | SP | HT\n * CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>\n * SP = <US-ASCII SP, space (32)>\n * HT = <US-ASCII HT, horizontal-tab (9)>\n * @note The type, subtype, and parameter attribute names are case-insensitive.\n * @see {@link https://datatracker.ietf.org/doc/html/rfc2616#autoid-23}\n */\nconst JSON_MIME = /^application\\/(?:[^()<>@,;:/[\\]\\\\?={} \\t]+\\+)?json$/i\n\nexport type ResponseTransformer = Transformer<Response>\nexport type ResponseMessageGetter = Transformer<Response, string | undefined>\n\nexport class FetchResponseError extends FetchError {\n constructor(\n public readonly response: Response,\n statusCode: number = response.status,\n message: string = response.statusText,\n options?: ErrorOptions,\n ) {\n super(statusCode, message, options)\n }\n\n static async from(\n response: Response,\n customMessage: string | ResponseMessageGetter = extractResponseMessage,\n statusCode = response.status,\n options?: ErrorOptions,\n ) {\n const message =\n typeof customMessage === 'string'\n ? customMessage\n : typeof customMessage === 'function'\n ? await customMessage(response)\n : undefined\n\n return new FetchResponseError(response, statusCode, message, options)\n }\n}\n\nconst extractResponseMessage: ResponseMessageGetter = async (response) => {\n const mimeType = extractMime(response)\n if (!mimeType) return undefined\n\n try {\n if (mimeType === 'text/plain') {\n return await response.text()\n } else if (JSON_MIME.test(mimeType)) {\n const json: unknown = await response.json()\n\n if (typeof json === 'string') return json\n if (typeof json === 'object' && json != null) {\n if ('error_description' in json) {\n const errorDescription = ifString(json.error_description)\n if (errorDescription) return errorDescription\n }\n\n if ('error' in json) {\n const error = ifString(json.error)\n if (error) return error\n }\n\n if ('message' in json) {\n const message = ifString(json.message)\n if (message) return message\n }\n }\n }\n } catch {\n // noop\n }\n\n return undefined\n}\n\nexport async function peekJson(\n response: Response,\n maxSize = Infinity,\n): Promise<undefined | Json> {\n const type = extractMime(response)\n if (type !== 'application/json') return undefined\n checkLength(response, maxSize)\n\n // 1) Clone the request so we can consume the body\n const clonedResponse = response.clone()\n\n // 2) Make sure the request's body is not too large\n const limitedResponse =\n response.body && maxSize < Infinity\n ? new TransformedResponse(\n clonedResponse,\n new MaxBytesTransformStream(maxSize),\n )\n : // Note: some runtimes (e.g. react-native) don't expose a body property\n clonedResponse\n\n // 3) Parse the JSON\n return limitedResponse.json()\n}\n\nexport function checkLength(response: Response, maxBytes: number) {\n // Note: negation accounts for invalid value types (NaN, non numbers)\n if (!(maxBytes >= 0)) {\n throw new TypeError('maxBytes must be a non-negative number')\n }\n const length = extractLength(response)\n if (length != null && length > maxBytes) {\n throw new FetchResponseError(response, 502, 'Response too large')\n }\n return length\n}\n\nexport function extractLength(response: Response) {\n const contentLength = response.headers.get('Content-Length')\n if (contentLength == null) return undefined\n if (!/^\\d+$/.test(contentLength)) {\n throw new FetchResponseError(response, 502, 'Invalid Content-Length')\n }\n const length = Number(contentLength)\n if (!Number.isSafeInteger(length)) {\n throw new FetchResponseError(response, 502, 'Content-Length too large')\n }\n return length\n}\n\nexport function extractMime(response: Response) {\n const contentType = response.headers.get('Content-Type')\n if (contentType == null) return undefined\n\n return contentType.split(';', 1)[0]!.trim()\n}\n\n/**\n * If the transformer results in an error, ensure that the response body is\n * consumed as, in some environments (Node 👀), the response will not\n * automatically be GC'd.\n *\n * @see {@link https://undici.nodejs.org/#/?id=garbage-collection}\n * @param [onCancellationError] - Callback to handle any async body cancelling\n * error. Defaults to logging the error. Do not use `null` if the request is\n * cloned.\n */\nexport function cancelBodyOnError<T>(\n transformer: Transformer<Response, T>,\n onCancellationError: null | ((err: unknown) => void) = logCancellationError,\n): (response: Response) => Promise<T> {\n return async (response) => {\n try {\n return await transformer(response)\n } catch (err) {\n await cancelBody(response, onCancellationError ?? undefined)\n throw err\n }\n }\n}\n\nexport function fetchOkProcessor(\n customMessage?: string | ResponseMessageGetter,\n): ResponseTransformer {\n return cancelBodyOnError((response) => {\n return fetchOkTransformer(response, customMessage)\n })\n}\n\nexport async function fetchOkTransformer(\n response: Response,\n customMessage?: string | ResponseMessageGetter,\n) {\n if (response.ok) return response\n throw await FetchResponseError.from(response, customMessage)\n}\n\nexport function fetchMaxSizeProcessor(maxBytes: number): ResponseTransformer {\n if (maxBytes === Infinity) return (response) => response\n if (!Number.isFinite(maxBytes) || maxBytes < 0) {\n throw new TypeError('maxBytes must be a 0, Infinity or a positive number')\n }\n return cancelBodyOnError((response) => {\n return fetchResponseMaxSizeChecker(response, maxBytes)\n })\n}\n\nexport function fetchResponseMaxSizeChecker(\n response: Response,\n maxBytes: number,\n): Response {\n if (maxBytes === Infinity) return response\n checkLength(response, maxBytes)\n\n // Some engines (react-native 👀) don't expose a body property. In that case,\n // we will only rely on the Content-Length header.\n if (!response.body) return response\n\n const transform = new MaxBytesTransformStream(maxBytes)\n return new TransformedResponse(response, transform)\n}\n\nexport type MimeTypeCheckFn = (mimeType: string) => boolean\nexport type MimeTypeCheck = string | RegExp | MimeTypeCheckFn\n\nexport function fetchTypeProcessor(\n expectedMime: MimeTypeCheck,\n contentTypeRequired = true,\n): ResponseTransformer {\n const isExpected: MimeTypeCheckFn =\n typeof expectedMime === 'string'\n ? (mimeType) => mimeType === expectedMime\n : expectedMime instanceof RegExp\n ? (mimeType) => expectedMime.test(mimeType)\n : expectedMime\n\n return cancelBodyOnError((response) => {\n return fetchResponseTypeChecker(response, isExpected, contentTypeRequired)\n })\n}\n\nexport async function fetchResponseTypeChecker(\n response: Response,\n isExpectedMime: MimeTypeCheckFn,\n contentTypeRequired = true,\n): Promise<Response> {\n const mimeType = extractMime(response)\n if (mimeType) {\n if (!isExpectedMime(mimeType.toLowerCase())) {\n throw await FetchResponseError.from(\n response,\n `Unexpected response Content-Type (${mimeType})`,\n 502,\n )\n }\n } else if (contentTypeRequired) {\n throw await FetchResponseError.from(\n response,\n 'Missing response Content-Type header',\n 502,\n )\n }\n\n return response\n}\n\nexport type ParsedJsonResponse<T = Json> = {\n response: Response\n json: T\n}\n\nexport async function fetchResponseJsonTransformer<T = Json>(\n response: Response,\n): Promise<ParsedJsonResponse<T>> {\n try {\n const json = (await response.json()) as T\n return { response, json }\n } catch (cause) {\n throw new FetchResponseError(\n response,\n 502,\n 'Unable to parse response as JSON',\n { cause },\n )\n }\n}\n\nexport function fetchJsonProcessor<T = Json>(\n expectedMime: MimeTypeCheck = JSON_MIME,\n contentTypeRequired = true,\n): Transformer<Response, ParsedJsonResponse<T>> {\n return pipe(\n fetchTypeProcessor(expectedMime, contentTypeRequired),\n cancelBodyOnError(fetchResponseJsonTransformer<T>),\n )\n}\n\nexport type SyncValidationSchema<S, P = unknown> = {\n parse(value: unknown, params?: P): S\n}\n\nexport type AsyncValidationSchema<S, P = unknown> = {\n parseAsync(value: unknown, params?: P): Promise<S>\n}\n\nexport function fetchJsonValidatorProcessor<S, P = unknown>(\n schema: SyncValidationSchema<S, P> | AsyncValidationSchema<S, P>,\n params?: P,\n): Transformer<ParsedJsonResponse, S> {\n if ('parseAsync' in schema && typeof schema.parseAsync === 'function') {\n return async (jsonResponse: ParsedJsonResponse): Promise<S> =>\n schema.parseAsync(jsonResponse.json, params)\n }\n\n if ('parse' in schema && typeof schema.parse === 'function') {\n return async (jsonResponse: ParsedJsonResponse): Promise<S> =>\n schema.parse(jsonResponse.json, params)\n }\n\n // Needed for type safety (and allows fool proofing the usage of this function)\n throw new TypeError('Invalid schema')\n}\n\n/** @note Use {@link fetchJsonValidatorProcessor} instead */\nexport const fetchJsonZodProcessor = fetchJsonValidatorProcessor\n"]}
|
package/dist/fetch-wrap.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-wrap.js","sourceRoot":"","sources":["../src/fetch-wrap.ts"],"names":[],"mappings":"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","sourcesContent":["import { FetchRequestError } from './fetch-request.js'\nimport { Fetch, FetchContext, toRequestTransformer } from './fetch.js'\nimport { TransformedResponse } from './transformed-response.js'\nimport { padLines, stringifyMessage } from './util.js'\n\ntype LogFn<Args extends unknown[]> = (...args: Args) => void | PromiseLike<void>\n\nexport function loggedFetch<C = FetchContext>({\n fetch = globalThis.fetch as Fetch<C>,\n logRequest = true as boolean | LogFn<[request: Request]>,\n logResponse = true as boolean | LogFn<[response: Response, request: Request]>,\n logError = true as boolean | LogFn<[error: unknown, request: Request]>,\n}) {\n const onRequest =\n logRequest === true\n ? async (request) => {\n const requestMessage = await stringifyMessage(request)\n console.info(\n `> ${request.method} ${request.url}\\n${padLines(requestMessage, ' ')}`,\n )\n }\n : logRequest || undefined\n\n const onResponse =\n logResponse === true\n ? async (response) => {\n const responseMessage = await stringifyMessage(response.clone())\n console.info(\n `< HTTP/1.1 ${response.status} ${response.statusText}\\n${padLines(responseMessage, ' ')}`,\n )\n }\n : logResponse || undefined\n\n const onError =\n logError === true\n ? async (error) => {\n console.error(`< Error:`, error)\n }\n : logError || undefined\n\n if (!onRequest && !onResponse && !onError) return fetch\n\n return toRequestTransformer(async function (\n this: C,\n request,\n ): Promise<Response> {\n if (onRequest) await onRequest(request)\n\n try {\n const response = await fetch.call(this, request)\n\n if (onResponse) await onResponse(response, request)\n\n return response\n } catch (error) {\n if (onError) await onError(error, request)\n\n throw error\n }\n })\n}\n\nexport const timedFetch = <C = FetchContext>(\n timeout = 60e3,\n fetch: Fetch<C> = globalThis.fetch,\n): Fetch<C> => {\n if (timeout === Infinity) return fetch\n if (!Number.isFinite(timeout) || timeout <= 0) {\n throw new TypeError('Timeout must be positive')\n }\n return toRequestTransformer(async function (\n this: C,\n request,\n ): Promise<Response> {\n const controller = new AbortController()\n const signal = controller.signal\n\n const abort = () => {\n controller.abort()\n }\n const cleanup = () => {\n clearTimeout(timer)\n request.signal?.removeEventListener('abort', abort)\n }\n\n const timer = setTimeout(abort, timeout)\n if (typeof timer === 'object') timer.unref?.() // only on node\n request.signal?.addEventListener('abort', abort)\n\n signal.addEventListener('abort', cleanup)\n\n const response = await fetch.call(this, request, { signal })\n\n if (!response.body) {\n cleanup()\n return response\n } else {\n // Cleanup the timer & event listeners when the body stream is closed\n const transform = new TransformStream({ flush: cleanup })\n return new TransformedResponse(response, transform)\n }\n })\n}\n\n/**\n * Wraps a fetch function to bind it to a specific context, and wrap any thrown\n * errors into a FetchRequestError.\n *\n * @example\n *\n * ```ts\n * class MyClient {\n * constructor(private fetch = globalThis.fetch) {}\n *\n * async get(url: string) {\n * // This will generate an error, because the context used is not a\n * // FetchContext (it's a MyClient instance).\n * return this.fetch(url)\n * }\n * }\n * ```\n *\n * @example\n *\n * ```ts\n * class MyClient {\n * private fetch: Fetch<unknown>\n *\n * constructor(fetch = globalThis.fetch) {\n * this.fetch = bindFetch(fetch)\n * }\n *\n * async get(url: string) {\n * return this.fetch(url) // no more error\n * }\n * }\n * ```\n */\nexport function bindFetch<C = FetchContext>(\n fetch: Fetch<C> = globalThis.fetch,\n context: C = globalThis as C,\n) {\n return toRequestTransformer(async (request) => {\n try {\n return await fetch.call(context, request)\n } catch (err) {\n throw FetchRequestError.from(request, err)\n }\n })\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fetch-wrap.js","sourceRoot":"","sources":["../src/fetch-wrap.ts"],"names":[],"mappings":"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","sourcesContent":["import { FetchRequestError } from './fetch-request.js'\nimport { Fetch, FetchContext, toRequestTransformer } from './fetch.js'\nimport { TransformedResponse } from './transformed-response.js'\nimport { padLines, stringifyMessage } from './util.js'\n\ntype LogFn<Args extends unknown[]> = (...args: Args) => void | PromiseLike<void>\n\nexport function loggedFetch<C = FetchContext>({\n fetch = globalThis.fetch as Fetch<C>,\n logRequest = true as boolean | LogFn<[request: Request]>,\n logResponse = true as boolean | LogFn<[response: Response, request: Request]>,\n logError = true as boolean | LogFn<[error: unknown, request: Request]>,\n}) {\n const onRequest: undefined | LogFn<[request: Request]> =\n logRequest === true\n ? async (request) => {\n const requestMessage = await stringifyMessage(request)\n console.info(\n `> ${request.method} ${request.url}\\n${padLines(requestMessage, ' ')}`,\n )\n }\n : logRequest || undefined\n\n const onResponse: undefined | LogFn<[response: Response, request: Request]> =\n logResponse === true\n ? async (response) => {\n const responseMessage = await stringifyMessage(response.clone())\n console.info(\n `< HTTP/1.1 ${response.status} ${response.statusText}\\n${padLines(responseMessage, ' ')}`,\n )\n }\n : logResponse || undefined\n\n const onError: undefined | LogFn<[error: unknown, request: Request]> =\n logError === true\n ? async (error) => {\n console.error(`< Error:`, error)\n }\n : logError || undefined\n\n if (!onRequest && !onResponse && !onError) return fetch\n\n return toRequestTransformer(async function (\n this: C,\n request,\n ): Promise<Response> {\n if (onRequest) await onRequest(request)\n\n try {\n const response = await fetch.call(this, request)\n\n if (onResponse) await onResponse(response, request)\n\n return response\n } catch (error) {\n if (onError) await onError(error, request)\n\n throw error\n }\n })\n}\n\nexport const timedFetch = <C = FetchContext>(\n timeout = 60e3,\n fetch: Fetch<C> = globalThis.fetch,\n): Fetch<C> => {\n if (timeout === Infinity) return fetch\n if (!Number.isFinite(timeout) || timeout <= 0) {\n throw new TypeError('Timeout must be positive')\n }\n return toRequestTransformer(async function (\n this: C,\n request,\n ): Promise<Response> {\n const controller = new AbortController()\n const signal = controller.signal\n\n const abort = () => {\n controller.abort()\n }\n const cleanup = () => {\n clearTimeout(timer)\n request.signal?.removeEventListener('abort', abort)\n }\n\n const timer = setTimeout(abort, timeout)\n if (typeof timer === 'object') timer.unref?.() // only on node\n request.signal?.addEventListener('abort', abort)\n\n signal.addEventListener('abort', cleanup)\n\n const response = await fetch.call(this, request, { signal })\n\n if (!response.body) {\n cleanup()\n return response\n } else {\n // Cleanup the timer & event listeners when the body stream is closed\n const transform = new TransformStream({ flush: cleanup })\n return new TransformedResponse(response, transform)\n }\n })\n}\n\n/**\n * Wraps a fetch function to bind it to a specific context, and wrap any thrown\n * errors into a FetchRequestError.\n *\n * @example\n *\n * ```ts\n * class MyClient {\n * constructor(private fetch = globalThis.fetch) {}\n *\n * async get(url: string) {\n * // This will generate an error, because the context used is not a\n * // FetchContext (it's a MyClient instance).\n * return this.fetch(url)\n * }\n * }\n * ```\n *\n * @example\n *\n * ```ts\n * class MyClient {\n * private fetch: Fetch<unknown>\n *\n * constructor(fetch = globalThis.fetch) {\n * this.fetch = bindFetch(fetch)\n * }\n *\n * async get(url: string) {\n * return this.fetch(url) // no more error\n * }\n * }\n * ```\n */\nexport function bindFetch<C = FetchContext>(\n fetch: Fetch<C> = globalThis.fetch,\n context: C = globalThis as C,\n) {\n return toRequestTransformer(async (request) => {\n try {\n return await fetch.call(context, request)\n } catch (err) {\n throw FetchRequestError.from(request, err)\n }\n })\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto-labs/fetch",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"engines": {
|
|
5
|
-
"node": ">=22"
|
|
6
|
-
},
|
|
3
|
+
"version": "0.3.3",
|
|
7
4
|
"license": "MIT",
|
|
8
5
|
"description": "Isomorphic wrapper utilities for fetch API",
|
|
9
6
|
"keywords": [
|
|
@@ -16,6 +13,10 @@
|
|
|
16
13
|
"url": "https://github.com/bluesky-social/atproto",
|
|
17
14
|
"directory": "packages/internal/fetch"
|
|
18
15
|
},
|
|
16
|
+
"files": [
|
|
17
|
+
"./dist",
|
|
18
|
+
"./CHANGELOG.md"
|
|
19
|
+
],
|
|
19
20
|
"type": "module",
|
|
20
21
|
"exports": {
|
|
21
22
|
".": {
|
|
@@ -23,10 +24,12 @@
|
|
|
23
24
|
"default": "./dist/index.js"
|
|
24
25
|
}
|
|
25
26
|
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=22"
|
|
29
|
+
},
|
|
26
30
|
"dependencies": {
|
|
27
|
-
"@atproto-labs/pipe": "^0.2.
|
|
31
|
+
"@atproto-labs/pipe": "^0.2.3"
|
|
28
32
|
},
|
|
29
|
-
"devDependencies": {},
|
|
30
33
|
"scripts": {
|
|
31
34
|
"build": "tsgo --build tsconfig.json"
|
|
32
35
|
}
|
package/src/fetch-error.ts
DELETED
package/src/fetch-request.ts
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import { FetchError } from './fetch-error.js'
|
|
2
|
-
import { asRequest } from './fetch.js'
|
|
3
|
-
import { extractUrl, isIp } from './util.js'
|
|
4
|
-
|
|
5
|
-
export class FetchRequestError extends FetchError {
|
|
6
|
-
constructor(
|
|
7
|
-
public readonly request: Request,
|
|
8
|
-
statusCode?: number,
|
|
9
|
-
message?: string,
|
|
10
|
-
options?: ErrorOptions,
|
|
11
|
-
) {
|
|
12
|
-
if (statusCode == null || !message) {
|
|
13
|
-
const info = extractInfo(extractRootCause(options?.cause))
|
|
14
|
-
statusCode ??= info[0]
|
|
15
|
-
message ||= info[1]
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
super(statusCode, message, options)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
get expose() {
|
|
22
|
-
// A 500 request error means that the request was not made due to an infra,
|
|
23
|
-
// programming or server side issue. The message should no be exposed to
|
|
24
|
-
// downstream clients.
|
|
25
|
-
return this.statusCode !== 500
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
static from(request: Request, cause: unknown): FetchRequestError {
|
|
29
|
-
if (cause instanceof FetchRequestError) return cause
|
|
30
|
-
return new FetchRequestError(request, undefined, undefined, { cause })
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function extractRootCause(err: unknown): unknown {
|
|
35
|
-
// Unwrap the Network error from undici (i.e. Node's internal fetch() implementation)
|
|
36
|
-
// https://github.com/nodejs/undici/blob/3274c975947ce11a08508743df026f73598bfead/lib/web/fetch/index.js#L223-L228
|
|
37
|
-
if (
|
|
38
|
-
err instanceof TypeError &&
|
|
39
|
-
err.message === 'fetch failed' &&
|
|
40
|
-
err.cause !== undefined
|
|
41
|
-
) {
|
|
42
|
-
return err.cause
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return err
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function extractInfo(err: unknown): [statusCode: number, message: string] {
|
|
49
|
-
if (typeof err === 'string' && err.length > 0) {
|
|
50
|
-
return [500, err]
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (!(err instanceof Error)) {
|
|
54
|
-
return [500, 'Failed to fetch']
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Undici fetch() "network" errors
|
|
58
|
-
switch (err.message) {
|
|
59
|
-
case 'failed to fetch the data URL':
|
|
60
|
-
return [400, err.message]
|
|
61
|
-
case 'unexpected redirect':
|
|
62
|
-
case 'cors failure':
|
|
63
|
-
case 'blocked':
|
|
64
|
-
case 'proxy authentication required':
|
|
65
|
-
// These cases could be represented either as a 4xx user error (invalid
|
|
66
|
-
// URL provided), or as a 5xx server error (server didn't behave as
|
|
67
|
-
// expected).
|
|
68
|
-
return [502, err.message]
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// NodeJS errors
|
|
72
|
-
const code = err['code']
|
|
73
|
-
if (typeof code === 'string') {
|
|
74
|
-
switch (true) {
|
|
75
|
-
case code === 'ENOTFOUND':
|
|
76
|
-
return [400, 'Invalid hostname']
|
|
77
|
-
case code === 'ECONNREFUSED':
|
|
78
|
-
return [502, 'Connection refused']
|
|
79
|
-
case code === 'DEPTH_ZERO_SELF_SIGNED_CERT':
|
|
80
|
-
return [502, 'Self-signed certificate']
|
|
81
|
-
case code.startsWith('ERR_TLS'):
|
|
82
|
-
return [502, 'TLS error']
|
|
83
|
-
case code.startsWith('ECONN'):
|
|
84
|
-
return [502, 'Connection error']
|
|
85
|
-
default:
|
|
86
|
-
return [500, `${code} error`]
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return [500, err.message]
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function protocolCheckRequestTransform(protocols: {
|
|
94
|
-
'about:'?: boolean
|
|
95
|
-
'blob:'?: boolean
|
|
96
|
-
'data:'?: boolean
|
|
97
|
-
'file:'?: boolean
|
|
98
|
-
'http:'?: boolean | { allowCustomPort: boolean }
|
|
99
|
-
'https:'?: boolean | { allowCustomPort: boolean }
|
|
100
|
-
}) {
|
|
101
|
-
return (input: Request | string | URL, init?: RequestInit) => {
|
|
102
|
-
const { protocol, port } = extractUrl(input)
|
|
103
|
-
|
|
104
|
-
const request = asRequest(input, init)
|
|
105
|
-
|
|
106
|
-
const config: undefined | boolean | { allowCustomPort?: boolean } =
|
|
107
|
-
Object.hasOwn(protocols, protocol) ? protocols[protocol] : undefined
|
|
108
|
-
|
|
109
|
-
if (!config) {
|
|
110
|
-
throw new FetchRequestError(
|
|
111
|
-
request,
|
|
112
|
-
400,
|
|
113
|
-
`Forbidden protocol "${protocol}"`,
|
|
114
|
-
)
|
|
115
|
-
} else if (config === true) {
|
|
116
|
-
// Safe to proceed
|
|
117
|
-
} else if (!config['allowCustomPort'] && port !== '') {
|
|
118
|
-
throw new FetchRequestError(
|
|
119
|
-
request,
|
|
120
|
-
400,
|
|
121
|
-
`Custom ${protocol} ports not allowed`,
|
|
122
|
-
)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return request
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function explicitRedirectCheckRequestTransform() {
|
|
130
|
-
return (input: Request | string | URL, init?: RequestInit): Request => {
|
|
131
|
-
const request = asRequest(input, init)
|
|
132
|
-
|
|
133
|
-
// We want to avoid the case where the user of this code forgot to explicit
|
|
134
|
-
// a redirect strategy.
|
|
135
|
-
if (init?.redirect != null) return request
|
|
136
|
-
|
|
137
|
-
// Sadly, if the `input` is a request, and `init` was omitted, there is no
|
|
138
|
-
// way to tell if the `redirect === 'follow'` value comes from the user, or
|
|
139
|
-
// fetch's default. In order to prevent accidental omission, this case is
|
|
140
|
-
// forbidden.
|
|
141
|
-
if (request.redirect === 'follow') {
|
|
142
|
-
throw new FetchRequestError(
|
|
143
|
-
request,
|
|
144
|
-
500,
|
|
145
|
-
'Request redirect must be "error" or "manual"',
|
|
146
|
-
)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return request
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export function requireHostHeaderTransform() {
|
|
154
|
-
return (input: Request | string | URL, init?: RequestInit) => {
|
|
155
|
-
// Note that fetch() will automatically add the Host header from the URL and
|
|
156
|
-
// discard any Host header manually set in the request.
|
|
157
|
-
|
|
158
|
-
const { protocol, hostname } = extractUrl(input)
|
|
159
|
-
|
|
160
|
-
const request = asRequest(input, init)
|
|
161
|
-
|
|
162
|
-
// "Host" header only makes sense in the context of an HTTP request
|
|
163
|
-
if (protocol !== 'http:' && protocol !== 'https:') {
|
|
164
|
-
throw new FetchRequestError(
|
|
165
|
-
request,
|
|
166
|
-
400,
|
|
167
|
-
`"${protocol}" requests are not allowed`,
|
|
168
|
-
)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (!hostname || isIp(hostname)) {
|
|
172
|
-
throw new FetchRequestError(request, 400, 'Invalid hostname')
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return request
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export const DEFAULT_FORBIDDEN_DOMAIN_NAMES = [
|
|
180
|
-
'example.com',
|
|
181
|
-
'*.example.com',
|
|
182
|
-
'example.org',
|
|
183
|
-
'*.example.org',
|
|
184
|
-
'example.net',
|
|
185
|
-
'*.example.net',
|
|
186
|
-
'googleusercontent.com',
|
|
187
|
-
'*.googleusercontent.com',
|
|
188
|
-
]
|
|
189
|
-
|
|
190
|
-
export function forbiddenDomainNameRequestTransform(
|
|
191
|
-
denyList: Iterable<string> = DEFAULT_FORBIDDEN_DOMAIN_NAMES,
|
|
192
|
-
) {
|
|
193
|
-
const denySet = new Set<string>(denyList)
|
|
194
|
-
|
|
195
|
-
// Optimization: if no forbidden domain names are provided, we can skip the
|
|
196
|
-
// check entirely.
|
|
197
|
-
if (denySet.size === 0) {
|
|
198
|
-
return asRequest
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return async (input: Request | string | URL, init?: RequestInit) => {
|
|
202
|
-
const { hostname } = extractUrl(input)
|
|
203
|
-
|
|
204
|
-
const request = asRequest(input, init)
|
|
205
|
-
|
|
206
|
-
// Full domain name check
|
|
207
|
-
if (denySet.has(hostname)) {
|
|
208
|
-
throw new FetchRequestError(request, 403, 'Forbidden hostname')
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Sub domain name check
|
|
212
|
-
let curDot = hostname.indexOf('.')
|
|
213
|
-
while (curDot !== -1) {
|
|
214
|
-
const subdomain = hostname.slice(curDot + 1)
|
|
215
|
-
if (denySet.has(`*.${subdomain}`)) {
|
|
216
|
-
throw new FetchRequestError(request, 403, 'Forbidden hostname')
|
|
217
|
-
}
|
|
218
|
-
curDot = hostname.indexOf('.', curDot + 1)
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return request
|
|
222
|
-
}
|
|
223
|
-
}
|
package/src/fetch-response.ts
DELETED
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
import { Transformer, pipe } from '@atproto-labs/pipe'
|
|
2
|
-
import { FetchError } from './fetch-error.js'
|
|
3
|
-
import { TransformedResponse } from './transformed-response.js'
|
|
4
|
-
import {
|
|
5
|
-
Json,
|
|
6
|
-
MaxBytesTransformStream,
|
|
7
|
-
cancelBody,
|
|
8
|
-
ifString,
|
|
9
|
-
logCancellationError,
|
|
10
|
-
} from './util.js'
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* media-type = type "/" subtype *( ";" parameter )
|
|
14
|
-
* type = token
|
|
15
|
-
* subtype = token
|
|
16
|
-
* token = 1*<any CHAR except CTLs or separators>
|
|
17
|
-
* separators = "(" | ")" | "<" | ">" | "@"
|
|
18
|
-
* | "," | ";" | ":" | "\" | <">
|
|
19
|
-
* | "/" | "[" | "]" | "?" | "="
|
|
20
|
-
* | "{" | "}" | SP | HT
|
|
21
|
-
* CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
|
|
22
|
-
* SP = <US-ASCII SP, space (32)>
|
|
23
|
-
* HT = <US-ASCII HT, horizontal-tab (9)>
|
|
24
|
-
* @note The type, subtype, and parameter attribute names are case-insensitive.
|
|
25
|
-
* @see {@link https://datatracker.ietf.org/doc/html/rfc2616#autoid-23}
|
|
26
|
-
*/
|
|
27
|
-
const JSON_MIME = /^application\/(?:[^()<>@,;:/[\]\\?={} \t]+\+)?json$/i
|
|
28
|
-
|
|
29
|
-
export type ResponseTransformer = Transformer<Response>
|
|
30
|
-
export type ResponseMessageGetter = Transformer<Response, string | undefined>
|
|
31
|
-
|
|
32
|
-
export class FetchResponseError extends FetchError {
|
|
33
|
-
constructor(
|
|
34
|
-
public readonly response: Response,
|
|
35
|
-
statusCode: number = response.status,
|
|
36
|
-
message: string = response.statusText,
|
|
37
|
-
options?: ErrorOptions,
|
|
38
|
-
) {
|
|
39
|
-
super(statusCode, message, options)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
static async from(
|
|
43
|
-
response: Response,
|
|
44
|
-
customMessage: string | ResponseMessageGetter = extractResponseMessage,
|
|
45
|
-
statusCode = response.status,
|
|
46
|
-
options?: ErrorOptions,
|
|
47
|
-
) {
|
|
48
|
-
const message =
|
|
49
|
-
typeof customMessage === 'string'
|
|
50
|
-
? customMessage
|
|
51
|
-
: typeof customMessage === 'function'
|
|
52
|
-
? await customMessage(response)
|
|
53
|
-
: undefined
|
|
54
|
-
|
|
55
|
-
return new FetchResponseError(response, statusCode, message, options)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const extractResponseMessage: ResponseMessageGetter = async (response) => {
|
|
60
|
-
const mimeType = extractMime(response)
|
|
61
|
-
if (!mimeType) return undefined
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
if (mimeType === 'text/plain') {
|
|
65
|
-
return await response.text()
|
|
66
|
-
} else if (JSON_MIME.test(mimeType)) {
|
|
67
|
-
const json: unknown = await response.json()
|
|
68
|
-
|
|
69
|
-
if (typeof json === 'string') return json
|
|
70
|
-
if (typeof json === 'object' && json != null) {
|
|
71
|
-
const errorDescription = ifString(json['error_description'])
|
|
72
|
-
if (errorDescription) return errorDescription
|
|
73
|
-
|
|
74
|
-
const error = ifString(json['error'])
|
|
75
|
-
if (error) return error
|
|
76
|
-
|
|
77
|
-
const message = ifString(json['message'])
|
|
78
|
-
if (message) return message
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
} catch {
|
|
82
|
-
// noop
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return undefined
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export async function peekJson(
|
|
89
|
-
response: Response,
|
|
90
|
-
maxSize = Infinity,
|
|
91
|
-
): Promise<undefined | Json> {
|
|
92
|
-
const type = extractMime(response)
|
|
93
|
-
if (type !== 'application/json') return undefined
|
|
94
|
-
checkLength(response, maxSize)
|
|
95
|
-
|
|
96
|
-
// 1) Clone the request so we can consume the body
|
|
97
|
-
const clonedResponse = response.clone()
|
|
98
|
-
|
|
99
|
-
// 2) Make sure the request's body is not too large
|
|
100
|
-
const limitedResponse =
|
|
101
|
-
response.body && maxSize < Infinity
|
|
102
|
-
? new TransformedResponse(
|
|
103
|
-
clonedResponse,
|
|
104
|
-
new MaxBytesTransformStream(maxSize),
|
|
105
|
-
)
|
|
106
|
-
: // Note: some runtimes (e.g. react-native) don't expose a body property
|
|
107
|
-
clonedResponse
|
|
108
|
-
|
|
109
|
-
// 3) Parse the JSON
|
|
110
|
-
return limitedResponse.json()
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function checkLength(response: Response, maxBytes: number) {
|
|
114
|
-
// Note: negation accounts for invalid value types (NaN, non numbers)
|
|
115
|
-
if (!(maxBytes >= 0)) {
|
|
116
|
-
throw new TypeError('maxBytes must be a non-negative number')
|
|
117
|
-
}
|
|
118
|
-
const length = extractLength(response)
|
|
119
|
-
if (length != null && length > maxBytes) {
|
|
120
|
-
throw new FetchResponseError(response, 502, 'Response too large')
|
|
121
|
-
}
|
|
122
|
-
return length
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function extractLength(response: Response) {
|
|
126
|
-
const contentLength = response.headers.get('Content-Length')
|
|
127
|
-
if (contentLength == null) return undefined
|
|
128
|
-
if (!/^\d+$/.test(contentLength)) {
|
|
129
|
-
throw new FetchResponseError(response, 502, 'Invalid Content-Length')
|
|
130
|
-
}
|
|
131
|
-
const length = Number(contentLength)
|
|
132
|
-
if (!Number.isSafeInteger(length)) {
|
|
133
|
-
throw new FetchResponseError(response, 502, 'Content-Length too large')
|
|
134
|
-
}
|
|
135
|
-
return length
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export function extractMime(response: Response) {
|
|
139
|
-
const contentType = response.headers.get('Content-Type')
|
|
140
|
-
if (contentType == null) return undefined
|
|
141
|
-
|
|
142
|
-
return contentType.split(';', 1)[0]!.trim()
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* If the transformer results in an error, ensure that the response body is
|
|
147
|
-
* consumed as, in some environments (Node 👀), the response will not
|
|
148
|
-
* automatically be GC'd.
|
|
149
|
-
*
|
|
150
|
-
* @see {@link https://undici.nodejs.org/#/?id=garbage-collection}
|
|
151
|
-
* @param [onCancellationError] - Callback to handle any async body cancelling
|
|
152
|
-
* error. Defaults to logging the error. Do not use `null` if the request is
|
|
153
|
-
* cloned.
|
|
154
|
-
*/
|
|
155
|
-
export function cancelBodyOnError<T>(
|
|
156
|
-
transformer: Transformer<Response, T>,
|
|
157
|
-
onCancellationError: null | ((err: unknown) => void) = logCancellationError,
|
|
158
|
-
): (response: Response) => Promise<T> {
|
|
159
|
-
return async (response) => {
|
|
160
|
-
try {
|
|
161
|
-
return await transformer(response)
|
|
162
|
-
} catch (err) {
|
|
163
|
-
await cancelBody(response, onCancellationError ?? undefined)
|
|
164
|
-
throw err
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export function fetchOkProcessor(
|
|
170
|
-
customMessage?: string | ResponseMessageGetter,
|
|
171
|
-
): ResponseTransformer {
|
|
172
|
-
return cancelBodyOnError((response) => {
|
|
173
|
-
return fetchOkTransformer(response, customMessage)
|
|
174
|
-
})
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export async function fetchOkTransformer(
|
|
178
|
-
response: Response,
|
|
179
|
-
customMessage?: string | ResponseMessageGetter,
|
|
180
|
-
) {
|
|
181
|
-
if (response.ok) return response
|
|
182
|
-
throw await FetchResponseError.from(response, customMessage)
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export function fetchMaxSizeProcessor(maxBytes: number): ResponseTransformer {
|
|
186
|
-
if (maxBytes === Infinity) return (response) => response
|
|
187
|
-
if (!Number.isFinite(maxBytes) || maxBytes < 0) {
|
|
188
|
-
throw new TypeError('maxBytes must be a 0, Infinity or a positive number')
|
|
189
|
-
}
|
|
190
|
-
return cancelBodyOnError((response) => {
|
|
191
|
-
return fetchResponseMaxSizeChecker(response, maxBytes)
|
|
192
|
-
})
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export function fetchResponseMaxSizeChecker(
|
|
196
|
-
response: Response,
|
|
197
|
-
maxBytes: number,
|
|
198
|
-
): Response {
|
|
199
|
-
if (maxBytes === Infinity) return response
|
|
200
|
-
checkLength(response, maxBytes)
|
|
201
|
-
|
|
202
|
-
// Some engines (react-native 👀) don't expose a body property. In that case,
|
|
203
|
-
// we will only rely on the Content-Length header.
|
|
204
|
-
if (!response.body) return response
|
|
205
|
-
|
|
206
|
-
const transform = new MaxBytesTransformStream(maxBytes)
|
|
207
|
-
return new TransformedResponse(response, transform)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export type MimeTypeCheckFn = (mimeType: string) => boolean
|
|
211
|
-
export type MimeTypeCheck = string | RegExp | MimeTypeCheckFn
|
|
212
|
-
|
|
213
|
-
export function fetchTypeProcessor(
|
|
214
|
-
expectedMime: MimeTypeCheck,
|
|
215
|
-
contentTypeRequired = true,
|
|
216
|
-
): ResponseTransformer {
|
|
217
|
-
const isExpected: MimeTypeCheckFn =
|
|
218
|
-
typeof expectedMime === 'string'
|
|
219
|
-
? (mimeType) => mimeType === expectedMime
|
|
220
|
-
: expectedMime instanceof RegExp
|
|
221
|
-
? (mimeType) => expectedMime.test(mimeType)
|
|
222
|
-
: expectedMime
|
|
223
|
-
|
|
224
|
-
return cancelBodyOnError((response) => {
|
|
225
|
-
return fetchResponseTypeChecker(response, isExpected, contentTypeRequired)
|
|
226
|
-
})
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
export async function fetchResponseTypeChecker(
|
|
230
|
-
response: Response,
|
|
231
|
-
isExpectedMime: MimeTypeCheckFn,
|
|
232
|
-
contentTypeRequired = true,
|
|
233
|
-
): Promise<Response> {
|
|
234
|
-
const mimeType = extractMime(response)
|
|
235
|
-
if (mimeType) {
|
|
236
|
-
if (!isExpectedMime(mimeType.toLowerCase())) {
|
|
237
|
-
throw await FetchResponseError.from(
|
|
238
|
-
response,
|
|
239
|
-
`Unexpected response Content-Type (${mimeType})`,
|
|
240
|
-
502,
|
|
241
|
-
)
|
|
242
|
-
}
|
|
243
|
-
} else if (contentTypeRequired) {
|
|
244
|
-
throw await FetchResponseError.from(
|
|
245
|
-
response,
|
|
246
|
-
'Missing response Content-Type header',
|
|
247
|
-
502,
|
|
248
|
-
)
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return response
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
export type ParsedJsonResponse<T = Json> = {
|
|
255
|
-
response: Response
|
|
256
|
-
json: T
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
export async function fetchResponseJsonTransformer<T = Json>(
|
|
260
|
-
response: Response,
|
|
261
|
-
): Promise<ParsedJsonResponse<T>> {
|
|
262
|
-
try {
|
|
263
|
-
const json = (await response.json()) as T
|
|
264
|
-
return { response, json }
|
|
265
|
-
} catch (cause) {
|
|
266
|
-
throw new FetchResponseError(
|
|
267
|
-
response,
|
|
268
|
-
502,
|
|
269
|
-
'Unable to parse response as JSON',
|
|
270
|
-
{ cause },
|
|
271
|
-
)
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
export function fetchJsonProcessor<T = Json>(
|
|
276
|
-
expectedMime: MimeTypeCheck = JSON_MIME,
|
|
277
|
-
contentTypeRequired = true,
|
|
278
|
-
): Transformer<Response, ParsedJsonResponse<T>> {
|
|
279
|
-
return pipe(
|
|
280
|
-
fetchTypeProcessor(expectedMime, contentTypeRequired),
|
|
281
|
-
cancelBodyOnError(fetchResponseJsonTransformer<T>),
|
|
282
|
-
)
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
export type SyncValidationSchema<S, P = unknown> = {
|
|
286
|
-
parse(value: unknown, params?: P): S
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
export type AsyncValidationSchema<S, P = unknown> = {
|
|
290
|
-
parseAsync(value: unknown, params?: P): Promise<S>
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
export function fetchJsonValidatorProcessor<S, P = unknown>(
|
|
294
|
-
schema: SyncValidationSchema<S, P> | AsyncValidationSchema<S, P>,
|
|
295
|
-
params?: P,
|
|
296
|
-
): Transformer<ParsedJsonResponse, S> {
|
|
297
|
-
if ('parseAsync' in schema && typeof schema.parseAsync === 'function') {
|
|
298
|
-
return async (jsonResponse: ParsedJsonResponse): Promise<S> =>
|
|
299
|
-
schema.parseAsync(jsonResponse.json, params)
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
if ('parse' in schema && typeof schema.parse === 'function') {
|
|
303
|
-
return async (jsonResponse: ParsedJsonResponse): Promise<S> =>
|
|
304
|
-
schema.parse(jsonResponse.json, params)
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Needed for type safety (and allows fool proofing the usage of this function)
|
|
308
|
-
throw new TypeError('Invalid schema')
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/** @note Use {@link fetchJsonValidatorProcessor} instead */
|
|
312
|
-
export const fetchJsonZodProcessor = fetchJsonValidatorProcessor
|
package/src/fetch-wrap.ts
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { FetchRequestError } from './fetch-request.js'
|
|
2
|
-
import { Fetch, FetchContext, toRequestTransformer } from './fetch.js'
|
|
3
|
-
import { TransformedResponse } from './transformed-response.js'
|
|
4
|
-
import { padLines, stringifyMessage } from './util.js'
|
|
5
|
-
|
|
6
|
-
type LogFn<Args extends unknown[]> = (...args: Args) => void | PromiseLike<void>
|
|
7
|
-
|
|
8
|
-
export function loggedFetch<C = FetchContext>({
|
|
9
|
-
fetch = globalThis.fetch as Fetch<C>,
|
|
10
|
-
logRequest = true as boolean | LogFn<[request: Request]>,
|
|
11
|
-
logResponse = true as boolean | LogFn<[response: Response, request: Request]>,
|
|
12
|
-
logError = true as boolean | LogFn<[error: unknown, request: Request]>,
|
|
13
|
-
}) {
|
|
14
|
-
const onRequest =
|
|
15
|
-
logRequest === true
|
|
16
|
-
? async (request) => {
|
|
17
|
-
const requestMessage = await stringifyMessage(request)
|
|
18
|
-
console.info(
|
|
19
|
-
`> ${request.method} ${request.url}\n${padLines(requestMessage, ' ')}`,
|
|
20
|
-
)
|
|
21
|
-
}
|
|
22
|
-
: logRequest || undefined
|
|
23
|
-
|
|
24
|
-
const onResponse =
|
|
25
|
-
logResponse === true
|
|
26
|
-
? async (response) => {
|
|
27
|
-
const responseMessage = await stringifyMessage(response.clone())
|
|
28
|
-
console.info(
|
|
29
|
-
`< HTTP/1.1 ${response.status} ${response.statusText}\n${padLines(responseMessage, ' ')}`,
|
|
30
|
-
)
|
|
31
|
-
}
|
|
32
|
-
: logResponse || undefined
|
|
33
|
-
|
|
34
|
-
const onError =
|
|
35
|
-
logError === true
|
|
36
|
-
? async (error) => {
|
|
37
|
-
console.error(`< Error:`, error)
|
|
38
|
-
}
|
|
39
|
-
: logError || undefined
|
|
40
|
-
|
|
41
|
-
if (!onRequest && !onResponse && !onError) return fetch
|
|
42
|
-
|
|
43
|
-
return toRequestTransformer(async function (
|
|
44
|
-
this: C,
|
|
45
|
-
request,
|
|
46
|
-
): Promise<Response> {
|
|
47
|
-
if (onRequest) await onRequest(request)
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
const response = await fetch.call(this, request)
|
|
51
|
-
|
|
52
|
-
if (onResponse) await onResponse(response, request)
|
|
53
|
-
|
|
54
|
-
return response
|
|
55
|
-
} catch (error) {
|
|
56
|
-
if (onError) await onError(error, request)
|
|
57
|
-
|
|
58
|
-
throw error
|
|
59
|
-
}
|
|
60
|
-
})
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export const timedFetch = <C = FetchContext>(
|
|
64
|
-
timeout = 60e3,
|
|
65
|
-
fetch: Fetch<C> = globalThis.fetch,
|
|
66
|
-
): Fetch<C> => {
|
|
67
|
-
if (timeout === Infinity) return fetch
|
|
68
|
-
if (!Number.isFinite(timeout) || timeout <= 0) {
|
|
69
|
-
throw new TypeError('Timeout must be positive')
|
|
70
|
-
}
|
|
71
|
-
return toRequestTransformer(async function (
|
|
72
|
-
this: C,
|
|
73
|
-
request,
|
|
74
|
-
): Promise<Response> {
|
|
75
|
-
const controller = new AbortController()
|
|
76
|
-
const signal = controller.signal
|
|
77
|
-
|
|
78
|
-
const abort = () => {
|
|
79
|
-
controller.abort()
|
|
80
|
-
}
|
|
81
|
-
const cleanup = () => {
|
|
82
|
-
clearTimeout(timer)
|
|
83
|
-
request.signal?.removeEventListener('abort', abort)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const timer = setTimeout(abort, timeout)
|
|
87
|
-
if (typeof timer === 'object') timer.unref?.() // only on node
|
|
88
|
-
request.signal?.addEventListener('abort', abort)
|
|
89
|
-
|
|
90
|
-
signal.addEventListener('abort', cleanup)
|
|
91
|
-
|
|
92
|
-
const response = await fetch.call(this, request, { signal })
|
|
93
|
-
|
|
94
|
-
if (!response.body) {
|
|
95
|
-
cleanup()
|
|
96
|
-
return response
|
|
97
|
-
} else {
|
|
98
|
-
// Cleanup the timer & event listeners when the body stream is closed
|
|
99
|
-
const transform = new TransformStream({ flush: cleanup })
|
|
100
|
-
return new TransformedResponse(response, transform)
|
|
101
|
-
}
|
|
102
|
-
})
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Wraps a fetch function to bind it to a specific context, and wrap any thrown
|
|
107
|
-
* errors into a FetchRequestError.
|
|
108
|
-
*
|
|
109
|
-
* @example
|
|
110
|
-
*
|
|
111
|
-
* ```ts
|
|
112
|
-
* class MyClient {
|
|
113
|
-
* constructor(private fetch = globalThis.fetch) {}
|
|
114
|
-
*
|
|
115
|
-
* async get(url: string) {
|
|
116
|
-
* // This will generate an error, because the context used is not a
|
|
117
|
-
* // FetchContext (it's a MyClient instance).
|
|
118
|
-
* return this.fetch(url)
|
|
119
|
-
* }
|
|
120
|
-
* }
|
|
121
|
-
* ```
|
|
122
|
-
*
|
|
123
|
-
* @example
|
|
124
|
-
*
|
|
125
|
-
* ```ts
|
|
126
|
-
* class MyClient {
|
|
127
|
-
* private fetch: Fetch<unknown>
|
|
128
|
-
*
|
|
129
|
-
* constructor(fetch = globalThis.fetch) {
|
|
130
|
-
* this.fetch = bindFetch(fetch)
|
|
131
|
-
* }
|
|
132
|
-
*
|
|
133
|
-
* async get(url: string) {
|
|
134
|
-
* return this.fetch(url) // no more error
|
|
135
|
-
* }
|
|
136
|
-
* }
|
|
137
|
-
* ```
|
|
138
|
-
*/
|
|
139
|
-
export function bindFetch<C = FetchContext>(
|
|
140
|
-
fetch: Fetch<C> = globalThis.fetch,
|
|
141
|
-
context: C = globalThis as C,
|
|
142
|
-
) {
|
|
143
|
-
return toRequestTransformer(async (request) => {
|
|
144
|
-
try {
|
|
145
|
-
return await fetch.call(context, request)
|
|
146
|
-
} catch (err) {
|
|
147
|
-
throw FetchRequestError.from(request, err)
|
|
148
|
-
}
|
|
149
|
-
})
|
|
150
|
-
}
|
package/src/fetch.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { ThisParameterOverride } from './util.js'
|
|
2
|
-
|
|
3
|
-
export type FetchContext = void | null | typeof globalThis
|
|
4
|
-
|
|
5
|
-
export type FetchBound = (
|
|
6
|
-
input: string | URL | Request,
|
|
7
|
-
init?: RequestInit,
|
|
8
|
-
) => Promise<Response>
|
|
9
|
-
|
|
10
|
-
// NOT using "typeof globalThis.fetch" here because "globalThis.fetch" does not
|
|
11
|
-
// have a "this" parameter, while runtimes do ensure that "fetch" is called with
|
|
12
|
-
// the correct "this" parameter (either null, undefined, or window).
|
|
13
|
-
|
|
14
|
-
export type Fetch<C = FetchContext> = ThisParameterOverride<C, FetchBound>
|
|
15
|
-
|
|
16
|
-
export type SimpleFetchBound = (input: Request) => Promise<Response>
|
|
17
|
-
export type SimpleFetch<C = FetchContext> = ThisParameterOverride<
|
|
18
|
-
C,
|
|
19
|
-
SimpleFetchBound
|
|
20
|
-
>
|
|
21
|
-
|
|
22
|
-
export function toRequestTransformer<C, O>(
|
|
23
|
-
requestTransformer: (this: C, input: Request) => O,
|
|
24
|
-
): ThisParameterOverride<
|
|
25
|
-
C,
|
|
26
|
-
(input: string | URL | Request, init?: RequestInit) => O
|
|
27
|
-
> {
|
|
28
|
-
return function (this: C, input, init) {
|
|
29
|
-
return requestTransformer.call(this, asRequest(input, init))
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function asRequest(
|
|
34
|
-
input: string | URL | Request,
|
|
35
|
-
init?: RequestInit,
|
|
36
|
-
): Request {
|
|
37
|
-
if (!init && input instanceof Request) return input
|
|
38
|
-
return new Request(input, init)
|
|
39
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
export class TransformedResponse extends Response {
|
|
2
|
-
#response: Response
|
|
3
|
-
|
|
4
|
-
constructor(response: Response, transform: TransformStream) {
|
|
5
|
-
if (!response.body) {
|
|
6
|
-
throw new TypeError('Response body is not available')
|
|
7
|
-
}
|
|
8
|
-
if (response.bodyUsed) {
|
|
9
|
-
throw new TypeError('Response body is already used')
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
super(response.body.pipeThrough(transform), {
|
|
13
|
-
status: response.status,
|
|
14
|
-
statusText: response.statusText,
|
|
15
|
-
headers: response.headers,
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
this.#response = response
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Some props can't be set through ResponseInit, so we need to proxy them
|
|
23
|
-
*/
|
|
24
|
-
get url() {
|
|
25
|
-
return this.#response.url
|
|
26
|
-
}
|
|
27
|
-
get redirected() {
|
|
28
|
-
return this.#response.redirected
|
|
29
|
-
}
|
|
30
|
-
get type() {
|
|
31
|
-
return this.#response.type
|
|
32
|
-
}
|
|
33
|
-
get statusText() {
|
|
34
|
-
return this.#response.statusText
|
|
35
|
-
}
|
|
36
|
-
}
|
package/src/util.ts
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
// @TODO: Move some of these to a shared package ?
|
|
2
|
-
|
|
3
|
-
export type JsonScalar = string | number | boolean | null
|
|
4
|
-
export type Json = JsonScalar | Json[] | { [key: string]: undefined | Json }
|
|
5
|
-
export type JsonObject = { [key: string]: Json }
|
|
6
|
-
export type JsonArray = Json[]
|
|
7
|
-
|
|
8
|
-
export type ThisParameterOverride<
|
|
9
|
-
C,
|
|
10
|
-
Fn extends (...a: any) => any,
|
|
11
|
-
> = Fn extends (...args: infer P) => infer R
|
|
12
|
-
? ((this: C, ...args: P) => R) & {
|
|
13
|
-
bind(context: C): (...args: P) => R
|
|
14
|
-
}
|
|
15
|
-
: never
|
|
16
|
-
|
|
17
|
-
export function isIp(hostname: string) {
|
|
18
|
-
// IPv4
|
|
19
|
-
if (hostname.match(/^\d+\.\d+\.\d+\.\d+$/)) return true
|
|
20
|
-
|
|
21
|
-
// IPv6
|
|
22
|
-
if (hostname.startsWith('[') && hostname.endsWith(']')) return true
|
|
23
|
-
|
|
24
|
-
return false
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export const ifString = <V>(v: V) => (typeof v === 'string' ? v : undefined)
|
|
28
|
-
|
|
29
|
-
export class MaxBytesTransformStream extends TransformStream<
|
|
30
|
-
Uint8Array,
|
|
31
|
-
Uint8Array
|
|
32
|
-
> {
|
|
33
|
-
constructor(maxBytes: number) {
|
|
34
|
-
// Note: negation accounts for invalid value types (NaN, non numbers)
|
|
35
|
-
if (!(maxBytes >= 0)) {
|
|
36
|
-
throw new TypeError('maxBytes must be a non-negative number')
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
let bytesRead = 0
|
|
40
|
-
|
|
41
|
-
super({
|
|
42
|
-
transform: (
|
|
43
|
-
chunk: Uint8Array,
|
|
44
|
-
ctrl: TransformStreamDefaultController<Uint8Array>,
|
|
45
|
-
) => {
|
|
46
|
-
if ((bytesRead += chunk.length) <= maxBytes) {
|
|
47
|
-
ctrl.enqueue(chunk)
|
|
48
|
-
} else {
|
|
49
|
-
ctrl.error(new Error('Response too large'))
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
})
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const LINE_BREAK = /\r?\n/g
|
|
57
|
-
export function padLines(input: string, pad: string) {
|
|
58
|
-
if (!input) return input
|
|
59
|
-
return pad + input.replace(LINE_BREAK, `$&${pad}`)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* @param [onCancellationError] - Callback that will trigger to asynchronously
|
|
64
|
-
* handle any error that occurs while cancelling the response body. Providing
|
|
65
|
-
* this will speed up the process and avoid potential deadlocks. Defaults to
|
|
66
|
-
* awaiting the cancellation operation. use `"log"` to log the error.
|
|
67
|
-
* @see {@link https://undici.nodejs.org/#/?id=garbage-collection}
|
|
68
|
-
* @note awaiting this function's result, when no `onCancellationError` is
|
|
69
|
-
* provided, might result in a dead lock. Indeed, if the response was cloned(),
|
|
70
|
-
* the response.body.cancel() method will not resolve until the other response's
|
|
71
|
-
* body is consumed/cancelled.
|
|
72
|
-
*
|
|
73
|
-
* @example
|
|
74
|
-
* ```ts
|
|
75
|
-
* // Make sure response was not cloned, or that every cloned response was
|
|
76
|
-
* // consumed/cancelled before awaiting this function's result.
|
|
77
|
-
* await cancelBody(response)
|
|
78
|
-
* ```
|
|
79
|
-
* @example
|
|
80
|
-
* ```ts
|
|
81
|
-
* await cancelBody(response, (err) => {
|
|
82
|
-
* // No biggie, let's just log the error
|
|
83
|
-
* console.warn('Failed to cancel response body', err)
|
|
84
|
-
* })
|
|
85
|
-
* ```
|
|
86
|
-
* @example
|
|
87
|
-
* ```ts
|
|
88
|
-
* // Will generate an "unhandledRejection" if an error occurs while cancelling
|
|
89
|
-
* // the response body. This will likely crash the process.
|
|
90
|
-
* await cancelBody(response, (err) => { throw err })
|
|
91
|
-
* ```
|
|
92
|
-
*/
|
|
93
|
-
export async function cancelBody(
|
|
94
|
-
body: Body,
|
|
95
|
-
onCancellationError?: 'log' | ((err: unknown) => void),
|
|
96
|
-
): Promise<void> {
|
|
97
|
-
if (
|
|
98
|
-
body.body &&
|
|
99
|
-
!body.bodyUsed &&
|
|
100
|
-
!body.body.locked &&
|
|
101
|
-
// Support for alternative fetch implementations
|
|
102
|
-
typeof body.body.cancel === 'function'
|
|
103
|
-
) {
|
|
104
|
-
if (typeof onCancellationError === 'function') {
|
|
105
|
-
void body.body.cancel().catch(onCancellationError)
|
|
106
|
-
} else if (onCancellationError === 'log') {
|
|
107
|
-
void body.body.cancel().catch(logCancellationError)
|
|
108
|
-
} else {
|
|
109
|
-
await body.body.cancel()
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function logCancellationError(err: unknown): void {
|
|
115
|
-
console.warn('Failed to cancel response body', err)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export async function stringifyMessage(input: Body & { headers: Headers }) {
|
|
119
|
-
try {
|
|
120
|
-
const headers = stringifyHeaders(input.headers)
|
|
121
|
-
const payload = await stringifyBody(input)
|
|
122
|
-
return headers && payload ? `${headers}\n${payload}` : headers || payload
|
|
123
|
-
} finally {
|
|
124
|
-
void cancelBody(input, 'log')
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function stringifyHeaders(headers: Headers) {
|
|
129
|
-
return Array.from(headers)
|
|
130
|
-
.map(([name, value]) => `${name}: ${value}`)
|
|
131
|
-
.join('\n')
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
async function stringifyBody(body: Body) {
|
|
135
|
-
try {
|
|
136
|
-
const blob = await body.blob()
|
|
137
|
-
if (blob.type?.startsWith('text/')) {
|
|
138
|
-
const text = await blob.text()
|
|
139
|
-
return JSON.stringify(text)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (/application\/(?:\w+\+)?json/.test(blob.type)) {
|
|
143
|
-
const text = await blob.text()
|
|
144
|
-
return text.includes('\n') ? JSON.stringify(JSON.parse(text)) : text
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return `[Body size: ${blob.size}, type: ${JSON.stringify(blob.type)} ]`
|
|
148
|
-
} catch {
|
|
149
|
-
return '[Body could not be read]'
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export const extractUrl = (input: Request | string | URL) =>
|
|
154
|
-
typeof input === 'string'
|
|
155
|
-
? new URL(input)
|
|
156
|
-
: input instanceof URL
|
|
157
|
-
? input
|
|
158
|
-
: new URL(input.url)
|
package/tsconfig.build.json
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":"7.0.0-dev.20260614.1","root":["./src/fetch-error.ts","./src/fetch-request.ts","./src/fetch-response.ts","./src/fetch-wrap.ts","./src/fetch.ts","./src/index.ts","./src/transformed-response.ts","./src/util.ts"]}
|
package/tsconfig.json
DELETED