@mcp-abap-adt/connection 1.10.2 → 2.0.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 +758 -0
- package/README.md +42 -11
- package/dist/__tests__/helpers/session.d.ts +15 -0
- package/dist/__tests__/helpers/session.d.ts.map +1 -0
- package/dist/__tests__/helpers/session.js +19 -0
- package/dist/auth/ntlm.d.ts +15 -0
- package/dist/auth/ntlm.d.ts.map +1 -1
- package/dist/auth/ntlm.js +38 -0
- package/dist/connection/AbstractAbapConnection.d.ts +163 -11
- package/dist/connection/AbstractAbapConnection.d.ts.map +1 -1
- package/dist/connection/AbstractAbapConnection.js +351 -14
- package/dist/connection/BaseAbapConnection.d.ts +5 -1
- package/dist/connection/BaseAbapConnection.d.ts.map +1 -1
- package/dist/connection/BaseAbapConnection.js +11 -1
- package/dist/connection/CertificateAbapConnection.d.ts +5 -1
- package/dist/connection/CertificateAbapConnection.d.ts.map +1 -1
- package/dist/connection/CertificateAbapConnection.js +11 -1
- package/dist/connection/JwtAbapConnection.d.ts +3 -2
- package/dist/connection/JwtAbapConnection.d.ts.map +1 -1
- package/dist/connection/JwtAbapConnection.js +19 -8
- package/dist/connection/KerberosAbapConnection.d.ts +5 -1
- package/dist/connection/KerberosAbapConnection.d.ts.map +1 -1
- package/dist/connection/KerberosAbapConnection.js +54 -3
- package/dist/connection/SamlAbapConnection.d.ts +5 -1
- package/dist/connection/SamlAbapConnection.d.ts.map +1 -1
- package/dist/connection/SamlAbapConnection.js +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/session/SessionLifecycle.d.ts +2 -9
- package/dist/session/SessionLifecycle.d.ts.map +1 -1
- package/dist/session/SessionLifecycle.js +8 -8
- package/docs/INDEX.md +106 -0
- package/docs/INSTALLATION.md +304 -0
- package/docs/JWT_AUTH_TOOLS.md +142 -0
- package/docs/MIGRATION-2.0.md +125 -0
- package/docs/SCOPE.md +44 -0
- package/docs/STATEFUL_SESSION_GUIDE.md +121 -0
- package/docs/USAGE.md +749 -0
- package/examples/README.md +112 -0
- package/examples/basic-connection.js +55 -0
- package/examples/jwt-with-token-refresh.js +87 -0
- package/examples/saml-connection.js +52 -0
- package/examples/websocket-transport.js +87 -0
- package/package.json +11 -4
package/README.md
CHANGED
|
@@ -34,18 +34,19 @@ The package uses a clean separation of concerns:
|
|
|
34
34
|
|
|
35
35
|
- **`AbstractAbapConnection`** (abstract, internal only):
|
|
36
36
|
- Common HTTP request logic
|
|
37
|
+
- Session lifecycle: `connect()` / `disconnect()`, admission, lock windows, teardown draining
|
|
37
38
|
- Session management (cookies, CSRF tokens)
|
|
38
39
|
- CSRF token fetching with retry
|
|
39
40
|
- Auth-agnostic - knows nothing about Basic or JWT
|
|
40
41
|
|
|
41
42
|
- **`BaseAbapConnection`** (concrete, exported):
|
|
42
43
|
- Basic Authentication implementation
|
|
43
|
-
-
|
|
44
|
+
- `connect()` establishes the session (required before any request)
|
|
44
45
|
- Suitable for on-premise SAP systems
|
|
45
46
|
|
|
46
47
|
- **`JwtAbapConnection`** (concrete, exported):
|
|
47
48
|
- JWT/OAuth2 Authentication implementation
|
|
48
|
-
-
|
|
49
|
+
- `connect()` establishes the session with the JWT token (required before any request)
|
|
49
50
|
- Suitable for SAP BTP ABAP Environment
|
|
50
51
|
- Token refresh handled by auth-broker package
|
|
51
52
|
|
|
@@ -112,6 +113,7 @@ This package interacts with external packages **ONLY through interfaces**:
|
|
|
112
113
|
|
|
113
114
|
- 📦 **[Installation Guide](./docs/INSTALLATION.md)** - Setup and installation instructions
|
|
114
115
|
- 📚 **[Usage Guide](./docs/USAGE.md)** - Detailed usage examples and API documentation
|
|
116
|
+
- 🚚 **[Migration: the explicit session lifecycle](./docs/MIGRATION-2.0.md)** - `connect()` is now required; start here if you are coming from 1.x
|
|
115
117
|
- 💡 **[Examples](./examples/)** - Working code examples
|
|
116
118
|
|
|
117
119
|
## Features
|
|
@@ -156,6 +158,7 @@ const logger = {
|
|
|
156
158
|
|
|
157
159
|
// Create connection
|
|
158
160
|
const connection = createAbapConnection(config, logger);
|
|
161
|
+
await connection.connect(); // required before any request
|
|
159
162
|
|
|
160
163
|
// Make ADT request
|
|
161
164
|
const response = await connection.makeAdtRequest({
|
|
@@ -186,6 +189,7 @@ const logger = {
|
|
|
186
189
|
|
|
187
190
|
// Logger is optional - if not provided, no logging output
|
|
188
191
|
const connection = createAbapConnection(config, logger);
|
|
192
|
+
await connection.connect();
|
|
189
193
|
|
|
190
194
|
// Note: Token refresh is handled by @mcp-abap-adt/auth-broker package
|
|
191
195
|
const response = await connection.makeAdtRequest({
|
|
@@ -206,6 +210,8 @@ const config: SapConfig = {
|
|
|
206
210
|
};
|
|
207
211
|
|
|
208
212
|
const connection = createAbapConnection(config, logger);
|
|
213
|
+
await connection.connect();
|
|
214
|
+
|
|
209
215
|
const response = await connection.makeAdtRequest({
|
|
210
216
|
method: "GET",
|
|
211
217
|
url: "/sap/bc/adt/programs/programs/your-program",
|
|
@@ -236,8 +242,11 @@ const config: SapConfig = {
|
|
|
236
242
|
|
|
237
243
|
// Create connection with token refresher - 401/403 handled automatically
|
|
238
244
|
const connection = new JwtAbapConnection(config, logger, undefined, tokenRefresher);
|
|
245
|
+
await connection.connect();
|
|
239
246
|
|
|
240
|
-
// Requests automatically retry with refreshed token on auth errors
|
|
247
|
+
// Requests automatically retry with refreshed token on auth errors. A refresh
|
|
248
|
+
// replaces the SAP session, so if a lock window is open the request fails with
|
|
249
|
+
// ADT_SESSION_REPLACED instead of continuing on a session your lock is not in.
|
|
241
250
|
const response = await connection.makeAdtRequest({
|
|
242
251
|
method: "GET",
|
|
243
252
|
url: "/sap/bc/adt/programs/programs/your-program",
|
|
@@ -252,6 +261,7 @@ For operations that require session state (e.g., object modifications), you can
|
|
|
252
261
|
import { createAbapConnection } from "@mcp-abap-adt/connection";
|
|
253
262
|
|
|
254
263
|
const connection = createAbapConnection(config, logger);
|
|
264
|
+
await connection.connect();
|
|
255
265
|
|
|
256
266
|
// Enable stateful session mode (adds x-sap-adt-sessiontype: stateful header)
|
|
257
267
|
connection.setSessionType("stateful");
|
|
@@ -388,19 +398,41 @@ type SapConfig = {
|
|
|
388
398
|
Main interface for ABAP connections.
|
|
389
399
|
|
|
390
400
|
```typescript
|
|
401
|
+
// The shared contract (IAbapConnection), what every connection provides:
|
|
391
402
|
interface AbapConnection {
|
|
403
|
+
connect(): Promise<void>; // REQUIRED before any request; rejects on failure
|
|
392
404
|
makeAdtRequest(options: AbapRequestOptions): Promise<AxiosResponse>;
|
|
393
|
-
|
|
405
|
+
getBaseUrl(): Promise<string>;
|
|
394
406
|
setSessionType(type: "stateless" | "stateful"): void; // Switch session type
|
|
395
|
-
|
|
396
|
-
getSessionId(): string | null; // Get current session ID
|
|
407
|
+
getSessionId(): string | null; // Client-side conversation id
|
|
397
408
|
}
|
|
398
409
|
```
|
|
399
410
|
|
|
411
|
+
The HTTP connection classes carry the rest of the session lifecycle. It is on the
|
|
412
|
+
shared contract as two **capability atoms** in `@mcp-abap-adt/interfaces` (11.5.0+)
|
|
413
|
+
rather than as methods on `IAbapConnection`, which is why `RfcAbapConnection` — a
|
|
414
|
+
transport that owns no HTTP session — is unaffected by their existence:
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
// ISessionLifecycleAware
|
|
418
|
+
disconnect(): Promise<ITeardownReport>; // never throws; reports what it could not finish
|
|
419
|
+
isConnected(): boolean;
|
|
420
|
+
getSessionIdentity(): string | null; // WHICH SAP session; null is not "disconnected"
|
|
421
|
+
|
|
422
|
+
// ILockWindowAware
|
|
423
|
+
beginWindow(label: string): WindowToken;
|
|
424
|
+
endWindow(token: WindowToken): void;
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Import those names from `@mcp-abap-adt/interfaces`, not from this package: a
|
|
428
|
+
contract type re-exported under a second name is a contract type that can drift.
|
|
429
|
+
|
|
430
|
+
See [docs/USAGE.md — Session Lifecycle](./docs/USAGE.md#session-lifecycle).
|
|
431
|
+
|
|
400
432
|
**Session Management:**
|
|
401
|
-
- `setSessionType(type)`: Programmatically switch between stateful and stateless modes
|
|
402
|
-
- `
|
|
403
|
-
- `
|
|
433
|
+
- `setSessionType(type)`: Programmatically switch between stateful and stateless modes *(on the contract)*
|
|
434
|
+
- `getSessionId()`: Returns the client-side conversation id, an auto-generated UUID *(on the contract)*
|
|
435
|
+
- `getSessionMode()`: Returns current session mode *(HTTP classes only)*
|
|
404
436
|
|
|
405
437
|
#### `ILogger`
|
|
406
438
|
|
|
@@ -489,7 +521,6 @@ async function fetchCsrfToken(baseUrl: string): Promise<string> {
|
|
|
489
521
|
}
|
|
490
522
|
```
|
|
491
523
|
|
|
492
|
-
See [PR Proposal](./PR_PROPOSAL_CSRF_CONFIG.md) for more details.
|
|
493
524
|
|
|
494
525
|
## Requirements
|
|
495
526
|
|
|
@@ -500,7 +531,7 @@ See [PR Proposal](./PR_PROPOSAL_CSRF_CONFIG.md) for more details.
|
|
|
500
531
|
|
|
501
532
|
See [CHANGELOG.md](./CHANGELOG.md) for detailed version history and breaking changes.
|
|
502
533
|
|
|
503
|
-
**
|
|
534
|
+
**Version history:** [CHANGELOG.md](./CHANGELOG.md)
|
|
504
535
|
- Removed token refresh functionality (handled by `@mcp-abap-adt/auth-broker`)
|
|
505
536
|
- Removed session storage functionality (handled by `@mcp-abap-adt/auth-broker`)
|
|
506
537
|
- Logger is now optional
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test precondition: put a connection into the connected state without a
|
|
3
|
+
* network round trip.
|
|
4
|
+
*
|
|
5
|
+
* `connect()` is mandatory now, so a test whose subject is something else —
|
|
6
|
+
* timeouts, headers, retry classification — needs the session to exist before
|
|
7
|
+
* it can say anything. Establishing it for real would mean teaching every such
|
|
8
|
+
* mock to answer a CSRF fetch, which tests the wrong thing and adds retry
|
|
9
|
+
* delays to suites that are not about connecting.
|
|
10
|
+
*
|
|
11
|
+
* Tests that ARE about connecting use a real stub instead; see
|
|
12
|
+
* `connection/sessionComposition.test.ts`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function markConnectedForTest(connection: unknown, fingerprint?: Map<string, string>): void;
|
|
15
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,OAAO,EACnB,WAAW,GAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAA4C,GAC1E,IAAI,CAMN"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.markConnectedForTest = markConnectedForTest;
|
|
4
|
+
/**
|
|
5
|
+
* Test precondition: put a connection into the connected state without a
|
|
6
|
+
* network round trip.
|
|
7
|
+
*
|
|
8
|
+
* `connect()` is mandatory now, so a test whose subject is something else —
|
|
9
|
+
* timeouts, headers, retry classification — needs the session to exist before
|
|
10
|
+
* it can say anything. Establishing it for real would mean teaching every such
|
|
11
|
+
* mock to answer a CSRF fetch, which tests the wrong thing and adds retry
|
|
12
|
+
* delays to suites that are not about connecting.
|
|
13
|
+
*
|
|
14
|
+
* Tests that ARE about connecting use a real stub instead; see
|
|
15
|
+
* `connection/sessionComposition.test.ts`.
|
|
16
|
+
*/
|
|
17
|
+
function markConnectedForTest(connection, fingerprint = new Map([['SAP_SESSIONID_T_100', 'S1']])) {
|
|
18
|
+
connection.lifecycle.markConnected(fingerprint);
|
|
19
|
+
}
|
package/dist/auth/ntlm.d.ts
CHANGED
|
@@ -8,4 +8,19 @@
|
|
|
8
8
|
* bytes starting with the NTLM signature "NTLMSSP\0" (base64 prefix "TlRMTVNTUAA").
|
|
9
9
|
*/
|
|
10
10
|
export declare function isNtlmChallenge(wwwAuthenticate: string | undefined): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* `Negotiate <gssapi-data>` — the server has handed back a token for the client
|
|
13
|
+
* to feed into its GSS context and retry (RFC 4559 continuation).
|
|
14
|
+
*/
|
|
15
|
+
export declare function isNegotiateContinuation(wwwAuthenticate: string | undefined): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* A bare `Negotiate`, with no token.
|
|
18
|
+
*
|
|
19
|
+
* Told apart from a continuation deliberately: once the client has ALREADY sent
|
|
20
|
+
* its initial Authorization, a bare challenge is the server declining it — the
|
|
21
|
+
* credentials were not accepted. There is no server token here, so nothing
|
|
22
|
+
* could be stepped even by a client that supports multi-leg. Diagnosing the two
|
|
23
|
+
* alike sends the reader after the wrong problem.
|
|
24
|
+
*/
|
|
25
|
+
export declare function isBareNegotiateChallenge(wwwAuthenticate: string | undefined): boolean;
|
|
11
26
|
//# sourceMappingURL=ntlm.d.ts.map
|
package/dist/auth/ntlm.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ntlm.d.ts","sourceRoot":"","sources":["../../src/auth/ntlm.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAiB5E"}
|
|
1
|
+
{"version":3,"file":"ntlm.d.ts","sourceRoot":"","sources":["../../src/auth/ntlm.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAiB5E;AAcD;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,eAAe,EAAE,MAAM,GAAG,SAAS,GAClC,OAAO,CAKT;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,eAAe,EAAE,MAAM,GAAG,SAAS,GAClC,OAAO,CAIT"}
|
package/dist/auth/ntlm.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.isNtlmChallenge = isNtlmChallenge;
|
|
4
|
+
exports.isNegotiateContinuation = isNegotiateContinuation;
|
|
5
|
+
exports.isBareNegotiateChallenge = isBareNegotiateChallenge;
|
|
4
6
|
/**
|
|
5
7
|
* Detect whether a WWW-Authenticate header (or a Negotiate challenge token) indicates NTLM.
|
|
6
8
|
* NTLM is rejected: only Kerberos/SPNEGO Negotiate is acceptable.
|
|
@@ -32,3 +34,39 @@ function isNtlmChallenge(wwwAuthenticate) {
|
|
|
32
34
|
}
|
|
33
35
|
return false;
|
|
34
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* A `Negotiate` challenge that is not NTLM in disguise. Says nothing about
|
|
39
|
+
* whether it carries a token — callers that care must ask which kind it is.
|
|
40
|
+
*/
|
|
41
|
+
function isNegotiate(wwwAuthenticate) {
|
|
42
|
+
if (!wwwAuthenticate)
|
|
43
|
+
return false;
|
|
44
|
+
if (isNtlmChallenge(wwwAuthenticate))
|
|
45
|
+
return false;
|
|
46
|
+
return wwwAuthenticate
|
|
47
|
+
.split(',')
|
|
48
|
+
.some((part) => /^negotiate\b/i.test(part.trim()));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* `Negotiate <gssapi-data>` — the server has handed back a token for the client
|
|
52
|
+
* to feed into its GSS context and retry (RFC 4559 continuation).
|
|
53
|
+
*/
|
|
54
|
+
function isNegotiateContinuation(wwwAuthenticate) {
|
|
55
|
+
if (!isNegotiate(wwwAuthenticate))
|
|
56
|
+
return false;
|
|
57
|
+
return wwwAuthenticate
|
|
58
|
+
.split(',')
|
|
59
|
+
.some((part) => /^negotiate\s+\S/i.test(part.trim()));
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A bare `Negotiate`, with no token.
|
|
63
|
+
*
|
|
64
|
+
* Told apart from a continuation deliberately: once the client has ALREADY sent
|
|
65
|
+
* its initial Authorization, a bare challenge is the server declining it — the
|
|
66
|
+
* credentials were not accepted. There is no server token here, so nothing
|
|
67
|
+
* could be stepped even by a client that supports multi-leg. Diagnosing the two
|
|
68
|
+
* alike sends the reader after the wrong problem.
|
|
69
|
+
*/
|
|
70
|
+
function isBareNegotiateChallenge(wwwAuthenticate) {
|
|
71
|
+
return (isNegotiate(wwwAuthenticate) && !isNegotiateContinuation(wwwAuthenticate));
|
|
72
|
+
}
|
|
@@ -1,10 +1,23 @@
|
|
|
1
|
-
import { type IAdtResponse } from '@mcp-abap-adt/interfaces';
|
|
1
|
+
import { type IAdtResponse, type ILockWindowAware, type ISessionLifecycleAware, type ITeardownReport, type WindowToken } from '@mcp-abap-adt/interfaces';
|
|
2
2
|
import type { SapConfig } from '../config/sapConfig.js';
|
|
3
3
|
import type { ILogger } from '../logger.js';
|
|
4
|
+
import { SessionLifecycle } from '../session/SessionLifecycle.js';
|
|
4
5
|
import type { AbapConnection, AbapRequestOptions } from './AbapConnection.js';
|
|
5
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Declares the capabilities explicitly rather than satisfying them by accident.
|
|
8
|
+
* `AbapConnection` is the base contract every transport honours; these two are
|
|
9
|
+
* the HTTP session's own, and naming them means a signature that drifts from
|
|
10
|
+
* the published contract fails to compile here instead of at the consumer.
|
|
11
|
+
*/
|
|
12
|
+
declare abstract class AbstractAbapConnection implements AbapConnection, ISessionLifecycleAware, ILockWindowAware {
|
|
6
13
|
private readonly config;
|
|
7
14
|
protected readonly logger: ILogger | null;
|
|
15
|
+
/**
|
|
16
|
+
* Owns session state, admission and teardown ordering. Composed rather than
|
|
17
|
+
* inherited: RfcAbapConnection implements the interface directly, so the two
|
|
18
|
+
* transports share this unit instead of a base class.
|
|
19
|
+
*/
|
|
20
|
+
protected readonly lifecycle: SessionLifecycle;
|
|
8
21
|
private axiosInstance;
|
|
9
22
|
private csrfToken;
|
|
10
23
|
private cookies;
|
|
@@ -79,20 +92,153 @@ declare abstract class AbstractAbapConnection implements AbapConnection {
|
|
|
79
92
|
*/
|
|
80
93
|
getSessionId(): string | null;
|
|
81
94
|
getConfig(): SapConfig;
|
|
95
|
+
/**
|
|
96
|
+
* Establish the session for this auth type. Implementations do their own auth
|
|
97
|
+
* preparation and fetch the CSRF token; connect() owns everything around it.
|
|
98
|
+
*/
|
|
99
|
+
protected abstract establishSession(): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Establishes the session, once, under the lifecycle.
|
|
102
|
+
*
|
|
103
|
+
* Idempotent, and concurrent callers share one establishment: the transition
|
|
104
|
+
* joins the tail of its own kind. A teardown requested while establishment
|
|
105
|
+
* was in flight means the caller asked to stop, so usability is NOT published
|
|
106
|
+
* and what was established is released — otherwise a slow connect would hand
|
|
107
|
+
* back a session someone had already discarded.
|
|
108
|
+
*/
|
|
109
|
+
connect(): Promise<void>;
|
|
110
|
+
/**
|
|
111
|
+
* Tears the session down. Never throws: the report says what did not finish
|
|
112
|
+
* rather than failing. Sends no ADT session-close — see the design's D2.
|
|
113
|
+
*/
|
|
114
|
+
disconnect(): Promise<ITeardownReport>;
|
|
115
|
+
isConnected(): boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Fingerprint of the SAP-side session, or null when none is known.
|
|
118
|
+
*
|
|
119
|
+
* `null` is NOT a statement about the connection. Two situations produce it:
|
|
120
|
+
* no session exists, or the connection is live over a server that issued no
|
|
121
|
+
* session cookie. Use {@link isConnected} for connection state.
|
|
122
|
+
*
|
|
123
|
+
* It follows that null → non-null is not a replacement but an identity being
|
|
124
|
+
* learned; only a CHANGED value means the session was replaced.
|
|
125
|
+
*/
|
|
126
|
+
getSessionIdentity(): string | null;
|
|
127
|
+
/** Opens a lock window. See the design: a lock outlives its request. */
|
|
128
|
+
beginWindow(label: string): WindowToken;
|
|
129
|
+
endWindow(token: WindowToken): void;
|
|
130
|
+
/**
|
|
131
|
+
* Discards the session at a caller's request: cancels queued recoveries and
|
|
132
|
+
* queues the cleanup rather than tearing down under a live request.
|
|
133
|
+
*/
|
|
82
134
|
reset(): void;
|
|
83
|
-
getBaseUrl(): Promise<string>;
|
|
84
|
-
getAuthHeaders(): Promise<Record<string, string>>;
|
|
85
135
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
136
|
+
* Re-establishes the session for a request that is recovering from a
|
|
137
|
+
* credential renewal, then lets that request retry.
|
|
138
|
+
*
|
|
139
|
+
* Runs as its own `recover` transition, which never joins another: each
|
|
140
|
+
* recovery carries the baseline of its own request. It yields to a caller's
|
|
141
|
+
* teardown — if the epoch moved since `baselineEpoch`, someone asked to stop
|
|
142
|
+
* while this was being prepared, and a retry must not resurrect a session
|
|
143
|
+
* they discarded.
|
|
144
|
+
*
|
|
145
|
+
* The transition queues behind the cleanup that the renewal itself raised, so
|
|
146
|
+
* it never re-establishes on top of stale transport state.
|
|
147
|
+
*/
|
|
148
|
+
protected recoverSession(baselineEpoch: number): Promise<void>;
|
|
149
|
+
/**
|
|
150
|
+
* Establishes a session and publishes it — but only if nobody asked to stop
|
|
151
|
+
* meanwhile.
|
|
89
152
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
153
|
+
* The epoch is checked BEFORE, so a teardown already requested costs no round
|
|
154
|
+
* trip, and AFTER, because establishment takes time and a caller can ask to
|
|
155
|
+
* stop during it. Checking only before is the defect this exists to prevent:
|
|
156
|
+
* markConnected() would then clear the teardown state and hand back a session
|
|
157
|
+
* the caller had already discarded.
|
|
158
|
+
*
|
|
159
|
+
* Shared by connect() and recoverSession() rather than written twice —
|
|
160
|
+
* the two drifted apart once already, and a third caller would drift again.
|
|
93
161
|
*/
|
|
94
|
-
|
|
162
|
+
private establishAndCommit;
|
|
163
|
+
/** The teardown epoch, for a recovery to capture before it starts. */
|
|
164
|
+
protected get teardownEpoch(): number;
|
|
165
|
+
/**
|
|
166
|
+
* Raises a session-lost teardown from inside request handling.
|
|
167
|
+
*
|
|
168
|
+
* There are exactly three things that can cost us the ABAP session, and they
|
|
169
|
+
* were found one at a time precisely because they were written apart. They go
|
|
170
|
+
* through here so a fourth joins the list instead of inventing its own
|
|
171
|
+
* sequence:
|
|
172
|
+
*
|
|
173
|
+
* - the credential was renewed (the injected auth says so);
|
|
174
|
+
* - the server says the session is gone (a dead-session response);
|
|
175
|
+
* - the tracked cookie changed under us while a lock was held.
|
|
176
|
+
*
|
|
177
|
+
* `internal` origin, so it does not cancel the recovery that raised it, and
|
|
178
|
+
* `sessionLost`, so admission shuts at once and the identity is dropped
|
|
179
|
+
* immediately — a later comparison must see the change, and on a dead session
|
|
180
|
+
* the cookie is unchanged, so only the state can tell.
|
|
181
|
+
*
|
|
182
|
+
* Does not await: it is called from inside a request, and the queued cleanup
|
|
183
|
+
* drains that very request.
|
|
184
|
+
*/
|
|
185
|
+
protected raiseSessionLost(reason: string): void;
|
|
186
|
+
/** The credential-renewal raiser; see raiseSessionLost(). */
|
|
187
|
+
protected discardSession(): void;
|
|
188
|
+
/**
|
|
189
|
+
* Whether an error is this connection's own verdict about the session rather
|
|
190
|
+
* than something the server said about a request.
|
|
191
|
+
*
|
|
192
|
+
* A retry path that swallows one of these and rethrows the original error
|
|
193
|
+
* turns "your lock is dead" back into "your request 403'd", which is the very
|
|
194
|
+
* information the caller needs and the only one it cannot recover itself.
|
|
195
|
+
*/
|
|
196
|
+
private isSessionVerdict;
|
|
197
|
+
/**
|
|
198
|
+
* Folds a response into the session state AND acts on what it means, in one
|
|
199
|
+
* step.
|
|
200
|
+
*
|
|
201
|
+
* Never call updateCookiesFromResponse() directly: it MUTATES the fingerprint,
|
|
202
|
+
* so discarding its classification absorbs a replacement silently and every
|
|
203
|
+
* later check reads `unchanged`. That is one call site forgetting, and it
|
|
204
|
+
* happened — on the error path and on every retry response.
|
|
205
|
+
*/
|
|
206
|
+
private observeResponse;
|
|
207
|
+
/**
|
|
208
|
+
* Acts on what a response said about the session identity.
|
|
209
|
+
*
|
|
210
|
+
* A replacement is fatal only while a lock is held — and "a lock is held"
|
|
211
|
+
* means an open window, not `sessionMode`: a mode flag cannot represent
|
|
212
|
+
* windows, another handler can flip it back, and a batch never sets it.
|
|
213
|
+
* With no lock held the same replacement is not a loss: nothing was being
|
|
214
|
+
* held, so the new identity simply becomes the current one.
|
|
215
|
+
*/
|
|
216
|
+
private applyIdentityPolicy;
|
|
217
|
+
/**
|
|
218
|
+
* Whether the server is telling us the session it was given no longer exists.
|
|
219
|
+
*
|
|
220
|
+
* The E19 shape was HTTP 400 with "Session not found", answered in ~60 ms
|
|
221
|
+
* with the cookie present — which is why identity comparison cannot see this:
|
|
222
|
+
* the cookie, and therefore the fingerprint, is completely unchanged. The
|
|
223
|
+
* exact match is landscape-specific and is one of the live probes this design
|
|
224
|
+
* still owes.
|
|
225
|
+
*/
|
|
226
|
+
private isDeadSessionResponse;
|
|
227
|
+
/** Drops everything that described the session. Not a lifecycle transition. */
|
|
228
|
+
private clearSessionState;
|
|
229
|
+
/**
|
|
230
|
+
* The session-bearing cookies, and only those.
|
|
231
|
+
*
|
|
232
|
+
* `sap-XSRF_*` is excluded deliberately: it changes on a token refresh WITHIN
|
|
233
|
+
* the same session, so including it would report an ordinary refresh as a new
|
|
234
|
+
* session and fail exactly where nothing is wrong. `sap-usercontext` is ours,
|
|
235
|
+
* overwritten on every response.
|
|
236
|
+
*/
|
|
237
|
+
protected sessionFingerprint(): Map<string, string>;
|
|
238
|
+
getBaseUrl(): Promise<string>;
|
|
239
|
+
getAuthHeaders(): Promise<Record<string, string>>;
|
|
95
240
|
makeAdtRequest<T = any, D = any>(options: AbapRequestOptions): Promise<IAdtResponse<T, D>>;
|
|
241
|
+
private performRequest;
|
|
96
242
|
protected abstract buildAuthorizationHeader(): string;
|
|
97
243
|
/**
|
|
98
244
|
* Fetch CSRF token from SAP system
|
|
@@ -116,6 +262,12 @@ declare abstract class AbstractAbapConnection implements AbapConnection {
|
|
|
116
262
|
*/
|
|
117
263
|
protected getCookies(): string | null;
|
|
118
264
|
protected setInitialCookies(cookies: string): void;
|
|
265
|
+
/**
|
|
266
|
+
* Folds a response's cookies into the jar and classifies what that means for
|
|
267
|
+
* the session identity. Returns the classification rather than acting on it:
|
|
268
|
+
* cookie parsing stays free of policy, and no exception fires in the middle
|
|
269
|
+
* of a state update. The caller decides.
|
|
270
|
+
*/
|
|
119
271
|
private updateCookiesFromResponse;
|
|
120
272
|
/**
|
|
121
273
|
* Subclasses override to inject extra https.Agent options (e.g. mTLS cert/key/pfx).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AbstractAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/AbstractAbapConnection.ts"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"AbstractAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/AbstractAbapConnection.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EAEpB,KAAK,WAAW,EACjB,MAAM,0BAA0B,CAAC;AAMlC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAEL,gBAAgB,EAEjB,MAAM,gCAAgC,CAAC;AAExC,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAG9E;;;;;GAKG;AACH,uBAAe,sBACb,YAAW,cAAc,EAAE,sBAAsB,EAAE,gBAAgB;IA+BjE,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IA9B3C;;;;OAIG;IACH,SAAS,CAAC,QAAQ,CAAC,SAAS,mBAA0B;IAEtD,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,WAAW,CAAkC;IACrD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,WAAW,CAAyC;IAC5D,OAAO,CAAC,eAAe,CAAU;IACjC;;;;;;;;OAQG;IACH,OAAO,CAAC,iBAAiB,CAAS;IAClC,oFAAoF;IACpF,OAAO,CAAC,oBAAoB,CAAK;IAEjC,SAAS,aACU,MAAM,EAAE,SAAS,EACf,MAAM,EAAE,OAAO,GAAG,IAAI,EACzC,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE;IAqBzC;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,WAAW,GAAG,IAAI;IAUpD;;OAEG;IACH,cAAc,IAAI,WAAW,GAAG,UAAU;IAI1C;;;;;;;;;;;;OAYG;IACH,oBAAoB,IAAI,IAAI;IAQ5B;;;;OAIG;IACH,kBAAkB,IAAI,IAAI;IAY1B;;OAEG;IACH,mBAAmB,IAAI,OAAO;IAI9B;;;OAGG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKrC;;OAEG;IACH,YAAY,IAAI,MAAM,GAAG,IAAI;IAI7B,SAAS,IAAI,SAAS;IAItB;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAEpD;;;;;;;;OAQG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAc9B;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,eAAe,CAAC;IAgB5C,WAAW,IAAI,OAAO;IAItB;;;;;;;;;OASG;IACH,kBAAkB,IAAI,MAAM,GAAG,IAAI;IAInC,wEAAwE;IACxE,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW;IAIvC,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAInC;;;OAGG;IACH,KAAK,IAAI,IAAI;IASb;;;;;;;;;;;;OAYG;cACa,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMpE;;;;;;;;;;;;OAYG;YACW,kBAAkB;IAiDhC,sEAAsE;IACtE,SAAS,KAAK,aAAa,IAAI,MAAM,CAEpC;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,SAAS,CAAC,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAUhD,6DAA6D;IAC7D,SAAS,CAAC,cAAc,IAAI,IAAI;IAIhC;;;;;;;OAOG;IACH,OAAO,CAAC,gBAAgB;IASxB;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe;IAIvB;;;;;;;;OAQG;IACH,OAAO,CAAC,mBAAmB;IAmB3B;;;;;;;;OAQG;IACH,OAAO,CAAC,qBAAqB;IAa7B,+EAA+E;IAC/E,OAAO,CAAC,iBAAiB;IAYzB;;;;;;;OAOG;IACH,SAAS,CAAC,kBAAkB,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IAU7C,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAI7B,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAejD,cAAc,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EACnC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAchB,cAAc;IAmU5B,SAAS,CAAC,QAAQ,CAAC,wBAAwB,IAAI,MAAM;IAErD;;;OAGG;cACa,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,UAAU,GAAE,MAAgC,EAC5C,UAAU,GAAE,MAAgC,GAC3C,OAAO,CAAC,MAAM,CAAC;IAkDlB;;OAEG;YACW,0BAA0B;IAyLxC;;OAEG;IACH,SAAS,CAAC,YAAY,IAAI,MAAM,GAAG,IAAI;IAIvC;;OAEG;IACH,SAAS,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAIlD;;OAEG;IACH,SAAS,CAAC,UAAU,IAAI,MAAM,GAAG,IAAI;IAIrC,SAAS,CAAC,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAIlD;;;;;OAKG;IACH,OAAO,CAAC,yBAAyB;IAqEjC;;;OAGG;IACH,SAAS,CAAC,oBAAoB,IAAI,OAAO,YAAY,EAAE,YAAY;IAInE,OAAO,CAAC,gBAAgB;YAsBV,oBAAoB;IAiClC;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,eAAe;CA+BxB;AAGD,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
|