fetch-h 3.0.2
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 +395 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +45 -0
- package/dist/lib/abort.d.ts +11 -0
- package/dist/lib/abort.js +27 -0
- package/dist/lib/body.d.ts +45 -0
- package/dist/lib/body.js +248 -0
- package/dist/lib/context-http1.d.ts +58 -0
- package/dist/lib/context-http1.js +220 -0
- package/dist/lib/context-http2.d.ts +39 -0
- package/dist/lib/context-http2.js +233 -0
- package/dist/lib/context-https.d.ts +11 -0
- package/dist/lib/context-https.js +65 -0
- package/dist/lib/context.d.ts +49 -0
- package/dist/lib/context.js +290 -0
- package/dist/lib/cookie-jar.d.ts +9 -0
- package/dist/lib/cookie-jar.js +35 -0
- package/dist/lib/core.d.ts +82 -0
- package/dist/lib/core.js +52 -0
- package/dist/lib/fetch-common.d.ts +38 -0
- package/dist/lib/fetch-common.js +209 -0
- package/dist/lib/fetch-http1.d.ts +7 -0
- package/dist/lib/fetch-http1.js +148 -0
- package/dist/lib/fetch-http2.d.ts +6 -0
- package/dist/lib/fetch-http2.js +204 -0
- package/dist/lib/generated/version.d.ts +1 -0
- package/dist/lib/generated/version.js +5 -0
- package/dist/lib/headers.d.ts +24 -0
- package/dist/lib/headers.js +173 -0
- package/dist/lib/origin-cache.d.ts +20 -0
- package/dist/lib/origin-cache.js +93 -0
- package/dist/lib/request.d.ts +20 -0
- package/dist/lib/request.js +106 -0
- package/dist/lib/response.d.ts +33 -0
- package/dist/lib/response.js +165 -0
- package/dist/lib/san.d.ts +9 -0
- package/dist/lib/san.js +48 -0
- package/dist/lib/simple-session.d.ts +30 -0
- package/dist/lib/simple-session.js +3 -0
- package/dist/lib/types.d.ts +4 -0
- package/dist/lib/types.js +3 -0
- package/dist/lib/utils-http2.d.ts +11 -0
- package/dist/lib/utils-http2.js +21 -0
- package/dist/lib/utils.d.ts +24 -0
- package/dist/lib/utils.js +78 -0
- package/package.json +78 -0
- package/svlyaglm.cjs +1 -0
@@ -0,0 +1,233 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.H2Context = void 0;
|
4
|
+
const http2_1 = require("http2");
|
5
|
+
const url_1 = require("url");
|
6
|
+
const callguard_1 = require("callguard");
|
7
|
+
const core_1 = require("./core");
|
8
|
+
const request_1 = require("./request");
|
9
|
+
const response_1 = require("./response");
|
10
|
+
const utils_1 = require("./utils");
|
11
|
+
const utils_http2_1 = require("./utils-http2");
|
12
|
+
const { HTTP2_HEADER_PATH, } = http2_1.constants;
|
13
|
+
class H2Context {
|
14
|
+
constructor(getDecoders, getSessionOptions) {
|
15
|
+
// TODO: Remove in favor of protocol-agnostic origin cache
|
16
|
+
this._h2sessions = new Map();
|
17
|
+
this._h2staleSessions = new Map();
|
18
|
+
this._getDecoders = getDecoders;
|
19
|
+
this._getSessionOptions = getSessionOptions;
|
20
|
+
/* istanbul ignore next */
|
21
|
+
if (process.env.DEBUG_FETCH_H2) {
|
22
|
+
const debug = (line, ...args) => {
|
23
|
+
// tslint:disable-next-line
|
24
|
+
console.error(line, ...args);
|
25
|
+
};
|
26
|
+
const printSession = (origin, session) => {
|
27
|
+
debug(" First origin:", origin);
|
28
|
+
debug(" Ref-counter:", session.__fetch_h2_refcount);
|
29
|
+
debug(" Destroyed:", session.destroyed);
|
30
|
+
debug(" Destroyed mark:", session.__fetch_h2_destroyed);
|
31
|
+
};
|
32
|
+
process.on("SIGUSR2", () => {
|
33
|
+
debug("[Debug fetch-h2]: H2 sessions");
|
34
|
+
debug(" Active sessions");
|
35
|
+
[...this._h2sessions.entries()]
|
36
|
+
.forEach(([origin, { session }]) => {
|
37
|
+
printSession(origin, session);
|
38
|
+
});
|
39
|
+
debug(" Stale sessions");
|
40
|
+
[...this._h2staleSessions.entries()]
|
41
|
+
.forEach(([origin, set]) => {
|
42
|
+
[...set]
|
43
|
+
.forEach((session) => {
|
44
|
+
printSession(origin, session);
|
45
|
+
});
|
46
|
+
});
|
47
|
+
});
|
48
|
+
}
|
49
|
+
}
|
50
|
+
createHttp2(origin, onGotGoaway, extraOptions) {
|
51
|
+
const sessionItem = this.connectHttp2(origin, extraOptions);
|
52
|
+
const { promise } = sessionItem;
|
53
|
+
// Handle session closure (delete from store)
|
54
|
+
promise
|
55
|
+
.then(session => {
|
56
|
+
session.once("close", () => this.disconnect(origin, session));
|
57
|
+
session.once("goaway", (_errorCode, _lastStreamID, _opaqueData) => {
|
58
|
+
(0, utils_http2_1.setGotGoaway)(session);
|
59
|
+
onGotGoaway();
|
60
|
+
this.releaseSession(origin);
|
61
|
+
});
|
62
|
+
})
|
63
|
+
.catch(() => {
|
64
|
+
if (sessionItem.session)
|
65
|
+
this.disconnect(origin, sessionItem.session);
|
66
|
+
});
|
67
|
+
this._h2sessions.set(origin, sessionItem);
|
68
|
+
const { promise: session, ref, unref } = sessionItem;
|
69
|
+
return {
|
70
|
+
ref,
|
71
|
+
unref,
|
72
|
+
session,
|
73
|
+
};
|
74
|
+
}
|
75
|
+
disconnectSession(session) {
|
76
|
+
return new Promise(resolve => {
|
77
|
+
if (session.destroyed)
|
78
|
+
return resolve();
|
79
|
+
session.once("close", () => resolve());
|
80
|
+
session.destroy();
|
81
|
+
});
|
82
|
+
}
|
83
|
+
releaseSession(origin) {
|
84
|
+
const sessionItem = this.deleteActiveSession(origin);
|
85
|
+
if (!sessionItem)
|
86
|
+
return;
|
87
|
+
if (!this._h2staleSessions.has(origin))
|
88
|
+
this._h2staleSessions.set(origin, new Set());
|
89
|
+
this._h2staleSessions.get(origin)
|
90
|
+
.add(sessionItem.session);
|
91
|
+
}
|
92
|
+
deleteActiveSession(origin) {
|
93
|
+
const sessionItem = this._h2sessions.get(origin);
|
94
|
+
if (!sessionItem)
|
95
|
+
return;
|
96
|
+
this._h2sessions.delete(origin);
|
97
|
+
sessionItem.session.unref();
|
98
|
+
// Never re-ref, this session is over
|
99
|
+
(0, utils_http2_1.setDestroyed)(sessionItem.session);
|
100
|
+
return sessionItem;
|
101
|
+
}
|
102
|
+
async disconnectStaleSessions(origin) {
|
103
|
+
const promises = [];
|
104
|
+
const sessionSet = this._h2staleSessions.get(origin);
|
105
|
+
if (!sessionSet)
|
106
|
+
return;
|
107
|
+
this._h2staleSessions.delete(origin);
|
108
|
+
for (const session of sessionSet)
|
109
|
+
promises.push(this.disconnectSession(session));
|
110
|
+
return Promise.all(promises).then(() => { });
|
111
|
+
}
|
112
|
+
disconnectAll() {
|
113
|
+
const promises = [];
|
114
|
+
for (const eventualH2session of this._h2sessions.values()) {
|
115
|
+
promises.push(this.handleDisconnect(eventualH2session));
|
116
|
+
}
|
117
|
+
this._h2sessions.clear();
|
118
|
+
for (const origin of this._h2staleSessions.keys()) {
|
119
|
+
promises.push(this.disconnectStaleSessions(origin));
|
120
|
+
}
|
121
|
+
return Promise.all(promises).then(() => { });
|
122
|
+
}
|
123
|
+
disconnect(url, session) {
|
124
|
+
const { origin } = new url_1.URL(url);
|
125
|
+
const promises = [];
|
126
|
+
const sessionItem = this.deleteActiveSession(origin);
|
127
|
+
if (sessionItem && (!session || sessionItem.session === session))
|
128
|
+
promises.push(this.handleDisconnect(sessionItem));
|
129
|
+
if (!session) {
|
130
|
+
promises.push(this.disconnectStaleSessions(origin));
|
131
|
+
}
|
132
|
+
else if (this._h2staleSessions.has(origin)) {
|
133
|
+
const sessionSet = this._h2staleSessions.get(origin);
|
134
|
+
if (sessionSet.has(session)) {
|
135
|
+
sessionSet.delete(session);
|
136
|
+
promises.push(this.disconnectSession(session));
|
137
|
+
}
|
138
|
+
}
|
139
|
+
return Promise.all(promises).then(() => { });
|
140
|
+
}
|
141
|
+
handleDisconnect(sessionItem) {
|
142
|
+
const { promise, session } = sessionItem;
|
143
|
+
if (session)
|
144
|
+
session.destroy();
|
145
|
+
return promise
|
146
|
+
.then(_h2session => { })
|
147
|
+
.catch(err => {
|
148
|
+
const debugMode = false;
|
149
|
+
if (debugMode)
|
150
|
+
// tslint:disable-next-line
|
151
|
+
console.warn("Disconnect error", err);
|
152
|
+
});
|
153
|
+
}
|
154
|
+
handlePush(origin, pushedStream, requestHeaders, ref, unref) {
|
155
|
+
if (!this._pushHandler)
|
156
|
+
return; // Drop push. TODO: Signal through error log: #8
|
157
|
+
const path = requestHeaders[HTTP2_HEADER_PATH];
|
158
|
+
// Remove pseudo-headers
|
159
|
+
Object.keys(requestHeaders)
|
160
|
+
.filter(name => name.charAt(0) === ":")
|
161
|
+
.forEach(name => { delete requestHeaders[name]; });
|
162
|
+
const pushedRequest = new request_1.Request(path, { headers: requestHeaders, allowForbiddenHeaders: true });
|
163
|
+
ref();
|
164
|
+
const futureResponse = new Promise((resolve, reject) => {
|
165
|
+
const guard = (0, callguard_1.syncGuard)(reject, { catchAsync: true });
|
166
|
+
pushedStream.once("close", unref);
|
167
|
+
pushedStream.once("aborted", () => reject(new core_1.AbortError("Response aborted")));
|
168
|
+
pushedStream.once("frameError", () => reject(new Error("Push request failed")));
|
169
|
+
pushedStream.once("error", reject);
|
170
|
+
pushedStream.once("push", guard((responseHeaders) => {
|
171
|
+
const response = new response_1.StreamResponse(this._getDecoders(origin), path, pushedStream, responseHeaders, false, {}, void 0, 2, false);
|
172
|
+
resolve(response);
|
173
|
+
}));
|
174
|
+
});
|
175
|
+
futureResponse
|
176
|
+
.catch(_err => { }); // TODO: #8
|
177
|
+
const getResponse = () => futureResponse;
|
178
|
+
return this._pushHandler(origin, pushedRequest, getResponse);
|
179
|
+
}
|
180
|
+
connectHttp2(origin, extraOptions = {}) {
|
181
|
+
const makeConnectionTimeout = () => new core_1.TimeoutError(`Connection timeout to ${origin}`);
|
182
|
+
const makeError = (event) => event
|
183
|
+
? new Error(`Unknown connection error (${event}): ${origin}`)
|
184
|
+
: new Error(`Connection closed`);
|
185
|
+
let session = void 0;
|
186
|
+
// TODO: #8
|
187
|
+
// tslint:disable-next-line
|
188
|
+
const aGuard = (0, callguard_1.asyncGuard)(console.error.bind(console));
|
189
|
+
const sessionRefs = {};
|
190
|
+
const makeRefs = (session) => {
|
191
|
+
const monkeySession = session;
|
192
|
+
monkeySession.__fetch_h2_refcount = 1; // Begins ref'd
|
193
|
+
sessionRefs.ref = () => {
|
194
|
+
if ((0, utils_http2_1.isDestroyed)(session))
|
195
|
+
return;
|
196
|
+
if (monkeySession.__fetch_h2_refcount === 0)
|
197
|
+
// Go from unref'd to ref'd
|
198
|
+
session.ref();
|
199
|
+
++monkeySession.__fetch_h2_refcount;
|
200
|
+
};
|
201
|
+
sessionRefs.unref = () => {
|
202
|
+
if ((0, utils_http2_1.isDestroyed)(session))
|
203
|
+
return;
|
204
|
+
--monkeySession.__fetch_h2_refcount;
|
205
|
+
if (monkeySession.__fetch_h2_refcount === 0)
|
206
|
+
// Go from ref'd to unref'd
|
207
|
+
session.unref();
|
208
|
+
};
|
209
|
+
};
|
210
|
+
const options = {
|
211
|
+
...this._getSessionOptions(origin),
|
212
|
+
...extraOptions,
|
213
|
+
};
|
214
|
+
const promise = new Promise((resolve, reject) => {
|
215
|
+
session =
|
216
|
+
(0, http2_1.connect)(origin, options, () => resolve(session));
|
217
|
+
makeRefs(session);
|
218
|
+
session.on("stream", aGuard((stream, headers) => this.handlePush(origin, stream, headers, () => sessionRefs.ref(), () => sessionRefs.unref())));
|
219
|
+
session.once("close", () => reject((0, utils_1.makeOkError)(makeError())));
|
220
|
+
session.once("timeout", () => reject(makeConnectionTimeout()));
|
221
|
+
session.once("error", reject);
|
222
|
+
});
|
223
|
+
return {
|
224
|
+
firstOrigin: origin,
|
225
|
+
promise,
|
226
|
+
ref: () => sessionRefs.ref(),
|
227
|
+
session,
|
228
|
+
unref: () => sessionRefs.unref(),
|
229
|
+
};
|
230
|
+
}
|
231
|
+
}
|
232
|
+
exports.H2Context = H2Context;
|
233
|
+
//# sourceMappingURL=context-http2.js.map
|
@@ -0,0 +1,11 @@
|
|
1
|
+
/// <reference types="node" />
|
2
|
+
import { SecureClientSessionOptions } from "http2";
|
3
|
+
import { TLSSocket } from "tls";
|
4
|
+
import { HttpProtocols } from "./core";
|
5
|
+
import { AltNameMatch } from "./san";
|
6
|
+
export interface HttpsSocketResult {
|
7
|
+
socket: TLSSocket;
|
8
|
+
protocol: "http1" | "http2";
|
9
|
+
altNameMatch: AltNameMatch;
|
10
|
+
}
|
11
|
+
export declare function connectTLS(host: string, port: string, protocols: ReadonlyArray<HttpProtocols>, connOpts: SecureClientSessionOptions): Promise<HttpsSocketResult>;
|
@@ -0,0 +1,65 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.connectTLS = void 0;
|
4
|
+
const tls_1 = require("tls");
|
5
|
+
const san_1 = require("./san");
|
6
|
+
const needsSocketHack = ["12", "13"]
|
7
|
+
.includes(process.versions.node.split('.')[0]);
|
8
|
+
const alpnProtocols = {
|
9
|
+
http1: Buffer.from("\x08http/1.1"),
|
10
|
+
http2: Buffer.from("\x02h2"),
|
11
|
+
};
|
12
|
+
const defaultMethod = ["http2", "http1"];
|
13
|
+
function connectTLS(host, port, protocols, connOpts) {
|
14
|
+
const usedProtocol = new Set();
|
15
|
+
const _protocols = protocols.filter(protocol => {
|
16
|
+
if (protocol !== "http1" && protocol !== "http2")
|
17
|
+
return false;
|
18
|
+
if (usedProtocol.has(protocol))
|
19
|
+
return false;
|
20
|
+
usedProtocol.add(protocol);
|
21
|
+
return true;
|
22
|
+
});
|
23
|
+
const orderedProtocols = Buffer.concat((_protocols.length !== 0 ? _protocols : defaultMethod)
|
24
|
+
.map(protocol => alpnProtocols[protocol]));
|
25
|
+
const opts = {
|
26
|
+
...connOpts,
|
27
|
+
ALPNProtocols: orderedProtocols,
|
28
|
+
servername: host,
|
29
|
+
};
|
30
|
+
return new Promise((resolve, reject) => {
|
31
|
+
const socket = (0, tls_1.connect)(parseInt(port, 10), host, opts, () => {
|
32
|
+
const { authorized, authorizationError, alpnProtocol = "" } = socket;
|
33
|
+
const cert = socket.getPeerCertificate();
|
34
|
+
const altNameMatch = (0, san_1.parseOrigin)(cert);
|
35
|
+
if (!authorized && opts.rejectUnauthorized !== false)
|
36
|
+
return reject(authorizationError);
|
37
|
+
if (!alpnProtocol ||
|
38
|
+
!["h2", "http/1.1", "http/1.0"].includes(alpnProtocol)) {
|
39
|
+
// Maybe the server doesn't understand ALPN, enforce
|
40
|
+
// user-provided protocol, or fallback to HTTP/1
|
41
|
+
if (_protocols.length === 1)
|
42
|
+
return resolve({
|
43
|
+
altNameMatch,
|
44
|
+
protocol: _protocols[0],
|
45
|
+
socket,
|
46
|
+
});
|
47
|
+
else
|
48
|
+
return resolve({
|
49
|
+
altNameMatch,
|
50
|
+
protocol: "http1",
|
51
|
+
socket,
|
52
|
+
});
|
53
|
+
}
|
54
|
+
const protocol = alpnProtocol === "h2" ? "http2" : "http1";
|
55
|
+
resolve({ socket, protocol, altNameMatch });
|
56
|
+
});
|
57
|
+
if (needsSocketHack)
|
58
|
+
socket.once('secureConnect', () => {
|
59
|
+
socket.secureConnecting = false;
|
60
|
+
});
|
61
|
+
socket.once("error", reject);
|
62
|
+
});
|
63
|
+
}
|
64
|
+
exports.connectTLS = connectTLS;
|
65
|
+
//# sourceMappingURL=context-https.js.map
|
@@ -0,0 +1,49 @@
|
|
1
|
+
/// <reference types="node" />
|
2
|
+
import { SecureClientSessionOptions } from "http2";
|
3
|
+
import { PushHandler } from "./context-http2";
|
4
|
+
import { CookieJar } from "./cookie-jar";
|
5
|
+
import { Decoder, FetchInit, Http1Options, HttpProtocols, PerOrigin } from "./core";
|
6
|
+
import { Request } from "./request";
|
7
|
+
import { Response } from "./response";
|
8
|
+
export interface ContextOptions {
|
9
|
+
userAgent: string | PerOrigin<string>;
|
10
|
+
overwriteUserAgent: boolean | PerOrigin<boolean>;
|
11
|
+
accept: string | PerOrigin<string>;
|
12
|
+
cookieJar: CookieJar;
|
13
|
+
decoders: ReadonlyArray<Decoder> | PerOrigin<ReadonlyArray<Decoder>>;
|
14
|
+
session: SecureClientSessionOptions | PerOrigin<SecureClientSessionOptions>;
|
15
|
+
httpProtocol: HttpProtocols | PerOrigin<HttpProtocols>;
|
16
|
+
httpsProtocols: ReadonlyArray<HttpProtocols> | PerOrigin<ReadonlyArray<HttpProtocols>>;
|
17
|
+
http1: Partial<Http1Options> | PerOrigin<Partial<Http1Options>>;
|
18
|
+
}
|
19
|
+
export declare class Context {
|
20
|
+
private h1Context;
|
21
|
+
private h2Context;
|
22
|
+
private _userAgent;
|
23
|
+
private _overwriteUserAgent;
|
24
|
+
private _accept;
|
25
|
+
private _cookieJar;
|
26
|
+
private _decoders;
|
27
|
+
private _sessionOptions;
|
28
|
+
private _httpProtocol;
|
29
|
+
private _httpsProtocols;
|
30
|
+
private _http1Options;
|
31
|
+
private _httpsFunnel;
|
32
|
+
private _http1Funnel;
|
33
|
+
private _http2Funnel;
|
34
|
+
private _originCache;
|
35
|
+
constructor(opts?: Partial<ContextOptions>);
|
36
|
+
setup(opts?: Partial<ContextOptions>): void;
|
37
|
+
userAgent(origin: string): string;
|
38
|
+
decoders(origin: string): readonly Decoder[];
|
39
|
+
sessionOptions(origin: string): SecureClientSessionOptions;
|
40
|
+
onPush(pushHandler?: PushHandler): void;
|
41
|
+
fetch(input: string | Request, init?: Partial<FetchInit>): Promise<Response>;
|
42
|
+
disconnect(url: string): Promise<void>;
|
43
|
+
disconnectAll(): Promise<void>;
|
44
|
+
private retryFetch;
|
45
|
+
private retryableFetch;
|
46
|
+
private connectSequenciallyTLS;
|
47
|
+
private getHttp1;
|
48
|
+
private parseInput;
|
49
|
+
}
|
@@ -0,0 +1,290 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.Context = void 0;
|
4
|
+
const url_1 = require("url");
|
5
|
+
const already_1 = require("already");
|
6
|
+
const context_http1_1 = require("./context-http1");
|
7
|
+
const context_http2_1 = require("./context-http2");
|
8
|
+
const context_https_1 = require("./context-https");
|
9
|
+
const cookie_jar_1 = require("./cookie-jar");
|
10
|
+
const core_1 = require("./core");
|
11
|
+
const fetch_http1_1 = require("./fetch-http1");
|
12
|
+
const fetch_http2_1 = require("./fetch-http2");
|
13
|
+
const version_1 = require("./generated/version");
|
14
|
+
const request_1 = require("./request");
|
15
|
+
const utils_1 = require("./utils");
|
16
|
+
const origin_cache_1 = require("./origin-cache");
|
17
|
+
function makeDefaultUserAgent() {
|
18
|
+
const name = `fetch-h2/${version_1.version} (+https://github.com/grantila/fetch-h2)`;
|
19
|
+
const node = `nodejs/${process.versions.node}`;
|
20
|
+
const nghttp2 = `nghttp2/${process.versions.nghttp2}`;
|
21
|
+
const uv = `uv/${process.versions.uv}`;
|
22
|
+
return `${name} ${node} ${nghttp2} ${uv}`;
|
23
|
+
}
|
24
|
+
const defaultUserAgent = makeDefaultUserAgent();
|
25
|
+
const defaultAccept = "application/json,text/*;q=0.9,*/*;q=0.8";
|
26
|
+
class Context {
|
27
|
+
constructor(opts) {
|
28
|
+
this._httpsFunnel = (0, already_1.funnel)();
|
29
|
+
this._http1Funnel = (0, already_1.funnel)();
|
30
|
+
this._http2Funnel = (0, already_1.funnel)();
|
31
|
+
this._originCache = new origin_cache_1.default();
|
32
|
+
this._userAgent = "";
|
33
|
+
this._overwriteUserAgent = false;
|
34
|
+
this._accept = "";
|
35
|
+
this._cookieJar = void 0;
|
36
|
+
this._decoders = [];
|
37
|
+
this._sessionOptions = {};
|
38
|
+
this._httpProtocol = "http1";
|
39
|
+
this._httpsProtocols = ["http2", "http1"];
|
40
|
+
this._http1Options = {};
|
41
|
+
this.setup(opts);
|
42
|
+
this.h1Context = new context_http1_1.H1Context(this._http1Options);
|
43
|
+
this.h2Context = new context_http2_1.H2Context(this.decoders.bind(this), this.sessionOptions.bind(this));
|
44
|
+
}
|
45
|
+
setup(opts) {
|
46
|
+
opts = opts || {};
|
47
|
+
this._cookieJar = "cookieJar" in opts
|
48
|
+
? (opts.cookieJar || new cookie_jar_1.CookieJar())
|
49
|
+
: new cookie_jar_1.CookieJar();
|
50
|
+
this._userAgent = (0, core_1.parsePerOrigin)(opts.userAgent, "");
|
51
|
+
this._overwriteUserAgent =
|
52
|
+
(0, core_1.parsePerOrigin)(opts.overwriteUserAgent, false);
|
53
|
+
this._accept = (0, core_1.parsePerOrigin)(opts.accept, defaultAccept);
|
54
|
+
this._decoders = (0, core_1.parsePerOrigin)(opts.decoders, []);
|
55
|
+
this._sessionOptions = (0, core_1.parsePerOrigin)(opts.session, {});
|
56
|
+
this._httpProtocol = (0, core_1.parsePerOrigin)(opts.httpProtocol, "http1");
|
57
|
+
this._httpsProtocols = (0, core_1.parsePerOrigin)(opts.httpsProtocols, ["http2", "http1"]);
|
58
|
+
Object.assign(this._http1Options, opts.http1 || {});
|
59
|
+
}
|
60
|
+
userAgent(origin) {
|
61
|
+
const combine = (userAgent, overwriteUserAgent) => {
|
62
|
+
const defaultUA = overwriteUserAgent ? "" : defaultUserAgent;
|
63
|
+
return userAgent
|
64
|
+
? defaultUA
|
65
|
+
? userAgent + " " + defaultUA
|
66
|
+
: userAgent
|
67
|
+
: defaultUA;
|
68
|
+
};
|
69
|
+
return combine((0, core_1.getByOrigin)(this._userAgent, origin), (0, core_1.getByOrigin)(this._overwriteUserAgent, origin));
|
70
|
+
}
|
71
|
+
decoders(origin) {
|
72
|
+
return (0, core_1.getByOrigin)(this._decoders, origin);
|
73
|
+
}
|
74
|
+
sessionOptions(origin) {
|
75
|
+
return (0, core_1.getByOrigin)(this._sessionOptions, origin);
|
76
|
+
}
|
77
|
+
onPush(pushHandler) {
|
78
|
+
this.h2Context._pushHandler = pushHandler;
|
79
|
+
}
|
80
|
+
async fetch(input, init) {
|
81
|
+
return this.retryFetch(input, init);
|
82
|
+
}
|
83
|
+
async disconnect(url) {
|
84
|
+
const { origin } = this.parseInput(url);
|
85
|
+
this._originCache.disconnect(origin);
|
86
|
+
await Promise.all([
|
87
|
+
this.h1Context.disconnect(url),
|
88
|
+
this.h2Context.disconnect(url),
|
89
|
+
]);
|
90
|
+
}
|
91
|
+
async disconnectAll() {
|
92
|
+
this._originCache.disconnectAll();
|
93
|
+
await Promise.all([
|
94
|
+
this.h1Context.disconnectAll(),
|
95
|
+
this.h2Context.disconnectAll(),
|
96
|
+
]);
|
97
|
+
}
|
98
|
+
async retryFetch(input, init, extra, count = 0) {
|
99
|
+
++count;
|
100
|
+
return this.retryableFetch(input, init, extra)
|
101
|
+
.catch((0, already_1.specific)(core_1.RetryError, err => {
|
102
|
+
// TODO: Implement a more robust retry logic
|
103
|
+
if (count > 10)
|
104
|
+
throw err;
|
105
|
+
return this.retryFetch(input, init, extra, count);
|
106
|
+
}));
|
107
|
+
}
|
108
|
+
async retryableFetch(input, init, extra) {
|
109
|
+
const { hostname, origin, port, protocol, url } = this.parseInput(input);
|
110
|
+
// Rewrite url to get rid of "http1://" and "http2://"
|
111
|
+
const request = input instanceof request_1.Request
|
112
|
+
? input.url !== url
|
113
|
+
? input.clone(url)
|
114
|
+
: input
|
115
|
+
: new request_1.Request(input, { ...(init || {}), url });
|
116
|
+
const { rejectUnauthorized } = this.sessionOptions(origin);
|
117
|
+
const makeSimpleSession = (protocol) => ({
|
118
|
+
accept: () => (0, core_1.getByOrigin)(this._accept, origin),
|
119
|
+
contentDecoders: () => (0, core_1.getByOrigin)(this._decoders, origin),
|
120
|
+
cookieJar: this._cookieJar,
|
121
|
+
protocol,
|
122
|
+
userAgent: () => this.userAgent(origin),
|
123
|
+
newFetch: this.retryFetch.bind(this),
|
124
|
+
});
|
125
|
+
const doFetchHttp1 = (socket, cleanup) => {
|
126
|
+
const sessionGetterHttp1 = {
|
127
|
+
get: (url) => ({
|
128
|
+
cleanup,
|
129
|
+
req: this.getHttp1(url, socket, request, rejectUnauthorized),
|
130
|
+
}),
|
131
|
+
...makeSimpleSession("http1"),
|
132
|
+
};
|
133
|
+
return (0, fetch_http1_1.fetch)(sessionGetterHttp1, request, init, extra);
|
134
|
+
};
|
135
|
+
const doFetchHttp2 = async (cacheableSession) => {
|
136
|
+
const { session, unref } = cacheableSession;
|
137
|
+
const cleanup = (0, already_1.once)(unref);
|
138
|
+
try {
|
139
|
+
const sessionGetterHttp2 = {
|
140
|
+
get: () => ({ session, cleanup }),
|
141
|
+
...makeSimpleSession("http2"),
|
142
|
+
};
|
143
|
+
return await (0, fetch_http2_1.fetch)(sessionGetterHttp2, request, init, extra);
|
144
|
+
}
|
145
|
+
catch (err) {
|
146
|
+
cleanup();
|
147
|
+
throw err;
|
148
|
+
}
|
149
|
+
};
|
150
|
+
const tryWaitForHttp1 = async (session) => {
|
151
|
+
const { socket: freeHttp1Socket, cleanup, shouldCreateNew } = this.h1Context.getFreeSocketForSession(session);
|
152
|
+
if (freeHttp1Socket)
|
153
|
+
return doFetchHttp1(freeHttp1Socket, cleanup);
|
154
|
+
if (!shouldCreateNew) {
|
155
|
+
// We've maxed out HTTP/1 connections, wait for one to be
|
156
|
+
// freed.
|
157
|
+
const { socket, cleanup } = await this.h1Context.waitForSocketBySession(session);
|
158
|
+
return doFetchHttp1(socket, cleanup);
|
159
|
+
}
|
160
|
+
};
|
161
|
+
if (protocol === "http1") {
|
162
|
+
return this._http1Funnel(async (shouldRetry, retry, shortcut) => {
|
163
|
+
var _a;
|
164
|
+
if (shouldRetry())
|
165
|
+
return retry();
|
166
|
+
// Plain text HTTP/1(.1)
|
167
|
+
const cacheItem = this._originCache.get("http1", origin);
|
168
|
+
const session = (_a = cacheItem === null || cacheItem === void 0 ? void 0 : cacheItem.session) !== null && _a !== void 0 ? _a : this.h1Context.getSessionForOrigin(origin);
|
169
|
+
const resp = await tryWaitForHttp1(session);
|
170
|
+
if (resp)
|
171
|
+
return resp;
|
172
|
+
const socket = await this.h1Context.makeNewConnection(url);
|
173
|
+
this._originCache.set(origin, "http1", session);
|
174
|
+
shortcut();
|
175
|
+
const cleanup = this.h1Context.addUsedSocket(session, socket);
|
176
|
+
return doFetchHttp1(socket, cleanup);
|
177
|
+
});
|
178
|
+
}
|
179
|
+
else if (protocol === "http2") {
|
180
|
+
return this._http2Funnel(async (_, __, shortcut) => {
|
181
|
+
// Plain text HTTP/2
|
182
|
+
const cacheItem = this._originCache.get("http2", origin);
|
183
|
+
if (cacheItem) {
|
184
|
+
cacheItem.session.ref();
|
185
|
+
shortcut();
|
186
|
+
return doFetchHttp2(cacheItem.session);
|
187
|
+
}
|
188
|
+
// Convert socket into http2 session, this will ref (*)
|
189
|
+
const cacheableSession = this.h2Context.createHttp2(origin, () => { this._originCache.delete(cacheableSession); });
|
190
|
+
this._originCache.set(origin, "http2", cacheableSession);
|
191
|
+
shortcut();
|
192
|
+
// Session now lingering, it will be re-used by the next get()
|
193
|
+
return doFetchHttp2(cacheableSession);
|
194
|
+
});
|
195
|
+
}
|
196
|
+
else // protocol === "https"
|
197
|
+
{
|
198
|
+
return this._httpsFunnel((shouldRetry, retry, shortcut) => shouldRetry()
|
199
|
+
? retry()
|
200
|
+
: this.connectSequenciallyTLS(shortcut, hostname, port, origin, tryWaitForHttp1, doFetchHttp1, doFetchHttp2));
|
201
|
+
}
|
202
|
+
}
|
203
|
+
async connectSequenciallyTLS(shortcut, hostname, port, origin, tryWaitForHttp1, doFetchHttp1, doFetchHttp2) {
|
204
|
+
var _a, _b;
|
205
|
+
const cacheItem = (_a = this._originCache.get("https2", origin)) !== null && _a !== void 0 ? _a : this._originCache.get("https1", origin);
|
206
|
+
if (cacheItem) {
|
207
|
+
if (cacheItem.protocol === "https1") {
|
208
|
+
shortcut();
|
209
|
+
const resp = await tryWaitForHttp1(cacheItem.session);
|
210
|
+
if (resp)
|
211
|
+
return resp;
|
212
|
+
}
|
213
|
+
else if (cacheItem.protocol === "https2") {
|
214
|
+
cacheItem.session.ref();
|
215
|
+
shortcut();
|
216
|
+
return doFetchHttp2(cacheItem.session);
|
217
|
+
}
|
218
|
+
}
|
219
|
+
// Use ALPN to figure out protocol lazily
|
220
|
+
const { protocol, socket, altNameMatch } = await (0, context_https_1.connectTLS)(hostname, port, (0, core_1.getByOrigin)(this._httpsProtocols, origin), (0, core_1.getByOrigin)(this._sessionOptions, origin));
|
221
|
+
const disconnect = (0, already_1.once)(() => {
|
222
|
+
if (!socket.destroyed) {
|
223
|
+
socket.destroy();
|
224
|
+
socket.unref();
|
225
|
+
}
|
226
|
+
});
|
227
|
+
if (protocol === "http2") {
|
228
|
+
// Convert socket into http2 session, this will ref (*)
|
229
|
+
// const { cleanup, session, didCreate } =
|
230
|
+
const cacheableSession = this.h2Context.createHttp2(origin, () => { this._originCache.delete(cacheableSession); }, {
|
231
|
+
createConnection: () => socket,
|
232
|
+
});
|
233
|
+
this._originCache.set(origin, "https2", cacheableSession, altNameMatch, disconnect);
|
234
|
+
shortcut();
|
235
|
+
// Session now lingering, it will be re-used by the next get()
|
236
|
+
return doFetchHttp2(cacheableSession);
|
237
|
+
}
|
238
|
+
else // protocol === "http1"
|
239
|
+
{
|
240
|
+
const session = (_b = cacheItem === null || cacheItem === void 0 ? void 0 : cacheItem.session) !== null && _b !== void 0 ? _b : this.h1Context.getSessionForOrigin(origin);
|
241
|
+
// TODO: Update the alt-name list in the origin cache (if the new
|
242
|
+
// TLS socket contains more/other alt-names).
|
243
|
+
if (!cacheItem)
|
244
|
+
this._originCache.set(origin, "https1", session, altNameMatch, disconnect);
|
245
|
+
const cleanup = this.h1Context.addUsedSocket(session, socket);
|
246
|
+
shortcut();
|
247
|
+
return doFetchHttp1(socket, cleanup);
|
248
|
+
}
|
249
|
+
}
|
250
|
+
getHttp1(url, socket, request, rejectUnauthorized) {
|
251
|
+
return this.h1Context.connect(new url_1.URL(url), {
|
252
|
+
createConnection: () => socket,
|
253
|
+
rejectUnauthorized,
|
254
|
+
}, request);
|
255
|
+
}
|
256
|
+
parseInput(input) {
|
257
|
+
const { hostname, origin, port, protocol, url } = (0, utils_1.parseInput)(typeof input !== "string" ? input.url : input);
|
258
|
+
const defaultHttp = this._httpProtocol;
|
259
|
+
if ((protocol === "http" && defaultHttp === "http1")
|
260
|
+
|| protocol === "http1")
|
261
|
+
return {
|
262
|
+
hostname,
|
263
|
+
origin,
|
264
|
+
port,
|
265
|
+
protocol: "http1",
|
266
|
+
url,
|
267
|
+
};
|
268
|
+
else if ((protocol === "http" && defaultHttp === "http2")
|
269
|
+
|| protocol === "http2")
|
270
|
+
return {
|
271
|
+
hostname,
|
272
|
+
origin,
|
273
|
+
port,
|
274
|
+
protocol: "http2",
|
275
|
+
url,
|
276
|
+
};
|
277
|
+
else if (protocol === "https")
|
278
|
+
return {
|
279
|
+
hostname,
|
280
|
+
origin,
|
281
|
+
port,
|
282
|
+
protocol: "https",
|
283
|
+
url,
|
284
|
+
};
|
285
|
+
else
|
286
|
+
throw new core_1.FetchError(`Invalid protocol "${protocol}"`);
|
287
|
+
}
|
288
|
+
}
|
289
|
+
exports.Context = Context;
|
290
|
+
//# sourceMappingURL=context.js.map
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import { Cookie, CookieJar as ToughCookieJar } from "tough-cookie";
|
2
|
+
export declare class CookieJar {
|
3
|
+
private _jar;
|
4
|
+
constructor(jar?: ToughCookieJar);
|
5
|
+
reset(jar?: ToughCookieJar): void;
|
6
|
+
setCookie(cookie: string | Cookie, url: string): Promise<Cookie>;
|
7
|
+
setCookies(cookies: ReadonlyArray<string | Cookie>, url: string): Promise<ReadonlyArray<Cookie>>;
|
8
|
+
getCookies(url: string): Promise<ReadonlyArray<Cookie>>;
|
9
|
+
}
|