fetch-h 3.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +395 -0
  3. package/dist/index.d.ts +22 -0
  4. package/dist/index.js +45 -0
  5. package/dist/lib/abort.d.ts +11 -0
  6. package/dist/lib/abort.js +27 -0
  7. package/dist/lib/body.d.ts +45 -0
  8. package/dist/lib/body.js +248 -0
  9. package/dist/lib/context-http1.d.ts +58 -0
  10. package/dist/lib/context-http1.js +220 -0
  11. package/dist/lib/context-http2.d.ts +39 -0
  12. package/dist/lib/context-http2.js +233 -0
  13. package/dist/lib/context-https.d.ts +11 -0
  14. package/dist/lib/context-https.js +65 -0
  15. package/dist/lib/context.d.ts +49 -0
  16. package/dist/lib/context.js +290 -0
  17. package/dist/lib/cookie-jar.d.ts +9 -0
  18. package/dist/lib/cookie-jar.js +35 -0
  19. package/dist/lib/core.d.ts +82 -0
  20. package/dist/lib/core.js +52 -0
  21. package/dist/lib/fetch-common.d.ts +38 -0
  22. package/dist/lib/fetch-common.js +209 -0
  23. package/dist/lib/fetch-http1.d.ts +7 -0
  24. package/dist/lib/fetch-http1.js +148 -0
  25. package/dist/lib/fetch-http2.d.ts +6 -0
  26. package/dist/lib/fetch-http2.js +204 -0
  27. package/dist/lib/generated/version.d.ts +1 -0
  28. package/dist/lib/generated/version.js +5 -0
  29. package/dist/lib/headers.d.ts +24 -0
  30. package/dist/lib/headers.js +173 -0
  31. package/dist/lib/origin-cache.d.ts +20 -0
  32. package/dist/lib/origin-cache.js +93 -0
  33. package/dist/lib/request.d.ts +20 -0
  34. package/dist/lib/request.js +106 -0
  35. package/dist/lib/response.d.ts +33 -0
  36. package/dist/lib/response.js +165 -0
  37. package/dist/lib/san.d.ts +9 -0
  38. package/dist/lib/san.js +48 -0
  39. package/dist/lib/simple-session.d.ts +30 -0
  40. package/dist/lib/simple-session.js +3 -0
  41. package/dist/lib/types.d.ts +4 -0
  42. package/dist/lib/types.js +3 -0
  43. package/dist/lib/utils-http2.d.ts +11 -0
  44. package/dist/lib/utils-http2.js +21 -0
  45. package/dist/lib/utils.d.ts +24 -0
  46. package/dist/lib/utils.js +78 -0
  47. package/package.json +78 -0
  48. 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
+ }