tunnelfetch 1.12.0 → 1.13.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/README.md +36 -0
- package/README.zh-CN.md +32 -0
- package/package.json +1 -1
- package/src/connect.js +187 -0
- package/src/index.js +1 -0
- package/types/connect.d.ts +78 -0
- package/types/index.d.ts +1 -0
package/README.md
CHANGED
|
@@ -149,6 +149,42 @@ Two related rules of §5.7 are **not** implemented, and are worth knowing if you
|
|
|
149
149
|
security: "Leave Secure Cookies Alone" (step 16), so a plain-named `Secure` cookie set over https
|
|
150
150
|
can still be overwritten from http, and the 4096-octet name-plus-value cap (step 4).
|
|
151
151
|
|
|
152
|
+
### Obtaining the socket factory
|
|
153
|
+
|
|
154
|
+
`connect` is an argument rather than an import on purpose: it is the one piece this package cannot
|
|
155
|
+
supply itself, and taking it from the caller is what keeps every layer above it testable over an
|
|
156
|
+
in-memory pipe. Resolve one robustly:
|
|
157
|
+
|
|
158
|
+
```js
|
|
159
|
+
import { resolveConnect } from 'tunnelfetch';
|
|
160
|
+
|
|
161
|
+
const connect = await resolveConnect({
|
|
162
|
+
specifiers: ['cloudflare:sockets'],
|
|
163
|
+
});
|
|
164
|
+
const client = new Client({ connect, proxy: env.PROXY_URL });
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The first specifier that yields a callable export wins, and a total failure names every specifier
|
|
168
|
+
tried and why it failed — the mistake surfaces at startup, not as "connect is not a function" from
|
|
169
|
+
inside the TLS layer. Two traps are closed on the way:
|
|
170
|
+
|
|
171
|
+
1. The import is dynamic and its failure is caught. A specifier that only resolves on one runtime,
|
|
172
|
+
imported statically, makes the whole package unloadable everywhere else; a portable list naming
|
|
173
|
+
several runtimes is safe.
|
|
174
|
+
2. Bundlers cannot see through a variable specifier. On a bundled deploy pass `connect` straight
|
|
175
|
+
in, or hand `resolveConnect` an `importModule` that closes over a literal the bundler can see:
|
|
176
|
+
|
|
177
|
+
```js
|
|
178
|
+
const connect = await resolveConnect({
|
|
179
|
+
importModule: () => import('cloudflare:sockets'),
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`normaliseSocket` flattens one runtime socket into a plain duplex, and `normalisingConnect` wraps a
|
|
184
|
+
factory so every socket it returns is flattened. They exist because on the edge runtime a socket's
|
|
185
|
+
`readable` and `writable` are prototype accessors, so `{ ...socket }` copies neither and the first
|
|
186
|
+
read fails far away, inside the TLS layer, with a complaint about `getReader`.
|
|
187
|
+
|
|
152
188
|
### Replacing the global
|
|
153
189
|
|
|
154
190
|
For libraries that only ever call the bare global:
|
package/README.zh-CN.md
CHANGED
|
@@ -117,6 +117,38 @@ await client.close(); // 必须调用:释放池中的 socket
|
|
|
117
117
|
(第 16 步),所以一个普通名字的 `Secure` cookie 即便是在 https 上设置的,仍可能被 http 覆盖;以及名字加值
|
|
118
118
|
4096 字节的上限(第 4 步)。
|
|
119
119
|
|
|
120
|
+
### 获取 socket 工厂
|
|
121
|
+
|
|
122
|
+
`connect` 是参数而不是 import,这是刻意的:它是这个包唯一无法自带的部分,由调用方提供也正是上面每一层都能在
|
|
123
|
+
内存管道上做字节级测试的原因。稳妥地获取一个:
|
|
124
|
+
|
|
125
|
+
```js
|
|
126
|
+
import { resolveConnect } from 'tunnelfetch';
|
|
127
|
+
|
|
128
|
+
const connect = await resolveConnect({
|
|
129
|
+
specifiers: ['cloudflare:sockets'],
|
|
130
|
+
});
|
|
131
|
+
const client = new Client({ connect, proxy: env.PROXY_URL });
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
第一个能导出可调用函数的 specifier 胜出;全部失败时报错会列出试过的每一个 specifier 及其失败原因——错误在
|
|
135
|
+
启动时就现身,而不是以 "connect is not a function" 的样子从 TLS 层深处冒出来。过程中关掉了两个坑:
|
|
136
|
+
|
|
137
|
+
1. 导入是动态的,失败会被捕获。某个只在一个运行时上能解析的 specifier,一旦被静态导入,就会让整个包在其它
|
|
138
|
+
所有平台上无法加载;列着多个运行时的可移植列表则是安全的。
|
|
139
|
+
2. 打包器看不穿变量形式的 specifier。打包部署时直接把 `connect` 传进来,或者给 `resolveConnect` 传一个闭包了
|
|
140
|
+
字面量的 `importModule`,让打包器看得到:
|
|
141
|
+
|
|
142
|
+
```js
|
|
143
|
+
const connect = await resolveConnect({
|
|
144
|
+
importModule: () => import('cloudflare:sockets'),
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`normaliseSocket` 把一个运行时 socket 拍平成普通双工对象,`normalisingConnect` 包一层工厂,让每个返回的 socket
|
|
149
|
+
都被拍平。它们的存在是因为 edge 运行时上 socket 的 `readable` 和 `writable` 是原型上的访问器,`{ ...socket }`
|
|
150
|
+
两个都拷不到,第一次读取就会在 TLS 层深处报 `getReader` 的错误。
|
|
151
|
+
|
|
120
152
|
### 替换全局 fetch
|
|
121
153
|
|
|
122
154
|
有的库只会直接调用全局 `fetch`,为它们准备的是:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tunnelfetch",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "A fetch-shaped HTTP client that can route through HTTP CONNECT / HTTPS / SOCKS5 proxies on runtimes with only raw TCP, such as Cloudflare Workers. Implements TLS in userland because the runtime cannot verify a tunnelled peer.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fetch",
|
package/src/connect.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// Obtaining the host runtime's raw-socket factory.
|
|
2
|
+
//
|
|
3
|
+
// Every layer above this one takes `connect` as an argument, and that is deliberate: it is what
|
|
4
|
+
// keeps src/ free of runtime-specific imports and what makes every protocol path testable over an
|
|
5
|
+
// in-memory pipe. This module does not weaken that. It answers a narrower question that callers
|
|
6
|
+
// kept answering badly by hand:
|
|
7
|
+
//
|
|
8
|
+
// given a runtime whose socket factory lives behind a module specifier, produce a validated
|
|
9
|
+
// ConnectFn, or fail with an error that says what was tried.
|
|
10
|
+
//
|
|
11
|
+
// Two rules shape the design.
|
|
12
|
+
//
|
|
13
|
+
// 1. The specifier is DATA, supplied by the caller. No module name is baked in here, because a
|
|
14
|
+
// name baked in is behaviour conditioned on one vendor's runtime, and this package is not
|
|
15
|
+
// allowed to have any. The host application knows which runtime it is running on; this module
|
|
16
|
+
// does not need to guess, and a wrong guess is worse than an argument.
|
|
17
|
+
//
|
|
18
|
+
// 2. The import is dynamic and its failure is caught. A static import of a specifier that only
|
|
19
|
+
// resolves on one runtime makes this module unloadable everywhere else, which would take the
|
|
20
|
+
// whole package down with it on every other platform.
|
|
21
|
+
//
|
|
22
|
+
// A BUNDLER CAVEAT that matters more than it looks. The default importer calls `import(spec)` with
|
|
23
|
+
// a variable. Bundlers cannot see through a variable specifier, so they will not include the
|
|
24
|
+
// target module in the output, and on a bundled deployment the import fails at runtime even though
|
|
25
|
+
// the module exists. Where a bundler is in play — which on edge runtimes is nearly always — pass
|
|
26
|
+
// the socket factory straight in as `connect`, or pass an `importModule` that closes over a
|
|
27
|
+
// LITERAL specifier the bundler can see:
|
|
28
|
+
//
|
|
29
|
+
// resolveConnect({ importModule: () => import('some:sockets') })
|
|
30
|
+
//
|
|
31
|
+
// That is why `connect` is checked first and short-circuits without importing anything: direct
|
|
32
|
+
// injection is the recommended path, and the specifier list is the convenience, not the contract.
|
|
33
|
+
|
|
34
|
+
import { ConfigError, codes } from './errors.js';
|
|
35
|
+
|
|
36
|
+
/** @typedef {import('./proxy/index.js').ConnectFn} ConnectFn */
|
|
37
|
+
/** @typedef {import('./proxy/index.js').Duplex} Duplex */
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {object} ResolveConnectOptions
|
|
41
|
+
* @property {ConnectFn} [connect] an already-obtained socket factory. Checked first and returned
|
|
42
|
+
* as-is, so the recommended path costs no import and cannot be defeated by a bundler.
|
|
43
|
+
* @property {string[]} [specifiers] module specifiers to try, in order. The first module whose
|
|
44
|
+
* `exportName` is callable wins.
|
|
45
|
+
* @property {(specifier: string) => Promise<object>} [importModule] the importer. Defaults to a
|
|
46
|
+
* dynamic `import()`; override it to hand the bundler a literal specifier.
|
|
47
|
+
* @property {string} [exportName] the export to read off each module. Default 'connect'.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Is `value` shaped like a socket factory? Only callability can be checked without dialling, so
|
|
52
|
+
* that is what this checks — the duplex contract is enforced when a socket is actually opened.
|
|
53
|
+
* @param {unknown} value
|
|
54
|
+
* @returns {value is ConnectFn}
|
|
55
|
+
*/
|
|
56
|
+
export function isConnectFn(value) {
|
|
57
|
+
return typeof value === 'function';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Resolve a ConnectFn from an injected factory or from the first module specifier that yields one.
|
|
62
|
+
*
|
|
63
|
+
* Resolution order, and nothing else is consulted:
|
|
64
|
+
* 1. `options.connect`, when callable.
|
|
65
|
+
* 2. each entry of `options.specifiers`, in order.
|
|
66
|
+
*
|
|
67
|
+
* Throws ConfigError (CONFIG_UNSATISFIABLE) when nothing resolves, listing every specifier tried
|
|
68
|
+
* with the reason it failed. A socket factory that cannot be found is a deployment mistake, and a
|
|
69
|
+
* deployment mistake deserves to name the thing that was missing rather than surface later as
|
|
70
|
+
* "connect is not a function" from inside the TLS layer.
|
|
71
|
+
*
|
|
72
|
+
* @param {ResolveConnectOptions} [options]
|
|
73
|
+
* @returns {Promise<ConnectFn>}
|
|
74
|
+
*/
|
|
75
|
+
export async function resolveConnect(options = {}) {
|
|
76
|
+
const { connect, specifiers = [], importModule = defaultImport, exportName = 'connect' } = options;
|
|
77
|
+
|
|
78
|
+
if (connect !== undefined && connect !== null) {
|
|
79
|
+
if (!isConnectFn(connect)) {
|
|
80
|
+
throw new ConfigError(
|
|
81
|
+
codes.CONFIG_INVALID,
|
|
82
|
+
`connect must be a function returning { readable, writable, opened?, close? }, got ` +
|
|
83
|
+
`${describe(connect)}`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return connect;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!Array.isArray(specifiers)) {
|
|
90
|
+
throw new ConfigError(codes.CONFIG_INVALID, 'specifiers must be an array of module specifiers');
|
|
91
|
+
}
|
|
92
|
+
if (typeof importModule !== 'function') {
|
|
93
|
+
throw new ConfigError(codes.CONFIG_INVALID, 'importModule must be a function');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** @type {string[]} */
|
|
97
|
+
const tried = [];
|
|
98
|
+
for (const specifier of specifiers) {
|
|
99
|
+
if (typeof specifier !== 'string' || specifier === '') {
|
|
100
|
+
throw new ConfigError(
|
|
101
|
+
codes.CONFIG_INVALID,
|
|
102
|
+
`specifiers must be non-empty strings, got ${describe(specifier)}`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
let mod;
|
|
106
|
+
try {
|
|
107
|
+
mod = await importModule(specifier);
|
|
108
|
+
} catch (cause) {
|
|
109
|
+
// Not resolvable on this runtime, which for a portable specifier list is the expected
|
|
110
|
+
// outcome for every entry but one. Recorded, not thrown.
|
|
111
|
+
tried.push(`${specifier}: not importable (${cause?.message ?? cause})`);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const candidate = mod?.[exportName] ?? mod?.default?.[exportName];
|
|
115
|
+
if (isConnectFn(candidate)) return candidate;
|
|
116
|
+
tried.push(`${specifier}: imported, but has no callable "${exportName}" export`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
throw new ConfigError(
|
|
120
|
+
codes.CONFIG_UNSATISFIABLE,
|
|
121
|
+
'no socket factory could be resolved. Supply `connect` directly — the raw-TCP socket factory ' +
|
|
122
|
+
'of the host runtime, returning { readable, writable, opened?, close? } — or name a module ' +
|
|
123
|
+
'that exports one. ' +
|
|
124
|
+
(tried.length ? `Tried:\n ${tried.join('\n ')}` : 'No specifiers were given.'),
|
|
125
|
+
{ tried },
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Flatten a runtime socket into a plain duplex object.
|
|
131
|
+
*
|
|
132
|
+
* Worth its own function because of a trap that has already cost this package a bug: on the edge
|
|
133
|
+
* runtime a socket's `readable` and `writable` are ACCESSORS ON THE PROTOTYPE, so `{ ...socket }`
|
|
134
|
+
* copies neither and the first read fails far away, inside the TLS layer, with "Cannot read
|
|
135
|
+
* properties of undefined (reading 'getReader')". Reading the properties explicitly — which is
|
|
136
|
+
* what this does — is the only spread-safe way to hand a host socket to code that may copy it.
|
|
137
|
+
*
|
|
138
|
+
* @param {Duplex} socket
|
|
139
|
+
* @returns {Duplex}
|
|
140
|
+
*/
|
|
141
|
+
export function normaliseSocket(socket) {
|
|
142
|
+
if (!socket || typeof socket !== 'object') {
|
|
143
|
+
throw new ConfigError(
|
|
144
|
+
codes.CONFIG_INVALID,
|
|
145
|
+
`connect must return a socket object, got ${describe(socket)}`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
const { readable, writable } = socket;
|
|
149
|
+
if (!readable || typeof readable.getReader !== 'function') {
|
|
150
|
+
throw new ConfigError(codes.CONFIG_INVALID, 'socket has no ReadableStream `readable`');
|
|
151
|
+
}
|
|
152
|
+
if (!writable || typeof writable.getWriter !== 'function') {
|
|
153
|
+
throw new ConfigError(codes.CONFIG_INVALID, 'socket has no WritableStream `writable`');
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
readable,
|
|
157
|
+
writable,
|
|
158
|
+
opened: socket.opened,
|
|
159
|
+
close: () => socket.close?.(),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Wrap a ConnectFn so every socket it returns is flattened by normaliseSocket. Useful when the
|
|
165
|
+
* factory comes from a runtime whose sockets are host objects rather than plain records.
|
|
166
|
+
* @param {ConnectFn} connect
|
|
167
|
+
* @returns {ConnectFn}
|
|
168
|
+
*/
|
|
169
|
+
export function normalisingConnect(connect) {
|
|
170
|
+
if (!isConnectFn(connect)) {
|
|
171
|
+
throw new ConfigError(codes.CONFIG_INVALID, `connect must be a function, got ${describe(connect)}`);
|
|
172
|
+
}
|
|
173
|
+
return (addr, opts) => normaliseSocket(connect(addr, opts));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** The default importer: a dynamic import a bundler cannot see through. See the header caveat. */
|
|
177
|
+
function defaultImport(specifier) {
|
|
178
|
+
return import(specifier);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** A short, safe rendering of a bad argument for an error message. */
|
|
182
|
+
function describe(value) {
|
|
183
|
+
if (value === null) return 'null';
|
|
184
|
+
if (Array.isArray(value)) return 'an array';
|
|
185
|
+
const t = typeof value;
|
|
186
|
+
return t === 'object' ? 'an object' : t === 'string' ? JSON.stringify(value) : t;
|
|
187
|
+
}
|
package/src/index.js
CHANGED
|
@@ -16,6 +16,7 @@ export { warmup } from './warmup.js';
|
|
|
16
16
|
export { ConnectionPool, poolKey } from './pool.js';
|
|
17
17
|
export { openConnection, targetFromUrl, nativeFetchCanServe } from './transport.js';
|
|
18
18
|
export { openTunnel, parseProxy } from './proxy/index.js';
|
|
19
|
+
export { resolveConnect, isConnectFn, normaliseSocket, normalisingConnect } from './connect.js';
|
|
19
20
|
export { CookieJar } from './client/cookies.js';
|
|
20
21
|
export { verifyChain, rootStoreProvenance } from './trust/index.js';
|
|
21
22
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** @typedef {import('./proxy/index.js').ConnectFn} ConnectFn */
|
|
2
|
+
/** @typedef {import('./proxy/index.js').Duplex} Duplex */
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {object} ResolveConnectOptions
|
|
5
|
+
* @property {ConnectFn} [connect] an already-obtained socket factory. Checked first and returned
|
|
6
|
+
* as-is, so the recommended path costs no import and cannot be defeated by a bundler.
|
|
7
|
+
* @property {string[]} [specifiers] module specifiers to try, in order. The first module whose
|
|
8
|
+
* `exportName` is callable wins.
|
|
9
|
+
* @property {(specifier: string) => Promise<object>} [importModule] the importer. Defaults to a
|
|
10
|
+
* dynamic `import()`; override it to hand the bundler a literal specifier.
|
|
11
|
+
* @property {string} [exportName] the export to read off each module. Default 'connect'.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Is `value` shaped like a socket factory? Only callability can be checked without dialling, so
|
|
15
|
+
* that is what this checks — the duplex contract is enforced when a socket is actually opened.
|
|
16
|
+
* @param {unknown} value
|
|
17
|
+
* @returns {value is ConnectFn}
|
|
18
|
+
*/
|
|
19
|
+
export function isConnectFn(value: unknown): value is ConnectFn;
|
|
20
|
+
/**
|
|
21
|
+
* Resolve a ConnectFn from an injected factory or from the first module specifier that yields one.
|
|
22
|
+
*
|
|
23
|
+
* Resolution order, and nothing else is consulted:
|
|
24
|
+
* 1. `options.connect`, when callable.
|
|
25
|
+
* 2. each entry of `options.specifiers`, in order.
|
|
26
|
+
*
|
|
27
|
+
* Throws ConfigError (CONFIG_UNSATISFIABLE) when nothing resolves, listing every specifier tried
|
|
28
|
+
* with the reason it failed. A socket factory that cannot be found is a deployment mistake, and a
|
|
29
|
+
* deployment mistake deserves to name the thing that was missing rather than surface later as
|
|
30
|
+
* "connect is not a function" from inside the TLS layer.
|
|
31
|
+
*
|
|
32
|
+
* @param {ResolveConnectOptions} [options]
|
|
33
|
+
* @returns {Promise<ConnectFn>}
|
|
34
|
+
*/
|
|
35
|
+
export function resolveConnect(options?: ResolveConnectOptions): Promise<ConnectFn>;
|
|
36
|
+
/**
|
|
37
|
+
* Flatten a runtime socket into a plain duplex object.
|
|
38
|
+
*
|
|
39
|
+
* Worth its own function because of a trap that has already cost this package a bug: on the edge
|
|
40
|
+
* runtime a socket's `readable` and `writable` are ACCESSORS ON THE PROTOTYPE, so `{ ...socket }`
|
|
41
|
+
* copies neither and the first read fails far away, inside the TLS layer, with "Cannot read
|
|
42
|
+
* properties of undefined (reading 'getReader')". Reading the properties explicitly — which is
|
|
43
|
+
* what this does — is the only spread-safe way to hand a host socket to code that may copy it.
|
|
44
|
+
*
|
|
45
|
+
* @param {Duplex} socket
|
|
46
|
+
* @returns {Duplex}
|
|
47
|
+
*/
|
|
48
|
+
export function normaliseSocket(socket: Duplex): Duplex;
|
|
49
|
+
/**
|
|
50
|
+
* Wrap a ConnectFn so every socket it returns is flattened by normaliseSocket. Useful when the
|
|
51
|
+
* factory comes from a runtime whose sockets are host objects rather than plain records.
|
|
52
|
+
* @param {ConnectFn} connect
|
|
53
|
+
* @returns {ConnectFn}
|
|
54
|
+
*/
|
|
55
|
+
export function normalisingConnect(connect: ConnectFn): ConnectFn;
|
|
56
|
+
export type ConnectFn = import("./proxy/index.js").ConnectFn;
|
|
57
|
+
export type Duplex = import("./proxy/index.js").Duplex;
|
|
58
|
+
export type ResolveConnectOptions = {
|
|
59
|
+
/**
|
|
60
|
+
* an already-obtained socket factory. Checked first and returned
|
|
61
|
+
* as-is, so the recommended path costs no import and cannot be defeated by a bundler.
|
|
62
|
+
*/
|
|
63
|
+
connect?: import("./proxy/index.js").ConnectFn | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* module specifiers to try, in order. The first module whose
|
|
66
|
+
* `exportName` is callable wins.
|
|
67
|
+
*/
|
|
68
|
+
specifiers?: string[] | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* the importer. Defaults to a
|
|
71
|
+
* dynamic `import()`; override it to hand the bundler a literal specifier.
|
|
72
|
+
*/
|
|
73
|
+
importModule?: ((specifier: string) => Promise<object>) | undefined;
|
|
74
|
+
/**
|
|
75
|
+
* the export to read off each module. Default 'connect'.
|
|
76
|
+
*/
|
|
77
|
+
exportName?: string | undefined;
|
|
78
|
+
};
|
package/types/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export { Client, createFetch, install } from "./client.js";
|
|
|
13
13
|
export { ConnectionPool, poolKey } from "./pool.js";
|
|
14
14
|
export { openConnection, targetFromUrl, nativeFetchCanServe } from "./transport.js";
|
|
15
15
|
export { openTunnel, parseProxy } from "./proxy/index.js";
|
|
16
|
+
export { resolveConnect, isConnectFn, normaliseSocket, normalisingConnect } from "./connect.js";
|
|
16
17
|
export { verifyChain, rootStoreProvenance } from "./trust/index.js";
|
|
17
18
|
export { TunnelFetchError, ProxyError, HttpError, TlsError, TlsUnsupportedError, Http2Error, CertificateError, TimeoutError, LimitError, ConfigError, codes } from "./errors.js";
|
|
18
19
|
export { profiles, curl, chrome, applyProfile } from "./profiles.js";
|