request-got-adapter 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +152 -0
- package/dist/args.d.ts +7 -0
- package/dist/args.js +39 -0
- package/dist/auth.d.ts +3 -0
- package/dist/auth.js +72 -0
- package/dist/cookies.d.ts +10 -0
- package/dist/cookies.js +33 -0
- package/dist/core.d.ts +7 -0
- package/dist/core.js +133 -0
- package/dist/errors.d.ts +24 -0
- package/dist/errors.js +52 -0
- package/dist/got-loader.d.ts +3 -0
- package/dist/got-loader.js +29 -0
- package/dist/headers.d.ts +7 -0
- package/dist/headers.js +34 -0
- package/dist/index.d.ts +106 -0
- package/dist/index.js +57 -0
- package/dist/oauth.d.ts +5 -0
- package/dist/oauth.js +101 -0
- package/dist/response.d.ts +45 -0
- package/dist/response.js +43 -0
- package/dist/rp-options.d.ts +81 -0
- package/dist/rp-options.js +2 -0
- package/dist/translate.d.ts +18 -0
- package/dist/translate.js +306 -0
- package/errors.d.ts +1 -0
- package/errors.js +3 -0
- package/package.json +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Torii
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# request-got-adapter
|
|
2
|
+
|
|
3
|
+
A drop-in replacement for [`request-promise-native`](https://github.com/request/request-promise-native), backed by [`got`](https://github.com/sindresorhus/got) — with **zero request-family dependencies**.
|
|
4
|
+
|
|
5
|
+
The [`request`](https://github.com/request/request) ecosystem was deprecated in 2020 but still powers a lot of production code. Rewriting every call site to a modern HTTP client is a big, risky migration. This package takes the other path: keep your code exactly as it is and swap the engine underneath.
|
|
6
|
+
|
|
7
|
+
```diff
|
|
8
|
+
- const request = require('request-promise-native')
|
|
9
|
+
+ const request = require('request-got-adapter')
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
That's the whole migration. Same options, same response shapes, same error classes, same TypeScript types.
|
|
13
|
+
|
|
14
|
+
You can even do it without touching code, via an npm alias:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"request-promise-native": "npm:request-got-adapter@^0.1.0"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install request-got-adapter
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Requires Node.js >= 22. Works from plain CommonJS (`require`) — the ESM-only `got` is loaded internally.
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
Exactly like request-promise-native:
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
const rp = require('request-got-adapter')
|
|
38
|
+
|
|
39
|
+
// simple GET — resolves with the body
|
|
40
|
+
const html = await rp('https://example.com')
|
|
41
|
+
|
|
42
|
+
// options object
|
|
43
|
+
const user = await rp({
|
|
44
|
+
uri: 'https://api.example.com/users/42',
|
|
45
|
+
qs: { include: 'profile' },
|
|
46
|
+
headers: { Authorization: 'Bearer token' },
|
|
47
|
+
json: true
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// full response
|
|
51
|
+
const response = await rp({
|
|
52
|
+
uri: 'https://api.example.com/health',
|
|
53
|
+
resolveWithFullResponse: true,
|
|
54
|
+
simple: false
|
|
55
|
+
})
|
|
56
|
+
console.log(response.statusCode, response.headers)
|
|
57
|
+
|
|
58
|
+
// errors behave identically
|
|
59
|
+
const { StatusCodeError, RequestError } = require('request-got-adapter/errors')
|
|
60
|
+
try {
|
|
61
|
+
await rp({ uri: 'https://api.example.com/missing', json: true })
|
|
62
|
+
} catch (err) {
|
|
63
|
+
if (err instanceof StatusCodeError) {
|
|
64
|
+
console.log(err.statusCode) // 404
|
|
65
|
+
console.log(err.error) // parsed body
|
|
66
|
+
console.log(err.message) // '404 - {"error":"not found"}'
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
TypeScript works the same way as with `@types/request-promise-native`:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import request, { type Options, type FullResponse } from 'request-got-adapter'
|
|
75
|
+
import { StatusCodeError } from 'request-got-adapter/errors'
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Compatibility philosophy
|
|
79
|
+
|
|
80
|
+
The contract is **observable behavior parity** with request-promise-native — down to error message formats, redirect semantics, and header behavior. A separate repo, [`request-got-adapter-parity-tests`](https://github.com/toriihq/request-got-adapter-parity-tests), runs one behavioral test suite against both the real `request-promise-native` and this adapter and expects identical results. If a test passes there for rpn and fails for this package, that's a bug here — no debate.
|
|
81
|
+
|
|
82
|
+
Notable parity details handled for you:
|
|
83
|
+
|
|
84
|
+
- No default `user-agent` header (got normally adds one)
|
|
85
|
+
- No `accept-encoding` / decompression unless you pass `gzip: true` (got normally auto-negotiates)
|
|
86
|
+
- No automatic retries (got defaults to 2)
|
|
87
|
+
- `StatusCodeError.message` is `` `${statusCode} - ${JSON.stringify(body)}` `` — strings included
|
|
88
|
+
- `RequestError.message` is `String(cause)`, e.g. `'Error: getaddrinfo ENOTFOUND nope.example'`
|
|
89
|
+
- Timeouts map to `ETIMEDOUT` (connect phase, with `connect: true`) / `ESOCKETTIMEDOUT` (after connect)
|
|
90
|
+
- Redirects: GET/HEAD followed by default, other methods only with `followAllRedirects: true`; non-307/308 redirects switch to GET like request does
|
|
91
|
+
- `qs`-based query/form serialization (arrays as `a[0]=x&a[1]=y`, RFC 3986 escaping), `useQuerystring` supported
|
|
92
|
+
- Option-validation errors reject the returned promise (and reach a provided callback) — never synchronous throws
|
|
93
|
+
|
|
94
|
+
One deliberate improvement over the reference: passing an object `body` without `json: true` rejects cleanly with request's `Argument error, options.body.` — real request on Node >= 22 *also* crashes the process with an uncaught async TypeError in that case.
|
|
95
|
+
|
|
96
|
+
## Supported options
|
|
97
|
+
|
|
98
|
+
| Option | Status |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `uri` / `url` / `baseUrl` / `method` | ✅ |
|
|
101
|
+
| `qs` / `qsStringifyOptions` / `qsParseOptions` / `useQuerystring` | ✅ |
|
|
102
|
+
| `headers` (case-preserving, case-insensitive lookup) | ✅ |
|
|
103
|
+
| `body` / `json` (boolean or value) | ✅ |
|
|
104
|
+
| `form` / `formData` (multipart via form-data) | ✅ |
|
|
105
|
+
| `auth` — Basic, Bearer, `sendImmediately: false`, **Digest** | ✅ |
|
|
106
|
+
| `oauth` — OAuth 1.0 (HMAC-SHA1/SHA256, RSA-SHA1, PLAINTEXT; header/query/body transports, body_hash) | ✅ |
|
|
107
|
+
| `simple` / `resolveWithFullResponse` | ✅ |
|
|
108
|
+
| `gzip` / `encoding` (incl. `null` → Buffer) | ✅ |
|
|
109
|
+
| `followRedirect` / `followAllRedirects` / `followOriginalHttpMethod` / `maxRedirects` | ✅ |
|
|
110
|
+
| `timeout` / `time` (elapsedTime, timingPhases) | ✅ |
|
|
111
|
+
| `strictSSL` / `rejectUnauthorized` / `ca` / `cert` / `key` / `pfx` / `passphrase` | ✅ |
|
|
112
|
+
| `agent` / `agentOptions` / `forever` / `.forever()` | ✅ |
|
|
113
|
+
| `jar` / `request.jar()` / `request.cookie()` (tough-cookie) | ✅ |
|
|
114
|
+
| `transform` / `transform2xxOnly` | ✅ |
|
|
115
|
+
| `localAddress` / `family` / `lookup` | ✅ |
|
|
116
|
+
| `.defaults()` (chainable) | ✅ |
|
|
117
|
+
| Callback style `(err, response, body)` alongside promises | ✅ |
|
|
118
|
+
| Verb helpers `.get/.post/.put/.patch/.del/.delete/.head/.options` | ✅ |
|
|
119
|
+
|
|
120
|
+
## GAPS / TODO
|
|
121
|
+
|
|
122
|
+
These request features are **not implemented**. Passing them throws a clear `not implemented` error rather than silently misbehaving (except where noted). PRs welcome.
|
|
123
|
+
|
|
124
|
+
| Option / feature | Status |
|
|
125
|
+
|---|---|
|
|
126
|
+
| Stream mode — `.pipe()`, `.on('response')`, `.on('data')` on the returned object | ❌ TODO — the returned object is a promise, not a duplex stream |
|
|
127
|
+
| `.cancel()` on the returned promise | ❌ not present (matches request-promise-**native**, which also lacks it) |
|
|
128
|
+
| `har` | ❌ throws |
|
|
129
|
+
| `aws` (AWS signing) | ❌ throws |
|
|
130
|
+
| `httpSignature` | ❌ throws |
|
|
131
|
+
| `proxy` / `tunnel` | ❌ throws — use an `agent` (e.g. [hpagent](https://github.com/delvedor/hpagent)) instead |
|
|
132
|
+
| `multipart` / `preambleCRLF` / `postambleCRLF` (raw multipart, not formData) | ❌ throws |
|
|
133
|
+
| `jsonReviver` / `jsonReplacer` | ❌ throws |
|
|
134
|
+
| `pool` | ❌ throws — use `agent` |
|
|
135
|
+
| `removeRefererHeader` | ❌ throws |
|
|
136
|
+
| `followRedirect` as a function | ❌ throws |
|
|
137
|
+
| `request.debug` / `request.initParams` | ❌ not present |
|
|
138
|
+
| TypeScript: `rp.delete(...)` | runtime works; types expose `.del` (`delete` is a reserved word in the type namespace) |
|
|
139
|
+
|
|
140
|
+
## How it works
|
|
141
|
+
|
|
142
|
+
A thin, clean-room translation layer:
|
|
143
|
+
|
|
144
|
+
1. **Translate** request-promise-native options into got options (`translate.ts`)
|
|
145
|
+
2. **Execute** via got with parity settings (`throwHttpErrors: false`, `retry: {limit: 0}`, `decompress` off unless `gzip: true`)
|
|
146
|
+
3. **Shape** got's response back into request's response shape, or throw re-implemented `StatusCodeError` / `RequestError` / `TransformError`
|
|
147
|
+
|
|
148
|
+
Dependencies: `got`, `qs`, `tough-cookie`, `form-data`. OAuth 1.0 signing and Digest auth are implemented natively with `node:crypto`. CI fails if any request-family package sneaks into the dependency tree.
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
MIT — see [LICENSE](LICENSE)
|
package/dist/args.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Callback, RpOptions } from './rp-options';
|
|
2
|
+
export interface NormalizedArgs {
|
|
3
|
+
options: RpOptions;
|
|
4
|
+
callback?: Callback;
|
|
5
|
+
}
|
|
6
|
+
export declare function normalizeArgs(a: unknown, b?: unknown, c?: unknown): NormalizedArgs;
|
|
7
|
+
export declare function mergeDefaults(defaults: RpOptions | undefined, options: RpOptions): RpOptions;
|
package/dist/args.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeArgs = normalizeArgs;
|
|
4
|
+
exports.mergeDefaults = mergeDefaults;
|
|
5
|
+
function normalizeArgs(a, b, c) {
|
|
6
|
+
let options;
|
|
7
|
+
let callback;
|
|
8
|
+
if (typeof a === 'string' || a instanceof URL) {
|
|
9
|
+
options = typeof b === 'object' && b !== null ? { ...b } : {};
|
|
10
|
+
options.uri = String(a);
|
|
11
|
+
delete options.url;
|
|
12
|
+
if (typeof b === 'function')
|
|
13
|
+
callback = b;
|
|
14
|
+
else if (typeof c === 'function')
|
|
15
|
+
callback = c;
|
|
16
|
+
}
|
|
17
|
+
else if (typeof a === 'object' && a !== null) {
|
|
18
|
+
options = { ...a };
|
|
19
|
+
if (typeof b === 'function')
|
|
20
|
+
callback = b;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
throw new Error(`${String(a)} is not a valid uri or options object.`);
|
|
24
|
+
}
|
|
25
|
+
if (!callback && typeof options.callback === 'function')
|
|
26
|
+
callback = options.callback;
|
|
27
|
+
delete options.callback;
|
|
28
|
+
return { options, callback };
|
|
29
|
+
}
|
|
30
|
+
function mergeDefaults(defaults, options) {
|
|
31
|
+
if (!defaults)
|
|
32
|
+
return options;
|
|
33
|
+
const merged = { ...defaults, ...options };
|
|
34
|
+
if (defaults.headers || options.headers)
|
|
35
|
+
merged.headers = { ...defaults.headers, ...options.headers };
|
|
36
|
+
if (defaults.qs || options.qs)
|
|
37
|
+
merged.qs = { ...defaults.qs, ...options.qs };
|
|
38
|
+
return merged;
|
|
39
|
+
}
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Prepared } from './translate';
|
|
2
|
+
export declare function buildDigestHeader(authHeader: string, method: string, path: string, user: string, pass: string): string;
|
|
3
|
+
export declare function answerAuthChallenge(challenge: unknown, prepared: Prepared): string | undefined;
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildDigestHeader = buildDigestHeader;
|
|
4
|
+
exports.answerAuthChallenge = answerAuthChallenge;
|
|
5
|
+
const node_crypto_1 = require("node:crypto");
|
|
6
|
+
const md5 = (value) => (0, node_crypto_1.createHash)('md5').update(value).digest('hex');
|
|
7
|
+
function parseChallenge(header) {
|
|
8
|
+
const values = {};
|
|
9
|
+
for (const match of header.matchAll(/([a-z0-9_-]+)=(?:"([^"]*)"|([^,\s]+))/gi)) {
|
|
10
|
+
values[match[1].toLowerCase()] = match[2] ?? match[3];
|
|
11
|
+
}
|
|
12
|
+
return values;
|
|
13
|
+
}
|
|
14
|
+
// Mirrors request's lib/auth.js digest implementation (RFC 2617/7616, qop="auth").
|
|
15
|
+
function buildDigestHeader(authHeader, method, path, user, pass) {
|
|
16
|
+
const challenge = parseChallenge(authHeader);
|
|
17
|
+
const qop = /(^|,)\s*auth\s*($|,)/.test(challenge.qop ?? '') ? 'auth' : undefined;
|
|
18
|
+
const nc = qop ? '00000001' : undefined;
|
|
19
|
+
const cnonce = qop ? (0, node_crypto_1.randomBytes)(16).toString('hex') : undefined;
|
|
20
|
+
let ha1 = md5(`${user}:${challenge.realm}:${pass}`);
|
|
21
|
+
if (challenge.algorithm?.toLowerCase() === 'md5-sess') {
|
|
22
|
+
ha1 = md5(`${ha1}:${challenge.nonce}:${cnonce}`);
|
|
23
|
+
}
|
|
24
|
+
const ha2 = md5(`${method}:${path}`);
|
|
25
|
+
const digestResponse = qop
|
|
26
|
+
? md5(`${ha1}:${challenge.nonce}:${nc}:${cnonce}:${qop}:${ha2}`)
|
|
27
|
+
: md5(`${ha1}:${challenge.nonce}:${ha2}`);
|
|
28
|
+
const authValues = {
|
|
29
|
+
username: user,
|
|
30
|
+
realm: challenge.realm,
|
|
31
|
+
nonce: challenge.nonce,
|
|
32
|
+
uri: path,
|
|
33
|
+
qop,
|
|
34
|
+
response: digestResponse,
|
|
35
|
+
nc,
|
|
36
|
+
cnonce,
|
|
37
|
+
algorithm: challenge.algorithm,
|
|
38
|
+
opaque: challenge.opaque
|
|
39
|
+
};
|
|
40
|
+
const parts = [];
|
|
41
|
+
for (const [key, value] of Object.entries(authValues)) {
|
|
42
|
+
if (value === undefined)
|
|
43
|
+
continue;
|
|
44
|
+
if (key === 'qop' || key === 'nc' || key === 'algorithm')
|
|
45
|
+
parts.push(`${key}=${value}`);
|
|
46
|
+
else
|
|
47
|
+
parts.push(`${key}="${value.replace(/"/g, '\\"')}"`);
|
|
48
|
+
}
|
|
49
|
+
return `Digest ${parts.join(', ')}`;
|
|
50
|
+
}
|
|
51
|
+
function answerAuthChallenge(challenge, prepared) {
|
|
52
|
+
const auth = prepared.deferredAuth;
|
|
53
|
+
if (!auth)
|
|
54
|
+
return undefined;
|
|
55
|
+
const header = Array.isArray(challenge) ? challenge[0] : challenge;
|
|
56
|
+
if (typeof header !== 'string' || header.length === 0)
|
|
57
|
+
return undefined;
|
|
58
|
+
const verb = header.split(' ')[0].toLowerCase();
|
|
59
|
+
const user = auth.user ?? auth.username ?? '';
|
|
60
|
+
const pass = auth.pass ?? auth.password ?? '';
|
|
61
|
+
if (verb === 'basic')
|
|
62
|
+
return `Basic ${Buffer.from(`${user}:${pass}`).toString('base64')}`;
|
|
63
|
+
if (verb === 'bearer' && auth.bearer !== undefined) {
|
|
64
|
+
const token = typeof auth.bearer === 'function' ? auth.bearer() : auth.bearer;
|
|
65
|
+
return `Bearer ${token}`;
|
|
66
|
+
}
|
|
67
|
+
if (verb === 'digest') {
|
|
68
|
+
const parsed = new URL(prepared.url);
|
|
69
|
+
return buildDigestHeader(header, prepared.method, `${parsed.pathname}${parsed.search}`, user, pass);
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Cookie, CookieJar } from 'tough-cookie';
|
|
2
|
+
export interface RequestJar {
|
|
3
|
+
_jar: CookieJar;
|
|
4
|
+
setCookie: (cookieOrString: Cookie | string, uri: string, options?: Record<string, unknown>) => Cookie | undefined;
|
|
5
|
+
getCookieString: (uri: string) => string;
|
|
6
|
+
getCookies: (uri: string) => Cookie[];
|
|
7
|
+
}
|
|
8
|
+
export declare function jar(): RequestJar;
|
|
9
|
+
export declare function cookie(str: string): Cookie | undefined;
|
|
10
|
+
export declare function resolveCookieJar(option: unknown): CookieJar | undefined;
|
package/dist/cookies.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.jar = jar;
|
|
4
|
+
exports.cookie = cookie;
|
|
5
|
+
exports.resolveCookieJar = resolveCookieJar;
|
|
6
|
+
const tough_cookie_1 = require("tough-cookie");
|
|
7
|
+
function jar() {
|
|
8
|
+
const cookieJar = new tough_cookie_1.CookieJar(undefined, { looseMode: true });
|
|
9
|
+
return {
|
|
10
|
+
_jar: cookieJar,
|
|
11
|
+
setCookie: (cookieOrString, uri, options = {}) => cookieJar.setCookieSync(cookieOrString, uri, { ignoreError: true, ...options }),
|
|
12
|
+
getCookieString: (uri) => cookieJar.getCookieStringSync(uri),
|
|
13
|
+
getCookies: (uri) => cookieJar.getCookiesSync(uri)
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function cookie(str) {
|
|
17
|
+
return tough_cookie_1.Cookie.parse(str, { loose: true });
|
|
18
|
+
}
|
|
19
|
+
let globalJar;
|
|
20
|
+
function resolveCookieJar(option) {
|
|
21
|
+
if (option === undefined || option === null || option === false)
|
|
22
|
+
return undefined;
|
|
23
|
+
if (option === true) {
|
|
24
|
+
globalJar ??= jar();
|
|
25
|
+
return globalJar._jar;
|
|
26
|
+
}
|
|
27
|
+
const candidate = option;
|
|
28
|
+
if (candidate._jar instanceof tough_cookie_1.CookieJar)
|
|
29
|
+
return candidate._jar;
|
|
30
|
+
if (option instanceof tough_cookie_1.CookieJar)
|
|
31
|
+
return option;
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Callback, RpOptions } from './rp-options';
|
|
2
|
+
export interface RequestPromiseLike<T = unknown> extends PromiseLike<T> {
|
|
3
|
+
catch: (onRejected?: (reason: unknown) => unknown) => Promise<unknown>;
|
|
4
|
+
finally: (onFinally?: () => void) => Promise<T>;
|
|
5
|
+
promise: () => Promise<T>;
|
|
6
|
+
}
|
|
7
|
+
export declare function doRequest(rpOptions: RpOptions, callback?: Callback): RequestPromiseLike;
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.doRequest = doRequest;
|
|
4
|
+
const auth_1 = require("./auth");
|
|
5
|
+
const errors_1 = require("./errors");
|
|
6
|
+
const got_loader_1 = require("./got-loader");
|
|
7
|
+
const response_1 = require("./response");
|
|
8
|
+
const translate_1 = require("./translate");
|
|
9
|
+
class RequestPromiseImpl {
|
|
10
|
+
settled;
|
|
11
|
+
constructor(settled) {
|
|
12
|
+
this.settled = settled;
|
|
13
|
+
}
|
|
14
|
+
then(onFulfilled, onRejected) {
|
|
15
|
+
return this.settled.then(onFulfilled, onRejected);
|
|
16
|
+
}
|
|
17
|
+
catch(onRejected) {
|
|
18
|
+
return this.settled.catch(onRejected);
|
|
19
|
+
}
|
|
20
|
+
finally(onFinally) {
|
|
21
|
+
return this.settled.finally(onFinally);
|
|
22
|
+
}
|
|
23
|
+
promise() {
|
|
24
|
+
return this.settled;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function toRequestError(err, prepared) {
|
|
28
|
+
if (err instanceof errors_1.RequestError)
|
|
29
|
+
return err;
|
|
30
|
+
const gotError = err;
|
|
31
|
+
if (gotError?.name === 'MaxRedirectsError') {
|
|
32
|
+
const href = gotError.response?.url ?? prepared.url;
|
|
33
|
+
return new errors_1.RequestError(new Error(`Exceeded maxRedirects. Probably stuck in a redirect loop ${href}`), prepared.rpOptions, undefined);
|
|
34
|
+
}
|
|
35
|
+
if (gotError?.name === 'TimeoutError') {
|
|
36
|
+
const connectPhase = ['lookup', 'connect', 'secureConnect'].includes(gotError.event ?? '');
|
|
37
|
+
const code = connectPhase ? 'ETIMEDOUT' : 'ESOCKETTIMEDOUT';
|
|
38
|
+
const cause = Object.assign(new Error(code), { code, ...(connectPhase ? { connect: true } : {}) });
|
|
39
|
+
return new errors_1.RequestError(cause, prepared.rpOptions, undefined);
|
|
40
|
+
}
|
|
41
|
+
const original = (gotError?.cause ?? gotError);
|
|
42
|
+
const cause = new Error(original?.message ?? String(err));
|
|
43
|
+
for (const field of ['code', 'errno', 'syscall', 'address', 'port', 'hostname']) {
|
|
44
|
+
if (original?.[field] !== undefined)
|
|
45
|
+
cause[field] = original[field];
|
|
46
|
+
}
|
|
47
|
+
return new errors_1.RequestError(cause, prepared.rpOptions, undefined);
|
|
48
|
+
}
|
|
49
|
+
async function gotCall(prepared, extraHeaders) {
|
|
50
|
+
const gotModule = await (0, got_loader_1.loadGot)();
|
|
51
|
+
const got = gotModule.default;
|
|
52
|
+
const options = extraHeaders
|
|
53
|
+
? {
|
|
54
|
+
...prepared.gotOptions,
|
|
55
|
+
headers: { ...prepared.gotOptions.headers, ...extraHeaders }
|
|
56
|
+
}
|
|
57
|
+
: prepared.gotOptions;
|
|
58
|
+
try {
|
|
59
|
+
return await got(prepared.url, options);
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
throw toRequestError(err, prepared);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function decodeBody(rawBody, prepared) {
|
|
66
|
+
// request's empty-body rule: undefined in json mode, '' (or empty Buffer) otherwise
|
|
67
|
+
if (rawBody.length === 0) {
|
|
68
|
+
if (prepared.jsonMode)
|
|
69
|
+
return undefined;
|
|
70
|
+
return prepared.encoding === null ? rawBody : '';
|
|
71
|
+
}
|
|
72
|
+
if (prepared.encoding === null)
|
|
73
|
+
return rawBody;
|
|
74
|
+
const text = rawBody.toString((prepared.encoding ?? 'utf8'));
|
|
75
|
+
if (prepared.jsonMode && text.length > 0) {
|
|
76
|
+
try {
|
|
77
|
+
return JSON.parse(text);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return text;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return text;
|
|
84
|
+
}
|
|
85
|
+
async function run(prepared, state) {
|
|
86
|
+
let gotResponse = await gotCall(prepared);
|
|
87
|
+
if (gotResponse.statusCode === 401 && prepared.deferredAuth) {
|
|
88
|
+
const challenge = gotResponse.headers['www-authenticate'];
|
|
89
|
+
const answer = (0, auth_1.answerAuthChallenge)(challenge, prepared);
|
|
90
|
+
if (answer) {
|
|
91
|
+
prepared.requestHeaders.authorization = answer;
|
|
92
|
+
gotResponse = await gotCall(prepared, { authorization: answer });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const body = decodeBody(gotResponse.rawBody ?? Buffer.alloc(0), prepared);
|
|
96
|
+
const full = (0, response_1.buildFullResponse)(gotResponse, prepared, body);
|
|
97
|
+
state.full = full;
|
|
98
|
+
const is2xx = /^2/.test(String(full.statusCode));
|
|
99
|
+
const shouldTransform = prepared.transform !== undefined && (is2xx || !prepared.transform2xxOnly);
|
|
100
|
+
const applyTransform = async () => {
|
|
101
|
+
try {
|
|
102
|
+
return await prepared.transform?.(body, full, prepared.resolveWithFullResponse);
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
throw new errors_1.TransformError(err, prepared.rpOptions, full);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
if (prepared.simple && !is2xx) {
|
|
109
|
+
const responseForError = shouldTransform ? await applyTransform() : full;
|
|
110
|
+
throw new errors_1.StatusCodeError(full.statusCode, body, prepared.rpOptions, responseForError);
|
|
111
|
+
}
|
|
112
|
+
if (shouldTransform)
|
|
113
|
+
return applyTransform();
|
|
114
|
+
return prepared.resolveWithFullResponse ? full : body;
|
|
115
|
+
}
|
|
116
|
+
function doRequest(rpOptions, callback) {
|
|
117
|
+
const state = {};
|
|
118
|
+
// request-promise-native surfaces validation errors as rejections, never sync throws
|
|
119
|
+
const settled = (async () => run((0, translate_1.prepare)(rpOptions), state))();
|
|
120
|
+
if (callback) {
|
|
121
|
+
settled.then(() => callback(null, state.full, state.full?.body), (err) => {
|
|
122
|
+
if (state.full && err instanceof errors_1.StatusCodeError) {
|
|
123
|
+
callback(null, state.full, state.full.body);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
callback(err?.cause ?? err, state.full, state.full?.body);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
// callback consumers rarely touch the returned promise — keep its rejection handled
|
|
130
|
+
settled.catch(() => { });
|
|
131
|
+
}
|
|
132
|
+
return new RequestPromiseImpl(settled);
|
|
133
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare class StatusCodeError extends Error {
|
|
2
|
+
name: "StatusCodeError";
|
|
3
|
+
statusCode: number;
|
|
4
|
+
error: unknown;
|
|
5
|
+
options: unknown;
|
|
6
|
+
response: unknown;
|
|
7
|
+
constructor(statusCode: number, body: unknown, options?: unknown, response?: unknown);
|
|
8
|
+
}
|
|
9
|
+
export declare class RequestError extends Error {
|
|
10
|
+
name: "RequestError";
|
|
11
|
+
cause: unknown;
|
|
12
|
+
error: unknown;
|
|
13
|
+
options: unknown;
|
|
14
|
+
response: unknown;
|
|
15
|
+
constructor(cause: unknown, options?: unknown, response?: unknown);
|
|
16
|
+
}
|
|
17
|
+
export declare class TransformError extends Error {
|
|
18
|
+
name: "TransformError";
|
|
19
|
+
cause: unknown;
|
|
20
|
+
error: unknown;
|
|
21
|
+
options: unknown;
|
|
22
|
+
response: unknown;
|
|
23
|
+
constructor(cause: unknown, options?: unknown, response?: unknown);
|
|
24
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Error classes matching request-promise-core's errors 1:1 (names, fields, message formats).
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.TransformError = exports.RequestError = exports.StatusCodeError = void 0;
|
|
5
|
+
class StatusCodeError extends Error {
|
|
6
|
+
name = 'StatusCodeError';
|
|
7
|
+
statusCode;
|
|
8
|
+
error;
|
|
9
|
+
options;
|
|
10
|
+
response;
|
|
11
|
+
constructor(statusCode, body, options, response) {
|
|
12
|
+
super(`${statusCode} - ${JSON.stringify(body)}`);
|
|
13
|
+
this.statusCode = statusCode;
|
|
14
|
+
this.error = body;
|
|
15
|
+
this.options = options;
|
|
16
|
+
this.response = response;
|
|
17
|
+
Error.captureStackTrace(this, StatusCodeError);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.StatusCodeError = StatusCodeError;
|
|
21
|
+
class RequestError extends Error {
|
|
22
|
+
name = 'RequestError';
|
|
23
|
+
cause;
|
|
24
|
+
error;
|
|
25
|
+
options;
|
|
26
|
+
response;
|
|
27
|
+
constructor(cause, options, response) {
|
|
28
|
+
super(String(cause));
|
|
29
|
+
this.cause = cause;
|
|
30
|
+
this.error = cause;
|
|
31
|
+
this.options = options;
|
|
32
|
+
this.response = response;
|
|
33
|
+
Error.captureStackTrace(this, RequestError);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.RequestError = RequestError;
|
|
37
|
+
class TransformError extends Error {
|
|
38
|
+
name = 'TransformError';
|
|
39
|
+
cause;
|
|
40
|
+
error;
|
|
41
|
+
options;
|
|
42
|
+
response;
|
|
43
|
+
constructor(cause, options, response) {
|
|
44
|
+
super(String(cause));
|
|
45
|
+
this.cause = cause;
|
|
46
|
+
this.error = cause;
|
|
47
|
+
this.options = options;
|
|
48
|
+
this.response = response;
|
|
49
|
+
Error.captureStackTrace(this, TransformError);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.TransformError = TransformError;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadGot = loadGot;
|
|
4
|
+
const node_module_1 = require("node:module");
|
|
5
|
+
// Node >= 20.19 / >= 22.12 can require() ESM directly. process.getBuiltinModule
|
|
6
|
+
// yields the genuine node:module even inside sandboxed test runners (e.g. Jest)
|
|
7
|
+
// whose own require/createRequire reject ESM.
|
|
8
|
+
const builtinModule = process.getBuiltinModule?.('node:module');
|
|
9
|
+
const nativeRequire = (builtinModule?.createRequire ?? node_module_1.createRequire)(__filename);
|
|
10
|
+
// Fallback for older Node: tsc (CJS output) would rewrite a plain `import()` into
|
|
11
|
+
// `require()`, which cannot load ESM there — route through native import via new Function.
|
|
12
|
+
const dynamicImport = new Function('specifier', 'return import(specifier)');
|
|
13
|
+
let cached;
|
|
14
|
+
function loadSync() {
|
|
15
|
+
try {
|
|
16
|
+
const mod = nativeRequire('got');
|
|
17
|
+
return mod?.default === undefined ? undefined : mod;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function loadGot() {
|
|
24
|
+
if (!cached) {
|
|
25
|
+
const sync = loadSync();
|
|
26
|
+
cached = sync ? Promise.resolve(sync) : dynamicImport('got');
|
|
27
|
+
}
|
|
28
|
+
return cached;
|
|
29
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type Headers = Record<string, unknown>;
|
|
2
|
+
export declare function findHeaderKey(headers: Headers, name: string): string | undefined;
|
|
3
|
+
export declare function hasHeader(headers: Headers, name: string): boolean;
|
|
4
|
+
export declare function getHeader(headers: Headers, name: string): unknown;
|
|
5
|
+
export declare function setHeader(headers: Headers, name: string, value: unknown): void;
|
|
6
|
+
export declare function setHeaderIfAbsent(headers: Headers, name: string, value: unknown): void;
|
|
7
|
+
export declare function deleteHeader(headers: Headers, name: string): void;
|
package/dist/headers.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// request treats header names case-insensitively (via caseless) while preserving
|
|
3
|
+
// the caller's original casing on the wire and on response.request.headers.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.findHeaderKey = findHeaderKey;
|
|
6
|
+
exports.hasHeader = hasHeader;
|
|
7
|
+
exports.getHeader = getHeader;
|
|
8
|
+
exports.setHeader = setHeader;
|
|
9
|
+
exports.setHeaderIfAbsent = setHeaderIfAbsent;
|
|
10
|
+
exports.deleteHeader = deleteHeader;
|
|
11
|
+
function findHeaderKey(headers, name) {
|
|
12
|
+
const lower = name.toLowerCase();
|
|
13
|
+
return Object.keys(headers).find((key) => key.toLowerCase() === lower);
|
|
14
|
+
}
|
|
15
|
+
function hasHeader(headers, name) {
|
|
16
|
+
return findHeaderKey(headers, name) !== undefined;
|
|
17
|
+
}
|
|
18
|
+
function getHeader(headers, name) {
|
|
19
|
+
const key = findHeaderKey(headers, name);
|
|
20
|
+
return key === undefined ? undefined : headers[key];
|
|
21
|
+
}
|
|
22
|
+
function setHeader(headers, name, value) {
|
|
23
|
+
const key = findHeaderKey(headers, name) ?? name;
|
|
24
|
+
headers[key] = value;
|
|
25
|
+
}
|
|
26
|
+
function setHeaderIfAbsent(headers, name, value) {
|
|
27
|
+
if (!hasHeader(headers, name))
|
|
28
|
+
headers[name] = value;
|
|
29
|
+
}
|
|
30
|
+
function deleteHeader(headers, name) {
|
|
31
|
+
const key = findHeaderKey(headers, name);
|
|
32
|
+
if (key !== undefined)
|
|
33
|
+
delete headers[key];
|
|
34
|
+
}
|