midline-agent 0.4.0 → 0.5.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/CHANGELOG.md +25 -0
- package/README.md +10 -7
- package/dist/agent.d.ts +9 -1
- package/dist/agent.js +44 -68
- package/dist/browser/client.d.ts +1 -0
- package/dist/browser/client.js +16 -2
- package/dist/browser/types.d.ts +9 -1
- package/dist/browser/version.d.ts +1 -1
- package/dist/browser/version.js +1 -1
- package/dist/config.d.ts +8 -7
- package/dist/config.js +12 -24
- package/dist/esm/browser/client.js +16 -2
- package/dist/esm/browser/version.js +1 -1
- package/dist/socket-transport.d.ts +58 -0
- package/dist/socket-transport.js +157 -0
- package/package.json +6 -2
- package/src/agent.ts +46 -73
- package/src/browser/client.ts +16 -2
- package/src/browser/types.ts +9 -1
- package/src/browser/version.ts +1 -1
- package/src/config.ts +12 -23
- package/src/socket-transport.ts +188 -0
- package/test/agent.test.js +47 -51
- package/test/browser.test.js +48 -2
- package/test/console.test.js +11 -10
- package/test/helpers.js +54 -1
- package/test/middleware.test.js +5 -5
- package/test/proxy.test.js +4 -4
- package/src/transport.ts +0 -125
package/test/proxy.test.js
CHANGED
|
@@ -5,7 +5,7 @@ const assert = require("node:assert/strict");
|
|
|
5
5
|
const net = require("net");
|
|
6
6
|
const { MidlineAgent, createMidlineProxy, startMidlineProxy, ConfigError } = require("../dist");
|
|
7
7
|
const { REDACTED } = require("../dist/redact");
|
|
8
|
-
const { makeCerts,
|
|
8
|
+
const { makeCerts, startSocketCollector, listen, request, closedPort, waitFor } = require("./helpers");
|
|
9
9
|
|
|
10
10
|
const certs = makeCerts();
|
|
11
11
|
const needsOpenssl = certs ? {} : { skip: "openssl not available" };
|
|
@@ -27,7 +27,7 @@ function echo(req, res) {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
async function setup({ target, collectorOptions, agentOptions, proxyOptions } = {}) {
|
|
30
|
-
const collector = await
|
|
30
|
+
const collector = await startSocketCollector(collectorOptions);
|
|
31
31
|
const agent = new MidlineAgent({ apiKey: "ak_proxy", endpoint: collector.url, flushIntervalMs: 60_000, ...agentOptions });
|
|
32
32
|
const server = await startMidlineProxy({ target, port: 0, agent, onError: () => {}, ...proxyOptions });
|
|
33
33
|
const base = `http://127.0.0.1:${server.address().port}`;
|
|
@@ -93,7 +93,7 @@ test("forwards to the destination and reports the exchange to Midline separately
|
|
|
93
93
|
await waitFor(() => ctx.agent.queued === 1);
|
|
94
94
|
await ctx.agent.flush();
|
|
95
95
|
const [event] = ctx.collector.events();
|
|
96
|
-
assert.equal(ctx.collector.requests
|
|
96
|
+
assert.equal(ctx.collector.requests.length, 1, "events go to Midline, not the destination");
|
|
97
97
|
assert.equal(event.route, "/v1/items");
|
|
98
98
|
assert.equal(event.metadata.integrationType, "proxy");
|
|
99
99
|
assert.equal(event.metadata.requestId, "rid-7");
|
|
@@ -254,7 +254,7 @@ test("https destination with a private CA: trusted only via targetCa, never by d
|
|
|
254
254
|
|
|
255
255
|
test("the destination CA and the Midline CA are independent", needsOpenssl, async () => {
|
|
256
256
|
const destination = await listen(echo, certs.trusted);
|
|
257
|
-
const collector = await
|
|
257
|
+
const collector = await startSocketCollector({ tls: certs.selfSigned });
|
|
258
258
|
const agent = new MidlineAgent({ apiKey: "ak_proxy", endpoint: collector.url, flushIntervalMs: 60_000, onError: () => {} });
|
|
259
259
|
const server = await startMidlineProxy({ target: `https://localhost:${destination.port}`, targetCa: certs.ca, port: 0, agent });
|
|
260
260
|
try {
|
package/src/transport.ts
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import * as http from "http";
|
|
2
|
-
import * as https from "https";
|
|
3
|
-
|
|
4
|
-
export interface PostResult {
|
|
5
|
-
status: number;
|
|
6
|
-
headers: http.IncomingHttpHeaders;
|
|
7
|
-
body: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface TransportOptions {
|
|
11
|
-
/** Full trust store for https, or undefined for Node's default. Verification is never disabled. */
|
|
12
|
-
ca?: Array<string | Buffer>;
|
|
13
|
-
connectTimeoutMs: number;
|
|
14
|
-
timeoutMs: number;
|
|
15
|
-
userAgent: string;
|
|
16
|
-
/** Response bodies are only read for diagnostics; anything past this is discarded. */
|
|
17
|
-
maxResponseBytes?: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** A transport failure with a stable `code`, including the two timeouts Node doesn't name. */
|
|
21
|
-
export class TransportError extends Error {
|
|
22
|
-
constructor(message: string, readonly code: string) {
|
|
23
|
-
super(message);
|
|
24
|
-
this.name = "TransportError";
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Minimal JSON POST over Node's own http/https.
|
|
30
|
-
*
|
|
31
|
-
* Node's modules rather than fetch because this needs things fetch doesn't expose
|
|
32
|
-
* portably: a per-endpoint CA that extends rather than replaces the trust store, a
|
|
33
|
-
* connect timeout separate from the request deadline, and sockets that don't keep
|
|
34
|
-
* the host process alive.
|
|
35
|
-
*/
|
|
36
|
-
export class Transport {
|
|
37
|
-
private readonly agent: http.Agent;
|
|
38
|
-
|
|
39
|
-
constructor(origin: URL, private readonly options: TransportOptions) {
|
|
40
|
-
this.agent = origin.protocol === "https:"
|
|
41
|
-
? new https.Agent({ keepAlive: true, maxSockets: 4, ca: options.ca })
|
|
42
|
-
: new http.Agent({ keepAlive: true, maxSockets: 4 });
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
post(url: URL, body: string, headers: Record<string, string>, keepProcessAlive: boolean): Promise<PostResult> {
|
|
46
|
-
const { connectTimeoutMs, timeoutMs } = this.options;
|
|
47
|
-
const maxResponseBytes = this.options.maxResponseBytes ?? 64 * 1024;
|
|
48
|
-
const isHttps = url.protocol === "https:";
|
|
49
|
-
|
|
50
|
-
return new Promise<PostResult>((resolve, reject) => {
|
|
51
|
-
let settled = false;
|
|
52
|
-
let connectTimer: NodeJS.Timeout | undefined;
|
|
53
|
-
|
|
54
|
-
const settle = (fn: () => void) => {
|
|
55
|
-
if (settled) return;
|
|
56
|
-
settled = true;
|
|
57
|
-
clearTimeout(deadline);
|
|
58
|
-
if (connectTimer) clearTimeout(connectTimer);
|
|
59
|
-
fn();
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const request = (isHttps ? https : http).request(url, {
|
|
63
|
-
method: "POST",
|
|
64
|
-
agent: this.agent,
|
|
65
|
-
headers: {
|
|
66
|
-
...headers,
|
|
67
|
-
"content-type": "application/json",
|
|
68
|
-
"content-length": String(Buffer.byteLength(body)),
|
|
69
|
-
"user-agent": this.options.userAgent,
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
const deadline = setTimeout(() => {
|
|
74
|
-
request.destroy(new TransportError(`no response within ${timeoutMs}ms`, "ETIMEDOUT"));
|
|
75
|
-
}, timeoutMs);
|
|
76
|
-
|
|
77
|
-
request.on("socket", (socket) => {
|
|
78
|
-
if (!keepProcessAlive) {
|
|
79
|
-
socket.unref();
|
|
80
|
-
} else {
|
|
81
|
-
socket.ref();
|
|
82
|
-
}
|
|
83
|
-
// A pooled keep-alive socket is already connected; only time fresh ones.
|
|
84
|
-
// The overall deadline still covers a handshake that stalls after connect.
|
|
85
|
-
if ((socket as any).connecting) {
|
|
86
|
-
connectTimer = setTimeout(() => {
|
|
87
|
-
request.destroy(new TransportError(`connection not established within ${connectTimeoutMs}ms`, "ECONNECT_TIMEOUT"));
|
|
88
|
-
}, connectTimeoutMs);
|
|
89
|
-
socket.once(isHttps ? "secureConnect" : "connect", () => clearTimeout(connectTimer));
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
request.on("response", (response) => {
|
|
94
|
-
const chunks: Buffer[] = [];
|
|
95
|
-
let received = 0;
|
|
96
|
-
response.on("data", (chunk: Buffer) => {
|
|
97
|
-
if (received < maxResponseBytes) {
|
|
98
|
-
chunks.push(chunk.subarray(0, maxResponseBytes - received));
|
|
99
|
-
}
|
|
100
|
-
received += chunk.length;
|
|
101
|
-
});
|
|
102
|
-
response.on("end", () =>
|
|
103
|
-
settle(() =>
|
|
104
|
-
resolve({
|
|
105
|
-
status: response.statusCode ?? 0,
|
|
106
|
-
headers: response.headers,
|
|
107
|
-
body: Buffer.concat(chunks).toString("utf8"),
|
|
108
|
-
}),
|
|
109
|
-
),
|
|
110
|
-
);
|
|
111
|
-
response.on("error", (err) => settle(() => reject(err)));
|
|
112
|
-
response.on("aborted", () =>
|
|
113
|
-
settle(() => reject(new TransportError("response aborted by the server", "ECONNRESET"))),
|
|
114
|
-
);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
request.on("error", (err) => settle(() => reject(err)));
|
|
118
|
-
request.end(body);
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
destroy(): void {
|
|
123
|
-
this.agent.destroy();
|
|
124
|
-
}
|
|
125
|
-
}
|