@mcp-abap-adt/connection 4.0.0 → 6.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 +407 -1
- package/README.md +239 -53
- package/dist/auth/providers.d.ts +124 -0
- package/dist/auth/providers.d.ts.map +1 -0
- package/dist/auth/providers.js +183 -0
- package/dist/connection/AbstractAbapConnection.d.ts +209 -57
- package/dist/connection/AbstractAbapConnection.d.ts.map +1 -1
- package/dist/connection/AbstractAbapConnection.js +459 -457
- package/dist/connection/AdtCloudConnector.d.ts +34 -0
- package/dist/connection/AdtCloudConnector.d.ts.map +1 -0
- package/dist/connection/AdtCloudConnector.js +27 -0
- package/dist/connection/AdtOnPremConnector.d.ts +43 -0
- package/dist/connection/AdtOnPremConnector.d.ts.map +1 -0
- package/dist/connection/AdtOnPremConnector.js +28 -0
- package/dist/connection/CloudHttpTransport.d.ts +37 -0
- package/dist/connection/CloudHttpTransport.d.ts.map +1 -0
- package/dist/connection/CloudHttpTransport.js +145 -0
- package/dist/connection/CredentialAbapConnection.d.ts +55 -0
- package/dist/connection/CredentialAbapConnection.d.ts.map +1 -0
- package/dist/connection/CredentialAbapConnection.js +128 -0
- package/dist/connection/HttpTransport.d.ts +178 -0
- package/dist/connection/HttpTransport.d.ts.map +1 -0
- package/dist/connection/HttpTransport.js +402 -0
- package/dist/connection/IAdtTransport.d.ts +232 -0
- package/dist/connection/IAdtTransport.d.ts.map +1 -0
- package/dist/connection/IAdtTransport.js +28 -0
- package/dist/connection/LegacyOnPremHttpTransport.d.ts +40 -0
- package/dist/connection/LegacyOnPremHttpTransport.d.ts.map +1 -0
- package/dist/connection/LegacyOnPremHttpTransport.js +57 -0
- package/dist/connection/OnPremHttpTransport.d.ts +45 -0
- package/dist/connection/OnPremHttpTransport.d.ts.map +1 -0
- package/dist/connection/OnPremHttpTransport.js +91 -0
- package/dist/connection/RfcTransport.d.ts +89 -0
- package/dist/connection/RfcTransport.d.ts.map +1 -0
- package/dist/connection/RfcTransport.js +256 -0
- package/dist/connection/rfcConversation.d.ts +44 -0
- package/dist/connection/rfcConversation.d.ts.map +1 -0
- package/dist/connection/rfcConversation.js +71 -0
- package/dist/index.d.ts +10 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -18
- 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/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 +6 -3
- package/dist/utils/timeouts.d.ts.map +1 -1
- package/dist/utils/timeouts.js +6 -3
- package/docs/INDEX.md +5 -2
- package/docs/INSTALLATION.md +28 -6
- package/docs/JWT_AUTH_TOOLS.md +20 -4
- package/docs/MIGRATION-2.0.md +1 -1
- package/docs/MIGRATION-5.0.md +116 -0
- package/docs/MIGRATION-6.0.md +359 -0
- package/docs/SCOPE.md +1 -1
- package/docs/STATEFUL_SESSION_GUIDE.md +155 -20
- package/docs/USAGE.md +322 -111
- package/examples/basic-connection.js +15 -3
- package/examples/jwt-with-token-refresh.js +15 -7
- package/examples/saml-connection.js +15 -2
- package/package.json +12 -10
- package/dist/__tests__/helpers/session.d.ts +0 -15
- package/dist/__tests__/helpers/session.d.ts.map +0 -1
- package/dist/__tests__/helpers/session.js +0 -19
- package/dist/connection/BaseAbapConnection.d.ts +0 -23
- package/dist/connection/BaseAbapConnection.d.ts.map +0 -1
- package/dist/connection/BaseAbapConnection.js +0 -75
- package/dist/connection/CertificateAbapConnection.d.ts +0 -25
- package/dist/connection/CertificateAbapConnection.d.ts.map +0 -1
- package/dist/connection/CertificateAbapConnection.js +0 -79
- package/dist/connection/JwtAbapConnection.d.ts +0 -115
- package/dist/connection/JwtAbapConnection.d.ts.map +0 -1
- package/dist/connection/JwtAbapConnection.js +0 -358
- package/dist/connection/KerberosAbapConnection.d.ts +0 -24
- package/dist/connection/KerberosAbapConnection.d.ts.map +0 -1
- package/dist/connection/KerberosAbapConnection.js +0 -120
- package/dist/connection/RfcAbapConnection.d.ts +0 -49
- package/dist/connection/RfcAbapConnection.d.ts.map +0 -1
- package/dist/connection/RfcAbapConnection.js +0 -331
- package/dist/connection/SamlAbapConnection.d.ts +0 -25
- package/dist/connection/SamlAbapConnection.d.ts.map +0 -1
- package/dist/connection/SamlAbapConnection.js +0 -75
- package/dist/connection/connectionFactory.d.ts +0 -9
- package/dist/connection/connectionFactory.d.ts.map +0 -1
- package/dist/connection/connectionFactory.js +0 -32
|
@@ -26,17 +26,47 @@ The connection layer **does not** decide when to lock/unlock objects—that logi
|
|
|
26
26
|
|
|
27
27
|
## Enabling Stateful Sessions
|
|
28
28
|
|
|
29
|
+
Every example below builds on this much, so it is stated once:
|
|
30
|
+
|
|
29
31
|
```ts
|
|
30
|
-
import {
|
|
32
|
+
import type { SapConfig } from '@mcp-abap-adt/connection';
|
|
33
|
+
|
|
34
|
+
const config: SapConfig = {
|
|
35
|
+
url: 'https://your-sap-server.com',
|
|
36
|
+
authType: 'basic',
|
|
37
|
+
username: 'your-username',
|
|
38
|
+
password: 'your-password',
|
|
39
|
+
client: '100',
|
|
40
|
+
};
|
|
41
|
+
const user = config.username!;
|
|
42
|
+
const pass = config.password!;
|
|
43
|
+
const logger = console;
|
|
44
|
+
```
|
|
31
45
|
|
|
32
|
-
|
|
46
|
+
```ts
|
|
47
|
+
import {
|
|
48
|
+
AdtOnPremConnector,
|
|
49
|
+
BasicAuthProvider,
|
|
50
|
+
OnPremHttpTransport,
|
|
51
|
+
getTimeout,
|
|
52
|
+
} from '@mcp-abap-adt/connection';
|
|
53
|
+
|
|
54
|
+
const connection = new AdtOnPremConnector(
|
|
55
|
+
config,
|
|
56
|
+
new BasicAuthProvider(config.username!, config.password!),
|
|
57
|
+
new OnPremHttpTransport(() => ({}), logger, {
|
|
58
|
+
client: config.client,
|
|
59
|
+
baseUrl: config.url,
|
|
60
|
+
}),
|
|
61
|
+
logger,
|
|
62
|
+
);
|
|
33
63
|
await connection.connect(); // required before any request
|
|
34
64
|
|
|
35
65
|
// Enable stateful session mode (adds x-sap-adt-sessiontype: stateful header)
|
|
36
66
|
connection.setSessionType('stateful');
|
|
37
67
|
|
|
38
68
|
// Now all requests share the same session (cookies, CSRF token)
|
|
39
|
-
await connection.makeAdtRequest({ method: 'GET', url: '/sap/bc/adt/discovery' });
|
|
69
|
+
await connection.makeAdtRequest({ method: 'GET', url: '/sap/bc/adt/discovery' , timeout: getTimeout('default') });
|
|
40
70
|
|
|
41
71
|
// Switch back to stateless
|
|
42
72
|
connection.setSessionType('stateless');
|
|
@@ -47,11 +77,11 @@ connection.setSessionType('stateless');
|
|
|
47
77
|
## Knowing Which Session You Are In
|
|
48
78
|
|
|
49
79
|
```ts
|
|
50
|
-
import {
|
|
80
|
+
import { AdtOnPremConnector, BasicAuthProvider, OnPremHttpTransport } from '@mcp-abap-adt/connection';
|
|
51
81
|
|
|
52
82
|
// getSessionIdentity() is on the HTTP connection classes, NOT on the
|
|
53
|
-
// IAbapConnection type
|
|
54
|
-
const connection = new
|
|
83
|
+
// bare IAbapConnection type a caller may hand you.
|
|
84
|
+
const connection = new AdtOnPremConnector(config, new BasicAuthProvider(user, pass), new OnPremHttpTransport(() => ({}), logger, { client: config.client, baseUrl: config.url }), logger);
|
|
55
85
|
await connection.connect();
|
|
56
86
|
|
|
57
87
|
// Which SAP session this connection is talking to. Changes only when the
|
|
@@ -82,11 +112,13 @@ Every ADT request issued through `makeAdtRequest` automatically:
|
|
|
82
112
|
|
|
83
113
|
This logic is transparent to callers (Builders, handlers, CLI scripts).
|
|
84
114
|
|
|
85
|
-
On a JWT connection
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
115
|
+
On a JWT connection a 401 is **not** handled here at all, and that is the point: since 6.0.0 the
|
|
116
|
+
refusal surfaces and the session is left alone. Nothing replaces the credential behind you, so
|
|
117
|
+
nothing replaces the SAP session behind you either — a lock window is not torn down by an
|
|
118
|
+
authentication answer. A 403 never did this: it is an authorization answer, not a credential one.
|
|
119
|
+
|
|
120
|
+
If you decide the refusal meant a stale token, `renew()` and reconnect are yours to call — and a
|
|
121
|
+
reconnect is a NEW session, so do it outside a lock window rather than inside one.
|
|
90
122
|
|
|
91
123
|
---
|
|
92
124
|
|
|
@@ -102,22 +134,125 @@ answer, not a credential one, and nothing is torn down for it.
|
|
|
102
134
|
|
|
103
135
|
---
|
|
104
136
|
|
|
137
|
+
## What This Layer Actually Solves
|
|
138
|
+
|
|
139
|
+
The link to the ABAP session is not guaranteed, and that is the whole problem:
|
|
140
|
+
|
|
141
|
+
- the ABAP session can be terminated by the system while this client still
|
|
142
|
+
believes it has one — the next lock-bound request answers
|
|
143
|
+
`400 "Session not found"`;
|
|
144
|
+
- or it is never created at all, because the system will not open another one
|
|
145
|
+
for this user right now.
|
|
146
|
+
|
|
147
|
+
Both are reported, neither is worked around. `connect()` fails with the reason
|
|
148
|
+
when no session was opened; a session lost afterwards surfaces as
|
|
149
|
+
`ADT_SESSION_REPLACED` rather than a request quietly continuing somewhere it
|
|
150
|
+
does not belong. What to do about either — wait, retry, reconnect, release
|
|
151
|
+
sessions the user still holds, carry on read-only — is the caller's decision,
|
|
152
|
+
made with the cookies and the answers in hand.
|
|
153
|
+
|
|
154
|
+
## A Lock Lives In The ABAP Session
|
|
155
|
+
|
|
156
|
+
### Three lifetimes, and a dropped connection ends none of them
|
|
157
|
+
|
|
158
|
+
Before anything else, because conflating these is the mistake that produces
|
|
159
|
+
wrong fixes:
|
|
160
|
+
|
|
161
|
+
| | what it is | what ends it |
|
|
162
|
+
|---|---|---|
|
|
163
|
+
| **the connection** | one TCP socket | you, the network, a timeout firing. **Ends nothing on the server** |
|
|
164
|
+
| **the HTTP session** | the ICF conversation, held by the cookie jar on this side | dropping the cookies, or a logoff. Nothing about it is in doubt |
|
|
165
|
+
| **the ABAP session** | the server-side context named by `SAP_SESSIONID_<SID>_<CLIENT>` — the roll area a stateful request runs in | **only the server.** Its own idle timeout, which this side can neither read nor influence, or an explicit logoff |
|
|
166
|
+
| **the lock** | an enqueue entry, **owned by the ABAP session** | that ABAP session ending. **When it goes, its locks go with it** |
|
|
167
|
+
|
|
168
|
+
The two middle rows are the ones worth keeping apart, because everything that
|
|
169
|
+
goes wrong here goes wrong between them. The lock does not belong to the socket
|
|
170
|
+
and does not belong to the cookies — it belongs to the **ABAP session**, and
|
|
171
|
+
that session is the server's. This side never ends one: it can ask (a logoff),
|
|
172
|
+
and otherwise waits for a timeout it cannot see.
|
|
173
|
+
|
|
174
|
+
A closed socket is just a client that stopped listening. The ABAP session
|
|
175
|
+
neither knows nor cares — which is why `SM04` shows sessions left behind by
|
|
176
|
+
connections that never said they were finished, and why this package sends an
|
|
177
|
+
explicit goodbye at all.
|
|
178
|
+
|
|
179
|
+
And **a lock is never stranded by something dying** — the opposite. If the ABAP
|
|
180
|
+
session ended, the enqueue entry went with it and there is nothing left to
|
|
181
|
+
strand. A lock is stranded when that session **survives** while the caller no
|
|
182
|
+
longer holds the handle needed to unlock it: both sit there, held, until the
|
|
183
|
+
server's own timeout releases them together.
|
|
184
|
+
|
|
185
|
+
This is what makes an aborted request expensive, and it is not what it looks
|
|
186
|
+
like. Aborting costs you **knowledge**, not a session: whether the modification
|
|
187
|
+
was applied becomes unknowable, and the handle `unlock` needs is gone, while the
|
|
188
|
+
lock is still very much held. That — not any teardown — is why
|
|
189
|
+
`beginCriticalSection()` raises the effective timeout to a large ceiling for the
|
|
190
|
+
duration of a `lock → modify → unlock` chain.
|
|
191
|
+
|
|
192
|
+
### Two sessions, and they are not the same thing
|
|
193
|
+
|
|
194
|
+
There are two sessions here, and they are not the same thing:
|
|
195
|
+
|
|
196
|
+
- the **HTTP session** — the conversation this client is having. It exists as
|
|
197
|
+
long as the cookies do, and nothing about it is in doubt;
|
|
198
|
+
- the **ABAP session** — named by `SAP_SESSIONID_<SID>_<CLIENT>`, and the thing
|
|
199
|
+
locks are bound to.
|
|
200
|
+
|
|
201
|
+
The doubt is only ever about the second. It may not have been created, or it may
|
|
202
|
+
have been terminated while this client still holds the cookies and believes it
|
|
203
|
+
has one — and a lock is dead in either case, whether or not the client noticed.
|
|
204
|
+
|
|
205
|
+
Two ways to lose one, and they are different problems:
|
|
206
|
+
|
|
207
|
+
- **The server never opened one.** No `SAP_SESSIONID` came back, so there is no
|
|
208
|
+
ABAP session known to this connection and nothing a lock could be bound to —
|
|
209
|
+
while the HTTP side is perfectly fine, which is why it does not look like a
|
|
210
|
+
failure at all. `connect()` refuses rather than handing back a connection
|
|
211
|
+
whose first lock would be dead on arrival. Sessions are limited per user and shared with every other tool
|
|
212
|
+
logged on as them, so this says nothing about your code — it says the system
|
|
213
|
+
would not open another one right now.
|
|
214
|
+
- **It timed out while you were quiet.** The timeout is an idle one, and it is
|
|
215
|
+
the *silence* that spends it, not the elapsed time. Measured on an on-prem
|
|
216
|
+
system with a 30-minute window: a small request once a minute kept one session
|
|
217
|
+
alive for 45 minutes with its identity unchanged, straight past the mark.
|
|
218
|
+
|
|
219
|
+
So a long chain under a lock is safe while it is doing something, and at risk
|
|
220
|
+
while it waits. **Any request in the session resets the window** — a poll, a
|
|
221
|
+
read, a status check. There is deliberately no keepalive timer in this package:
|
|
222
|
+
holding a session alive means holding a scarce, shared slot, and deciding to do
|
|
223
|
+
that belongs to the caller who knows why the session is worth keeping.
|
|
224
|
+
|
|
225
|
+
**The cookies are the session.** Nothing else ties a caller to one, so a second
|
|
226
|
+
connection given the same cookie jar works in the same ABAP session and can use
|
|
227
|
+
the locks taken in it — that is what makes handing them over a way to continue
|
|
228
|
+
someone else's work. It cuts both ways: `disconnect()` ends the session for
|
|
229
|
+
everyone holding those cookies, not just for the object it was called on, and no
|
|
230
|
+
connection can see the copies. Deciding who may hold them, and who is allowed to
|
|
231
|
+
close, belongs to whoever passes them around.
|
|
232
|
+
|
|
233
|
+
The server never tells the client how long it has: the session cookie carries no
|
|
234
|
+
expiry and no response header mentions one. The only honest signals are the ones
|
|
235
|
+
you get by asking — `getSessionIdentity()` for which session you are in, and
|
|
236
|
+
`ADT_SESSION_REPLACED` when the one you were in is gone.
|
|
237
|
+
|
|
105
238
|
## Troubleshooting
|
|
106
239
|
|
|
107
|
-
- **CSRF token errors**: discard the session and establish a new one.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
240
|
+
- **CSRF token errors**: discard the session and establish a new one. That is
|
|
241
|
+
`disconnect()` followed by `connect()` — there is no local-only discard, because
|
|
242
|
+
dropping the cookie leaves the ABAP session open on the server. Both live on
|
|
243
|
+
the HTTP connection classes and are **not** on the `IAbapConnection` type
|
|
244
|
+
a bare `IAbapConnection` carries — reach them through a connector type, or
|
|
245
|
+
through the `ISessionLifecycleAware` atom:
|
|
111
246
|
|
|
112
247
|
```ts
|
|
113
|
-
|
|
248
|
+
import { AdtOnPremConnector, BasicAuthProvider, OnPremHttpTransport } from '@mcp-abap-adt/connection';
|
|
114
249
|
|
|
115
|
-
const connection = new
|
|
116
|
-
connection.
|
|
117
|
-
await connection.connect();
|
|
250
|
+
const connection = new AdtOnPremConnector(config, new BasicAuthProvider(user, pass), new OnPremHttpTransport(() => ({}), logger, { client: config.client, baseUrl: config.url }), logger);
|
|
251
|
+
await connection.disconnect(); // ends the session on the server, then clears
|
|
252
|
+
await connection.connect(); // a new session, explicitly
|
|
118
253
|
```
|
|
119
254
|
- **Session expired**: reauthenticate to obtain a new session.
|
|
120
|
-
- **Multiple connections**: each
|
|
255
|
+
- **Multiple connections**: each connector holds its own wire, and the wire holds the cookie jar; share the instance if you need continuity.
|
|
121
256
|
|
|
122
257
|
---
|
|
123
258
|
|