@mcp-abap-adt/connection 1.10.1 → 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.
Files changed (44) hide show
  1. package/CHANGELOG.md +758 -0
  2. package/README.md +42 -11
  3. package/dist/__tests__/helpers/session.d.ts +15 -0
  4. package/dist/__tests__/helpers/session.d.ts.map +1 -0
  5. package/dist/__tests__/helpers/session.js +19 -0
  6. package/dist/auth/ntlm.d.ts +15 -0
  7. package/dist/auth/ntlm.d.ts.map +1 -1
  8. package/dist/auth/ntlm.js +38 -0
  9. package/dist/connection/AbstractAbapConnection.d.ts +163 -11
  10. package/dist/connection/AbstractAbapConnection.d.ts.map +1 -1
  11. package/dist/connection/AbstractAbapConnection.js +351 -14
  12. package/dist/connection/BaseAbapConnection.d.ts +5 -1
  13. package/dist/connection/BaseAbapConnection.d.ts.map +1 -1
  14. package/dist/connection/BaseAbapConnection.js +11 -1
  15. package/dist/connection/CertificateAbapConnection.d.ts +5 -1
  16. package/dist/connection/CertificateAbapConnection.d.ts.map +1 -1
  17. package/dist/connection/CertificateAbapConnection.js +11 -1
  18. package/dist/connection/JwtAbapConnection.d.ts +3 -2
  19. package/dist/connection/JwtAbapConnection.d.ts.map +1 -1
  20. package/dist/connection/JwtAbapConnection.js +19 -8
  21. package/dist/connection/KerberosAbapConnection.d.ts +5 -1
  22. package/dist/connection/KerberosAbapConnection.d.ts.map +1 -1
  23. package/dist/connection/KerberosAbapConnection.js +54 -3
  24. package/dist/connection/SamlAbapConnection.d.ts +5 -1
  25. package/dist/connection/SamlAbapConnection.d.ts.map +1 -1
  26. package/dist/connection/SamlAbapConnection.js +11 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +4 -0
  29. package/dist/session/SessionLifecycle.d.ts +131 -0
  30. package/dist/session/SessionLifecycle.d.ts.map +1 -0
  31. package/dist/session/SessionLifecycle.js +301 -0
  32. package/docs/INDEX.md +106 -0
  33. package/docs/INSTALLATION.md +304 -0
  34. package/docs/JWT_AUTH_TOOLS.md +142 -0
  35. package/docs/MIGRATION-2.0.md +125 -0
  36. package/docs/SCOPE.md +44 -0
  37. package/docs/STATEFUL_SESSION_GUIDE.md +121 -0
  38. package/docs/USAGE.md +749 -0
  39. package/examples/README.md +112 -0
  40. package/examples/basic-connection.js +55 -0
  41. package/examples/jwt-with-token-refresh.js +87 -0
  42. package/examples/saml-connection.js +52 -0
  43. package/examples/websocket-transport.js +87 -0
  44. 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
- - Simple connect() - fetches CSRF token
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
- - Simple connect() - establishes connection with JWT token
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
- reset(): void;
405
+ getBaseUrl(): Promise<string>;
394
406
  setSessionType(type: "stateless" | "stateful"): void; // Switch session type
395
- getSessionMode(): "stateless" | "stateful"; // Get current session mode
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
- - `getSessionMode()`: Returns current session mode
403
- - `getSessionId()`: Returns the current session ID (auto-generated UUID)
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
- **Latest version: 0.2.0**
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
+ }
@@ -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
@@ -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
- declare abstract class AbstractAbapConnection implements AbapConnection {
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
- * Connect to SAP system and initialize session (get CSRF token and cookies)
87
- * This should be called explicitly before making the first request to ensure
88
- * proper authentication and session initialization.
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
- * Concrete implementations must provide auth-specific connection logic:
91
- * - BaseAbapConnection: Basic auth with CSRF token fetch
92
- * - JwtAbapConnection: JWT auth with token refresh on 401/403
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
- abstract connect(): Promise<void>;
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":"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"}
1
+ {"version":3,"file":"AbstractAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/AbstractAbapConnection.ts"],"names":[],"mappings":"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"}