@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
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* An ADT request carried over RFC.
|
|
4
|
+
*
|
|
5
|
+
* `SADT_REST_RFC_ENDPOINT` is the standard FM Eclipse ADT uses for on-prem ADT
|
|
6
|
+
* through JCo, and what it carries is a request line, header fields and a body
|
|
7
|
+
* — an HTTP request in all but the wire. So this translates, and translating is
|
|
8
|
+
* the whole of its job.
|
|
9
|
+
*
|
|
10
|
+
* It exists because an RFC conversation is inherently stateful: one ABAP
|
|
11
|
+
* session for the connection's lifetime, which is the way through HTTP 423
|
|
12
|
+
* "invalid lock handle" on legacy systems (BASIS < 7.50) where stateful HTTP
|
|
13
|
+
* sessions are not usable.
|
|
14
|
+
*
|
|
15
|
+
* **What is NOT here.** Cookies, the CSRF token, `x-sap-adt-sessiontype` and
|
|
16
|
+
* the session lifecycle belong to the connection above this seam and are
|
|
17
|
+
* already in `request.headers` by the time `send()` is called. A transport that
|
|
18
|
+
* also captured cookies would be doing that work twice, and the two copies
|
|
19
|
+
* would disagree the first time one of them was cleared.
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.RfcTransport = void 0;
|
|
23
|
+
const node_crypto_1 = require("node:crypto");
|
|
24
|
+
/**
|
|
25
|
+
* ADT exception types, mapped to what they would have been over HTTP. Standard
|
|
26
|
+
* types, returned in `<exc:exception>` XML.
|
|
27
|
+
*/
|
|
28
|
+
const EXCEPTION_STATUS = {
|
|
29
|
+
ExceptionResourceNotFound: { code: 404, text: 'Not Found' },
|
|
30
|
+
ExceptionResourceNoAuthorization: { code: 403, text: 'Forbidden' },
|
|
31
|
+
ExceptionResourceAlreadyExists: { code: 409, text: 'Conflict' },
|
|
32
|
+
ExceptionResourceLocked: { code: 423, text: 'Locked' },
|
|
33
|
+
ExceptionBadRequest: { code: 400, text: 'Bad Request' },
|
|
34
|
+
ExceptionNotSupported: { code: 501, text: 'Not Implemented' },
|
|
35
|
+
ExceptionConflict: { code: 409, text: 'Conflict' },
|
|
36
|
+
};
|
|
37
|
+
function statusFromExceptionXml(body) {
|
|
38
|
+
const type = /<exc:type[^>]*>([^<]+)<\/exc:type>/.exec(body)?.[1]?.trim();
|
|
39
|
+
return ((type ? EXCEPTION_STATUS[type] : undefined) ?? {
|
|
40
|
+
code: 500,
|
|
41
|
+
text: 'Internal Server Error',
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function message(e) {
|
|
45
|
+
if (e instanceof Error)
|
|
46
|
+
return e.message;
|
|
47
|
+
if (e && typeof e === 'object')
|
|
48
|
+
return JSON.stringify(e);
|
|
49
|
+
return String(e);
|
|
50
|
+
}
|
|
51
|
+
/** Axios's own default, which the classification above this seam is written against. */
|
|
52
|
+
const admits2xx = (status) => status >= 200 && status < 300;
|
|
53
|
+
class RfcTransport {
|
|
54
|
+
connect;
|
|
55
|
+
logger;
|
|
56
|
+
kind = 'rfc';
|
|
57
|
+
/** Which system this wire is for. Read by the compiler, never at runtime. */
|
|
58
|
+
system = 'onprem';
|
|
59
|
+
conversation = null;
|
|
60
|
+
/** Names the conversation, and so the ABAP session it carries. */
|
|
61
|
+
conversationId = '';
|
|
62
|
+
/**
|
|
63
|
+
* Nothing to fold in. `SADT_REST_RFC_ENDPOINT` answers with two header
|
|
64
|
+
* fields — `~server_protocol` and `content-type` — on every call, including
|
|
65
|
+
* one that asks for a token with `x-csrf-token: fetch`, and on a stateful
|
|
66
|
+
* POST. Measured on E19. There is no ICM in this path, so there is no ICF
|
|
67
|
+
* session to cookie and no application server to be redispatched between.
|
|
68
|
+
*/
|
|
69
|
+
ingest() { }
|
|
70
|
+
/** None, and never any: see `ingest()`. */
|
|
71
|
+
cookies() {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Nothing to establish. `open()` made the ABAP session, and there is no token
|
|
76
|
+
* to earn: this endpoint answers `x-csrf-token: fetch` with the same two
|
|
77
|
+
* header fields it answers everything else with. A wire with no
|
|
78
|
+
* cross-site request to forge against needs no token to prove one was not.
|
|
79
|
+
*/
|
|
80
|
+
async establish(_context) { }
|
|
81
|
+
/** None: see `establish()`. */
|
|
82
|
+
csrfToken() {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Ignored. Nothing on this wire reads a token, and keeping one would be
|
|
87
|
+
* state that never leaves the object.
|
|
88
|
+
*/
|
|
89
|
+
adoptCsrfToken() { }
|
|
90
|
+
/**
|
|
91
|
+
* The conversation, which IS the session — so it is fingerprinted by its own
|
|
92
|
+
* existence rather than by an address the server hands out.
|
|
93
|
+
*/
|
|
94
|
+
/** The conversation IS the session, so it exists exactly while that does. */
|
|
95
|
+
sessionEstablished() {
|
|
96
|
+
return this.conversation?.alive ?? false;
|
|
97
|
+
}
|
|
98
|
+
sessionFingerprint() {
|
|
99
|
+
const fingerprint = new Map();
|
|
100
|
+
if (this.conversation?.alive) {
|
|
101
|
+
fingerprint.set('rfc-conversation', this.conversationId);
|
|
102
|
+
}
|
|
103
|
+
return fingerprint;
|
|
104
|
+
}
|
|
105
|
+
/** None: there is no dispatcher in front of this wire to stay bound to. */
|
|
106
|
+
affinityHeaders() {
|
|
107
|
+
return {};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Nothing to forget separately. The session ends when the conversation does,
|
|
111
|
+
* and that is `close()` — a wire whose state could be dropped while the
|
|
112
|
+
* conversation stayed open would be claiming a session it had disowned.
|
|
113
|
+
*/
|
|
114
|
+
forgetSession() { }
|
|
115
|
+
/**
|
|
116
|
+
* The client is built by a factory rather than constructed here: the SDK is
|
|
117
|
+
* an optional dependency loaded by `require` at open time, and a transport
|
|
118
|
+
* that reached for it in its constructor could not be built at all on a
|
|
119
|
+
* machine without it.
|
|
120
|
+
*/
|
|
121
|
+
constructor(connect, logger = null) {
|
|
122
|
+
this.connect = connect;
|
|
123
|
+
this.logger = logger;
|
|
124
|
+
}
|
|
125
|
+
async open() {
|
|
126
|
+
if (this.conversation?.alive)
|
|
127
|
+
return;
|
|
128
|
+
const conversation = this.connect();
|
|
129
|
+
try {
|
|
130
|
+
await conversation.open();
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
throw new Error(`Failed to open RFC connection: ${message(e)}`);
|
|
134
|
+
}
|
|
135
|
+
this.conversation = conversation;
|
|
136
|
+
// Minted here, where the session begins. A conversation opened after an
|
|
137
|
+
// earlier one closed is a DIFFERENT ABAP session, and the fingerprint has
|
|
138
|
+
// to say so — a constant would report `unchanged` across a reconnect and
|
|
139
|
+
// hide exactly the replacement the identity policy exists to catch.
|
|
140
|
+
this.conversationId = (0, node_crypto_1.randomUUID)();
|
|
141
|
+
this.logger?.debug('RFC conversation opened (stateful by nature)');
|
|
142
|
+
}
|
|
143
|
+
/** Never throws, and a repeat call finds nothing owed. */
|
|
144
|
+
async close() {
|
|
145
|
+
const conversation = this.conversation;
|
|
146
|
+
if (!conversation)
|
|
147
|
+
return;
|
|
148
|
+
this.conversation = null;
|
|
149
|
+
this.conversationId = '';
|
|
150
|
+
try {
|
|
151
|
+
await conversation.close();
|
|
152
|
+
this.logger?.debug('RFC conversation closed');
|
|
153
|
+
}
|
|
154
|
+
catch (e) {
|
|
155
|
+
this.logger?.debug(`RFC close error: ${message(e)}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async send(request) {
|
|
159
|
+
if (!this.conversation?.alive) {
|
|
160
|
+
throw new Error('RFC transport is not open. Call connect() first.');
|
|
161
|
+
}
|
|
162
|
+
const method = request.method.toUpperCase();
|
|
163
|
+
// HTTP clients serialise `params`; RFC has no such step, so they go into
|
|
164
|
+
// the URI here or they do not travel at all.
|
|
165
|
+
let uri = request.url;
|
|
166
|
+
const query = Object.entries(request.params ?? {}).filter(([, v]) => v !== undefined && v !== null);
|
|
167
|
+
if (query.length > 0) {
|
|
168
|
+
const serialised = new URLSearchParams(query.map(([k, v]) => [k, String(v)])).toString();
|
|
169
|
+
uri += (uri.includes('?') ? '&' : '?') + serialised;
|
|
170
|
+
}
|
|
171
|
+
const headerFields = Object.entries(request.headers ?? {}).map(([NAME, VALUE]) => ({ NAME, VALUE: String(VALUE) }));
|
|
172
|
+
// ADT refuses a request with no Accept — `400 ExceptionResourceBadRequest:
|
|
173
|
+
// Accept header missing`, measured on E19. Over HTTP axios supplies the
|
|
174
|
+
// default and nobody notices; this endpoint forwards only what it is
|
|
175
|
+
// handed, so without this the same call dies on this wire alone.
|
|
176
|
+
if (!headerFields.some((h) => h.NAME.toLowerCase() === 'accept')) {
|
|
177
|
+
headerFields.push({ NAME: 'Accept', VALUE: '*/*' });
|
|
178
|
+
}
|
|
179
|
+
const body = request.data !== undefined && request.data !== null
|
|
180
|
+
? String(request.data)
|
|
181
|
+
: '';
|
|
182
|
+
if (body &&
|
|
183
|
+
!headerFields.some((h) => h.NAME.toLowerCase() === 'content-type')) {
|
|
184
|
+
headerFields.push({
|
|
185
|
+
NAME: 'Content-Type',
|
|
186
|
+
VALUE: 'text/plain; charset=utf-8',
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
this.logger?.debug(`RFC → ${method} ${uri}`);
|
|
190
|
+
let raw;
|
|
191
|
+
try {
|
|
192
|
+
raw = await this.conversation.call('SADT_REST_RFC_ENDPOINT', {
|
|
193
|
+
REQUEST: {
|
|
194
|
+
REQUEST_LINE: {
|
|
195
|
+
METHOD: method,
|
|
196
|
+
URI: uri,
|
|
197
|
+
VERSION: 'HTTP/1.1',
|
|
198
|
+
},
|
|
199
|
+
HEADER_FIELDS: headerFields,
|
|
200
|
+
MESSAGE_BODY: body ? Buffer.from(body, 'utf-8') : Buffer.alloc(0),
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
catch (e) {
|
|
205
|
+
// An RFC-level failure, not an answer: nothing came back to classify.
|
|
206
|
+
const msg = message(e);
|
|
207
|
+
this.logger?.error(`RFC call failed: ${msg}`);
|
|
208
|
+
throw new Error(`RFC call to SADT_REST_RFC_ENDPOINT failed: ${msg}`);
|
|
209
|
+
}
|
|
210
|
+
const answer = raw.RESPONSE ?? raw;
|
|
211
|
+
const rawCode = answer.STATUS_LINE?.STATUS_CODE ?? answer.STATUS_LINE?.CODE;
|
|
212
|
+
let status = typeof rawCode === 'string'
|
|
213
|
+
? Number.parseInt(rawCode, 10)
|
|
214
|
+
: (rawCode ?? 0);
|
|
215
|
+
let statusText = answer.STATUS_LINE?.REASON_PHRASE ?? answer.STATUS_LINE?.REASON ?? '';
|
|
216
|
+
const data = answer.MESSAGE_BODY
|
|
217
|
+
? Buffer.isBuffer(answer.MESSAGE_BODY)
|
|
218
|
+
? answer.MESSAGE_BODY.toString('utf-8')
|
|
219
|
+
: String(answer.MESSAGE_BODY)
|
|
220
|
+
: '';
|
|
221
|
+
const headers = {};
|
|
222
|
+
for (const field of answer.HEADER_FIELDS ?? []) {
|
|
223
|
+
if (field.NAME && field.VALUE !== undefined) {
|
|
224
|
+
headers[String(field.NAME).toLowerCase()] = field.VALUE;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// BASIS < 7.50 answers without populating STATUS_LINE. Left alone, a
|
|
228
|
+
// failure would read as 200 with an error document in the body — which is
|
|
229
|
+
// the green run that inspected nothing, one layer down.
|
|
230
|
+
if (!status && data.includes('<exc:exception')) {
|
|
231
|
+
const detected = statusFromExceptionXml(data);
|
|
232
|
+
status = detected.code;
|
|
233
|
+
statusText = detected.text;
|
|
234
|
+
this.logger?.debug(`RFC: STATUS_LINE empty, read ${status} out of the exception XML`);
|
|
235
|
+
}
|
|
236
|
+
if (!status) {
|
|
237
|
+
status = 200;
|
|
238
|
+
statusText = statusText || 'OK';
|
|
239
|
+
}
|
|
240
|
+
this.logger?.debug(`RFC ← ${status} ${statusText} (${data.length} bytes)`);
|
|
241
|
+
const response = {
|
|
242
|
+
status,
|
|
243
|
+
statusText,
|
|
244
|
+
headers,
|
|
245
|
+
data,
|
|
246
|
+
};
|
|
247
|
+
const admits = request.validateStatus ?? admits2xx;
|
|
248
|
+
if (!admits(status)) {
|
|
249
|
+
const error = new Error(`Request failed with status ${status}: ${method} ${uri}`);
|
|
250
|
+
error.response = response;
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
return response;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
exports.RfcTransport = RfcTransport;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An RFC conversation, from the config a consumer already holds.
|
|
3
|
+
*
|
|
4
|
+
* `RfcTransport` takes a factory rather than connection parameters, and that is
|
|
5
|
+
* deliberate: `@mcp-abap-adt/sap-rfc-lite` needs the SAP NW RFC SDK on the
|
|
6
|
+
* machine, and a transport that reached for it in its constructor could not be
|
|
7
|
+
* built at all where it is absent. But a factory is a poor front door — without
|
|
8
|
+
* this, reaching the RFC wire means every consumer writing the `require` and
|
|
9
|
+
* deriving `ashost` and `sysnr` for themselves, which is the derivation the
|
|
10
|
+
* per-transport connection class used to do for them.
|
|
11
|
+
*
|
|
12
|
+
* So the derivation lives here, once, and the SDK is still only touched when a
|
|
13
|
+
* conversation is actually opened.
|
|
14
|
+
*/
|
|
15
|
+
import type { SapConfig } from '../config/sapConfig.js';
|
|
16
|
+
import type { IRfcConversation } from './RfcTransport.js';
|
|
17
|
+
/** What the native client is constructed with. */
|
|
18
|
+
export interface RfcConnectionParams {
|
|
19
|
+
ashost: string;
|
|
20
|
+
sysnr: string;
|
|
21
|
+
client: string;
|
|
22
|
+
user: string;
|
|
23
|
+
passwd: string;
|
|
24
|
+
lang: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Read out of the config, never asked of the server.
|
|
28
|
+
*
|
|
29
|
+
* The system number comes off the HTTP port by the SAP convention that 80XX is
|
|
30
|
+
* the ICM port for system XX — the convention the port itself follows, not a
|
|
31
|
+
* guess about the deployment. `SAP_SYSNR` overrides it for a port that follows
|
|
32
|
+
* no convention, which is the case on a system reached through 50400.
|
|
33
|
+
*/
|
|
34
|
+
export declare function rfcParamsFrom(config: SapConfig): RfcConnectionParams;
|
|
35
|
+
/**
|
|
36
|
+
* The factory `RfcTransport` asks for.
|
|
37
|
+
*
|
|
38
|
+
* The SDK is loaded when a conversation is opened, not when this is called, so
|
|
39
|
+
* a consumer can build the transport on a machine that has no SDK and find out
|
|
40
|
+
* at `connect()` rather than at construction — with a message that says what to
|
|
41
|
+
* install.
|
|
42
|
+
*/
|
|
43
|
+
export declare function rfcConversationFrom(config: SapConfig): () => IRfcConversation;
|
|
44
|
+
//# sourceMappingURL=rfcConversation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rfcConversation.d.ts","sourceRoot":"","sources":["../../src/connection/rfcConversation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,kDAAkD;AAClD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,mBAAmB,CAoBpE;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,gBAAgB,CAmB7E"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* An RFC conversation, from the config a consumer already holds.
|
|
4
|
+
*
|
|
5
|
+
* `RfcTransport` takes a factory rather than connection parameters, and that is
|
|
6
|
+
* deliberate: `@mcp-abap-adt/sap-rfc-lite` needs the SAP NW RFC SDK on the
|
|
7
|
+
* machine, and a transport that reached for it in its constructor could not be
|
|
8
|
+
* built at all where it is absent. But a factory is a poor front door — without
|
|
9
|
+
* this, reaching the RFC wire means every consumer writing the `require` and
|
|
10
|
+
* deriving `ashost` and `sysnr` for themselves, which is the derivation the
|
|
11
|
+
* per-transport connection class used to do for them.
|
|
12
|
+
*
|
|
13
|
+
* So the derivation lives here, once, and the SDK is still only touched when a
|
|
14
|
+
* conversation is actually opened.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.rfcParamsFrom = rfcParamsFrom;
|
|
18
|
+
exports.rfcConversationFrom = rfcConversationFrom;
|
|
19
|
+
/**
|
|
20
|
+
* Read out of the config, never asked of the server.
|
|
21
|
+
*
|
|
22
|
+
* The system number comes off the HTTP port by the SAP convention that 80XX is
|
|
23
|
+
* the ICM port for system XX — the convention the port itself follows, not a
|
|
24
|
+
* guess about the deployment. `SAP_SYSNR` overrides it for a port that follows
|
|
25
|
+
* no convention, which is the case on a system reached through 50400.
|
|
26
|
+
*/
|
|
27
|
+
function rfcParamsFrom(config) {
|
|
28
|
+
if (!config.url) {
|
|
29
|
+
throw new Error('An RFC conversation needs a url to take its host from');
|
|
30
|
+
}
|
|
31
|
+
if (!config.username || !config.password) {
|
|
32
|
+
throw new Error('An RFC conversation needs both a username and a password');
|
|
33
|
+
}
|
|
34
|
+
const parsed = new URL(config.url);
|
|
35
|
+
const port = Number.parseInt(parsed.port || '8000', 10);
|
|
36
|
+
const derived = String(port - 8000).padStart(2, '0');
|
|
37
|
+
return {
|
|
38
|
+
ashost: parsed.hostname,
|
|
39
|
+
sysnr: process.env.SAP_SYSNR?.trim() || derived,
|
|
40
|
+
client: config.client || '000',
|
|
41
|
+
user: config.username,
|
|
42
|
+
passwd: config.password,
|
|
43
|
+
lang: 'EN',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The factory `RfcTransport` asks for.
|
|
48
|
+
*
|
|
49
|
+
* The SDK is loaded when a conversation is opened, not when this is called, so
|
|
50
|
+
* a consumer can build the transport on a machine that has no SDK and find out
|
|
51
|
+
* at `connect()` rather than at construction — with a message that says what to
|
|
52
|
+
* install.
|
|
53
|
+
*/
|
|
54
|
+
function rfcConversationFrom(config) {
|
|
55
|
+
const params = rfcParamsFrom(config);
|
|
56
|
+
return () => {
|
|
57
|
+
let Client;
|
|
58
|
+
try {
|
|
59
|
+
// Dynamic, because the SDK is an optional peer: it needs the SAP NW RFC
|
|
60
|
+
// SDK installed on the machine, and most consumers travel over HTTP.
|
|
61
|
+
// biome-ignore lint/correctness/noNodejsModules: optional by design
|
|
62
|
+
Client = require('@mcp-abap-adt/sap-rfc-lite').Client;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
throw new Error('@mcp-abap-adt/sap-rfc-lite is not available. To take the RFC wire, ' +
|
|
66
|
+
'install the SAP NW RFC SDK and run: npm install @mcp-abap-adt/sap-rfc-lite. ' +
|
|
67
|
+
`Details: ${error instanceof Error ? error.message : String(error)}`);
|
|
68
|
+
}
|
|
69
|
+
return new Client(params);
|
|
70
|
+
};
|
|
71
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
export type { IWebSocketCloseInfo, IWebSocketConnectOptions, IWebSocketMessageEnvelope, IWebSocketMessageHandler, IWebSocketTransport, } from '@mcp-abap-adt/interfaces';
|
|
2
2
|
export { FileCertificateMaterialLoader } from './auth/FileCertificateMaterialLoader.js';
|
|
3
|
+
export { BasicAuthProvider, CertificateAuthProvider, SamlAuthProvider, TokenAuthProvider, } from './auth/providers.js';
|
|
3
4
|
export type { SapAuthType, SapConfig, SapConnectionType, } from './config/sapConfig.js';
|
|
4
5
|
export { sapConfigSignature } from './config/sapConfig.js';
|
|
5
6
|
export type { AbapConnection, AbapRequestOptions, } from './connection/AbapConnection.js';
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
8
|
-
export {
|
|
7
|
+
export { AdtCloudConnector } from './connection/AdtCloudConnector.js';
|
|
8
|
+
export { AdtOnPremConnector } from './connection/AdtOnPremConnector.js';
|
|
9
|
+
export { CloudHttpTransport } from './connection/CloudHttpTransport.js';
|
|
9
10
|
export { CSRF_CONFIG, CSRF_ERROR_MESSAGES } from './connection/csrfConfig.js';
|
|
10
11
|
export { GenericWebSocketTransport, type IWebSocketFactory, type IWebSocketLike, } from './connection/GenericWebSocketTransport.js';
|
|
11
|
-
export {
|
|
12
|
-
export {
|
|
13
|
-
export {
|
|
14
|
-
export {
|
|
12
|
+
export { HttpTransport } from './connection/HttpTransport.js';
|
|
13
|
+
export type { IAdtEstablishContext, IAdtSessionContext, IAdtTransport, IAdtTransportRequest, IAdtTransportResponse, ICloudTransport, IOnPremTransport, } from './connection/IAdtTransport.js';
|
|
14
|
+
export { LegacyOnPremHttpTransport } from './connection/LegacyOnPremHttpTransport.js';
|
|
15
|
+
export { OnPremHttpTransport } from './connection/OnPremHttpTransport.js';
|
|
16
|
+
export { type IRfcConversation, RfcTransport, } from './connection/RfcTransport.js';
|
|
17
|
+
export { type RfcConnectionParams, rfcConversationFrom, rfcParamsFrom, } from './connection/rfcConversation.js';
|
|
15
18
|
export type { ILogger } from './logger.js';
|
|
16
19
|
export { getTimeout, getTimeoutConfig, type TimeoutConfig, } from './utils/timeouts.js';
|
|
17
20
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,YAAY,EACV,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,EACzB,wBAAwB,EACxB,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,6BAA6B,EAAE,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,YAAY,EACV,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,EACzB,wBAAwB,EACxB,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,6BAA6B,EAAE,MAAM,yCAAyC,CAAC;AAKxF,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,WAAW,EACX,SAAS,EACT,iBAAiB,GAClB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,YAAY,EACV,cAAc,EACd,kBAAkB,GACnB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAQxE,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EACL,yBAAyB,EACzB,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,2CAA2C,CAAC;AAEnD,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,YAAY,EACV,oBAAoB,EACpB,kBAAkB,EAClB,aAAa,EACb,oBAAoB,EACpB,qBAAqB,EACrB,eAAe,EACf,gBAAgB,GACjB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EACL,KAAK,gBAAgB,EACrB,YAAY,GACb,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EACL,KAAK,mBAAmB,EACxB,mBAAmB,EACnB,aAAa,GACd,MAAM,iCAAiC,CAAC;AACzC,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,KAAK,aAAa,GACnB,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,41 +1,53 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// Types - re-exported from interfaces package with backward compatibility aliases
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.getTimeoutConfig = exports.getTimeout = exports.
|
|
4
|
+
exports.getTimeoutConfig = exports.getTimeout = exports.rfcParamsFrom = exports.rfcConversationFrom = exports.RfcTransport = exports.OnPremHttpTransport = exports.LegacyOnPremHttpTransport = exports.HttpTransport = exports.GenericWebSocketTransport = exports.CSRF_ERROR_MESSAGES = exports.CSRF_CONFIG = exports.CloudHttpTransport = exports.AdtOnPremConnector = exports.AdtCloudConnector = exports.sapConfigSignature = exports.TokenAuthProvider = exports.SamlAuthProvider = exports.CertificateAuthProvider = exports.BasicAuthProvider = exports.FileCertificateMaterialLoader = void 0;
|
|
5
5
|
var FileCertificateMaterialLoader_js_1 = require("./auth/FileCertificateMaterialLoader.js");
|
|
6
6
|
Object.defineProperty(exports, "FileCertificateMaterialLoader", { enumerable: true, get: function () { return FileCertificateMaterialLoader_js_1.FileCertificateMaterialLoader; } });
|
|
7
|
+
// IAuthProvider is deliberately NOT re-exported. It lives in
|
|
8
|
+
// @mcp-abap-adt/interfaces and a consumer imports it from there — the same rule
|
|
9
|
+
// the session-lifecycle vocabulary follows below, and for the same reason: two
|
|
10
|
+
// names for one contract let the two drift.
|
|
11
|
+
var providers_js_1 = require("./auth/providers.js");
|
|
12
|
+
Object.defineProperty(exports, "BasicAuthProvider", { enumerable: true, get: function () { return providers_js_1.BasicAuthProvider; } });
|
|
13
|
+
Object.defineProperty(exports, "CertificateAuthProvider", { enumerable: true, get: function () { return providers_js_1.CertificateAuthProvider; } });
|
|
14
|
+
Object.defineProperty(exports, "SamlAuthProvider", { enumerable: true, get: function () { return providers_js_1.SamlAuthProvider; } });
|
|
15
|
+
Object.defineProperty(exports, "TokenAuthProvider", { enumerable: true, get: function () { return providers_js_1.TokenAuthProvider; } });
|
|
7
16
|
// Config utilities
|
|
8
17
|
var sapConfig_js_1 = require("./config/sapConfig.js");
|
|
9
18
|
Object.defineProperty(exports, "sapConfigSignature", { enumerable: true, get: function () { return sapConfig_js_1.sapConfigSignature; } });
|
|
19
|
+
var AdtCloudConnector_js_1 = require("./connection/AdtCloudConnector.js");
|
|
20
|
+
Object.defineProperty(exports, "AdtCloudConnector", { enumerable: true, get: function () { return AdtCloudConnector_js_1.AdtCloudConnector; } });
|
|
21
|
+
var AdtOnPremConnector_js_1 = require("./connection/AdtOnPremConnector.js");
|
|
22
|
+
Object.defineProperty(exports, "AdtOnPremConnector", { enumerable: true, get: function () { return AdtOnPremConnector_js_1.AdtOnPremConnector; } });
|
|
23
|
+
var CloudHttpTransport_js_1 = require("./connection/CloudHttpTransport.js");
|
|
24
|
+
Object.defineProperty(exports, "CloudHttpTransport", { enumerable: true, get: function () { return CloudHttpTransport_js_1.CloudHttpTransport; } });
|
|
10
25
|
// The session lifecycle vocabulary — ISessionLifecycleAware, ADT_SESSION_ERROR —
|
|
11
26
|
// is deliberately NOT exported here. It lives in @mcp-abap-adt/interfaces, and a
|
|
12
27
|
// consumer imports it from there: re-exporting a contract type gives it two
|
|
13
28
|
// names and lets the two drift.
|
|
14
29
|
// Connection classes - only final implementations
|
|
15
30
|
// Deprecated aliases for backward compatibility
|
|
16
|
-
var BaseAbapConnection_js_1 = require("./connection/BaseAbapConnection.js");
|
|
17
|
-
Object.defineProperty(exports, "BaseAbapConnection", { enumerable: true, get: function () { return BaseAbapConnection_js_1.BaseAbapConnection; } });
|
|
18
|
-
Object.defineProperty(exports, "OnPremAbapConnection", { enumerable: true, get: function () { return BaseAbapConnection_js_1.BaseAbapConnection; } });
|
|
19
|
-
var CertificateAbapConnection_js_1 = require("./connection/CertificateAbapConnection.js");
|
|
20
|
-
Object.defineProperty(exports, "CertificateAbapConnection", { enumerable: true, get: function () { return CertificateAbapConnection_js_1.CertificateAbapConnection; } });
|
|
21
|
-
// Factory
|
|
22
|
-
var connectionFactory_js_1 = require("./connection/connectionFactory.js");
|
|
23
|
-
Object.defineProperty(exports, "createAbapConnection", { enumerable: true, get: function () { return connectionFactory_js_1.createAbapConnection; } });
|
|
24
31
|
// CSRF configuration
|
|
25
32
|
var csrfConfig_js_1 = require("./connection/csrfConfig.js");
|
|
26
33
|
Object.defineProperty(exports, "CSRF_CONFIG", { enumerable: true, get: function () { return csrfConfig_js_1.CSRF_CONFIG; } });
|
|
27
34
|
Object.defineProperty(exports, "CSRF_ERROR_MESSAGES", { enumerable: true, get: function () { return csrfConfig_js_1.CSRF_ERROR_MESSAGES; } });
|
|
28
35
|
var GenericWebSocketTransport_js_1 = require("./connection/GenericWebSocketTransport.js");
|
|
29
36
|
Object.defineProperty(exports, "GenericWebSocketTransport", { enumerable: true, get: function () { return GenericWebSocketTransport_js_1.GenericWebSocketTransport; } });
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
Object.defineProperty(exports, "
|
|
33
|
-
var
|
|
34
|
-
Object.defineProperty(exports, "
|
|
35
|
-
var
|
|
36
|
-
Object.defineProperty(exports, "
|
|
37
|
-
var
|
|
38
|
-
Object.defineProperty(exports, "
|
|
37
|
+
// The transport axis. Both ends are objects, so a caller can name either.
|
|
38
|
+
var HttpTransport_js_1 = require("./connection/HttpTransport.js");
|
|
39
|
+
Object.defineProperty(exports, "HttpTransport", { enumerable: true, get: function () { return HttpTransport_js_1.HttpTransport; } });
|
|
40
|
+
var LegacyOnPremHttpTransport_js_1 = require("./connection/LegacyOnPremHttpTransport.js");
|
|
41
|
+
Object.defineProperty(exports, "LegacyOnPremHttpTransport", { enumerable: true, get: function () { return LegacyOnPremHttpTransport_js_1.LegacyOnPremHttpTransport; } });
|
|
42
|
+
var OnPremHttpTransport_js_1 = require("./connection/OnPremHttpTransport.js");
|
|
43
|
+
Object.defineProperty(exports, "OnPremHttpTransport", { enumerable: true, get: function () { return OnPremHttpTransport_js_1.OnPremHttpTransport; } });
|
|
44
|
+
var RfcTransport_js_1 = require("./connection/RfcTransport.js");
|
|
45
|
+
Object.defineProperty(exports, "RfcTransport", { enumerable: true, get: function () { return RfcTransport_js_1.RfcTransport; } });
|
|
46
|
+
// The front door to the RFC wire: the derivation a consumer would otherwise
|
|
47
|
+
// copy, and the SDK loaded only when a conversation is opened.
|
|
48
|
+
var rfcConversation_js_1 = require("./connection/rfcConversation.js");
|
|
49
|
+
Object.defineProperty(exports, "rfcConversationFrom", { enumerable: true, get: function () { return rfcConversation_js_1.rfcConversationFrom; } });
|
|
50
|
+
Object.defineProperty(exports, "rfcParamsFrom", { enumerable: true, get: function () { return rfcConversation_js_1.rfcParamsFrom; } });
|
|
39
51
|
// Timeouts
|
|
40
52
|
var timeouts_js_1 = require("./utils/timeouts.js");
|
|
41
53
|
Object.defineProperty(exports, "getTimeout", { enumerable: true, get: function () { return timeouts_js_1.getTimeout; } });
|
|
@@ -7,8 +7,23 @@
|
|
|
7
7
|
* this unit testable without a server, and it is where the hard part lives:
|
|
8
8
|
* ordering, admission, and telling one session from the next.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* What it guarantees, since the design note it used to point at lived in another
|
|
11
|
+
* package and has been deleted with the work it described:
|
|
12
|
+
*
|
|
13
|
+
* - **One transition at a time**, on a serializing tail. Connects join
|
|
14
|
+
* connects and disconnects join disconnects, so concurrent callers share one
|
|
15
|
+
* establishment or one teardown rather than racing to build and demolish the
|
|
16
|
+
* same session.
|
|
17
|
+
* - **Admission is synchronous.** A request is admitted, or refused, in the
|
|
18
|
+
* same step that counts it — so nothing can be admitted and still be
|
|
19
|
+
* invisible to a teardown draining at that instant.
|
|
20
|
+
* - **A teardown waits for nothing.** In-flight requests run to completion
|
|
21
|
+
* untouched; their results are fenced by session generation so a late answer
|
|
22
|
+
* cannot write over a session established since.
|
|
23
|
+
* - **Identity is observed, never assumed.** A changed fingerprint means the
|
|
24
|
+
* session we had is gone and this is a different one, which is fatal to
|
|
25
|
+
* anything held against the old one;
|
|
26
|
+
* an unchanged one says nothing about whether the server still has it.
|
|
12
27
|
*/
|
|
13
28
|
import { type AdtSessionErrorCode } from '@mcp-abap-adt/interfaces';
|
|
14
29
|
export type TransitionKind = 'connect' | 'disconnect' | 'recover' | 'cleanup';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionLifecycle.d.ts","sourceRoot":"","sources":["../../src/session/SessionLifecycle.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"SessionLifecycle.d.ts","sourceRoot":"","sources":["../../src/session/SessionLifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,0BAA0B,CAAC;AAElC,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS,GAAG,SAAS,CAAC;AAE9E,MAAM,WAAW,YAAY;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,yDAAyD;IACzD,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,0FAA0F;IAC1F,MAAM,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC9B,iFAAiF;IACjF,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,mBAAmB,EACzB,OAAO,CAAC,EAAE,MAAM,GACf,KAAK,GAAG;IAAE,IAAI,EAAE,mBAAmB,CAAA;CAAE,CAMvC;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,KAAK,CAAgD;IAC7D,OAAO,CAAC,WAAW,CAA6B;IAChD,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,UAAU,CAAK;IAEvB,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAK;IAErB,OAAO,CAAC,IAAI,CAAuC;IACnD,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,WAAW,CAAiC;IAIpD,4EAA4E;IAC5E,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,0EAA0E;IAC1E,IAAI,QAAQ,IAAI,MAAM,GAAG,IAAI,CAM5B;IAED,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED;;;;;;;;;;;;;OAaG;IACH,IAAI,iBAAiB,IAAI,MAAM,CAE9B;IAED;;;;;OAKG;IACH,aAAa,CAAC,WAAW,GAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAa,GAAG,IAAI;IAOzE;;;;;;OAMG;IACH,cAAc,IAAI,IAAI;IAItB,gBAAgB,IAAI,IAAI;IAKxB;;;;;;OAMG;IACH,OAAO,CACL,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GACvC,WAAW,GAAG,aAAa,GAAG,UAAU;IAiB3C,YAAY,IAAI,IAAI;IAMpB,4EAA4E;IAC5E,YAAY,IAAI,YAAY;IAiB5B,2FAA2F;IAC3F,IAAI,gBAAgB,IAAI,MAAM,CAE7B;IAED;;;;;;;OAOG;IACH,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC,GAAG,OAAO;IAM3D,aAAa,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,oBAAoB,GAAG,IAAI;IAelE,IAAI,iBAAiB,IAAI,OAAO,CAE/B;IAID;;;;;;;;;OASG;IACH,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAuBvE"}
|
|
@@ -8,8 +8,23 @@
|
|
|
8
8
|
* this unit testable without a server, and it is where the hard part lives:
|
|
9
9
|
* ordering, admission, and telling one session from the next.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* What it guarantees, since the design note it used to point at lived in another
|
|
12
|
+
* package and has been deleted with the work it described:
|
|
13
|
+
*
|
|
14
|
+
* - **One transition at a time**, on a serializing tail. Connects join
|
|
15
|
+
* connects and disconnects join disconnects, so concurrent callers share one
|
|
16
|
+
* establishment or one teardown rather than racing to build and demolish the
|
|
17
|
+
* same session.
|
|
18
|
+
* - **Admission is synchronous.** A request is admitted, or refused, in the
|
|
19
|
+
* same step that counts it — so nothing can be admitted and still be
|
|
20
|
+
* invisible to a teardown draining at that instant.
|
|
21
|
+
* - **A teardown waits for nothing.** In-flight requests run to completion
|
|
22
|
+
* untouched; their results are fenced by session generation so a late answer
|
|
23
|
+
* cannot write over a session established since.
|
|
24
|
+
* - **Identity is observed, never assumed.** A changed fingerprint means the
|
|
25
|
+
* session we had is gone and this is a different one, which is fatal to
|
|
26
|
+
* anything held against the old one;
|
|
27
|
+
* an unchanged one says nothing about whether the server still has it.
|
|
13
28
|
*/
|
|
14
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
30
|
exports.SessionLifecycle = void 0;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Join two `Cookie` header values, later wins on a repeated name.
|
|
3
|
+
*
|
|
4
|
+
* Used wherever a request already carries cookies and more are added: a
|
|
5
|
+
* credential can BE cookies — a SAML session is handed over as `MYSAPSSO2` —
|
|
6
|
+
* so writing the session jar over the header drops the very thing the request
|
|
7
|
+
* authenticates with. Both belong on the wire: one says who we are, the other
|
|
8
|
+
* which session we are in. A name in both is the session's, because that is the
|
|
9
|
+
* one the server just issued.
|
|
10
|
+
*/
|
|
11
|
+
export declare function mergeCookieHeaders(existing: string | undefined, incoming: string | undefined): string;
|
|
12
|
+
//# sourceMappingURL=cookies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookies.d.ts","sourceRoot":"","sources":["../../src/utils/cookies.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,MAAM,CASR"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mergeCookieHeaders = mergeCookieHeaders;
|
|
4
|
+
/**
|
|
5
|
+
* Join two `Cookie` header values, later wins on a repeated name.
|
|
6
|
+
*
|
|
7
|
+
* Used wherever a request already carries cookies and more are added: a
|
|
8
|
+
* credential can BE cookies — a SAML session is handed over as `MYSAPSSO2` —
|
|
9
|
+
* so writing the session jar over the header drops the very thing the request
|
|
10
|
+
* authenticates with. Both belong on the wire: one says who we are, the other
|
|
11
|
+
* which session we are in. A name in both is the session's, because that is the
|
|
12
|
+
* one the server just issued.
|
|
13
|
+
*/
|
|
14
|
+
function mergeCookieHeaders(existing, incoming) {
|
|
15
|
+
const parts = [existing, incoming].filter(Boolean).join('; ');
|
|
16
|
+
const byName = new Map();
|
|
17
|
+
for (const part of parts.split(';')) {
|
|
18
|
+
const pair = part.trim();
|
|
19
|
+
if (!pair)
|
|
20
|
+
continue;
|
|
21
|
+
byName.set(pair.slice(0, pair.indexOf('=')), pair);
|
|
22
|
+
}
|
|
23
|
+
return [...byName.values()].join('; ');
|
|
24
|
+
}
|
package/dist/utils/timeouts.d.ts
CHANGED
|
@@ -5,9 +5,12 @@ export declare function getTimeout(type?: 'default' | 'csrf' | 'long' | number):
|
|
|
5
5
|
/**
|
|
6
6
|
* Large ceiling timeout applied while a connection is inside an uninterruptible
|
|
7
7
|
* critical section (lock → modify → unlock). A short per-request timeout must
|
|
8
|
-
* not abort such a request mid-flight
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* not abort such a request mid-flight — not because aborting ends anything on
|
|
9
|
+
* the server, but because it ends what the CLIENT knows: the modification may
|
|
10
|
+
* or may not have been applied, and the handle `unlock` needs is gone, while
|
|
11
|
+
* the lock and the ABAP session holding it sit there until that server-side
|
|
12
|
+
* idle timeout, which this side can neither read nor influence.
|
|
13
|
+
* Configurable via SAP_TIMEOUT_CRITICAL (ms).
|
|
11
14
|
* Default 600000 (10 minutes) — a generous ceiling that still guards against a
|
|
12
15
|
* permanently dead socket hanging forever.
|
|
13
16
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeouts.d.ts","sourceRoot":"","sources":["../../src/utils/timeouts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAG/D,MAAM,MAAM,aAAa,GAAG,cAAc,CAAC;AAE3C,wBAAgB,gBAAgB,IAAI,cAAc,CAajD;AAED,wBAAgB,UAAU,CACxB,IAAI,GAAE,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,MAAkB,GACrD,MAAM,CAOR;AAED
|
|
1
|
+
{"version":3,"file":"timeouts.d.ts","sourceRoot":"","sources":["../../src/utils/timeouts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAG/D,MAAM,MAAM,aAAa,GAAG,cAAc,CAAC;AAE3C,wBAAgB,gBAAgB,IAAI,cAAc,CAajD;AAED,wBAAgB,UAAU,CACxB,IAAI,GAAE,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,MAAkB,GACrD,MAAM,CAOR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CAElD"}
|
package/dist/utils/timeouts.js
CHANGED
|
@@ -23,9 +23,12 @@ function getTimeout(type = 'default') {
|
|
|
23
23
|
/**
|
|
24
24
|
* Large ceiling timeout applied while a connection is inside an uninterruptible
|
|
25
25
|
* critical section (lock → modify → unlock). A short per-request timeout must
|
|
26
|
-
* not abort such a request mid-flight
|
|
27
|
-
*
|
|
28
|
-
*
|
|
26
|
+
* not abort such a request mid-flight — not because aborting ends anything on
|
|
27
|
+
* the server, but because it ends what the CLIENT knows: the modification may
|
|
28
|
+
* or may not have been applied, and the handle `unlock` needs is gone, while
|
|
29
|
+
* the lock and the ABAP session holding it sit there until that server-side
|
|
30
|
+
* idle timeout, which this side can neither read nor influence.
|
|
31
|
+
* Configurable via SAP_TIMEOUT_CRITICAL (ms).
|
|
29
32
|
* Default 600000 (10 minutes) — a generous ceiling that still guards against a
|
|
30
33
|
* permanently dead socket hanging forever.
|
|
31
34
|
*/
|