@mcp-abap-adt/connection 4.0.0 → 5.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 +274 -1
- package/README.md +60 -30
- package/dist/auth/IAuthProvider.d.ts +84 -0
- package/dist/auth/IAuthProvider.d.ts.map +1 -0
- package/dist/auth/IAuthProvider.js +21 -0
- package/dist/auth/providers.d.ts +95 -0
- package/dist/auth/providers.d.ts.map +1 -0
- package/dist/auth/providers.js +140 -0
- package/dist/connection/AbstractAbapConnection.d.ts +222 -17
- package/dist/connection/AbstractAbapConnection.d.ts.map +1 -1
- package/dist/connection/AbstractAbapConnection.js +555 -31
- package/dist/connection/AdtCloudConnector.d.ts +29 -0
- package/dist/connection/AdtCloudConnector.d.ts.map +1 -0
- package/dist/connection/AdtCloudConnector.js +31 -0
- package/dist/connection/AdtOnPremConnector.d.ts +30 -0
- package/dist/connection/AdtOnPremConnector.d.ts.map +1 -0
- package/dist/connection/AdtOnPremConnector.js +32 -0
- package/dist/connection/BaseAbapConnection.d.ts +7 -1
- package/dist/connection/BaseAbapConnection.d.ts.map +1 -1
- package/dist/connection/BaseAbapConnection.js +7 -1
- package/dist/connection/CertificateAbapConnection.d.ts +11 -1
- package/dist/connection/CertificateAbapConnection.d.ts.map +1 -1
- package/dist/connection/CertificateAbapConnection.js +13 -1
- package/dist/connection/CredentialAbapConnection.d.ts +88 -0
- package/dist/connection/CredentialAbapConnection.d.ts.map +1 -0
- package/dist/connection/CredentialAbapConnection.js +195 -0
- package/dist/connection/JwtAbapConnection.d.ts +23 -7
- package/dist/connection/JwtAbapConnection.d.ts.map +1 -1
- package/dist/connection/JwtAbapConnection.js +26 -8
- package/dist/connection/KerberosAbapConnection.d.ts +9 -1
- package/dist/connection/KerberosAbapConnection.d.ts.map +1 -1
- package/dist/connection/KerberosAbapConnection.js +9 -1
- package/dist/connection/RfcAbapConnection.d.ts +0 -5
- package/dist/connection/RfcAbapConnection.d.ts.map +1 -1
- package/dist/connection/RfcAbapConnection.js +0 -7
- package/dist/connection/SamlAbapConnection.d.ts +7 -1
- package/dist/connection/SamlAbapConnection.d.ts.map +1 -1
- package/dist/connection/SamlAbapConnection.js +7 -1
- package/dist/connection/connectionFactory.d.ts +16 -0
- package/dist/connection/connectionFactory.d.ts.map +1 -1
- package/dist/connection/connectionFactory.js +52 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/session/CloudSecuritySessionStrategy.d.ts +32 -0
- package/dist/session/CloudSecuritySessionStrategy.d.ts.map +1 -0
- package/dist/session/CloudSecuritySessionStrategy.js +135 -0
- package/dist/session/IcfSessionStrategy.d.ts +27 -0
- package/dist/session/IcfSessionStrategy.d.ts.map +1 -0
- package/dist/session/IcfSessionStrategy.js +62 -0
- package/dist/session/SessionLifecycle.d.ts +17 -2
- package/dist/session/SessionLifecycle.d.ts.map +1 -1
- package/dist/session/SessionLifecycle.js +17 -2
- package/dist/session/SessionStrategy.d.ts +86 -0
- package/dist/session/SessionStrategy.d.ts.map +1 -0
- package/dist/session/SessionStrategy.js +33 -0
- package/dist/utils/cookies.d.ts +12 -0
- package/dist/utils/cookies.d.ts.map +1 -0
- package/dist/utils/cookies.js +24 -0
- package/dist/utils/timeouts.d.ts +16 -0
- package/dist/utils/timeouts.d.ts.map +1 -1
- package/dist/utils/timeouts.js +19 -0
- package/docs/INSTALLATION.md +6 -2
- package/docs/MIGRATION-2.0.md +1 -1
- package/docs/MIGRATION-5.0.md +116 -0
- package/docs/STATEFUL_SESSION_GUIDE.md +77 -11
- package/docs/USAGE.md +92 -22
- package/docs/superpowers/specs/2026-08-21-platform-connectors.md +108 -0
- package/package.json +1 -1
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The credentials a connector can be handed.
|
|
4
|
+
*
|
|
5
|
+
* Each is the authentication half of one of the connection classes that used to
|
|
6
|
+
* carry both halves, lifted out unchanged — the header a `BasicAuthProvider`
|
|
7
|
+
* builds is byte for byte what `BaseAbapConnection` built, and the TLS options a
|
|
8
|
+
* `CertificateAuthProvider` returns are what `CertificateAbapConnection`
|
|
9
|
+
* returned. Nothing about how a credential works changed; only who owns it.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.CertificateAuthProvider = exports.SamlAuthProvider = exports.TokenAuthProvider = exports.BasicAuthProvider = void 0;
|
|
13
|
+
/** Username and password, as `Basic base64(user:pass)`. */
|
|
14
|
+
class BasicAuthProvider {
|
|
15
|
+
username;
|
|
16
|
+
password;
|
|
17
|
+
kind = 'basic';
|
|
18
|
+
constructor(username, password) {
|
|
19
|
+
this.username = username;
|
|
20
|
+
this.password = password;
|
|
21
|
+
}
|
|
22
|
+
async authorizationHeader() {
|
|
23
|
+
return `Basic ${Buffer.from(`${this.username ?? ''}:${this.password ?? ''}`).toString('base64')}`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.BasicAuthProvider = BasicAuthProvider;
|
|
27
|
+
/**
|
|
28
|
+
* A bearer token, kept current by whoever issued it.
|
|
29
|
+
*
|
|
30
|
+
* Takes an `ITokenRefresher` — the contract `@mcp-abap-adt/auth-broker` already
|
|
31
|
+
* produces and `@mcp-abap-adt/auth-providers` already implements twelve ways.
|
|
32
|
+
* This package depends on neither: it speaks the contract, and the consumer
|
|
33
|
+
* brings the implementation, exactly as it does for `IAbapConnection`.
|
|
34
|
+
*
|
|
35
|
+
* **This carries no session recovery.** What happens to a session when the
|
|
36
|
+
* credential behind it is renewed mid-flight is the connection's business and
|
|
37
|
+
* stays there; see `JwtAbapConnection`, which still owns that machinery.
|
|
38
|
+
*/
|
|
39
|
+
class TokenAuthProvider {
|
|
40
|
+
source;
|
|
41
|
+
kind = 'token';
|
|
42
|
+
/**
|
|
43
|
+
* A token, or something that can produce one.
|
|
44
|
+
*
|
|
45
|
+
* A bare string is a token with no renewal behind it — honest, and fine for a
|
|
46
|
+
* short task. An `ITokenRefresher` or a function is a provider that checks
|
|
47
|
+
* expiry and renews on its own, which is where this belongs in a long-lived
|
|
48
|
+
* process.
|
|
49
|
+
*/
|
|
50
|
+
constructor(source) {
|
|
51
|
+
this.source = source;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Asked every time, and nothing kept.
|
|
55
|
+
*
|
|
56
|
+
* The provider behind this already caches the token, knows when it expires,
|
|
57
|
+
* and renews before handing one back. A second cache here would serve the
|
|
58
|
+
* stale one and hide exactly the renewal the provider exists to do — which
|
|
59
|
+
* is what the first version of this class did.
|
|
60
|
+
*/
|
|
61
|
+
async authorizationHeader() {
|
|
62
|
+
const token = typeof this.source === 'string'
|
|
63
|
+
? this.source
|
|
64
|
+
: typeof this.source === 'function'
|
|
65
|
+
? await this.source()
|
|
66
|
+
: await this.source.getToken();
|
|
67
|
+
return token ? `Bearer ${token}` : '';
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The token was refused, so force a new one.
|
|
71
|
+
*
|
|
72
|
+
* `getToken()` is documented to return the cached token while it believes it
|
|
73
|
+
* is still valid — which is exactly the situation after a 401 on a token the
|
|
74
|
+
* provider has not yet noticed is dead. `refreshToken()` is the contract's
|
|
75
|
+
* answer for that, and this is the only place it is called.
|
|
76
|
+
*/
|
|
77
|
+
async renew() {
|
|
78
|
+
if (typeof this.source === 'string' || typeof this.source === 'function') {
|
|
79
|
+
// Nothing to force: a fixed token has no source, and a function is asked
|
|
80
|
+
// afresh every time anyway.
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
await this.source.refreshToken();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
exports.TokenAuthProvider = TokenAuthProvider;
|
|
87
|
+
/**
|
|
88
|
+
* A SAML session, already negotiated, presented as cookies.
|
|
89
|
+
*
|
|
90
|
+
* No `Authorization` header at all — the cookies are the credential, and they
|
|
91
|
+
* are added by the connection alongside its own.
|
|
92
|
+
*/
|
|
93
|
+
class SamlAuthProvider {
|
|
94
|
+
sessionCookies;
|
|
95
|
+
kind = 'saml';
|
|
96
|
+
constructor(sessionCookies) {
|
|
97
|
+
this.sessionCookies = sessionCookies;
|
|
98
|
+
}
|
|
99
|
+
async authorizationHeader() {
|
|
100
|
+
return '';
|
|
101
|
+
}
|
|
102
|
+
/** The cookies to present. */
|
|
103
|
+
cookies() {
|
|
104
|
+
return this.sessionCookies;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
exports.SamlAuthProvider = SamlAuthProvider;
|
|
108
|
+
/**
|
|
109
|
+
* Client certificate: the credential lives in the TLS handshake.
|
|
110
|
+
*
|
|
111
|
+
* `prepare()` is where the material is read, which is why it exists at all —
|
|
112
|
+
* building an agent before it is loaded is what used to reject a connect
|
|
113
|
+
* before a single request went out.
|
|
114
|
+
*/
|
|
115
|
+
class CertificateAuthProvider {
|
|
116
|
+
loader;
|
|
117
|
+
config;
|
|
118
|
+
kind = 'certificate';
|
|
119
|
+
material = null;
|
|
120
|
+
constructor(loader, config) {
|
|
121
|
+
this.loader = loader;
|
|
122
|
+
this.config = config;
|
|
123
|
+
}
|
|
124
|
+
async prepare() {
|
|
125
|
+
if (!this.material) {
|
|
126
|
+
this.material = await this.loader.load(this.config);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async authorizationHeader() {
|
|
130
|
+
return '';
|
|
131
|
+
}
|
|
132
|
+
httpsAgentOptions() {
|
|
133
|
+
if (!this.material) {
|
|
134
|
+
throw new Error('CertificateAuthProvider: certificate material not loaded. connect() prepares it; a request before that has nothing to present.');
|
|
135
|
+
}
|
|
136
|
+
const { cert, key, pfx, passphrase } = this.material;
|
|
137
|
+
return { cert, key, pfx, passphrase };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
exports.CertificateAuthProvider = CertificateAuthProvider;
|
|
@@ -2,12 +2,29 @@ import { type IAdtResponse, type ISessionLifecycleAware } from '@mcp-abap-adt/in
|
|
|
2
2
|
import type { SapConfig } from '../config/sapConfig.js';
|
|
3
3
|
import type { ILogger } from '../logger.js';
|
|
4
4
|
import { SessionLifecycle } from '../session/SessionLifecycle.js';
|
|
5
|
+
import type { SessionStrategy } from '../session/SessionStrategy.js';
|
|
5
6
|
import type { AbapConnection, AbapRequestOptions } from './AbapConnection.js';
|
|
6
7
|
/**
|
|
7
8
|
* Declares the capabilities explicitly rather than satisfying them by accident.
|
|
8
9
|
* `AbapConnection` is the base contract every transport honours; these two are
|
|
9
10
|
* the HTTP session's own, and naming them means a signature that drifts from
|
|
10
11
|
* the published contract fails to compile here instead of at the consumer.
|
|
12
|
+
*
|
|
13
|
+
* **This gives the consumer instruments; it does not decide for it.** `connect()`
|
|
14
|
+
* opens one session and `disconnect()` closes it. How many connections to hold,
|
|
15
|
+
* how long to hold them and when to let go stays with the caller — there are no
|
|
16
|
+
* thresholds here, no pooling and no eviction, because none of that is knowable
|
|
17
|
+
* from inside a single connection. A session this one did not open is not its
|
|
18
|
+
* business: the session limit is per user and the pool is shared, so a SAP GUI
|
|
19
|
+
* logon of the same user sits in the same list.
|
|
20
|
+
*
|
|
21
|
+
* **Nothing the server decides is treated as something to count on.** Whether it
|
|
22
|
+
* issues a session cookie, whether it still holds a session it issued, how many
|
|
23
|
+
* it will tolerate, how fast it answers a logoff — all of that is its own and
|
|
24
|
+
* may differ by system and release. So each is observed and reported, never
|
|
25
|
+
* relied upon: the logoff is best effort under a bound the caller sets, a
|
|
26
|
+
* missing session cookie is a warning rather than a rule, and no code here
|
|
27
|
+
* counts sessions or predicts the next answer from the last one.
|
|
11
28
|
*/
|
|
12
29
|
declare abstract class AbstractAbapConnection implements AbapConnection, ISessionLifecycleAware {
|
|
13
30
|
private readonly config;
|
|
@@ -38,6 +55,53 @@ declare abstract class AbstractAbapConnection implements AbapConnection, ISessio
|
|
|
38
55
|
private inCriticalSection;
|
|
39
56
|
/** Reference count for nested beginCriticalSection()/endCriticalSection() pairs. */
|
|
40
57
|
private criticalSectionDepth;
|
|
58
|
+
/** The default release deadline, validated once at construction. */
|
|
59
|
+
private readonly releaseDeadlineMs;
|
|
60
|
+
/**
|
|
61
|
+
* The logoff for the session this connection last held, while it is on its
|
|
62
|
+
* way. One, because a connection holds one session: `connect()` opens it and
|
|
63
|
+
* `disconnect()` closes it, and how many connections to run is the caller's
|
|
64
|
+
* business, not something to be tracked here.
|
|
65
|
+
*
|
|
66
|
+
* Carries the session id, not just the promise, so a release still in flight
|
|
67
|
+
* for an EARLIER session is recognised as not being this one's — reusing it
|
|
68
|
+
* was what left the second session of a reconnect never released at all.
|
|
69
|
+
*
|
|
70
|
+
* The id is the `SAP_SESSIONID` value — the ABAP session, the one locks are
|
|
71
|
+
* bound to — never the cookie header: that header also carries `sap-XSRF_*`,
|
|
72
|
+
* which rotates within one and the same session, so comparing headers made a
|
|
73
|
+
* session stop recognising itself after a token refresh.
|
|
74
|
+
*/
|
|
75
|
+
/**
|
|
76
|
+
* How this server opens and gives back a session, decided by asking it rather
|
|
77
|
+
* than by guessing which system it is. Set at establishment; until then the
|
|
78
|
+
* on-prem mechanism, which is the one that needs no resource.
|
|
79
|
+
*/
|
|
80
|
+
/**
|
|
81
|
+
* The application server this session lives on, as the server named it.
|
|
82
|
+
*
|
|
83
|
+
* A session belongs to ONE application server. On a multi-node system a
|
|
84
|
+
* request that lands on another gets another session — and a lock held on the
|
|
85
|
+
* first is then dead through nobody's fault and no inactivity. Eclipse pins
|
|
86
|
+
* itself with these headers; without them every request is a fresh throw of
|
|
87
|
+
* the dice.
|
|
88
|
+
*
|
|
89
|
+
* Learned from `sap-adt-saplb` on a response, sent back as `saplb`. Cleared
|
|
90
|
+
* with the rest of the session state: it names a server for a session that no
|
|
91
|
+
* longer exists.
|
|
92
|
+
*/
|
|
93
|
+
private appServer;
|
|
94
|
+
/**
|
|
95
|
+
* Whether the preflight opened a session of its own.
|
|
96
|
+
*
|
|
97
|
+
* Distinct from "there are cookies": a failed establishment often leaves a
|
|
98
|
+
* cookie from the 401 that rejected it, and that is debris, not a session.
|
|
99
|
+
* Only a preflight answered with a session address opened one, and only that
|
|
100
|
+
* is worth saying goodbye to when establishment then fails.
|
|
101
|
+
*/
|
|
102
|
+
private preflightOpenedSession;
|
|
103
|
+
private sessionStrategy;
|
|
104
|
+
private pendingRelease;
|
|
41
105
|
protected constructor(config: SapConfig, logger: ILogger | null, sessionId?: string, options?: {
|
|
42
106
|
skipSessionType?: boolean;
|
|
43
107
|
});
|
|
@@ -97,6 +161,27 @@ declare abstract class AbstractAbapConnection implements AbapConnection, ISessio
|
|
|
97
161
|
* preparation and fetch the CSRF token; connect() owns everything around it.
|
|
98
162
|
*/
|
|
99
163
|
protected abstract establishSession(): Promise<void>;
|
|
164
|
+
/**
|
|
165
|
+
* Gets the credential ready before anything is sent.
|
|
166
|
+
*
|
|
167
|
+
* A no-op for the auth types whose credential is already in hand — basic
|
|
168
|
+
* builds a header from the configuration, JWT carries a token it was given.
|
|
169
|
+
* It exists for the ones that have to fetch or load theirs, because the
|
|
170
|
+
* preflight now runs BEFORE `establishSession()` and needs a credential to
|
|
171
|
+
* go out with: a certificate connection reads its material there, and
|
|
172
|
+
* without this the preflight throws `certificate material not loaded` while
|
|
173
|
+
* assembling the transport — before a single request is made, on every
|
|
174
|
+
* system, cloud or on-prem.
|
|
175
|
+
*
|
|
176
|
+
* Must be idempotent: `establishSession()` may prepare the same credential
|
|
177
|
+
* again, and does.
|
|
178
|
+
*
|
|
179
|
+
* Kerberos deliberately does NOT implement it. Minting the SPNEGO token this
|
|
180
|
+
* early changes when the exchange happens, and that connection is not
|
|
181
|
+
* production-tested — its preflight fails the way it already did, is caught
|
|
182
|
+
* inside the strategy, and the connection falls back to ICF as before.
|
|
183
|
+
*/
|
|
184
|
+
protected prepareCredential(): Promise<void>;
|
|
100
185
|
/**
|
|
101
186
|
* Establishes the session, once, under the lifecycle.
|
|
102
187
|
*
|
|
@@ -110,18 +195,84 @@ declare abstract class AbstractAbapConnection implements AbapConnection, ISessio
|
|
|
110
195
|
/**
|
|
111
196
|
* Tears the session down. Never throws, and always settles.
|
|
112
197
|
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
198
|
+
* Tells the server the session is no longer needed, then clears the local
|
|
199
|
+
* state. **When the server actually reclaims it is the server's business** —
|
|
200
|
+
* possibly not until the next connect asks it for one — and nothing here
|
|
201
|
+
* waits for that or depends on it. What matters is that the session stops
|
|
202
|
+
* being counted against the user, which dropping the cookie alone does not
|
|
203
|
+
* achieve: the server keeps it until its own timeout, so a process that
|
|
204
|
+
* connects repeatedly leaves one behind every time. Measured on S/4HANA on-prem, 25 connects in a row: with the logoff,
|
|
205
|
+
* 24-25 of them were given a session; without it, 2. A connection that gets
|
|
206
|
+
* no session still answers `200` to a LOCK and hands back a handle the next
|
|
207
|
+
* request cannot use, so the leak surfaces as a half-written object rather
|
|
208
|
+
* than as anything about sessions.
|
|
209
|
+
*
|
|
210
|
+
* **The logoff is the only thing waited for**, under `deadlineMs`, and
|
|
211
|
+
* deciding that bound is the caller's — see the parameter. Nothing else is
|
|
212
|
+
* waited for: finishing chains and releasing locks stay the caller's to do
|
|
213
|
+
* before calling, and waiting here on a request whose caller chose no timeout
|
|
214
|
+
* is what made a teardown unbounded, which blocks every later transition on
|
|
215
|
+
* the serialized tail.
|
|
216
|
+
*
|
|
217
|
+
* **Requests already in flight are not waited for, and the logoff ends the
|
|
218
|
+
* session they are running on** — so they will start failing against a
|
|
219
|
+
* session that no longer exists. That is the caller having asked to
|
|
220
|
+
* disconnect, not a race, and it is a change from the version that only
|
|
221
|
+
* dropped the cookie. Generation fencing (see `SessionLifecycle.isCurrent`)
|
|
222
|
+
* keeps their results from reaching this connection either way.
|
|
223
|
+
*
|
|
224
|
+
* @param options.deadlineMs How long to spend telling the server, measured
|
|
225
|
+
* from this call and including time spent queued behind another transition.
|
|
226
|
+
* **Defaults to `SAP_RELEASE_DEADLINE_MS`, which is `0` — do not wait.**
|
|
227
|
+
* Waiting is for steps whose successor needs the server to have caught up;
|
|
228
|
+
* a teardown has none. The logoff is still sent at `0`, because saying so
|
|
229
|
+
* is not conditional on caring when it lands; its outcome is logged when it
|
|
230
|
+
* arrives rather than awaited. Pass a positive value to bound a wait you
|
|
231
|
+
* have chosen to take. Anything that is not a finite, non-negative number
|
|
232
|
+
* is reported and the default used instead — this method is called from a
|
|
233
|
+
* `finally`, where throwing would replace the error that sent the caller
|
|
234
|
+
* there. The configured default is checked once, at construction, so a
|
|
235
|
+
* misconfigured `SAP_RELEASE_DEADLINE_MS` fails before a connection exists
|
|
236
|
+
* rather than at every teardown.
|
|
237
|
+
*/
|
|
238
|
+
disconnect(options?: {
|
|
239
|
+
deadlineMs?: number;
|
|
240
|
+
}): Promise<void>;
|
|
241
|
+
/**
|
|
242
|
+
* Tells the server this session is no longer needed. Best effort, and the
|
|
243
|
+
* answer is not depended on: whether and when the server frees it is its own
|
|
244
|
+
* affair, and this connection does not check afterwards.
|
|
245
|
+
*
|
|
246
|
+
* ICF rather than ADT because ADT publishes no such endpoint: its discovery
|
|
247
|
+
* document lists none on any reachable system — on-prem, cloud, or legacy —
|
|
248
|
+
* and the ADT logon is the discovery call itself. `/sap/public/bc/icf/logoff`
|
|
249
|
+
* is the platform's own, and answers `200` while expiring the session cookie.
|
|
117
250
|
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* connection
|
|
251
|
+
* **One session, this connection's own.** A repeat `connect()` opens a NEW
|
|
252
|
+
* one, with a new `SAP_SESSIONID`, so an earlier session is not this
|
|
253
|
+
* connection's business any more: its logoff is already on the wire, or the
|
|
254
|
+
* system will time it out. Nothing here retries, counts, or keeps a list —
|
|
255
|
+
* how many connections to run and how carefully stays with the caller.
|
|
121
256
|
*
|
|
122
|
-
*
|
|
257
|
+
* **It ends the session, not this object's use of it.** The cookies are the
|
|
258
|
+
* only thing tying anyone to a session, so a second connection given the same
|
|
259
|
+
* cookies works in the same ABAP session and can use the locks taken in it —
|
|
260
|
+
* and this logoff closes that session for all of them at once. Whoever hands
|
|
261
|
+
* the cookies around owns that decision; this method cannot see the copies.
|
|
262
|
+
*
|
|
263
|
+
* Never throws. `disconnect()` must always settle, and a session we could not
|
|
264
|
+
* close is better than a teardown that does not finish; the local state is
|
|
265
|
+
* cleared either way.
|
|
266
|
+
*/
|
|
267
|
+
private releaseServerSession;
|
|
268
|
+
/**
|
|
269
|
+
* Waits up to `budgetMs` for a release already on its way, then detaches.
|
|
270
|
+
*
|
|
271
|
+
* Detaching, not cancelling: when the budget runs out this stops waiting and
|
|
272
|
+
* the request carries on to the server. Each caller of `disconnect()` gets
|
|
273
|
+
* its own, so one caller's patience is never charged to another's.
|
|
123
274
|
*/
|
|
124
|
-
|
|
275
|
+
private awaitReleaseWithin;
|
|
125
276
|
isConnected(): boolean;
|
|
126
277
|
/**
|
|
127
278
|
* Fingerprint of the SAP-side session, or null when none is known.
|
|
@@ -131,14 +282,9 @@ declare abstract class AbstractAbapConnection implements AbapConnection, ISessio
|
|
|
131
282
|
* session cookie. Use {@link isConnected} for connection state.
|
|
132
283
|
*
|
|
133
284
|
* It follows that null → non-null is not a replacement but an identity being
|
|
134
|
-
* learned; only a CHANGED value means the session
|
|
285
|
+
* learned; only a CHANGED value means the session we had is gone.
|
|
135
286
|
*/
|
|
136
287
|
getSessionIdentity(): string | null;
|
|
137
|
-
/**
|
|
138
|
-
* Discards the session at a caller's request: cancels queued recoveries and
|
|
139
|
-
* queues the cleanup rather than tearing down under a live request.
|
|
140
|
-
*/
|
|
141
|
-
reset(): void;
|
|
142
288
|
/**
|
|
143
289
|
* Re-establishes the session for a request that is recovering from a
|
|
144
290
|
* credential renewal, then lets that request retry.
|
|
@@ -166,9 +312,50 @@ declare abstract class AbstractAbapConnection implements AbapConnection, ISessio
|
|
|
166
312
|
* Shared by connect() and recoverSession() rather than written twice —
|
|
167
313
|
* the two drifted apart once already, and a third caller would drift again.
|
|
168
314
|
*/
|
|
315
|
+
/**
|
|
316
|
+
* What a session strategy is given: enough to make one request and to prove
|
|
317
|
+
* the session is ours, and nothing else. It cannot reach session state, so a
|
|
318
|
+
* strategy can neither mark this connection connected nor tear it down.
|
|
319
|
+
*/
|
|
320
|
+
private sessionTransport;
|
|
321
|
+
/**
|
|
322
|
+
* Ask the server to open a session before the establishing call needs one.
|
|
323
|
+
*
|
|
324
|
+
* The strategy is chosen by what the server publishes, not by which system we
|
|
325
|
+
* believe it to be: ABAP Cloud offers a session resource and issues
|
|
326
|
+
* `SAP_SESSIONID` to whoever asks for one — with `x-sap-security-session:
|
|
327
|
+
* create` — while on-prem has no such resource and its session arrives with
|
|
328
|
+
* the establishing request. Believing cloud simply "issues no SAP_SESSIONID"
|
|
329
|
+
* is what happens when nobody asks.
|
|
330
|
+
*/
|
|
331
|
+
/**
|
|
332
|
+
* Which session management this system uses — decided by the connection, not
|
|
333
|
+
* discovered by asking.
|
|
334
|
+
*
|
|
335
|
+
* On-prem and cloud do not manage sessions the same way, and the two
|
|
336
|
+
* implementations exist for that reason. On-prem the session arrives with the
|
|
337
|
+
* establishing request and the platform's ICF logoff gives it back, exactly as
|
|
338
|
+
* it always has. Cloud opens a session resource and takes it back by DELETE on
|
|
339
|
+
* the address the server published.
|
|
340
|
+
*
|
|
341
|
+
* Probing was tried and is wrong: `/sap/bc/adt/core/http/sessions` answers on
|
|
342
|
+
* on-prem too — measured on S/4HANA, which publishes both the session resource
|
|
343
|
+
* and the ICF logoff in the same document — so a probe does not tell the two
|
|
344
|
+
* systems apart. It only tells whether an endpoint exists, and both have it.
|
|
345
|
+
*/
|
|
346
|
+
protected createSessionStrategy(): SessionStrategy;
|
|
347
|
+
private openServerSession;
|
|
169
348
|
private establishAndCommit;
|
|
170
349
|
/** The teardown epoch, for a recovery to capture before it starts. */
|
|
171
350
|
protected get teardownEpoch(): number;
|
|
351
|
+
/**
|
|
352
|
+
* Which session the connection is on now.
|
|
353
|
+
*
|
|
354
|
+
* Moves whenever the session does. A response that comes back carrying an
|
|
355
|
+
* older one belongs to a session that has already been replaced, and must not
|
|
356
|
+
* be acted on as if it said something about the current one.
|
|
357
|
+
*/
|
|
358
|
+
protected get sessionGeneration(): number;
|
|
172
359
|
/**
|
|
173
360
|
* Raises a session-lost teardown from inside request handling.
|
|
174
361
|
*
|
|
@@ -211,6 +398,22 @@ declare abstract class AbstractAbapConnection implements AbapConnection, ISessio
|
|
|
211
398
|
* happened — on the error path and on every retry response.
|
|
212
399
|
*/
|
|
213
400
|
private observeResponse;
|
|
401
|
+
/**
|
|
402
|
+
* Take the application server's name from a response, if it named one.
|
|
403
|
+
*
|
|
404
|
+
* Only ever set from the server's own answer — never guessed, and never kept
|
|
405
|
+
* across a teardown.
|
|
406
|
+
*/
|
|
407
|
+
private rememberAppServer;
|
|
408
|
+
/**
|
|
409
|
+
* Headers that keep this connection on the server its session lives on.
|
|
410
|
+
*
|
|
411
|
+
* `sap-adt-saplb: fetch` asks the server to name itself — it answers on every
|
|
412
|
+
* request, so the binding survives a restart that moves us. `saplb` is that
|
|
413
|
+
* name sent back. `REDISPATCH_ON_SHUTDOWN` is what Eclipse asks for: if the
|
|
414
|
+
* server is going down, send us elsewhere rather than fail.
|
|
415
|
+
*/
|
|
416
|
+
private affinityHeaders;
|
|
214
417
|
/**
|
|
215
418
|
* Acts on what a response said about the session identity.
|
|
216
419
|
*
|
|
@@ -231,7 +434,7 @@ declare abstract class AbstractAbapConnection implements AbapConnection, ISessio
|
|
|
231
434
|
/**
|
|
232
435
|
* Whether the server is telling us the session it was given no longer exists.
|
|
233
436
|
*
|
|
234
|
-
*
|
|
437
|
+
* One on-prem system answered HTTP 400 with "Session not found", answered in ~60 ms
|
|
235
438
|
* with the cookie present — which is why identity comparison cannot see this:
|
|
236
439
|
* the cookie, and therefore the fingerprint, is completely unchanged. The
|
|
237
440
|
* exact match is landscape-specific and is one of the live probes this design
|
|
@@ -297,7 +500,9 @@ declare abstract class AbstractAbapConnection implements AbapConnection, ISessio
|
|
|
297
500
|
* cookies (HTTP 401 on a mutation while a cached token exists). This forces the
|
|
298
501
|
* next request path to fetch a fresh token and a fresh SAP_SESSIONID cookie.
|
|
299
502
|
*
|
|
300
|
-
* Distinct from
|
|
503
|
+
* Distinct from disconnect(): this leaves the axios instance and interceptors
|
|
504
|
+
* in place, and tells the server nothing — it is a request-level repair, not a
|
|
505
|
+
* teardown.
|
|
301
506
|
*/
|
|
302
507
|
private invalidateSession;
|
|
303
508
|
private shouldRetryCsrf;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AbstractAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/AbstractAbapConnection.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,sBAAsB,EAE5B,MAAM,0BAA0B,CAAC;AAMlC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"AbstractAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/AbstractAbapConnection.ts"],"names":[],"mappings":"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"}
|