@zlink-systems/http-client 0.26.0 → 0.27.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/dist/.tsbuildinfo +1 -1
- package/dist/browser/index.mjs +357 -241
- package/dist/client.d.ts +1 -1
- package/dist/client.js +10 -7
- package/dist/request-builder.d.ts +2 -1
- package/dist/request-builder.js +29 -23
- package/dist/runtime/bounded-response-body.d.ts +2 -0
- package/dist/runtime/bounded-response-body.js +16 -0
- package/dist/runtime/browser-runtime.js +91 -35
- package/dist/runtime/compression.d.ts +5 -0
- package/dist/runtime/compression.js +10 -3
- package/dist/runtime/cookie-jar.js +9 -3
- package/dist/runtime/redirect-policy.js +11 -2
- package/dist/runtime/request-performer.js +65 -25
- package/dist/runtime/response-body-reader.js +12 -22
- package/dist/runtime/retry-policy.d.ts +7 -1
- package/dist/runtime/retry-policy.js +21 -10
- package/dist/runtime/runtime.js +1 -1
- package/dist/runtime/text.d.ts +17 -0
- package/dist/runtime/text.js +26 -1
- package/package.json +2 -2
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
/* SPDX-License-Identifier: Apache-2.0 */
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.RequestPerformer = void 0;
|
|
5
|
+
const compression_1 = require("./compression");
|
|
6
|
+
const text_1 = require("./text");
|
|
5
7
|
const node_stream_1 = require("node:stream");
|
|
6
8
|
const undici_1 = require("undici");
|
|
7
9
|
const redirect_policy_1 = require("./redirect-policy");
|
|
8
10
|
const response_body_reader_1 = require("./response-body-reader");
|
|
9
11
|
const http_client_errors_1 = require("./http-client-errors");
|
|
10
12
|
const version_1 = require("./version");
|
|
13
|
+
const retry_policy_1 = require("./retry-policy");
|
|
11
14
|
/**
|
|
12
15
|
* Performs a single logical request, driving the wrapper-owned redirect loop, cookie jar
|
|
13
16
|
* integration, and same-origin `Authorization` scrubbing. Redirect/URL rules live in
|
|
@@ -39,17 +42,23 @@ class RequestPerformer {
|
|
|
39
42
|
const keepAuthorization = current.origin === origin;
|
|
40
43
|
const hasBody = body !== undefined || bodyProvider !== undefined;
|
|
41
44
|
const headers = this.buildHeaders(spec, current, keepAuthorization, hasBody);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
let response;
|
|
46
|
+
try {
|
|
47
|
+
response = await (0, undici_1.request)(current.href, {
|
|
48
|
+
method,
|
|
49
|
+
headers,
|
|
50
|
+
body: this.buildBody(body, bodyProvider, signal),
|
|
51
|
+
dispatcher: this.dispatcher,
|
|
52
|
+
maxRedirections: 0,
|
|
53
|
+
signal
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
throw (0, retry_policy_1.mapFailure)(error, signal.aborted, retry_policy_1.HttpFailureStage.Transport);
|
|
58
|
+
}
|
|
50
59
|
const status = response.statusCode;
|
|
51
60
|
if (this.options.cookies) {
|
|
52
|
-
const setCookie = response.headers[
|
|
61
|
+
const setCookie = response.headers[text_1.HttpHeaderName.SetCookie];
|
|
53
62
|
const values = Array.isArray(setCookie)
|
|
54
63
|
? setCookie
|
|
55
64
|
: setCookie === undefined
|
|
@@ -59,28 +68,47 @@ class RequestPerformer {
|
|
|
59
68
|
this.cookieJar.store(current.hostname, value);
|
|
60
69
|
}
|
|
61
70
|
}
|
|
62
|
-
const location = headerValue(response.headers,
|
|
71
|
+
const location = headerValue(response.headers, text_1.HttpHeaderName.Location);
|
|
63
72
|
if (this.options.followRedirects > 0 &&
|
|
64
73
|
(0, redirect_policy_1.isRedirectStatus)(status) &&
|
|
65
74
|
location !== undefined &&
|
|
66
75
|
location.length > 0) {
|
|
67
76
|
if (redirectsLeft === 0) {
|
|
68
|
-
await drain(response.body);
|
|
77
|
+
await drain(response.body, signal);
|
|
69
78
|
throw (0, http_client_errors_1.redirectLimitExceeded)();
|
|
70
79
|
}
|
|
71
80
|
redirectsLeft--;
|
|
72
81
|
({ method, body } = (0, redirect_policy_1.rewriteForRedirect)(status, method, body));
|
|
73
82
|
bodyProvider = undefined; // consumed; never replay a non-rewindable stream on a redirect
|
|
74
|
-
await drain(response.body);
|
|
83
|
+
await drain(response.body, signal);
|
|
75
84
|
current = (0, redirect_policy_1.resolveLocation)(current, location);
|
|
76
85
|
continue;
|
|
77
86
|
}
|
|
78
87
|
const collectedHeaders = response_body_reader_1.ResponseBodyReader.collectHeaders(response.headers);
|
|
79
88
|
if (spec.sink !== undefined) {
|
|
80
|
-
|
|
89
|
+
const sink = spec.sink;
|
|
90
|
+
try {
|
|
91
|
+
await this.bodyReader.streamToSink(response.body, (chunk) => {
|
|
92
|
+
try {
|
|
93
|
+
sink(chunk);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
throw (0, retry_policy_1.mapFailure)(error, signal.aborted, retry_policy_1.HttpFailureStage.Application);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
throw (0, retry_policy_1.mapFailure)(error, signal.aborted, retry_policy_1.HttpFailureStage.Transport);
|
|
102
|
+
}
|
|
81
103
|
return { status, headers: collectedHeaders, body: '' };
|
|
82
104
|
}
|
|
83
|
-
let bytes
|
|
105
|
+
let bytes;
|
|
106
|
+
try {
|
|
107
|
+
bytes = await this.bodyReader.readBuffered(response.body);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
throw (0, retry_policy_1.mapFailure)(error, signal.aborted, retry_policy_1.HttpFailureStage.Transport);
|
|
111
|
+
}
|
|
84
112
|
let finalHeaders = collectedHeaders;
|
|
85
113
|
if (this.options.compression) {
|
|
86
114
|
const result = await this.bodyReader.decompress(bytes, finalHeaders);
|
|
@@ -102,11 +130,11 @@ class RequestPerformer {
|
|
|
102
130
|
}
|
|
103
131
|
buildHeaders(spec, target, keepAuthorization, hasBody) {
|
|
104
132
|
const headers = {
|
|
105
|
-
|
|
106
|
-
|
|
133
|
+
[text_1.HttpHeaderName.UserAgent]: version_1.httpClientUserAgent,
|
|
134
|
+
[text_1.HttpHeaderName.Accept]: text_1.HttpContentType.Json
|
|
107
135
|
};
|
|
108
136
|
if (this.options.compression) {
|
|
109
|
-
headers[
|
|
137
|
+
headers[text_1.HttpHeaderName.AcceptEncoding] = compression_1.HTTP_ACCEPTED_CONTENT_ENCODINGS;
|
|
110
138
|
}
|
|
111
139
|
// Proxy authentication is carried by the ProxyAgent dispatcher (see runtime.ts), not a header,
|
|
112
140
|
// so it is not leaked to the target over a CONNECT tunnel.
|
|
@@ -114,19 +142,26 @@ class RequestPerformer {
|
|
|
114
142
|
applyHeaders(headers, spec.headers, keepAuthorization);
|
|
115
143
|
if (!hasBody) {
|
|
116
144
|
// A body source dropped by a redirect must not leave stale content-type metadata behind.
|
|
117
|
-
delete headers[
|
|
145
|
+
delete headers[text_1.HttpHeaderName.ContentType];
|
|
118
146
|
}
|
|
119
147
|
if (this.options.cookies) {
|
|
120
148
|
const cookieHeader = this.cookieJar.headerFor(target.hostname, target.pathname, target.protocol === 'https:');
|
|
121
149
|
if (cookieHeader.length > 0) {
|
|
122
|
-
headers[
|
|
150
|
+
headers[text_1.HttpHeaderName.Cookie] = cookieHeader;
|
|
123
151
|
}
|
|
124
152
|
}
|
|
125
153
|
return headers;
|
|
126
154
|
}
|
|
127
|
-
buildBody(body, bodyProvider) {
|
|
155
|
+
buildBody(body, bodyProvider, signal) {
|
|
128
156
|
if (bodyProvider !== undefined) {
|
|
129
|
-
const provider =
|
|
157
|
+
const provider = () => {
|
|
158
|
+
try {
|
|
159
|
+
return bodyProvider();
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
throw (0, retry_policy_1.mapFailure)(error, signal.aborted, retry_policy_1.HttpFailureStage.Application);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
130
165
|
return node_stream_1.Readable.from((function* () {
|
|
131
166
|
for (let chunk = provider(); chunk !== null; chunk = provider()) {
|
|
132
167
|
if (chunk.length > 0) {
|
|
@@ -142,7 +177,7 @@ exports.RequestPerformer = RequestPerformer;
|
|
|
142
177
|
function applyHeaders(target, headers, keepAuthorization) {
|
|
143
178
|
for (const [name, value] of Object.entries(headers)) {
|
|
144
179
|
const lower = name.toLowerCase();
|
|
145
|
-
if (!keepAuthorization && lower ===
|
|
180
|
+
if (!keepAuthorization && lower === text_1.HttpHeaderName.Authorization) {
|
|
146
181
|
continue;
|
|
147
182
|
}
|
|
148
183
|
target[lower] = value;
|
|
@@ -155,8 +190,13 @@ function headerValue(headers, name) {
|
|
|
155
190
|
}
|
|
156
191
|
return Array.isArray(value) ? value[0] : value;
|
|
157
192
|
}
|
|
158
|
-
async function drain(stream) {
|
|
159
|
-
|
|
160
|
-
|
|
193
|
+
async function drain(stream, signal) {
|
|
194
|
+
try {
|
|
195
|
+
for await (const _chunk of stream) {
|
|
196
|
+
// discard
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
throw (0, retry_policy_1.mapFailure)(error, signal.aborted, retry_policy_1.HttpFailureStage.Transport);
|
|
161
201
|
}
|
|
162
202
|
}
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
/* SPDX-License-Identifier: Apache-2.0 */
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.ResponseBodyReader = void 0;
|
|
5
|
+
const text_1 = require("./text");
|
|
5
6
|
const compression_1 = require("./compression");
|
|
6
|
-
const
|
|
7
|
+
const bounded_response_body_1 = require("./bounded-response-body");
|
|
7
8
|
/**
|
|
8
9
|
* Reads and decodes undici response bodies for the wrapper: buffered read with the configured size
|
|
9
10
|
* limit, streaming delivery to a sink, header collection, and wrapper-controlled gzip/deflate
|
|
@@ -15,44 +16,33 @@ class ResponseBodyReader {
|
|
|
15
16
|
this.options = options;
|
|
16
17
|
}
|
|
17
18
|
async streamToSink(stream, sink) {
|
|
18
|
-
|
|
19
|
-
for await (const chunk of stream) {
|
|
20
|
-
const buffer = chunk;
|
|
21
|
-
total += buffer.length;
|
|
22
|
-
if (total > this.options.maxResponseBodySize) {
|
|
23
|
-
throw (0, http_client_errors_1.responseBodySizeExceeded)();
|
|
24
|
-
}
|
|
19
|
+
await (0, bounded_response_body_1.readResponseBody)(stream, this.options.maxResponseBodySize, (buffer) => {
|
|
25
20
|
sink(new Uint8Array(buffer));
|
|
26
|
-
}
|
|
21
|
+
});
|
|
27
22
|
}
|
|
28
23
|
async readBuffered(stream) {
|
|
29
24
|
const chunks = [];
|
|
30
|
-
|
|
31
|
-
for await (const chunk of stream) {
|
|
32
|
-
const buffer = chunk;
|
|
33
|
-
total += buffer.length;
|
|
34
|
-
if (total > this.options.maxResponseBodySize) {
|
|
35
|
-
throw (0, http_client_errors_1.responseBodySizeExceeded)();
|
|
36
|
-
}
|
|
25
|
+
await (0, bounded_response_body_1.readResponseBody)(stream, this.options.maxResponseBodySize, (buffer) => {
|
|
37
26
|
chunks.push(buffer);
|
|
38
|
-
}
|
|
27
|
+
});
|
|
39
28
|
return Buffer.concat(chunks);
|
|
40
29
|
}
|
|
41
30
|
async decompress(bytes, headers) {
|
|
42
|
-
const encoding = Object.prototype.hasOwnProperty.call(headers,
|
|
43
|
-
? headers[
|
|
31
|
+
const encoding = Object.prototype.hasOwnProperty.call(headers, text_1.HttpHeaderName.ContentEncoding)
|
|
32
|
+
? headers[text_1.HttpHeaderName.ContentEncoding]
|
|
44
33
|
: undefined;
|
|
45
34
|
// An empty body (HEAD / 204 / 304) carries no payload to decode even with Content-Encoding.
|
|
46
35
|
if (encoding === undefined || bytes.length === 0) {
|
|
47
36
|
return { body: bytes, headers };
|
|
48
37
|
}
|
|
49
|
-
|
|
38
|
+
const normalizedEncoding = encoding.toLowerCase();
|
|
39
|
+
if (normalizedEncoding === compression_1.CONTENT_ENCODING.Gzip) {
|
|
50
40
|
return {
|
|
51
41
|
body: await (0, compression_1.gunzip)(bytes, this.options.maxResponseBodySize),
|
|
52
42
|
headers: stripEncodingHeaders(headers)
|
|
53
43
|
};
|
|
54
44
|
}
|
|
55
|
-
if (
|
|
45
|
+
if (normalizedEncoding === compression_1.CONTENT_ENCODING.Deflate) {
|
|
56
46
|
return {
|
|
57
47
|
body: await (0, compression_1.inflateDeflate)(bytes, this.options.maxResponseBodySize),
|
|
58
48
|
headers: stripEncodingHeaders(headers)
|
|
@@ -78,7 +68,7 @@ function stripEncodingHeaders(headers) {
|
|
|
78
68
|
const copy = {};
|
|
79
69
|
for (const [key, value] of Object.entries(headers)) {
|
|
80
70
|
const lower = key.toLowerCase();
|
|
81
|
-
if (lower !==
|
|
71
|
+
if (lower !== text_1.HttpHeaderName.ContentEncoding && lower !== text_1.HttpHeaderName.ContentLength) {
|
|
82
72
|
copy[key] = value;
|
|
83
73
|
}
|
|
84
74
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ZLinkFrameworkException } from '@zlink-systems/framework';
|
|
1
2
|
import type { HttpClientOptions } from './options';
|
|
2
3
|
import type { HttpRequestSpec, RawResult } from './request-performer';
|
|
3
4
|
/**
|
|
@@ -10,5 +11,10 @@ import type { HttpRequestSpec, RawResult } from './request-performer';
|
|
|
10
11
|
export declare class RetryPolicy {
|
|
11
12
|
private readonly options;
|
|
12
13
|
constructor(options: HttpClientOptions);
|
|
13
|
-
execute(spec: HttpRequestSpec, perform: (spec: HttpRequestSpec,
|
|
14
|
+
execute(spec: HttpRequestSpec, perform: (spec: HttpRequestSpec, controller: AbortController) => Promise<RawResult>): Promise<RawResult>;
|
|
14
15
|
}
|
|
16
|
+
export declare enum HttpFailureStage {
|
|
17
|
+
Transport = 0,
|
|
18
|
+
Application = 1
|
|
19
|
+
}
|
|
20
|
+
export declare function mapFailure(error: unknown, aborted: boolean, stage: HttpFailureStage): ZLinkFrameworkException;
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/* SPDX-License-Identifier: Apache-2.0 */
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.RetryPolicy = void 0;
|
|
4
|
+
exports.HttpFailureStage = exports.RetryPolicy = void 0;
|
|
5
|
+
exports.mapFailure = mapFailure;
|
|
5
6
|
const framework_1 = require("@zlink-systems/framework");
|
|
7
|
+
const INITIAL_RETRY_DELAY_MS = 50;
|
|
8
|
+
const MAX_RETRY_DELAY_MS = 1000;
|
|
9
|
+
const MAX_RETRY_DELAY_SHIFT = Math.ceil(Math.log2(MAX_RETRY_DELAY_MS / INITIAL_RETRY_DELAY_MS));
|
|
6
10
|
// Exponential backoff with full jitter: base 50ms, doubling per attempt, capped at 1s.
|
|
7
11
|
// Fixed delays synchronize retries from many clients against an ailing server.
|
|
8
12
|
function delayMsFor(attempt) {
|
|
9
|
-
const ceiling = Math.min(
|
|
13
|
+
const ceiling = Math.min(MAX_RETRY_DELAY_MS, INITIAL_RETRY_DELAY_MS << Math.min(attempt, MAX_RETRY_DELAY_SHIFT));
|
|
10
14
|
return Math.floor(Math.random() * (ceiling + 1));
|
|
11
15
|
}
|
|
12
16
|
/**
|
|
@@ -30,10 +34,10 @@ class RetryPolicy {
|
|
|
30
34
|
controller.abort();
|
|
31
35
|
}, timeoutMs);
|
|
32
36
|
try {
|
|
33
|
-
return await perform(spec, controller
|
|
37
|
+
return await perform(spec, controller);
|
|
34
38
|
}
|
|
35
39
|
catch (error) {
|
|
36
|
-
const failure = mapFailure(error, controller.signal.aborted);
|
|
40
|
+
const failure = mapFailure(error, controller.signal.aborted, HttpFailureStage.Application);
|
|
37
41
|
if (isRetriableHttpFailure(failure) && attempt < maxRetries) {
|
|
38
42
|
await delay(delayMsFor(attempt));
|
|
39
43
|
continue;
|
|
@@ -47,16 +51,23 @@ class RetryPolicy {
|
|
|
47
51
|
}
|
|
48
52
|
}
|
|
49
53
|
exports.RetryPolicy = RetryPolicy;
|
|
50
|
-
|
|
54
|
+
var HttpFailureStage;
|
|
55
|
+
(function (HttpFailureStage) {
|
|
56
|
+
HttpFailureStage[HttpFailureStage["Transport"] = 0] = "Transport";
|
|
57
|
+
HttpFailureStage[HttpFailureStage["Application"] = 1] = "Application";
|
|
58
|
+
})(HttpFailureStage || (exports.HttpFailureStage = HttpFailureStage = {}));
|
|
59
|
+
function mapFailure(error, aborted, stage) {
|
|
51
60
|
if (error instanceof framework_1.ZLinkFrameworkException) {
|
|
52
61
|
return error;
|
|
53
62
|
}
|
|
54
|
-
if (
|
|
55
|
-
|
|
63
|
+
if (stage === HttpFailureStage.Transport && aborted) {
|
|
64
|
+
const cause = new DOMException('HTTP request exceeded timeout', 'TimeoutError');
|
|
65
|
+
return new framework_1.ZLinkFrameworkException(framework_1.ZLinkFrameworkErrorKind.DeadlineExceeded, cause.message, cause);
|
|
56
66
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
67
|
+
const message = error instanceof Error ? error.message : 'HTTP execution failure';
|
|
68
|
+
return new framework_1.ZLinkFrameworkException(stage === HttpFailureStage.Transport
|
|
69
|
+
? framework_1.ZLinkFrameworkErrorKind.Unavailable
|
|
70
|
+
: framework_1.ZLinkFrameworkErrorKind.InternalFailure, message, error);
|
|
60
71
|
}
|
|
61
72
|
function isRetriableHttpFailure(error) {
|
|
62
73
|
return (error.kind === framework_1.ZLinkFrameworkErrorKind.Unavailable ||
|
package/dist/runtime/runtime.js
CHANGED
|
@@ -22,7 +22,7 @@ class HttpClientRuntime {
|
|
|
22
22
|
this.retryPolicy = new retry_policy_1.RetryPolicy(options);
|
|
23
23
|
}
|
|
24
24
|
async executeAsync(spec) {
|
|
25
|
-
return this.retryPolicy.execute(spec, (request,
|
|
25
|
+
return this.retryPolicy.execute(spec, (request, controller) => this.performer.perform(request, controller.signal));
|
|
26
26
|
}
|
|
27
27
|
async close() {
|
|
28
28
|
if (this.dispatcher !== undefined) {
|
package/dist/runtime/text.d.ts
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
export declare const HttpHeaderName: Readonly<{
|
|
2
|
+
Accept: "accept";
|
|
3
|
+
Authorization: "authorization";
|
|
4
|
+
ContentType: "content-type";
|
|
5
|
+
Location: "location";
|
|
6
|
+
SetCookie: "set-cookie";
|
|
7
|
+
UserAgent: "user-agent";
|
|
8
|
+
AcceptEncoding: "accept-encoding";
|
|
9
|
+
Cookie: "cookie";
|
|
10
|
+
ContentEncoding: "content-encoding";
|
|
11
|
+
ContentLength: "content-length";
|
|
12
|
+
}>;
|
|
13
|
+
export declare const HttpContentType: Readonly<{
|
|
14
|
+
Json: "application/json";
|
|
15
|
+
UrlEncoded: "application/x-www-form-urlencoded";
|
|
16
|
+
Multipart: "multipart/form-data";
|
|
17
|
+
}>;
|
|
1
18
|
export declare function requireNonBlank(value: string, message: string): void;
|
|
2
19
|
export declare function requirePositiveTimeout(value: number): void;
|
|
3
20
|
export declare function percentEncode(value: string): string;
|
package/dist/runtime/text.js
CHANGED
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/* SPDX-License-Identifier: Apache-2.0 */
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.HttpContentType = exports.HttpHeaderName = void 0;
|
|
4
5
|
exports.requireNonBlank = requireNonBlank;
|
|
5
6
|
exports.requirePositiveTimeout = requirePositiveTimeout;
|
|
6
7
|
exports.percentEncode = percentEncode;
|
|
7
8
|
exports.basicAuthorization = basicAuthorization;
|
|
8
9
|
exports.makeMultipartBoundary = makeMultipartBoundary;
|
|
9
10
|
const framework_1 = require("@zlink-systems/framework");
|
|
11
|
+
exports.HttpHeaderName = Object.freeze({
|
|
12
|
+
Accept: 'accept',
|
|
13
|
+
Authorization: 'authorization',
|
|
14
|
+
ContentType: 'content-type',
|
|
15
|
+
Location: 'location',
|
|
16
|
+
SetCookie: 'set-cookie',
|
|
17
|
+
UserAgent: 'user-agent',
|
|
18
|
+
AcceptEncoding: 'accept-encoding',
|
|
19
|
+
Cookie: 'cookie',
|
|
20
|
+
ContentEncoding: 'content-encoding',
|
|
21
|
+
ContentLength: 'content-length'
|
|
22
|
+
});
|
|
23
|
+
exports.HttpContentType = Object.freeze({
|
|
24
|
+
Json: 'application/json',
|
|
25
|
+
UrlEncoded: 'application/x-www-form-urlencoded',
|
|
26
|
+
Multipart: 'multipart/form-data'
|
|
27
|
+
});
|
|
10
28
|
/** Shared text helpers mirroring the C++ `client.cpp` anonymous-namespace utilities. */
|
|
11
29
|
function isBlank(value) {
|
|
12
30
|
return value.length === 0 || /^[\s]*$/u.test(value);
|
|
@@ -39,6 +57,13 @@ function percentEncode(value) {
|
|
|
39
57
|
function basicAuthorization(user, password) {
|
|
40
58
|
return 'Basic ' + Buffer.from(`${user}:${password}`, 'utf8').toString('base64');
|
|
41
59
|
}
|
|
60
|
+
const MULTIPART_BOUNDARY_PREFIX = 'zlink-boundary-';
|
|
61
|
+
const MULTIPART_BOUNDARY_ENTROPY_CHARACTERS = 16;
|
|
42
62
|
function makeMultipartBoundary() {
|
|
43
|
-
return
|
|
63
|
+
return (MULTIPART_BOUNDARY_PREFIX +
|
|
64
|
+
Math.random()
|
|
65
|
+
.toString(16)
|
|
66
|
+
.slice(2)
|
|
67
|
+
.padEnd(MULTIPART_BOUNDARY_ENTROPY_CHARACTERS, '0')
|
|
68
|
+
.slice(0, MULTIPART_BOUNDARY_ENTROPY_CHARACTERS));
|
|
44
69
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zlink-systems/http-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@zlink-systems/framework": "0.
|
|
25
|
+
"@zlink-systems/framework": "0.27.0",
|
|
26
26
|
"undici": "6.27.0"
|
|
27
27
|
}
|
|
28
28
|
}
|