stealth-fetch-plus 0.1.1
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 +182 -0
- package/dist/client.d.ts +94 -0
- package/dist/client.js +969 -0
- package/dist/compat/web.d.ts +15 -0
- package/dist/compat/web.js +31 -0
- package/dist/connection-pool.d.ts +39 -0
- package/dist/connection-pool.js +84 -0
- package/dist/dns-cache.d.ts +25 -0
- package/dist/dns-cache.js +44 -0
- package/dist/http1/chunked.d.ts +35 -0
- package/dist/http1/chunked.js +87 -0
- package/dist/http1/client.d.ts +28 -0
- package/dist/http1/client.js +289 -0
- package/dist/http1/parser.d.ts +29 -0
- package/dist/http1/parser.js +78 -0
- package/dist/http2/client.d.ts +64 -0
- package/dist/http2/client.js +97 -0
- package/dist/http2/connection.d.ts +125 -0
- package/dist/http2/connection.js +666 -0
- package/dist/http2/constants.d.ts +72 -0
- package/dist/http2/constants.js +74 -0
- package/dist/http2/flow-control.d.ts +32 -0
- package/dist/http2/flow-control.js +76 -0
- package/dist/http2/framer.d.ts +47 -0
- package/dist/http2/framer.js +133 -0
- package/dist/http2/hpack.d.ts +54 -0
- package/dist/http2/hpack.js +186 -0
- package/dist/http2/parser.d.ts +35 -0
- package/dist/http2/parser.js +72 -0
- package/dist/http2/stream.d.ts +72 -0
- package/dist/http2/stream.js +252 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +33 -0
- package/dist/protocol-cache.d.ts +14 -0
- package/dist/protocol-cache.js +29 -0
- package/dist/socket/adapter.d.ts +59 -0
- package/dist/socket/adapter.js +145 -0
- package/dist/socket/nat64.d.ts +69 -0
- package/dist/socket/nat64.js +196 -0
- package/dist/socket/tls.d.ts +28 -0
- package/dist/socket/tls.js +33 -0
- package/dist/socket/wasm-pkg/wasm_tls.d.ts +107 -0
- package/dist/socket/wasm-pkg/wasm_tls.js +568 -0
- package/dist/socket/wasm-pkg/wasm_tls_bg.wasm +0 -0
- package/dist/socket/wasm-pkg/wasm_tls_bg.wasm.d.ts +20 -0
- package/dist/socket/wasm-tls-adapter.d.ts +39 -0
- package/dist/socket/wasm-tls-adapter.js +97 -0
- package/dist/socket/wasm-tls-bridge.d.ts +30 -0
- package/dist/socket/wasm-tls-bridge.js +160 -0
- package/dist/utils/headers.d.ts +21 -0
- package/dist/utils/headers.js +36 -0
- package/dist/utils/url.d.ts +16 -0
- package/dist/utils/url.js +12 -0
- package/package.json +87 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Duplex } from "node:stream";
|
|
2
|
+
import { CloudflareSocketAdapter } from "./adapter.js";
|
|
3
|
+
import { performTlsHandshake } from "./wasm-tls-bridge.js";
|
|
4
|
+
class WasmTlsSocketAdapter extends Duplex {
|
|
5
|
+
rawSocket = null;
|
|
6
|
+
tlsSession = null;
|
|
7
|
+
_connected = false;
|
|
8
|
+
hostname;
|
|
9
|
+
port;
|
|
10
|
+
alpnProtocols;
|
|
11
|
+
/** TCP-level hostname (may differ from TLS SNI hostname for NAT64) */
|
|
12
|
+
connectHostname;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
super();
|
|
15
|
+
this.hostname = options.hostname;
|
|
16
|
+
this.port = options.port;
|
|
17
|
+
this.alpnProtocols = options.alpnProtocols ?? ["h2", "http/1.1"];
|
|
18
|
+
this.connectHostname = options.connectHostname ?? options.hostname;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Establish connection: raw TCP -> WASM TLS handshake -> ready.
|
|
22
|
+
*/
|
|
23
|
+
async connect() {
|
|
24
|
+
this.rawSocket = new CloudflareSocketAdapter({
|
|
25
|
+
hostname: this.connectHostname,
|
|
26
|
+
port: this.port,
|
|
27
|
+
tls: false
|
|
28
|
+
// Critical: no CF built-in TLS
|
|
29
|
+
});
|
|
30
|
+
await this.rawSocket.connect();
|
|
31
|
+
this.tlsSession = await performTlsHandshake(this.rawSocket, {
|
|
32
|
+
hostname: this.hostname,
|
|
33
|
+
alpnProtocols: this.alpnProtocols
|
|
34
|
+
});
|
|
35
|
+
this.tlsSession.onPlaintext((data) => {
|
|
36
|
+
if (!this.destroyed) {
|
|
37
|
+
this.push(data);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
this.tlsSession.onClose(() => {
|
|
41
|
+
this._connected = false;
|
|
42
|
+
if (!this.destroyed) {
|
|
43
|
+
this.push(null);
|
|
44
|
+
this.emit("close");
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
this.tlsSession.onError((err) => {
|
|
48
|
+
this._connected = false;
|
|
49
|
+
if (!this.destroyed) {
|
|
50
|
+
this.destroy(err);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
this._connected = true;
|
|
54
|
+
this.emit("connect");
|
|
55
|
+
}
|
|
56
|
+
/** Get the negotiated ALPN protocol */
|
|
57
|
+
get negotiatedAlpn() {
|
|
58
|
+
return this.tlsSession?.negotiatedAlpn ?? null;
|
|
59
|
+
}
|
|
60
|
+
_read() {
|
|
61
|
+
}
|
|
62
|
+
_write(chunk, _encoding, callback) {
|
|
63
|
+
if (!this.tlsSession || !this._connected) {
|
|
64
|
+
callback(new Error("TLS socket not connected"));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const data = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);
|
|
68
|
+
this.tlsSession.write(data).then(
|
|
69
|
+
() => callback(),
|
|
70
|
+
(err) => callback(err)
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
_final(callback) {
|
|
74
|
+
if (this.tlsSession) {
|
|
75
|
+
this.tlsSession.close();
|
|
76
|
+
}
|
|
77
|
+
callback();
|
|
78
|
+
}
|
|
79
|
+
_destroy(error, callback) {
|
|
80
|
+
this._connected = false;
|
|
81
|
+
if (this.tlsSession) {
|
|
82
|
+
this.tlsSession.close();
|
|
83
|
+
this.tlsSession = null;
|
|
84
|
+
}
|
|
85
|
+
if (this.rawSocket) {
|
|
86
|
+
this.rawSocket.destroy();
|
|
87
|
+
this.rawSocket = null;
|
|
88
|
+
}
|
|
89
|
+
callback(error);
|
|
90
|
+
}
|
|
91
|
+
get isConnected() {
|
|
92
|
+
return this._connected;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export {
|
|
96
|
+
WasmTlsSocketAdapter
|
|
97
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { CloudflareSocketAdapter } from './adapter.js';
|
|
2
|
+
import 'node:stream';
|
|
3
|
+
|
|
4
|
+
/** Fire-and-forget preload of WASM TLS module to avoid lazy-init delay */
|
|
5
|
+
declare function preloadWasmTls(): void;
|
|
6
|
+
interface WasmTlsOptions {
|
|
7
|
+
hostname: string;
|
|
8
|
+
alpnProtocols?: string[];
|
|
9
|
+
}
|
|
10
|
+
interface WasmTlsSession {
|
|
11
|
+
/** Negotiated ALPN protocol (e.g. "h2", "http/1.1", or null) */
|
|
12
|
+
negotiatedAlpn: string | null;
|
|
13
|
+
/** Write plaintext to TLS layer (encrypts and sends to socket) */
|
|
14
|
+
write(data: Uint8Array): Promise<void>;
|
|
15
|
+
/** Register plaintext data callback */
|
|
16
|
+
onPlaintext(callback: (data: Uint8Array) => void): void;
|
|
17
|
+
/** Register close callback */
|
|
18
|
+
onClose(callback: () => void): void;
|
|
19
|
+
/** Register error callback */
|
|
20
|
+
onError(callback: (err: Error) => void): void;
|
|
21
|
+
/** Close TLS session */
|
|
22
|
+
close(): void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Perform TLS handshake over an existing raw TCP socket.
|
|
26
|
+
* The rawSocket must have been created with secureTransport: "off".
|
|
27
|
+
*/
|
|
28
|
+
declare function performTlsHandshake(rawSocket: CloudflareSocketAdapter, options: WasmTlsOptions): Promise<WasmTlsSession>;
|
|
29
|
+
|
|
30
|
+
export { type WasmTlsOptions, type WasmTlsSession, performTlsHandshake, preloadWasmTls };
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import initWasm, { TlsConnection } from "./wasm-pkg/wasm_tls.js";
|
|
2
|
+
import wasmModule from "./wasm-pkg/wasm_tls_bg.wasm";
|
|
3
|
+
let wasmInitPromise = null;
|
|
4
|
+
function ensureWasmInit() {
|
|
5
|
+
if (!wasmInitPromise) {
|
|
6
|
+
wasmInitPromise = initWasm(wasmModule).then(
|
|
7
|
+
() => {
|
|
8
|
+
},
|
|
9
|
+
(err) => {
|
|
10
|
+
wasmInitPromise = null;
|
|
11
|
+
throw err;
|
|
12
|
+
}
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
return wasmInitPromise;
|
|
16
|
+
}
|
|
17
|
+
function preloadWasmTls() {
|
|
18
|
+
ensureWasmInit().catch(() => {
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
async function performTlsHandshake(rawSocket, options) {
|
|
22
|
+
await ensureWasmInit();
|
|
23
|
+
const alpn = (options.alpnProtocols ?? ["h2", "http/1.1"]).join(",");
|
|
24
|
+
const tls = new TlsConnection(options.hostname, alpn);
|
|
25
|
+
try {
|
|
26
|
+
const clientHello = tls.flush_outgoing_tls();
|
|
27
|
+
if (clientHello.length > 0) {
|
|
28
|
+
await writeToSocket(rawSocket, clientHello);
|
|
29
|
+
}
|
|
30
|
+
await handshakeLoop(rawSocket, tls);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
tls.free();
|
|
33
|
+
throw err;
|
|
34
|
+
}
|
|
35
|
+
const negotiatedAlpn = tls.negotiated_alpn() ?? null;
|
|
36
|
+
return createSession(rawSocket, tls, negotiatedAlpn);
|
|
37
|
+
}
|
|
38
|
+
async function handshakeLoop(socket, tls) {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
const onData = async (chunk) => {
|
|
41
|
+
try {
|
|
42
|
+
const data = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);
|
|
43
|
+
const needsWrite = tls.feed_ciphertext(data);
|
|
44
|
+
if (needsWrite) {
|
|
45
|
+
const out = tls.flush_outgoing_tls();
|
|
46
|
+
if (out.length > 0) {
|
|
47
|
+
await writeToSocket(socket, out);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (!tls.is_handshaking()) {
|
|
51
|
+
socket.removeListener("data", onData);
|
|
52
|
+
socket.removeListener("error", onError);
|
|
53
|
+
resolve();
|
|
54
|
+
}
|
|
55
|
+
} catch (err) {
|
|
56
|
+
socket.removeListener("data", onData);
|
|
57
|
+
socket.removeListener("error", onError);
|
|
58
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const onError = (err) => {
|
|
62
|
+
socket.removeListener("data", onData);
|
|
63
|
+
socket.removeListener("error", onError);
|
|
64
|
+
reject(err);
|
|
65
|
+
};
|
|
66
|
+
socket.on("data", onData);
|
|
67
|
+
socket.on("error", onError);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function createSession(socket, tls, negotiatedAlpn) {
|
|
71
|
+
let plaintextCallback = null;
|
|
72
|
+
let closeCallback = null;
|
|
73
|
+
let errorCallback = null;
|
|
74
|
+
let closed = false;
|
|
75
|
+
const onData = async (chunk) => {
|
|
76
|
+
try {
|
|
77
|
+
const data = chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk);
|
|
78
|
+
const needsWrite = tls.feed_ciphertext(data);
|
|
79
|
+
const plaintext = tls.take_plaintext();
|
|
80
|
+
if (plaintext.length > 0 && plaintextCallback) {
|
|
81
|
+
plaintextCallback(plaintext);
|
|
82
|
+
}
|
|
83
|
+
if (needsWrite) {
|
|
84
|
+
const out = tls.flush_outgoing_tls();
|
|
85
|
+
if (out.length > 0) {
|
|
86
|
+
await writeToSocket(socket, out);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch (err) {
|
|
90
|
+
if (errorCallback) {
|
|
91
|
+
errorCallback(err instanceof Error ? err : new Error(String(err)));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const onEnd = () => {
|
|
96
|
+
if (!closed) {
|
|
97
|
+
closed = true;
|
|
98
|
+
closeCallback?.();
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const onError = (err) => {
|
|
102
|
+
errorCallback?.(err);
|
|
103
|
+
};
|
|
104
|
+
socket.on("data", onData);
|
|
105
|
+
socket.on("end", onEnd);
|
|
106
|
+
socket.on("error", onError);
|
|
107
|
+
return {
|
|
108
|
+
negotiatedAlpn,
|
|
109
|
+
async write(data) {
|
|
110
|
+
if (closed) throw new Error("TLS session closed");
|
|
111
|
+
const needsWrite = tls.write_plaintext(data);
|
|
112
|
+
if (needsWrite) {
|
|
113
|
+
const out = tls.flush_outgoing_tls();
|
|
114
|
+
if (out.length > 0) {
|
|
115
|
+
await writeToSocket(socket, out);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
onPlaintext(callback) {
|
|
120
|
+
plaintextCallback = callback;
|
|
121
|
+
},
|
|
122
|
+
onClose(callback) {
|
|
123
|
+
closeCallback = callback;
|
|
124
|
+
},
|
|
125
|
+
onError(callback) {
|
|
126
|
+
errorCallback = callback;
|
|
127
|
+
},
|
|
128
|
+
close() {
|
|
129
|
+
if (!closed) {
|
|
130
|
+
closed = true;
|
|
131
|
+
try {
|
|
132
|
+
tls.send_close_notify();
|
|
133
|
+
const out = tls.flush_outgoing_tls();
|
|
134
|
+
if (out.length > 0) {
|
|
135
|
+
writeToSocket(socket, out).catch(() => {
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
socket.removeListener("data", onData);
|
|
141
|
+
socket.removeListener("end", onEnd);
|
|
142
|
+
socket.removeListener("error", onError);
|
|
143
|
+
socket.end();
|
|
144
|
+
tls.free();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function writeToSocket(socket, data) {
|
|
150
|
+
return new Promise((resolve, reject) => {
|
|
151
|
+
socket.write(data, (err) => {
|
|
152
|
+
if (err) reject(err);
|
|
153
|
+
else resolve();
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
export {
|
|
158
|
+
performTlsHandshake,
|
|
159
|
+
preloadWasmTls
|
|
160
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Header utilities for HTTP/1.1 and HTTP/2.
|
|
3
|
+
* HTTP/2 requires lowercase header names (RFC 7540 Section 8.1.2).
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Build HTTP/2 pseudo-headers from request parameters.
|
|
7
|
+
* Pseudo-headers must come before regular headers (RFC 7540 Section 8.1.2.1).
|
|
8
|
+
*/
|
|
9
|
+
declare function buildPseudoHeaders(method: string, hostname: string, path: string, scheme: "https" | "http"): Array<[string, string]>;
|
|
10
|
+
/**
|
|
11
|
+
* Merge pseudo-headers and user headers into an ordered array.
|
|
12
|
+
* Pseudo-headers first, then regular headers (all lowercase).
|
|
13
|
+
*/
|
|
14
|
+
declare function mergeHeaders(pseudo: Array<[string, string]>, headers: Record<string, string>): Array<[string, string]>;
|
|
15
|
+
/**
|
|
16
|
+
* Serialize headers into HTTP/1.1 format: "Key: Value\r\n"
|
|
17
|
+
* Validates against header injection (CR/LF/NUL).
|
|
18
|
+
*/
|
|
19
|
+
declare function serializeHttp1Headers(headers: Record<string, string>): string;
|
|
20
|
+
|
|
21
|
+
export { buildPseudoHeaders, mergeHeaders, serializeHttp1Headers };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
function buildPseudoHeaders(method, hostname, path, scheme) {
|
|
2
|
+
return [
|
|
3
|
+
[":method", method.toUpperCase()],
|
|
4
|
+
[":path", path],
|
|
5
|
+
[":scheme", scheme],
|
|
6
|
+
[":authority", hostname]
|
|
7
|
+
];
|
|
8
|
+
}
|
|
9
|
+
function mergeHeaders(pseudo, headers) {
|
|
10
|
+
const result = [...pseudo];
|
|
11
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
12
|
+
const lower = key.toLowerCase();
|
|
13
|
+
if (lower.startsWith(":") || lower === "connection" || lower === "transfer-encoding" || lower === "keep-alive" || lower === "upgrade") {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
result.push([lower, value]);
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
const INVALID_HEADER_CHAR_RE = /[\r\n\0]/;
|
|
21
|
+
function serializeHttp1Headers(headers) {
|
|
22
|
+
let result = "";
|
|
23
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
24
|
+
if (INVALID_HEADER_CHAR_RE.test(key) || INVALID_HEADER_CHAR_RE.test(value)) {
|
|
25
|
+
throw new Error(`Invalid header: contains CR/LF/NUL in "${key}"`);
|
|
26
|
+
}
|
|
27
|
+
result += `${key}: ${value}\r
|
|
28
|
+
`;
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
buildPseudoHeaders,
|
|
34
|
+
mergeHeaders,
|
|
35
|
+
serializeHttp1Headers
|
|
36
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL parsing utility.
|
|
3
|
+
*/
|
|
4
|
+
interface ParsedUrl {
|
|
5
|
+
protocol: "https" | "http";
|
|
6
|
+
hostname: string;
|
|
7
|
+
port: number;
|
|
8
|
+
path: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Parse a URL string into its components.
|
|
12
|
+
* Avoids using the full URL constructor to minimize CPU overhead.
|
|
13
|
+
*/
|
|
14
|
+
declare function parseUrl(url: string): ParsedUrl;
|
|
15
|
+
|
|
16
|
+
export { type ParsedUrl, parseUrl };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
function parseUrl(url) {
|
|
2
|
+
const parsed = new URL(url);
|
|
3
|
+
const protocol = parsed.protocol === "https:" ? "https" : "http";
|
|
4
|
+
const hostname = parsed.hostname;
|
|
5
|
+
const defaultPort = protocol === "https" ? 443 : 80;
|
|
6
|
+
const port = parsed.port ? parseInt(parsed.port, 10) : defaultPort;
|
|
7
|
+
const path = parsed.pathname + parsed.search;
|
|
8
|
+
return { protocol, hostname, port, path: path || "/" };
|
|
9
|
+
}
|
|
10
|
+
export {
|
|
11
|
+
parseUrl
|
|
12
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "stealth-fetch-plus",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "HTTP/1.1 + HTTP/2 client for Cloudflare Workers via cloudflare:sockets, bypassing cf-* header injection",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"workerd": "./dist/index.js",
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build:wasm": "bash scripts/build-wasm.sh",
|
|
19
|
+
"dev": "wrangler dev",
|
|
20
|
+
"build": "tsup && bash scripts/copy-wasm.sh",
|
|
21
|
+
"test": "vitest",
|
|
22
|
+
"test:run": "vitest run",
|
|
23
|
+
"test:coverage": "vitest run --coverage",
|
|
24
|
+
"lint": "eslint src/ test/ examples/",
|
|
25
|
+
"lint:fix": "eslint src/ test/ examples/ --fix",
|
|
26
|
+
"lint:commit": "commitlint --from=HEAD~1",
|
|
27
|
+
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"examples/**/*.ts\" \"*.json\" \"*.ts\" \"*.js\" \"*.md\"",
|
|
28
|
+
"format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\" \"examples/**/*.ts\" \"*.json\" \"*.ts\" \"*.js\" \"*.md\"",
|
|
29
|
+
"type-check": "tsc --noEmit",
|
|
30
|
+
"prepublishOnly": "npm run type-check && npm run test:run && npm run build",
|
|
31
|
+
"prepare": "husky"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"hpack.js": "^2.1.6"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@cloudflare/vitest-pool-workers": "^0.12.0",
|
|
38
|
+
"@cloudflare/workers-types": "^4.20241230.0",
|
|
39
|
+
"@commitlint/cli": "^19.6.0",
|
|
40
|
+
"@commitlint/config-conventional": "^19.6.0",
|
|
41
|
+
"@types/node": "^25.2.1",
|
|
42
|
+
"eslint": "^9.0.0",
|
|
43
|
+
"eslint-config-prettier": "^10.1.8",
|
|
44
|
+
"eslint-plugin-jsdoc": "^62.5.3",
|
|
45
|
+
"husky": "^9.1.7",
|
|
46
|
+
"lint-staged": "^16.2.7",
|
|
47
|
+
"prettier": "^3.0.0",
|
|
48
|
+
"tsup": "^8.0.0",
|
|
49
|
+
"typescript": "^5.7.0",
|
|
50
|
+
"typescript-eslint": "^8.54.0",
|
|
51
|
+
"vitest": "^3.0.0",
|
|
52
|
+
"wrangler": "^4.0.0"
|
|
53
|
+
},
|
|
54
|
+
"sideEffects": false,
|
|
55
|
+
"files": [
|
|
56
|
+
"dist"
|
|
57
|
+
],
|
|
58
|
+
"keywords": [
|
|
59
|
+
"cloudflare-workers",
|
|
60
|
+
"http2",
|
|
61
|
+
"h2",
|
|
62
|
+
"http-client",
|
|
63
|
+
"wasm",
|
|
64
|
+
"tls",
|
|
65
|
+
"alpn",
|
|
66
|
+
"cloudflare-sockets",
|
|
67
|
+
"stealth-fetch",
|
|
68
|
+
"no-cf-headers"
|
|
69
|
+
],
|
|
70
|
+
"repository": {
|
|
71
|
+
"type": "git",
|
|
72
|
+
"url": "git+https://github.com/0XwX/stealth-fetch.git"
|
|
73
|
+
},
|
|
74
|
+
"license": "MIT",
|
|
75
|
+
"engines": {
|
|
76
|
+
"node": ">=18.0.0"
|
|
77
|
+
},
|
|
78
|
+
"lint-staged": {
|
|
79
|
+
"*.ts": [
|
|
80
|
+
"eslint --fix",
|
|
81
|
+
"prettier --write"
|
|
82
|
+
],
|
|
83
|
+
"*.{json,md,yml,yaml,js}": [
|
|
84
|
+
"prettier --write"
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
}
|