arc-1 1.0.2 → 1.1.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/bin/arc1-cli.js +3 -7
- package/bin/arc1.js +4 -9
- package/dist/adt/abapgit.d.ts +10 -5
- package/dist/adt/abapgit.d.ts.map +1 -1
- package/dist/adt/abapgit.js +357 -76
- package/dist/adt/abapgit.js.map +1 -1
- package/dist/adt/atc.d.ts +49 -0
- package/dist/adt/atc.d.ts.map +1 -0
- package/dist/adt/atc.js +294 -0
- package/dist/adt/atc.js.map +1 -0
- package/dist/adt/aunit-package.d.ts +22 -0
- package/dist/adt/aunit-package.d.ts.map +1 -0
- package/dist/adt/aunit-package.js +78 -0
- package/dist/adt/aunit-package.js.map +1 -0
- package/dist/adt/aunit.d.ts +143 -0
- package/dist/adt/aunit.d.ts.map +1 -0
- package/dist/adt/aunit.js +987 -0
- package/dist/adt/aunit.js.map +1 -0
- package/dist/adt/client.d.ts +9 -7
- package/dist/adt/client.d.ts.map +1 -1
- package/dist/adt/client.js +55 -64
- package/dist/adt/client.js.map +1 -1
- package/dist/adt/config.d.ts +2 -0
- package/dist/adt/config.d.ts.map +1 -1
- package/dist/adt/config.js +1 -0
- package/dist/adt/config.js.map +1 -1
- package/dist/adt/devtools.d.ts +8 -30
- package/dist/adt/devtools.d.ts.map +1 -1
- package/dist/adt/devtools.js +41 -218
- package/dist/adt/devtools.js.map +1 -1
- package/dist/adt/diagnostics.d.ts +1 -1
- package/dist/adt/diagnostics.d.ts.map +1 -1
- package/dist/adt/diagnostics.js +12 -12
- package/dist/adt/diagnostics.js.map +1 -1
- package/dist/adt/errors.d.ts +7 -0
- package/dist/adt/errors.d.ts.map +1 -1
- package/dist/adt/errors.js +7 -0
- package/dist/adt/errors.js.map +1 -1
- package/dist/adt/features.d.ts +9 -3
- package/dist/adt/features.d.ts.map +1 -1
- package/dist/adt/features.js +40 -8
- package/dist/adt/features.js.map +1 -1
- package/dist/adt/gcts.d.ts +12 -45
- package/dist/adt/gcts.d.ts.map +1 -1
- package/dist/adt/gcts.js +299 -171
- package/dist/adt/gcts.js.map +1 -1
- package/dist/adt/http-deadline.d.ts +23 -0
- package/dist/adt/http-deadline.d.ts.map +1 -0
- package/dist/adt/http-deadline.js +106 -0
- package/dist/adt/http-deadline.js.map +1 -0
- package/dist/adt/http-wire-body.d.ts +2 -0
- package/dist/adt/http-wire-body.d.ts.map +1 -0
- package/dist/adt/http-wire-body.js +14 -0
- package/dist/adt/http-wire-body.js.map +1 -0
- package/dist/adt/http.d.ts +10 -24
- package/dist/adt/http.d.ts.map +1 -1
- package/dist/adt/http.js +89 -53
- package/dist/adt/http.js.map +1 -1
- package/dist/adt/path-safety.d.ts +16 -0
- package/dist/adt/path-safety.d.ts.map +1 -0
- package/dist/adt/path-safety.js +101 -0
- package/dist/adt/path-safety.js.map +1 -0
- package/dist/adt/semaphore.d.ts +2 -2
- package/dist/adt/semaphore.d.ts.map +1 -1
- package/dist/adt/semaphore.js +26 -9
- package/dist/adt/semaphore.js.map +1 -1
- package/dist/adt/source-diff.d.ts +10 -1
- package/dist/adt/source-diff.d.ts.map +1 -1
- package/dist/adt/source-diff.js +33 -7
- package/dist/adt/source-diff.js.map +1 -1
- package/dist/adt/text-search.d.ts +25 -0
- package/dist/adt/text-search.d.ts.map +1 -0
- package/dist/adt/text-search.js +152 -0
- package/dist/adt/text-search.js.map +1 -0
- package/dist/adt/transport-diff.d.ts +128 -0
- package/dist/adt/transport-diff.d.ts.map +1 -0
- package/dist/adt/transport-diff.js +524 -0
- package/dist/adt/transport-diff.js.map +1 -0
- package/dist/adt/transport.d.ts +78 -13
- package/dist/adt/transport.d.ts.map +1 -1
- package/dist/adt/transport.js +443 -41
- package/dist/adt/transport.js.map +1 -1
- package/dist/adt/types.d.ts +23 -12
- package/dist/adt/types.d.ts.map +1 -1
- package/dist/adt/xml-parser.d.ts +1 -8
- package/dist/adt/xml-parser.d.ts.map +1 -1
- package/dist/adt/xml-parser.js +54 -60
- package/dist/adt/xml-parser.js.map +1 -1
- package/dist/authz/policy.d.ts.map +1 -1
- package/dist/authz/policy.js +5 -2
- package/dist/authz/policy.js.map +1 -1
- package/dist/cli-args.d.ts +2 -2
- package/dist/cli-args.js +2 -2
- package/dist/cli-checks.d.ts +56 -0
- package/dist/cli-checks.d.ts.map +1 -0
- package/dist/cli-checks.js +368 -0
- package/dist/cli-checks.js.map +1 -0
- package/dist/cli.d.ts +68 -15
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +677 -297
- package/dist/cli.js.map +1 -1
- package/dist/handlers/cds-hints.d.ts.map +1 -1
- package/dist/handlers/cds-hints.js +1 -10
- package/dist/handlers/cds-hints.js.map +1 -1
- package/dist/handlers/diagnose.d.ts +4 -0
- package/dist/handlers/diagnose.d.ts.map +1 -1
- package/dist/handlers/diagnose.js +616 -9
- package/dist/handlers/diagnose.js.map +1 -1
- package/dist/handlers/dispatch.d.ts.map +1 -1
- package/dist/handlers/dispatch.js +40 -1
- package/dist/handlers/dispatch.js.map +1 -1
- package/dist/handlers/git.d.ts.map +1 -1
- package/dist/handlers/git.js +165 -84
- package/dist/handlers/git.js.map +1 -1
- package/dist/handlers/read.d.ts.map +1 -1
- package/dist/handlers/read.js +15 -0
- package/dist/handlers/read.js.map +1 -1
- package/dist/handlers/schemas.d.ts +15 -2
- package/dist/handlers/schemas.d.ts.map +1 -1
- package/dist/handlers/schemas.js +75 -6
- package/dist/handlers/schemas.js.map +1 -1
- package/dist/handlers/search.js +1 -1
- package/dist/handlers/search.js.map +1 -1
- package/dist/handlers/tools.d.ts.map +1 -1
- package/dist/handlers/tools.js +51 -42
- package/dist/handlers/tools.js.map +1 -1
- package/dist/handlers/transport.d.ts +2 -1
- package/dist/handlers/transport.d.ts.map +1 -1
- package/dist/handlers/transport.js +128 -27
- package/dist/handlers/transport.js.map +1 -1
- package/dist/handlers/write/create.d.ts.map +1 -1
- package/dist/handlers/write/create.js +11 -0
- package/dist/handlers/write/create.js.map +1 -1
- package/dist/handlers/write/update-delete.d.ts.map +1 -1
- package/dist/handlers/write/update-delete.js +33 -10
- package/dist/handlers/write/update-delete.js.map +1 -1
- package/dist/handlers/write-helpers.d.ts.map +1 -1
- package/dist/handlers/write-helpers.js +3 -2
- package/dist/handlers/write-helpers.js.map +1 -1
- package/dist/server/audit.d.ts +1 -1
- package/dist/server/audit.d.ts.map +1 -1
- package/dist/server/audit.js +228 -21
- package/dist/server/audit.js.map +1 -1
- package/dist/server/config.d.ts +16 -0
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/config.js +145 -17
- package/dist/server/config.js.map +1 -1
- package/dist/server/effective-policy-log.d.ts +2 -1
- package/dist/server/effective-policy-log.d.ts.map +1 -1
- package/dist/server/effective-policy-log.js +8 -2
- package/dist/server/effective-policy-log.js.map +1 -1
- package/dist/server/multi-target-runtime.d.ts.map +1 -1
- package/dist/server/multi-target-runtime.js +3 -0
- package/dist/server/multi-target-runtime.js.map +1 -1
- package/dist/server/multi-target-tools.d.ts.map +1 -1
- package/dist/server/multi-target-tools.js +5 -0
- package/dist/server/multi-target-tools.js.map +1 -1
- package/dist/server/server.d.ts +15 -13
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +36 -26
- package/dist/server/server.js.map +1 -1
- package/dist/server/types.d.ts +2 -0
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js +1 -0
- package/dist/server/types.js.map +1 -1
- package/package.json +13 -13
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
2
|
+
import { AdtNetworkError } from './errors.js';
|
|
3
|
+
const DEFAULT_FETCH_TIMEOUT_MS = 120_000;
|
|
4
|
+
function callerDeadlineRemaining(options) {
|
|
5
|
+
if (options?.deadline === undefined)
|
|
6
|
+
return undefined;
|
|
7
|
+
if (!Number.isFinite(options.deadline)) {
|
|
8
|
+
throw new RangeError('ADT request deadline must be a finite epoch-millisecond value.');
|
|
9
|
+
}
|
|
10
|
+
return Math.ceil(options.deadline - Date.now());
|
|
11
|
+
}
|
|
12
|
+
function requestDeadlineRemaining(options) {
|
|
13
|
+
const callerRemaining = callerDeadlineRemaining(options);
|
|
14
|
+
const configuredTimeout = options?.fetchTimeoutMs;
|
|
15
|
+
if (configuredTimeout !== undefined && (!Number.isFinite(configuredTimeout) || configuredTimeout <= 0)) {
|
|
16
|
+
throw new RangeError('ADT fetch timeout must be a positive finite millisecond value.');
|
|
17
|
+
}
|
|
18
|
+
const fetchTimeout = configuredTimeout === undefined ? DEFAULT_FETCH_TIMEOUT_MS : Math.ceil(configuredTimeout);
|
|
19
|
+
return callerRemaining === undefined ? fetchTimeout : Math.min(fetchTimeout, callerRemaining);
|
|
20
|
+
}
|
|
21
|
+
function requestTimeoutError() {
|
|
22
|
+
return new DOMException('The ADT request deadline was exceeded.', 'TimeoutError');
|
|
23
|
+
}
|
|
24
|
+
function requestAbortError(signal) {
|
|
25
|
+
const cause = signal.reason instanceof Error ? signal.reason : new Error('The ADT request was aborted.');
|
|
26
|
+
return new AdtNetworkError(cause.message, cause);
|
|
27
|
+
}
|
|
28
|
+
export function throwIfRequestCancelled(options) {
|
|
29
|
+
if (options?.signal?.aborted) {
|
|
30
|
+
throw requestAbortError(options.signal);
|
|
31
|
+
}
|
|
32
|
+
const callerRemaining = callerDeadlineRemaining(options);
|
|
33
|
+
if (callerRemaining !== undefined && callerRemaining <= 0) {
|
|
34
|
+
const cause = requestTimeoutError();
|
|
35
|
+
throw new AdtNetworkError(cause.message, cause);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Caller-owned cancellation only (no built-in fetch timeout). */
|
|
39
|
+
export function requestBudgetSignal(options) {
|
|
40
|
+
throwIfRequestCancelled(options);
|
|
41
|
+
const signals = [];
|
|
42
|
+
if (options?.signal)
|
|
43
|
+
signals.push(options.signal);
|
|
44
|
+
const callerRemaining = callerDeadlineRemaining(options);
|
|
45
|
+
if (callerRemaining !== undefined)
|
|
46
|
+
signals.push(AbortSignal.timeout(Math.max(1, callerRemaining)));
|
|
47
|
+
if (signals.length === 0)
|
|
48
|
+
return undefined;
|
|
49
|
+
return signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
50
|
+
}
|
|
51
|
+
/** Await a non-fetch dependency without leaving an unhandled rejection after cancellation. */
|
|
52
|
+
export async function awaitWithRequestSignal(operation, signal) {
|
|
53
|
+
if (!signal)
|
|
54
|
+
return operation;
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
let settled = false;
|
|
57
|
+
const finish = (fn) => {
|
|
58
|
+
if (settled)
|
|
59
|
+
return;
|
|
60
|
+
settled = true;
|
|
61
|
+
signal.removeEventListener('abort', onAbort);
|
|
62
|
+
fn();
|
|
63
|
+
};
|
|
64
|
+
const onAbort = () => finish(() => reject(requestAbortError(signal)));
|
|
65
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
66
|
+
operation.then((value) => finish(() => resolve(value)), (error) => finish(() => reject(error)));
|
|
67
|
+
if (signal.aborted)
|
|
68
|
+
onAbort();
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
export function awaitWithinRequestBudget(operation, options) {
|
|
72
|
+
try {
|
|
73
|
+
return awaitWithRequestSignal(operation, requestBudgetSignal(options));
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
// `operation` was already created by the caller. Observe any later rejection
|
|
77
|
+
// even when the deadline expires while a provider is returning its promise.
|
|
78
|
+
void operation.catch(() => undefined);
|
|
79
|
+
return Promise.reject(error);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Compose the built-in per-fetch timeout with the caller's signal/deadline. */
|
|
83
|
+
export function requestSignal(options) {
|
|
84
|
+
throwIfRequestCancelled(options);
|
|
85
|
+
const signals = [AbortSignal.timeout(requestDeadlineRemaining(options))];
|
|
86
|
+
if (options?.signal)
|
|
87
|
+
signals.unshift(options.signal);
|
|
88
|
+
return signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
89
|
+
}
|
|
90
|
+
/** Retry delays consume the same caller budget as their surrounding HTTP request. */
|
|
91
|
+
export async function sleepWithinRequestBudget(delayMs, options) {
|
|
92
|
+
if (delayMs <= 0) {
|
|
93
|
+
throwIfRequestCancelled(options);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const signal = requestSignal(options);
|
|
97
|
+
try {
|
|
98
|
+
await sleep(delayMs, undefined, { signal });
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (signal.aborted)
|
|
102
|
+
throw signal.reason ?? requestTimeoutError();
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=http-deadline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-deadline.js","sourceRoot":"","sources":["../../src/adt/http-deadline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-wire-body.d.ts","sourceRoot":"","sources":["../../src/adt/http-wire-body.ts"],"names":[],"mappings":"AAIA,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,MAAM,GAAG,MAAM,GAAG,SAAS,CAS7B"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { gzipSync } from 'node:zlib';
|
|
2
|
+
const GZIP_DATA_PREVIEW_PATHS = new Set(['/sap/bc/adt/datapreview/freestyle', '/sap/bc/adt/datapreview/ddic']);
|
|
3
|
+
export function prepareDataPreviewWireBody(enabled, method, path, body, headers) {
|
|
4
|
+
const collectionPath = path.split(/[?#]/, 1)[0] ?? '';
|
|
5
|
+
if (!enabled || method !== 'POST' || !body || !GZIP_DATA_PREVIEW_PATHS.has(collectionPath))
|
|
6
|
+
return body;
|
|
7
|
+
for (const key of Object.keys(headers)) {
|
|
8
|
+
if (key.toLowerCase() === 'content-encoding')
|
|
9
|
+
delete headers[key];
|
|
10
|
+
}
|
|
11
|
+
headers['Content-Encoding'] = 'gzip';
|
|
12
|
+
return gzipSync(body);
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=http-wire-body.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-wire-body.js","sourceRoot":"","sources":["../../src/adt/http-wire-body.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,MAAM,uBAAuB,GAAG,IAAI,GAAG,CAAC,CAAC,mCAAmC,EAAE,8BAA8B,CAAC,CAAC,CAAC;AAE/G,MAAM,UAAU,0BAA0B,CACxC,OAAgB,EAChB,MAAc,EACd,IAAY,EACZ,IAAwB,EACxB,OAA+B;IAE/B,MAAM,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACtD,IAAI,CAAC,OAAO,IAAI,MAAM,KAAK,MAAM,IAAI,CAAC,IAAI,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,cAAc,CAAC;QAAE,OAAO,IAAI,CAAC;IAExG,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,IAAI,GAAG,CAAC,WAAW,EAAE,KAAK,kBAAkB;YAAE,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,CAAC,kBAAkB,CAAC,GAAG,MAAM,CAAC;IACrC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC;AACxB,CAAC"}
|
package/dist/adt/http.d.ts
CHANGED
|
@@ -27,25 +27,9 @@
|
|
|
27
27
|
* No external HTTP dependencies — undici ships with Node.js 22+.
|
|
28
28
|
*/
|
|
29
29
|
import type { BTPProxyConfig } from '@arc-mcp/xsuaa-auth/btp';
|
|
30
|
+
import { type AdtRequestOptions } from './http-deadline.js';
|
|
30
31
|
import type { Semaphore } from './semaphore.js';
|
|
31
|
-
export
|
|
32
|
-
/**
|
|
33
|
-
* Some ADT subresources are optional and legitimately return 404, for example
|
|
34
|
-
* class local includes such as testclasses on classes without ABAP Unit code.
|
|
35
|
-
* Callers that handle those 404s can opt into debug-level audit logging.
|
|
36
|
-
*/
|
|
37
|
-
suppressNotFoundLog?: boolean;
|
|
38
|
-
/**
|
|
39
|
-
* Startup capability/discovery probe. ANY non-2xx is expected here (the feature
|
|
40
|
-
* is simply absent/not activated, or the endpoint intentionally 400s without
|
|
41
|
-
* query params, e.g. the RAP probe on `/ddic/ddl/sources`). The outcome is
|
|
42
|
-
* captured as structured data and re-reported at a higher layer (feature map,
|
|
43
|
-
* the `Authorization probe: …` lines, the contextual discovery warn), so the
|
|
44
|
-
* raw `http_request` failure is logged at debug instead of warn to keep a
|
|
45
|
-
* healthy startup quiet. Unlike `suppressNotFoundLog` this is not limited to 404.
|
|
46
|
-
*/
|
|
47
|
-
probe?: boolean;
|
|
48
|
-
}
|
|
32
|
+
export type { AdtRequestOptions } from './http-deadline.js';
|
|
49
33
|
/** Session type for ADT requests */
|
|
50
34
|
export type SessionType = 'stateful' | 'stateless' | undefined;
|
|
51
35
|
/** Configuration for the ADT HTTP client */
|
|
@@ -56,6 +40,8 @@ export interface AdtHttpConfig {
|
|
|
56
40
|
client?: string;
|
|
57
41
|
language?: string;
|
|
58
42
|
insecure?: boolean;
|
|
43
|
+
/** Gzip non-empty data-preview POST bodies for approved WAF compatibility. */
|
|
44
|
+
gzipDataPreviewBody?: boolean;
|
|
59
45
|
cookies?: Record<string, string>;
|
|
60
46
|
/** Path to cookie file — enables hot-reload on stale auth */
|
|
61
47
|
cookieFile?: string;
|
|
@@ -119,6 +105,7 @@ export declare class AdtHttpClient {
|
|
|
119
105
|
private negotiatedHeaders;
|
|
120
106
|
private csrfToken;
|
|
121
107
|
private dispatcher;
|
|
108
|
+
private longOperationDispatcher;
|
|
122
109
|
private config;
|
|
123
110
|
/**
|
|
124
111
|
* Cookie jar — stores Set-Cookie headers from responses and sends them back.
|
|
@@ -152,13 +139,13 @@ export declare class AdtHttpClient {
|
|
|
152
139
|
/** GET request */
|
|
153
140
|
get(path: string, headers?: Record<string, string>, options?: AdtRequestOptions): Promise<AdtResponse>;
|
|
154
141
|
/** HEAD request — lightweight probe, no response body */
|
|
155
|
-
head(path: string, headers?: Record<string, string
|
|
142
|
+
head(path: string, headers?: Record<string, string>, options?: AdtRequestOptions): Promise<AdtResponse>;
|
|
156
143
|
/** POST request (includes CSRF token) */
|
|
157
|
-
post(path: string, body?: string, contentType?: string, headers?: Record<string, string
|
|
144
|
+
post(path: string, body?: string, contentType?: string, headers?: Record<string, string>, options?: AdtRequestOptions): Promise<AdtResponse>;
|
|
158
145
|
/** PUT request (includes CSRF token) */
|
|
159
|
-
put(path: string, body: string, contentType?: string, headers?: Record<string, string
|
|
146
|
+
put(path: string, body: string, contentType?: string, headers?: Record<string, string>, options?: AdtRequestOptions): Promise<AdtResponse>;
|
|
160
147
|
/** DELETE request (includes CSRF token) */
|
|
161
|
-
delete(path: string, headers?: Record<string, string
|
|
148
|
+
delete(path: string, headers?: Record<string, string>, options?: AdtRequestOptions): Promise<AdtResponse>;
|
|
162
149
|
/**
|
|
163
150
|
* Execute a function within an isolated stateful session.
|
|
164
151
|
* Ensures lock/modify/unlock share the same SAP session cookies.
|
|
@@ -181,7 +168,7 @@ export declare class AdtHttpClient {
|
|
|
181
168
|
* Fetch CSRF token from SAP.
|
|
182
169
|
* Uses HEAD /sap/bc/adt/core/discovery for speed.
|
|
183
170
|
*/
|
|
184
|
-
fetchCsrfToken(): Promise<void>;
|
|
171
|
+
fetchCsrfToken(options?: AdtRequestOptions): Promise<void>;
|
|
185
172
|
/**
|
|
186
173
|
* Detect "database connection is not open" error from SAP.
|
|
187
174
|
* This happens when a work process loses its HANA DB connection.
|
|
@@ -304,5 +291,4 @@ export declare function parseRetryAfter(header: string | null | undefined, fallb
|
|
|
304
291
|
delayMs: number;
|
|
305
292
|
source: 'header' | 'fallback';
|
|
306
293
|
};
|
|
307
|
-
export {};
|
|
308
294
|
//# sourceMappingURL=http.d.ts.map
|
package/dist/adt/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/adt/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAQ9D,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/adt/http.ts"],"names":[],"mappings":"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"}
|
package/dist/adt/http.js
CHANGED
|
@@ -33,6 +33,8 @@ import { traceHeaders } from '../server/trace-context.js';
|
|
|
33
33
|
import { resolveCookies } from './cookies.js';
|
|
34
34
|
import { resolveAcceptType, resolveContentType } from './discovery.js';
|
|
35
35
|
import { AdtApiError, AdtNetworkError } from './errors.js';
|
|
36
|
+
import { awaitWithinRequestBudget, requestBudgetSignal, requestSignal, sleepWithinRequestBudget, throwIfRequestCancelled, } from './http-deadline.js';
|
|
37
|
+
import { prepareDataPreviewWireBody } from './http-wire-body.js';
|
|
36
38
|
/**
|
|
37
39
|
* Opt-in wire-level debug logging.
|
|
38
40
|
*
|
|
@@ -95,6 +97,7 @@ export class AdtHttpClient {
|
|
|
95
97
|
negotiatedHeaders = new Map();
|
|
96
98
|
csrfToken = '';
|
|
97
99
|
dispatcher;
|
|
100
|
+
longOperationDispatcher;
|
|
98
101
|
config;
|
|
99
102
|
/**
|
|
100
103
|
* Cookie jar — stores Set-Cookie headers from responses and sends them back.
|
|
@@ -122,7 +125,7 @@ export class AdtHttpClient {
|
|
|
122
125
|
// (see doProxyRequest()) — undici 8.x ProxyAgent always uses CONNECT tunneling,
|
|
123
126
|
// which BTP's connectivity proxy doesn't support (HTTP 405).
|
|
124
127
|
if (!config.btpProxy && config.insecure) {
|
|
125
|
-
this.dispatcher = new Agent({ connect: { rejectUnauthorized: false } });
|
|
128
|
+
this.dispatcher = new Agent({ connect: { rejectUnauthorized: false }, headersTimeout: 0, bodyTimeout: 0 });
|
|
126
129
|
}
|
|
127
130
|
}
|
|
128
131
|
/** Inject startup discovery data used for proactive MIME negotiation. */
|
|
@@ -148,20 +151,20 @@ export class AdtHttpClient {
|
|
|
148
151
|
return this.request('GET', path, undefined, undefined, headers, options);
|
|
149
152
|
}
|
|
150
153
|
/** HEAD request — lightweight probe, no response body */
|
|
151
|
-
async head(path, headers) {
|
|
152
|
-
return this.request('HEAD', path, undefined, undefined, headers);
|
|
154
|
+
async head(path, headers, options) {
|
|
155
|
+
return this.request('HEAD', path, undefined, undefined, headers, options);
|
|
153
156
|
}
|
|
154
157
|
/** POST request (includes CSRF token) */
|
|
155
|
-
async post(path, body, contentType, headers) {
|
|
156
|
-
return this.request('POST', path, body, contentType, headers);
|
|
158
|
+
async post(path, body, contentType, headers, options) {
|
|
159
|
+
return this.request('POST', path, body, contentType, headers, options);
|
|
157
160
|
}
|
|
158
161
|
/** PUT request (includes CSRF token) */
|
|
159
|
-
async put(path, body, contentType, headers) {
|
|
160
|
-
return this.request('PUT', path, body, contentType, headers);
|
|
162
|
+
async put(path, body, contentType, headers, options) {
|
|
163
|
+
return this.request('PUT', path, body, contentType, headers, options);
|
|
161
164
|
}
|
|
162
165
|
/** DELETE request (includes CSRF token) */
|
|
163
|
-
async delete(path, headers) {
|
|
164
|
-
return this.request('DELETE', path, undefined, undefined, headers);
|
|
166
|
+
async delete(path, headers, options) {
|
|
167
|
+
return this.request('DELETE', path, undefined, undefined, headers, options);
|
|
165
168
|
}
|
|
166
169
|
/**
|
|
167
170
|
* Execute a function within an isolated stateful session.
|
|
@@ -185,43 +188,60 @@ export class AdtHttpClient {
|
|
|
185
188
|
}
|
|
186
189
|
/** Core request method — wraps requestInner with optional concurrency limiter */
|
|
187
190
|
async request(method, path, body, contentType, extraHeaders, options) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
+
throwIfRequestCancelled(options);
|
|
192
|
+
const execute = async () => {
|
|
193
|
+
try {
|
|
194
|
+
if (this.config.semaphore) {
|
|
195
|
+
return await this.config.semaphore.run(() => this.requestInner(method, path, body, contentType, extraHeaders, options), requestBudgetSignal(options));
|
|
196
|
+
}
|
|
197
|
+
return await this.requestInner(method, path, body, contentType, extraHeaders, options);
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
if (error instanceof AdtApiError || error instanceof AdtNetworkError)
|
|
201
|
+
throw error;
|
|
202
|
+
if (!options?.signal && options?.deadline === undefined)
|
|
203
|
+
throw error;
|
|
204
|
+
const cause = error instanceof Error ? error : new Error(String(error));
|
|
205
|
+
throw new AdtNetworkError(cause.message, cause);
|
|
191
206
|
}
|
|
192
|
-
return this.requestInner(method, path, body, contentType, extraHeaders, options);
|
|
193
207
|
};
|
|
194
208
|
// Shared Basic clients disable 401 retries. Serialize their internal HTTP fan-out as well:
|
|
195
209
|
// feature probes and compound handlers can otherwise start several requests with the same
|
|
196
210
|
// expired password before the first 401 is observed. Once rejected, later requests on this
|
|
197
211
|
// per-call client fail locally and never contact SAP.
|
|
198
212
|
if (this.config.retryUnauthorized === false) {
|
|
199
|
-
return this.runSerializedAuthenticationAttempt(path, execute);
|
|
213
|
+
return this.runSerializedAuthenticationAttempt(path, execute, options);
|
|
200
214
|
}
|
|
201
215
|
return execute();
|
|
202
216
|
}
|
|
203
|
-
async runSerializedAuthenticationAttempt(path, execute) {
|
|
217
|
+
async runSerializedAuthenticationAttempt(path, execute, options) {
|
|
204
218
|
const previous = this.authenticationAttemptState.tail;
|
|
205
219
|
let release;
|
|
206
220
|
this.authenticationAttemptState.tail = new Promise((resolve) => {
|
|
207
221
|
release = resolve;
|
|
208
222
|
});
|
|
209
|
-
|
|
223
|
+
let turnStarted = false;
|
|
210
224
|
try {
|
|
225
|
+
await awaitWithinRequestBudget(previous, options);
|
|
226
|
+
turnStarted = true;
|
|
211
227
|
if (this.authenticationAttemptState.rejected) {
|
|
212
228
|
throw new AdtApiError('Shared Basic authentication was already rejected for this request.', 401, path);
|
|
213
229
|
}
|
|
214
230
|
return await execute();
|
|
215
231
|
}
|
|
216
232
|
finally {
|
|
217
|
-
|
|
233
|
+
if (turnStarted)
|
|
234
|
+
release();
|
|
235
|
+
else
|
|
236
|
+
void previous.then(release, release);
|
|
218
237
|
}
|
|
219
238
|
}
|
|
220
239
|
/** Inner request method — CSRF, retries, content negotiation */
|
|
221
240
|
async requestInner(method, path, body, contentType, extraHeaders, options) {
|
|
241
|
+
throwIfRequestCancelled(options);
|
|
222
242
|
// Auto-fetch CSRF token for modifying requests
|
|
223
243
|
if (isModifyingMethod(method) && !this.csrfToken) {
|
|
224
|
-
await this.fetchCsrfToken();
|
|
244
|
+
await this.fetchCsrfToken(options);
|
|
225
245
|
}
|
|
226
246
|
const headers = { Accept: '*/*' };
|
|
227
247
|
const negotiationKey = this.normalizeHeaderCacheKey(path);
|
|
@@ -253,19 +273,19 @@ export class AdtHttpClient {
|
|
|
253
273
|
if (this.config.disableSaml) {
|
|
254
274
|
headers['X-SAP-SAML2'] = 'disabled';
|
|
255
275
|
}
|
|
256
|
-
if (isModifyingMethod(method)) {
|
|
257
|
-
headers['X-CSRF-Token'] = this.csrfToken;
|
|
258
|
-
}
|
|
259
276
|
if (this.config.sessionType === 'stateful') {
|
|
260
277
|
headers['X-sap-adt-sessiontype'] = 'stateful';
|
|
261
278
|
}
|
|
262
279
|
if (contentType) {
|
|
263
280
|
headers['Content-Type'] = contentType;
|
|
264
281
|
}
|
|
282
|
+
// Keep `body` as the logical plaintext for debug audit fields; only `wireBody` is sent.
|
|
283
|
+
const wireBody = prepareDataPreviewWireBody(this.config.gzipDataPreviewBody === true, method, path, body, headers);
|
|
265
284
|
// Auth: Bearer token (BTP ABAP) or Basic Auth (on-premise)
|
|
266
285
|
this.applyAuthHeader(headers);
|
|
267
286
|
if (this.config.bearerTokenProvider) {
|
|
268
|
-
|
|
287
|
+
throwIfRequestCancelled(options);
|
|
288
|
+
const token = await awaitWithinRequestBudget(this.config.bearerTokenProvider(), options);
|
|
269
289
|
headers.Authorization = `Bearer ${token}`;
|
|
270
290
|
}
|
|
271
291
|
// Lazy cookie reload: if cookies were cleared on a previous 401,
|
|
@@ -273,6 +293,12 @@ export class AdtHttpClient {
|
|
|
273
293
|
if (this.cookiesCleared && this.isCookieAuthMode()) {
|
|
274
294
|
this.reloadCookiesFromSource();
|
|
275
295
|
}
|
|
296
|
+
// Read the CSRF token together with the cookie header below — SAP binds the token to the
|
|
297
|
+
// session cookie, and a concurrent fetchCsrfToken() can replace both across the bearer
|
|
298
|
+
// await above; a torn pair 403s. The retry paths below already read them adjacently.
|
|
299
|
+
if (isModifyingMethod(method)) {
|
|
300
|
+
headers['X-CSRF-Token'] = this.csrfToken;
|
|
301
|
+
}
|
|
276
302
|
// Build cookie header from config cookies + cookie jar, with the jar winning
|
|
277
303
|
// on a name collision (see composeCookieHeader — issue #293).
|
|
278
304
|
const cookieHeader = this.composeCookieHeader();
|
|
@@ -297,7 +323,7 @@ export class AdtHttpClient {
|
|
|
297
323
|
let authRetried = false;
|
|
298
324
|
let retried429 = false;
|
|
299
325
|
try {
|
|
300
|
-
let response = await this.doFetch(url, method, headers,
|
|
326
|
+
let response = await this.doFetch(url, method, headers, wireBody, options);
|
|
301
327
|
let responseBody = await response.text();
|
|
302
328
|
// Persist any Set-Cookie headers from the response
|
|
303
329
|
this.storeCookies(response);
|
|
@@ -323,7 +349,7 @@ export class AdtHttpClient {
|
|
|
323
349
|
this.resetSession();
|
|
324
350
|
// Re-fetch CSRF token (needed for modifying requests, harmless for reads)
|
|
325
351
|
if (isModifyingMethod(method)) {
|
|
326
|
-
await this.fetchCsrfToken();
|
|
352
|
+
await this.fetchCsrfToken(options);
|
|
327
353
|
headers['X-CSRF-Token'] = this.csrfToken;
|
|
328
354
|
}
|
|
329
355
|
// Rebuild cookie header from fresh jar
|
|
@@ -337,7 +363,7 @@ export class AdtHttpClient {
|
|
|
337
363
|
else {
|
|
338
364
|
delete headers.Cookie;
|
|
339
365
|
}
|
|
340
|
-
const retryResp = await this.doFetch(url, method, headers,
|
|
366
|
+
const retryResp = await this.doFetch(url, method, headers, wireBody, options);
|
|
341
367
|
const retryBody = await retryResp.text();
|
|
342
368
|
this.storeCookies(retryResp);
|
|
343
369
|
const retryResult = this.handleResponse(retryResp.status, retryResp.headers, retryBody, path);
|
|
@@ -374,8 +400,8 @@ export class AdtHttpClient {
|
|
|
374
400
|
durationMs: Date.now() - httpStart,
|
|
375
401
|
errorBody: `503 Service Unavailable — retrying in ${Math.round(jitterMs)}ms (${source})`,
|
|
376
402
|
});
|
|
377
|
-
await
|
|
378
|
-
const retryResp = await this.doFetch(url, method, headers,
|
|
403
|
+
await sleepWithinRequestBudget(jitterMs, options);
|
|
404
|
+
const retryResp = await this.doFetch(url, method, headers, wireBody, options);
|
|
379
405
|
const retryBody = await retryResp.text();
|
|
380
406
|
this.storeCookies(retryResp);
|
|
381
407
|
logger.emitAudit({
|
|
@@ -410,8 +436,8 @@ export class AdtHttpClient {
|
|
|
410
436
|
durationMs: Date.now() - httpStart,
|
|
411
437
|
errorBody: `429 Too Many Requests — retrying in ${Math.round(jitterMs)}ms (${source})`,
|
|
412
438
|
});
|
|
413
|
-
await
|
|
414
|
-
const retryResp = await this.doFetch(url, method, headers,
|
|
439
|
+
await sleepWithinRequestBudget(jitterMs, options);
|
|
440
|
+
const retryResp = await this.doFetch(url, method, headers, wireBody, options);
|
|
415
441
|
const retryBody = await retryResp.text();
|
|
416
442
|
this.storeCookies(retryResp);
|
|
417
443
|
logger.emitAudit({
|
|
@@ -454,12 +480,13 @@ export class AdtHttpClient {
|
|
|
454
480
|
// Re-apply auth credentials
|
|
455
481
|
this.applyAuthHeader(headers);
|
|
456
482
|
if (this.config.bearerTokenProvider) {
|
|
457
|
-
|
|
483
|
+
throwIfRequestCancelled(options);
|
|
484
|
+
const token = await awaitWithinRequestBudget(this.config.bearerTokenProvider(), options);
|
|
458
485
|
headers.Authorization = `Bearer ${token}`;
|
|
459
486
|
}
|
|
460
487
|
// Re-fetch CSRF token for modifying requests
|
|
461
488
|
if (isModifyingMethod(method)) {
|
|
462
|
-
await this.fetchCsrfToken();
|
|
489
|
+
await this.fetchCsrfToken(options);
|
|
463
490
|
headers['X-CSRF-Token'] = this.csrfToken;
|
|
464
491
|
}
|
|
465
492
|
// Rebuild cookie header from config cookies + fresh jar (jar wins).
|
|
@@ -470,7 +497,7 @@ export class AdtHttpClient {
|
|
|
470
497
|
else {
|
|
471
498
|
delete headers.Cookie;
|
|
472
499
|
}
|
|
473
|
-
response = await this.doFetch(url, method, headers,
|
|
500
|
+
response = await this.doFetch(url, method, headers, wireBody, options);
|
|
474
501
|
responseBody = await response.text();
|
|
475
502
|
this.storeCookies(response);
|
|
476
503
|
logger.emitAudit({
|
|
@@ -492,7 +519,7 @@ export class AdtHttpClient {
|
|
|
492
519
|
}
|
|
493
520
|
// Handle CSRF token refresh on 403 (modifying requests only)
|
|
494
521
|
if (response.status === 403 && isModifyingMethod(method)) {
|
|
495
|
-
await this.fetchCsrfToken();
|
|
522
|
+
await this.fetchCsrfToken(options);
|
|
496
523
|
headers['X-CSRF-Token'] = this.csrfToken;
|
|
497
524
|
// Update cookie header after CSRF fetch may have set new cookies. Use the
|
|
498
525
|
// merged builder (jar wins) so auth cookies in config.cookies (e.g.
|
|
@@ -501,7 +528,7 @@ export class AdtHttpClient {
|
|
|
501
528
|
if (csrfRefreshedCookieHeader) {
|
|
502
529
|
headers.Cookie = csrfRefreshedCookieHeader;
|
|
503
530
|
}
|
|
504
|
-
const retryResponse = await this.doFetch(url, method, headers,
|
|
531
|
+
const retryResponse = await this.doFetch(url, method, headers, wireBody, options);
|
|
505
532
|
const retryBody = await retryResponse.text();
|
|
506
533
|
this.storeCookies(retryResponse);
|
|
507
534
|
const result = this.handleResponse(retryResponse.status, retryResponse.headers, retryBody, path);
|
|
@@ -570,7 +597,7 @@ export class AdtHttpClient {
|
|
|
570
597
|
durationMs: Date.now() - httpStart,
|
|
571
598
|
errorBody: `Content negotiation ${response.status} — retrying with fallback headers`,
|
|
572
599
|
});
|
|
573
|
-
const retryResp = await this.doFetch(url, method, fallbackHeaders,
|
|
600
|
+
const retryResp = await this.doFetch(url, method, fallbackHeaders, wireBody, options);
|
|
574
601
|
const retryBody = await retryResp.text();
|
|
575
602
|
this.storeCookies(retryResp);
|
|
576
603
|
// Store CSRF token from retry response
|
|
@@ -657,6 +684,8 @@ export class AdtHttpClient {
|
|
|
657
684
|
});
|
|
658
685
|
throw err;
|
|
659
686
|
}
|
|
687
|
+
if (err instanceof AdtNetworkError)
|
|
688
|
+
throw err;
|
|
660
689
|
const message = err instanceof Error ? err.message : String(err);
|
|
661
690
|
throw new AdtNetworkError(message, err instanceof Error ? err : undefined);
|
|
662
691
|
}
|
|
@@ -729,7 +758,8 @@ export class AdtHttpClient {
|
|
|
729
758
|
* Fetch CSRF token from SAP.
|
|
730
759
|
* Uses HEAD /sap/bc/adt/core/discovery for speed.
|
|
731
760
|
*/
|
|
732
|
-
async fetchCsrfToken() {
|
|
761
|
+
async fetchCsrfToken(options) {
|
|
762
|
+
throwIfRequestCancelled(options);
|
|
733
763
|
const url = this.buildUrl('/sap/bc/adt/core/discovery');
|
|
734
764
|
const headers = {
|
|
735
765
|
'X-CSRF-Token': 'fetch',
|
|
@@ -744,7 +774,8 @@ export class AdtHttpClient {
|
|
|
744
774
|
// Auth: Bearer token (BTP ABAP) or Basic Auth (on-premise)
|
|
745
775
|
this.applyAuthHeader(headers);
|
|
746
776
|
if (this.config.bearerTokenProvider) {
|
|
747
|
-
|
|
777
|
+
throwIfRequestCancelled(options);
|
|
778
|
+
const token = await awaitWithinRequestBudget(this.config.bearerTokenProvider(), options);
|
|
748
779
|
headers.Authorization = `Bearer ${token}`;
|
|
749
780
|
}
|
|
750
781
|
// Principal Propagation via SAP-Connectivity-Authentication header (Option 2).
|
|
@@ -765,7 +796,7 @@ export class AdtHttpClient {
|
|
|
765
796
|
headers.Cookie = cookieHeader;
|
|
766
797
|
}
|
|
767
798
|
try {
|
|
768
|
-
let response = await this.doFetch(url, 'HEAD', headers);
|
|
799
|
+
let response = await this.doFetch(url, 'HEAD', headers, undefined, options);
|
|
769
800
|
// Retry once on 503 — ICM may be temporarily overloaded (thread/MPI exhaustion)
|
|
770
801
|
if (response.status === 503) {
|
|
771
802
|
const jitterMs = 1000 + Math.random() * 1000;
|
|
@@ -779,8 +810,8 @@ export class AdtHttpClient {
|
|
|
779
810
|
durationMs: 0,
|
|
780
811
|
errorBody: `CSRF fetch got 503 — retrying in ${Math.round(jitterMs)}ms`,
|
|
781
812
|
});
|
|
782
|
-
await
|
|
783
|
-
response = await this.doFetch(url, 'HEAD', headers);
|
|
813
|
+
await sleepWithinRequestBudget(jitterMs, options);
|
|
814
|
+
response = await this.doFetch(url, 'HEAD', headers, undefined, options);
|
|
784
815
|
}
|
|
785
816
|
// Preserve any session established by HEAD before deciding whether GET is needed.
|
|
786
817
|
// The fallback request must use the same SAP session as the eventual write.
|
|
@@ -810,7 +841,7 @@ export class AdtHttpClient {
|
|
|
810
841
|
? 'CSRF HEAD returned 403 — retrying with GET (S/4HANA Public Cloud compat)'
|
|
811
842
|
: 'CSRF HEAD returned no usable token — retrying with GET',
|
|
812
843
|
});
|
|
813
|
-
response = await this.doFetch(url, 'GET', headers);
|
|
844
|
+
response = await this.doFetch(url, 'GET', headers, undefined, options);
|
|
814
845
|
}
|
|
815
846
|
// Store cookies from the final CSRF response — critical for session correlation.
|
|
816
847
|
this.storeCookies(response);
|
|
@@ -831,7 +862,7 @@ export class AdtHttpClient {
|
|
|
831
862
|
this.csrfToken = token;
|
|
832
863
|
}
|
|
833
864
|
catch (err) {
|
|
834
|
-
if (err instanceof AdtApiError)
|
|
865
|
+
if (err instanceof AdtApiError || err instanceof AdtNetworkError)
|
|
835
866
|
throw err;
|
|
836
867
|
const message = err instanceof Error ? err.message : String(err);
|
|
837
868
|
throw new AdtNetworkError(`CSRF token fetch failed: ${message}`, err instanceof Error ? err : undefined);
|
|
@@ -1018,22 +1049,27 @@ export class AdtHttpClient {
|
|
|
1018
1049
|
* Node 22's built-in fetch embeds an older undici version whose dispatcher
|
|
1019
1050
|
* interface is incompatible with npm undici@8 Agent/ProxyAgent instances.
|
|
1020
1051
|
*/
|
|
1021
|
-
async doFetch(url, method, headers, body) {
|
|
1052
|
+
async doFetch(url, method, headers, body, options) {
|
|
1053
|
+
throwIfRequestCancelled(options);
|
|
1022
1054
|
// Forward the caller's W3C trace context to SAP so one trace spans agent → ARC-1 → SAP.
|
|
1023
1055
|
// Empty unless the MCP client sent a valid `traceparent`; ARC-1 never originates a trace.
|
|
1024
1056
|
// Injected here because doFetch is the single outbound choke point (the proxy branch below
|
|
1025
1057
|
// spreads these headers too).
|
|
1026
1058
|
const outbound = { ...headers, ...traceHeaders(getCurrentContext()) };
|
|
1027
|
-
// BTP Connectivity proxy: use standard HTTP proxy protocol (not CONNECT)
|
|
1028
1059
|
if (this.config.btpProxy) {
|
|
1029
|
-
return this.doProxyRequest(url, method, outbound, body);
|
|
1060
|
+
return this.doProxyRequest(url, method, outbound, body, options);
|
|
1030
1061
|
}
|
|
1062
|
+
// Let the explicit operation budget override undici's 300-second header timeout.
|
|
1063
|
+
const dispatcher = this.dispatcher ??
|
|
1064
|
+
(options?.fetchTimeoutMs === undefined
|
|
1065
|
+
? undefined
|
|
1066
|
+
: (this.longOperationDispatcher ??= new Agent({ headersTimeout: 0, bodyTimeout: 0 })));
|
|
1031
1067
|
return undiciFetch(url, {
|
|
1032
1068
|
method,
|
|
1033
1069
|
headers: outbound,
|
|
1034
1070
|
body,
|
|
1035
|
-
signal:
|
|
1036
|
-
...(
|
|
1071
|
+
signal: requestSignal(options),
|
|
1072
|
+
...(dispatcher ? { dispatcher } : {}),
|
|
1037
1073
|
});
|
|
1038
1074
|
}
|
|
1039
1075
|
/**
|
|
@@ -1053,19 +1089,18 @@ export class AdtHttpClient {
|
|
|
1053
1089
|
* to the proxy, and by setting the `path` to the full target URL, the proxy
|
|
1054
1090
|
* forwards it to the Cloud Connector → on-premise SAP system.
|
|
1055
1091
|
*/
|
|
1056
|
-
async doProxyRequest(url, method, headers, body) {
|
|
1092
|
+
async doProxyRequest(url, method, headers, body, options) {
|
|
1057
1093
|
const proxy = this.config.btpProxy;
|
|
1058
1094
|
const proxyOrigin = `${proxy.protocol}://${proxy.host}:${proxy.port}`;
|
|
1059
|
-
// Get proxy auth token
|
|
1060
1095
|
let proxyAuth;
|
|
1096
|
+
throwIfRequestCancelled(options);
|
|
1061
1097
|
if (this.config.ppProxyAuth) {
|
|
1062
1098
|
proxyAuth = this.config.ppProxyAuth;
|
|
1063
1099
|
}
|
|
1064
1100
|
else {
|
|
1065
|
-
const proxyToken = await proxy.getProxyToken();
|
|
1101
|
+
const proxyToken = await awaitWithinRequestBudget(proxy.getProxyToken(), options);
|
|
1066
1102
|
proxyAuth = `Bearer ${proxyToken}`;
|
|
1067
1103
|
}
|
|
1068
|
-
// Extract host from target URL for the Host header
|
|
1069
1104
|
const targetUrl = new URL(url);
|
|
1070
1105
|
const hostHeader = targetUrl.port ? `${targetUrl.hostname}:${targetUrl.port}` : targetUrl.hostname;
|
|
1071
1106
|
// Merge proxy headers with request headers
|
|
@@ -1079,7 +1114,8 @@ export class AdtHttpClient {
|
|
|
1079
1114
|
if (proxy.locationId) {
|
|
1080
1115
|
proxyHeaders['SAP-Connectivity-SCC-Location_ID'] = proxy.locationId;
|
|
1081
1116
|
}
|
|
1082
|
-
const
|
|
1117
|
+
const clientOptions = options?.fetchTimeoutMs === undefined ? undefined : { headersTimeout: 0, bodyTimeout: 0 };
|
|
1118
|
+
const client = new Client(proxyOrigin, clientOptions);
|
|
1083
1119
|
try {
|
|
1084
1120
|
const resp = await client.request({
|
|
1085
1121
|
method: method,
|
|
@@ -1087,7 +1123,7 @@ export class AdtHttpClient {
|
|
|
1087
1123
|
path: url,
|
|
1088
1124
|
headers: proxyHeaders,
|
|
1089
1125
|
body: body ?? undefined,
|
|
1090
|
-
signal:
|
|
1126
|
+
signal: requestSignal(options),
|
|
1091
1127
|
});
|
|
1092
1128
|
// Convert undici response to a Response-like object that matches
|
|
1093
1129
|
// what fetch() returns, so the rest of AdtHttpClient works unchanged.
|