@continuedev/fetch 1.0.13 → 1.0.15
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/dist/certs.d.ts +17 -0
- package/dist/certs.js +105 -0
- package/dist/certs.test.d.ts +1 -0
- package/dist/certs.test.js +139 -0
- package/dist/fetch.js +7 -10
- package/dist/fetch.test.js +0 -1
- package/dist/getAgentOptions.d.ts +2 -2
- package/dist/getAgentOptions.js +13 -40
- package/dist/getAgentOptions.test.js +129 -77
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/node-fetch-patch.d.ts +16 -0
- package/dist/node-fetch-patch.js +395 -0
- package/dist/node-fetch-patch.test.d.ts +1 -0
- package/dist/node-fetch-patch.test.js +50 -0
- package/dist/stream.js +27 -4
- package/dist/util.d.ts +6 -1
- package/dist/util.js +57 -17
- package/dist/util.test.js +92 -18
- package/package.json +1 -1
- package/release.config.js +3 -0
- package/src/certs.test.ts +187 -0
- package/src/certs.ts +129 -0
- package/src/fetch.ts +7 -11
- package/src/getAgentOptions.test.ts +158 -91
- package/src/getAgentOptions.ts +23 -44
- package/src/index.ts +3 -0
- package/src/node-fetch-patch.js +518 -0
- package/src/node-fetch-patch.test.js +67 -0
- package/src/node_modules/.vite/vitest/d41d8cd98f00b204e9800998ecf8427e/results.json +1 -0
- package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/src/stream.ts +29 -4
- package/src/util.test.ts +130 -18
- package/src/util.ts +84 -19
- package/src/fetch.test.ts +0 -173
|
@@ -1,122 +1,189 @@
|
|
|
1
|
+
import { globalAgent } from "https";
|
|
1
2
|
import * as fs from "node:fs";
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}));
|
|
8
|
-
|
|
9
|
-
const mockReadFileSync = vi.mocked(fs.readFileSync);
|
|
10
|
-
|
|
11
|
-
// We need to access the private getCertificateContent function for testing
|
|
12
|
-
// Since it's not exported, we'll test it indirectly through scenarios or mock the module
|
|
13
|
-
const getCertificateContent = (input: string): string => {
|
|
14
|
-
if (input.startsWith("data:")) {
|
|
15
|
-
// Parse data URI: data:[<mediatype>][;base64],<data>
|
|
16
|
-
const [header, data] = input.split(",");
|
|
17
|
-
if (header.includes("base64")) {
|
|
18
|
-
return Buffer.from(data, "base64").toString("utf8");
|
|
19
|
-
} else {
|
|
20
|
-
return decodeURIComponent(data);
|
|
21
|
-
}
|
|
22
|
-
} else {
|
|
23
|
-
// Assume it's a file path
|
|
24
|
-
return fs.readFileSync(input, "utf8");
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
test("getCertificateContent should decode base64 data URI correctly", () => {
|
|
29
|
-
const testCert =
|
|
30
|
-
"-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----";
|
|
31
|
-
const base64Data = Buffer.from(testCert, "utf8").toString("base64");
|
|
32
|
-
const dataUri = `data:application/x-pem-file;base64,${base64Data}`;
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { afterEach, beforeEach, expect, test } from "vitest";
|
|
6
|
+
import { CertsCache } from "./certs.js";
|
|
7
|
+
import { getAgentOptions } from "./getAgentOptions.js";
|
|
33
8
|
|
|
34
|
-
|
|
9
|
+
// Store original env
|
|
10
|
+
const originalEnv = process.env;
|
|
11
|
+
const originalGlobalAgentOptions = { ...globalAgent.options };
|
|
35
12
|
|
|
36
|
-
|
|
37
|
-
|
|
13
|
+
// Temporary directory for test certificate files
|
|
14
|
+
let tempDir: string;
|
|
38
15
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const encodedData = encodeURIComponent(testCert);
|
|
43
|
-
const dataUri = `data:text/plain,${encodedData}`;
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
// Create a temporary directory for test files
|
|
18
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "fetch-test-"));
|
|
44
19
|
|
|
45
|
-
|
|
20
|
+
process.env = { ...originalEnv };
|
|
46
21
|
|
|
47
|
-
|
|
22
|
+
// Reset globalAgent for each test
|
|
23
|
+
globalAgent.options = { ...originalGlobalAgentOptions };
|
|
48
24
|
});
|
|
49
25
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
process.env = originalEnv;
|
|
28
|
+
globalAgent.options = originalGlobalAgentOptions;
|
|
53
29
|
|
|
54
|
-
|
|
30
|
+
CertsCache.getInstance().clear();
|
|
55
31
|
|
|
56
|
-
|
|
32
|
+
// Clean up temporary directory
|
|
33
|
+
try {
|
|
34
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error(`Failed to remove temp directory: ${error}`);
|
|
37
|
+
}
|
|
57
38
|
});
|
|
58
39
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
40
|
+
// Helper function to create test certificate files
|
|
41
|
+
function createTestCertFile(filename: string, content: string): string {
|
|
42
|
+
const filePath = path.join(tempDir, filename);
|
|
43
|
+
fs.writeFileSync(filePath, content, "utf8");
|
|
44
|
+
return filePath;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
test("getAgentOptions returns basic configuration with default values", async () => {
|
|
48
|
+
const options = await getAgentOptions();
|
|
49
|
+
|
|
50
|
+
// Check default timeout (7200 seconds = 2 hours = 7,200,000 ms)
|
|
51
|
+
expect(options.timeout).toBe(7200000);
|
|
52
|
+
expect(options.sessionTimeout).toBe(7200000);
|
|
53
|
+
expect(options.keepAliveMsecs).toBe(7200000);
|
|
54
|
+
expect(options.keepAlive).toBe(true);
|
|
55
|
+
|
|
56
|
+
// Verify certificates array exists and contains items
|
|
57
|
+
expect(options.ca).toBeInstanceOf(Array);
|
|
58
|
+
expect(options.ca.length).toBeGreaterThan(0);
|
|
59
|
+
|
|
60
|
+
// Verify at least one of the real TLS root certificates is included
|
|
61
|
+
// This assumes there's at least one certificate with "CERTIFICATE" in it
|
|
62
|
+
expect(options.ca.some((cert: any) => cert.includes("CERTIFICATE"))).toBe(
|
|
63
|
+
true,
|
|
64
|
+
);
|
|
70
65
|
});
|
|
71
66
|
|
|
72
|
-
test("
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
const dataUri = `data:application/x-x509-ca-cert;base64,${base64Data}`;
|
|
67
|
+
test("getAgentOptions respects custom timeout", async () => {
|
|
68
|
+
const customTimeout = 300; // 5 minutes
|
|
69
|
+
const options = await getAgentOptions({ timeout: customTimeout });
|
|
76
70
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
expect(
|
|
71
|
+
// Check timeout values (300 seconds = 300,000 ms)
|
|
72
|
+
expect(options.timeout).toBe(300000);
|
|
73
|
+
expect(options.sessionTimeout).toBe(300000);
|
|
74
|
+
expect(options.keepAliveMsecs).toBe(300000);
|
|
80
75
|
});
|
|
81
76
|
|
|
82
|
-
test("
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const result = getCertificateContent(dataUri);
|
|
77
|
+
test("getAgentOptions uses verifySsl setting", async () => {
|
|
78
|
+
// With verifySsl true
|
|
79
|
+
let options = await getAgentOptions({ verifySsl: true });
|
|
80
|
+
expect(options.rejectUnauthorized).toBe(true);
|
|
88
81
|
|
|
89
|
-
|
|
82
|
+
// With verifySsl false
|
|
83
|
+
options = await getAgentOptions({ verifySsl: false });
|
|
84
|
+
expect(options.rejectUnauthorized).toBe(false);
|
|
90
85
|
});
|
|
91
86
|
|
|
92
|
-
test("
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
|
|
87
|
+
test("getAgentOptions incorporates custom CA bundle paths", async () => {
|
|
88
|
+
// Create a test CA bundle file
|
|
89
|
+
const caBundleContent =
|
|
90
|
+
"-----BEGIN CERTIFICATE-----\nMIIDtTCCAp2gAwIBAgIJAMcuSp7chAYdMA==\n-----END CERTIFICATE-----";
|
|
91
|
+
const caBundlePath = createTestCertFile("ca-bundle.pem", caBundleContent);
|
|
92
|
+
|
|
93
|
+
// Single string path
|
|
94
|
+
let options = await getAgentOptions({ caBundlePath });
|
|
95
|
+
|
|
96
|
+
// Verify that our test certificate is included in the CA list
|
|
97
|
+
expect(options.ca).toContain(caBundleContent);
|
|
98
|
+
|
|
99
|
+
// Create multiple test CA bundle files
|
|
100
|
+
const caContent1 =
|
|
101
|
+
"-----BEGIN CERTIFICATE-----\nABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\n-----END CERTIFICATE-----";
|
|
102
|
+
const caContent2 =
|
|
103
|
+
"-----BEGIN CERTIFICATE-----\n0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\n-----END CERTIFICATE-----";
|
|
104
|
+
const caPath1 = createTestCertFile("ca1.pem", caContent1);
|
|
105
|
+
const caPath2 = createTestCertFile("ca2.pem", caContent2);
|
|
106
|
+
|
|
107
|
+
// Array of paths
|
|
108
|
+
options = await getAgentOptions({
|
|
109
|
+
caBundlePath: [caPath1, caPath2],
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Verify that both test certificates are included in the CA list
|
|
113
|
+
expect(options.ca).toContain(caContent1);
|
|
114
|
+
expect(options.ca).toContain(caContent2);
|
|
115
|
+
});
|
|
96
116
|
|
|
97
|
-
|
|
117
|
+
test("getAgentOptions includes global certs when running as binary", async () => {
|
|
118
|
+
// Set up test certs in globalAgent
|
|
119
|
+
globalAgent.options.ca = ["global-cert-1", "global-cert-2"];
|
|
98
120
|
|
|
99
|
-
|
|
100
|
-
|
|
121
|
+
// Set IS_BINARY environment variable
|
|
122
|
+
process.env.IS_BINARY = "true";
|
|
101
123
|
|
|
102
|
-
|
|
103
|
-
const filePath = "./certs/ca.pem";
|
|
104
|
-
const expectedContent = "certificate from relative path";
|
|
124
|
+
const options = await getAgentOptions();
|
|
105
125
|
|
|
106
|
-
|
|
126
|
+
// Test for global certs
|
|
127
|
+
expect(options.ca).toContain("global-cert-1");
|
|
128
|
+
expect(options.ca).toContain("global-cert-2");
|
|
129
|
+
});
|
|
107
130
|
|
|
108
|
-
|
|
131
|
+
test("getAgentOptions handles client certificate configuration", async () => {
|
|
132
|
+
// Create test certificate files
|
|
133
|
+
const clientCertContent =
|
|
134
|
+
"-----BEGIN CERTIFICATE-----\nCLIENTCERT\n-----END CERTIFICATE-----";
|
|
135
|
+
const clientKeyContent =
|
|
136
|
+
"-----BEGIN PRIVATE KEY-----\nCLIENTKEY\n-----END PRIVATE KEY-----";
|
|
137
|
+
const certPath = createTestCertFile("client.cert", clientCertContent);
|
|
138
|
+
const keyPath = createTestCertFile("client.key", clientKeyContent);
|
|
139
|
+
|
|
140
|
+
const clientCertOptions = {
|
|
141
|
+
clientCertificate: {
|
|
142
|
+
cert: certPath,
|
|
143
|
+
key: keyPath,
|
|
144
|
+
passphrase: "secret-passphrase",
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const options = await getAgentOptions(clientCertOptions);
|
|
149
|
+
|
|
150
|
+
expect(options.cert).toBe(clientCertContent);
|
|
151
|
+
expect(options.key).toBe(clientKeyContent);
|
|
152
|
+
expect(options.passphrase).toBe("secret-passphrase");
|
|
153
|
+
});
|
|
109
154
|
|
|
110
|
-
|
|
111
|
-
|
|
155
|
+
test("getAgentOptions handles client certificate without passphrase", async () => {
|
|
156
|
+
// Create test certificate files
|
|
157
|
+
const clientCertContent =
|
|
158
|
+
"-----BEGIN CERTIFICATE-----\nCLIENTCERT2\n-----END CERTIFICATE-----";
|
|
159
|
+
const clientKeyContent =
|
|
160
|
+
"-----BEGIN PRIVATE KEY-----\nCLIENTKEY2\n-----END PRIVATE KEY-----";
|
|
161
|
+
const certPath = createTestCertFile("client2.cert", clientCertContent);
|
|
162
|
+
const keyPath = createTestCertFile("client2.key", clientKeyContent);
|
|
163
|
+
|
|
164
|
+
const clientCertOptions = {
|
|
165
|
+
clientCertificate: {
|
|
166
|
+
cert: certPath,
|
|
167
|
+
key: keyPath,
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const options = await getAgentOptions(clientCertOptions);
|
|
172
|
+
|
|
173
|
+
expect(options.cert).toBe(clientCertContent);
|
|
174
|
+
expect(options.key).toBe(clientKeyContent);
|
|
175
|
+
expect(options.passphrase).toBeUndefined();
|
|
112
176
|
});
|
|
113
177
|
|
|
114
|
-
test("
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
const
|
|
178
|
+
test("getAgentOptions reads NODE_EXTRA_CA_CERTS", async () => {
|
|
179
|
+
const extraCertContent =
|
|
180
|
+
"-----BEGIN CERTIFICATE-----\nEXTRA_CERT\n-----END CERTIFICATE-----";
|
|
181
|
+
const certPath = createTestCertFile("extra-cert.cert", extraCertContent);
|
|
182
|
+
|
|
183
|
+
expect(CertsCache.getInstance().fixedCa).not.toContain(extraCertContent);
|
|
118
184
|
|
|
119
|
-
|
|
185
|
+
CertsCache.getInstance().clear();
|
|
120
186
|
|
|
121
|
-
|
|
187
|
+
process.env.NODE_EXTRA_CA_CERTS = certPath;
|
|
188
|
+
expect(CertsCache.getInstance().fixedCa).toContain(extraCertContent);
|
|
122
189
|
});
|
package/src/getAgentOptions.ts
CHANGED
|
@@ -1,56 +1,19 @@
|
|
|
1
|
-
import { globalAgent } from "https";
|
|
2
|
-
import * as fs from "node:fs";
|
|
3
|
-
import tls from "node:tls";
|
|
4
|
-
|
|
5
1
|
import { RequestOptions } from "@continuedev/config-types";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Extracts content from either a file path or data URI
|
|
9
|
-
*/
|
|
10
|
-
function getCertificateContent(input: string): string {
|
|
11
|
-
if (input.startsWith("data:")) {
|
|
12
|
-
// Parse data URI: data:[<mediatype>][;base64],<data>
|
|
13
|
-
const [header, data] = input.split(",");
|
|
14
|
-
if (header.includes("base64")) {
|
|
15
|
-
return Buffer.from(data, "base64").toString("utf8");
|
|
16
|
-
} else {
|
|
17
|
-
return decodeURIComponent(data);
|
|
18
|
-
}
|
|
19
|
-
} else {
|
|
20
|
-
// Assume it's a file path
|
|
21
|
-
return fs.readFileSync(input, "utf8");
|
|
22
|
-
}
|
|
23
|
-
}
|
|
2
|
+
import { CertsCache, getCertificateContent } from "./certs.js";
|
|
24
3
|
|
|
25
4
|
/**
|
|
26
5
|
* Prepares agent options based on request options and certificates
|
|
27
6
|
*/
|
|
28
|
-
export function getAgentOptions(
|
|
7
|
+
export async function getAgentOptions(
|
|
8
|
+
requestOptions?: RequestOptions,
|
|
9
|
+
): Promise<{
|
|
29
10
|
[key: string]: any;
|
|
30
|
-
} {
|
|
11
|
+
}> {
|
|
31
12
|
const TIMEOUT = 7200; // 7200 seconds = 2 hours
|
|
32
13
|
const timeout = (requestOptions?.timeout ?? TIMEOUT) * 1000; // measured in ms
|
|
33
14
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (process.env.IS_BINARY) {
|
|
37
|
-
if (Array.isArray(globalAgent.options.ca)) {
|
|
38
|
-
globalCerts = [...globalAgent.options.ca.map((cert) => cert.toString())];
|
|
39
|
-
} else if (typeof globalAgent.options.ca !== "undefined") {
|
|
40
|
-
globalCerts.push(globalAgent.options.ca.toString());
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const ca = Array.from(new Set([...tls.rootCertificates, ...globalCerts]));
|
|
45
|
-
const customCerts =
|
|
46
|
-
typeof requestOptions?.caBundlePath === "string"
|
|
47
|
-
? [requestOptions?.caBundlePath]
|
|
48
|
-
: requestOptions?.caBundlePath;
|
|
49
|
-
if (customCerts) {
|
|
50
|
-
ca.push(
|
|
51
|
-
...customCerts.map((customCert) => getCertificateContent(customCert)),
|
|
52
|
-
);
|
|
53
|
-
}
|
|
15
|
+
const certsCache = CertsCache.getInstance();
|
|
16
|
+
const ca = await certsCache.getCa(requestOptions?.caBundlePath);
|
|
54
17
|
|
|
55
18
|
const agentOptions: { [key: string]: any } = {
|
|
56
19
|
ca,
|
|
@@ -73,5 +36,21 @@ export function getAgentOptions(requestOptions?: RequestOptions): {
|
|
|
73
36
|
}
|
|
74
37
|
}
|
|
75
38
|
|
|
39
|
+
if (process.env.VERBOSE_FETCH) {
|
|
40
|
+
console.log(`Fetch agent options:`);
|
|
41
|
+
console.log(
|
|
42
|
+
`\tTimeout (sessionTimeout/keepAliveMsecs): ${agentOptions.timeout}`,
|
|
43
|
+
);
|
|
44
|
+
console.log(`\tTotal CA certs: ${ca.length}`);
|
|
45
|
+
console.log(`\tGlobal/Root CA certs: ${certsCache.fixedCa.length}`);
|
|
46
|
+
console.log(`\tCustom CA certs: ${ca.length - certsCache.fixedCa.length}`);
|
|
47
|
+
console.log(
|
|
48
|
+
`\tClient certificate: ${requestOptions?.clientCertificate ? "Yes" : "No"}`,
|
|
49
|
+
);
|
|
50
|
+
console.log(
|
|
51
|
+
`\trejectUnauthorized/verifySsl: ${agentOptions.rejectUnauthorized ?? "not set (defaults to true)"}`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
76
55
|
return agentOptions;
|
|
77
56
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,10 +5,13 @@ import {
|
|
|
5
5
|
toAsyncIterable,
|
|
6
6
|
} from "./stream.js";
|
|
7
7
|
|
|
8
|
+
import patchedFetch from "./node-fetch-patch.js";
|
|
9
|
+
|
|
8
10
|
import { fetchwithRequestOptions } from "./fetch.js";
|
|
9
11
|
|
|
10
12
|
export {
|
|
11
13
|
fetchwithRequestOptions,
|
|
14
|
+
patchedFetch,
|
|
12
15
|
streamJSON,
|
|
13
16
|
streamResponse,
|
|
14
17
|
streamSse,
|