@babamba2/mcp-abap-connection 1.5.3

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 (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +522 -0
  3. package/bin/sap-abap-auth.js +665 -0
  4. package/dist/config/sapConfig.d.ts +5 -0
  5. package/dist/config/sapConfig.d.ts.map +1 -0
  6. package/dist/config/sapConfig.js +26 -0
  7. package/dist/connection/AbapConnection.d.ts +4 -0
  8. package/dist/connection/AbapConnection.d.ts.map +1 -0
  9. package/dist/connection/AbapConnection.js +2 -0
  10. package/dist/connection/AbstractAbapConnection.d.ts +89 -0
  11. package/dist/connection/AbstractAbapConnection.d.ts.map +1 -0
  12. package/dist/connection/AbstractAbapConnection.js +612 -0
  13. package/dist/connection/BaseAbapConnection.d.ts +19 -0
  14. package/dist/connection/BaseAbapConnection.d.ts.map +1 -0
  15. package/dist/connection/BaseAbapConnection.js +65 -0
  16. package/dist/connection/GenericWebSocketTransport.d.ts +46 -0
  17. package/dist/connection/GenericWebSocketTransport.d.ts.map +1 -0
  18. package/dist/connection/GenericWebSocketTransport.js +135 -0
  19. package/dist/connection/JwtAbapConnection.d.ts +37 -0
  20. package/dist/connection/JwtAbapConnection.d.ts.map +1 -0
  21. package/dist/connection/JwtAbapConnection.js +181 -0
  22. package/dist/connection/RfcAbapConnection.d.ts +49 -0
  23. package/dist/connection/RfcAbapConnection.d.ts.map +1 -0
  24. package/dist/connection/RfcAbapConnection.js +322 -0
  25. package/dist/connection/SamlAbapConnection.d.ts +21 -0
  26. package/dist/connection/SamlAbapConnection.d.ts.map +1 -0
  27. package/dist/connection/SamlAbapConnection.js +65 -0
  28. package/dist/connection/connectionFactory.d.ts +8 -0
  29. package/dist/connection/connectionFactory.d.ts.map +1 -0
  30. package/dist/connection/connectionFactory.js +23 -0
  31. package/dist/connection/csrfConfig.d.ts +46 -0
  32. package/dist/connection/csrfConfig.d.ts.map +1 -0
  33. package/dist/connection/csrfConfig.js +48 -0
  34. package/dist/index.d.ts +14 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +32 -0
  37. package/dist/logger.d.ts +3 -0
  38. package/dist/logger.d.ts.map +1 -0
  39. package/dist/logger.js +2 -0
  40. package/dist/utils/timeouts.d.ts +5 -0
  41. package/dist/utils/timeouts.d.ts.map +1 -0
  42. package/dist/utils/timeouts.js +21 -0
  43. package/dist/utils/tokenRefresh.d.ts +15 -0
  44. package/dist/utils/tokenRefresh.d.ts.map +1 -0
  45. package/dist/utils/tokenRefresh.js +55 -0
  46. package/package.json +70 -0
@@ -0,0 +1,322 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RfcAbapConnection = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ /**
6
+ * Derive RFC connection parameters from ISapConfig.
7
+ * Parses hostname from config.url, system number from port (80XX → XX).
8
+ *
9
+ * Examples:
10
+ * http://saphost:8000 → ashost=saphost, sysnr=00
11
+ * http://saphost:8001 → ashost=saphost, sysnr=01
12
+ * http://saphost:8042 → ashost=saphost, sysnr=42
13
+ */
14
+ function buildRfcParams(config) {
15
+ const parsed = new URL(config.url);
16
+ const port = Number.parseInt(parsed.port || '8000', 10);
17
+ // SAP HTTP port convention: 80XX where XX = system number
18
+ const sysnr = String(port - 8000).padStart(2, '0');
19
+ return {
20
+ ashost: parsed.hostname,
21
+ sysnr,
22
+ client: config.client || '000',
23
+ user: config.username || '',
24
+ passwd: config.password || '',
25
+ lang: 'EN',
26
+ };
27
+ }
28
+ /**
29
+ * Map ADT exception type names to HTTP status codes.
30
+ * These are standard ADT exception types returned in `<exc:exception>` XML.
31
+ */
32
+ const EXCEPTION_STATUS_MAP = {
33
+ ExceptionResourceNotFound: { code: 404, text: 'Not Found' },
34
+ ExceptionResourceNoAuthorization: { code: 403, text: 'Forbidden' },
35
+ ExceptionResourceAlreadyExists: { code: 409, text: 'Conflict' },
36
+ ExceptionResourceLocked: { code: 423, text: 'Locked' },
37
+ ExceptionBadRequest: { code: 400, text: 'Bad Request' },
38
+ ExceptionNotSupported: { code: 501, text: 'Not Implemented' },
39
+ ExceptionConflict: { code: 409, text: 'Conflict' },
40
+ };
41
+ /**
42
+ * Detect HTTP status from ADT exception XML in response body.
43
+ * On some legacy systems, SADT_REST_RFC_ENDPOINT returns errors as XML
44
+ * exceptions without setting proper HTTP status codes in STATUS_LINE.
45
+ */
46
+ function detectExceptionStatus(body) {
47
+ // Extract exception type: <exc:exception ... xmlns:exc="..."><exc:type>ExceptionName</exc:type>
48
+ const typeMatch = body.match(/<exc:type[^>]*>([^<]+)<\/exc:type>/);
49
+ if (typeMatch) {
50
+ const exType = typeMatch[1].trim();
51
+ const mapped = EXCEPTION_STATUS_MAP[exType];
52
+ if (mapped) {
53
+ return { statusCode: mapped.code, statusText: mapped.text };
54
+ }
55
+ }
56
+ // Unknown exception type — return 500 as generic server error
57
+ return { statusCode: 500, statusText: 'Internal Server Error' };
58
+ }
59
+ /**
60
+ * RFC-based connection for on-premise SAP systems.
61
+ *
62
+ * Uses node-rfc to call SADT_REST_RFC_ENDPOINT — the same standard SAP FM
63
+ * that Eclipse ADT uses for all on-premise ADT operations via JCo.
64
+ *
65
+ * RFC connections are inherently stateful: one ABAP session persists for the
66
+ * entire connection lifetime. This solves the HTTP 423 "invalid lock handle"
67
+ * problem on legacy systems (BASIS < 7.50) where HTTP stateful sessions
68
+ * are not supported.
69
+ *
70
+ * Connection parameters are derived from the standard ISapConfig.url field:
71
+ * http://saphost:8000 → ashost=saphost, sysnr=00
72
+ *
73
+ * Prerequisites:
74
+ * - SAP NW RFC SDK installed on the machine
75
+ * - node-rfc package installed: npm install node-rfc
76
+ */
77
+ class RfcAbapConnection {
78
+ config;
79
+ logger;
80
+ rfcClient = null;
81
+ sessionId;
82
+ baseUrl;
83
+ rfcParams;
84
+ sessionType = 'stateless';
85
+ sessionCookie = null;
86
+ constructor(config, logger = null) {
87
+ this.config = config;
88
+ this.logger = logger;
89
+ RfcAbapConnection.validateConfig(config);
90
+ this.sessionId = (0, node_crypto_1.randomUUID)();
91
+ this.baseUrl = config.url;
92
+ this.rfcParams = buildRfcParams(config);
93
+ this.logger?.debug(`RfcAbapConnection created for ${this.rfcParams.ashost}:${this.rfcParams.sysnr}, client ${this.rfcParams.client}`);
94
+ }
95
+ async connect() {
96
+ let Client;
97
+ try {
98
+ // Dynamic require — node-rfc is NOT a declared dependency.
99
+ // Users who need RFC connections must install it manually:
100
+ // npm install node-rfc (+ SAP NW RFC SDK on the machine)
101
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
102
+ const noderfc = require('node-rfc');
103
+ Client = noderfc.Client;
104
+ }
105
+ catch (e) {
106
+ throw new Error('node-rfc is not available. To use RFC connections, install SAP NW RFC SDK ' +
107
+ 'and run: npm install node-rfc. ' +
108
+ `Details: ${e instanceof Error ? e.message : String(e)}`);
109
+ }
110
+ this.rfcClient = new Client(this.rfcParams);
111
+ try {
112
+ await this.rfcClient.open();
113
+ this.logger?.debug('RFC connection opened (stateful session)');
114
+ }
115
+ catch (e) {
116
+ this.rfcClient = null;
117
+ const msg = e instanceof Error ? e.message : String(e);
118
+ this.logger?.error(`RFC connection failed: ${msg}`);
119
+ throw new Error(`Failed to open RFC connection: ${msg}`);
120
+ }
121
+ }
122
+ async getBaseUrl() {
123
+ return this.baseUrl;
124
+ }
125
+ getSessionId() {
126
+ return this.sessionId;
127
+ }
128
+ setSessionType(type) {
129
+ this.sessionType = type;
130
+ if (type === 'stateless') {
131
+ this.sessionCookie = null;
132
+ this.logger?.debug('RFC session type: stateless (cookie cleared)');
133
+ }
134
+ else {
135
+ this.logger?.debug('RFC session type: stateful (will capture cookies)');
136
+ }
137
+ }
138
+ async makeAdtRequest(options) {
139
+ if (!this.rfcClient?.alive) {
140
+ throw new Error('RFC connection is not open. Call connect() first.');
141
+ }
142
+ const method = options.method.toUpperCase();
143
+ // Encode query params into URI (axios does this automatically for HTTP,
144
+ // but RFC needs it done manually)
145
+ let uri = options.url;
146
+ if (options.params && typeof options.params === 'object') {
147
+ const entries = Object.entries(options.params).filter(([, v]) => v !== undefined && v !== null);
148
+ if (entries.length > 0) {
149
+ const qs = new URLSearchParams(entries.map(([k, v]) => [k, String(v)]));
150
+ uri += (uri.includes('?') ? '&' : '?') + qs.toString();
151
+ }
152
+ }
153
+ // Note: sap-client is NOT added to URI for RFC connections.
154
+ // The RFC session is already logged into the correct client
155
+ // (via the 'client' param in RFC connection). Adding sap-client
156
+ // to the URI can cause "object not found" on some systems.
157
+ // Build header fields
158
+ const headerFields = [];
159
+ if (options.headers) {
160
+ for (const [name, value] of Object.entries(options.headers)) {
161
+ headerFields.push({ NAME: name, VALUE: value });
162
+ }
163
+ }
164
+ // Inject session type header so SAP creates/maintains an ICM session
165
+ if (this.sessionType === 'stateful') {
166
+ headerFields.push({ NAME: 'x-sap-adt-sessiontype', VALUE: 'stateful' });
167
+ }
168
+ // Replay session cookie for stateful operations (cookie captured from LOCK response)
169
+ if (this.sessionCookie) {
170
+ const existingCookie = headerFields.find((h) => h.NAME.toLowerCase() === 'cookie');
171
+ if (!existingCookie) {
172
+ headerFields.push({ NAME: 'Cookie', VALUE: this.sessionCookie });
173
+ }
174
+ }
175
+ // Ensure Content-Type for body
176
+ const body = options.data !== undefined && options.data !== null
177
+ ? String(options.data)
178
+ : '';
179
+ if (body &&
180
+ !headerFields.some((h) => h.NAME.toLowerCase() === 'content-type')) {
181
+ headerFields.push({
182
+ NAME: 'Content-Type',
183
+ VALUE: 'text/plain; charset=utf-8',
184
+ });
185
+ }
186
+ this.logger?.debug(`RFC → ${method} ${uri}`);
187
+ this.logger?.debug(`RFC → headers: ${JSON.stringify(headerFields)}`);
188
+ if (body)
189
+ this.logger?.debug(`RFC → body: ${body}`);
190
+ try {
191
+ const result = await this.rfcClient.call('SADT_REST_RFC_ENDPOINT', {
192
+ REQUEST: {
193
+ REQUEST_LINE: {
194
+ METHOD: method,
195
+ URI: uri,
196
+ VERSION: 'HTTP/1.1',
197
+ },
198
+ HEADER_FIELDS: headerFields,
199
+ MESSAGE_BODY: body ? Buffer.from(body, 'utf-8') : Buffer.alloc(0),
200
+ },
201
+ });
202
+ const resp = result.RESPONSE || result;
203
+ // Log raw RFC response structure for debugging
204
+ this.logger?.debug(`RFC raw response keys: ${Object.keys(resp).join(', ')}`);
205
+ if (resp.STATUS_LINE) {
206
+ this.logger?.debug(`RFC STATUS_LINE: ${JSON.stringify(resp.STATUS_LINE)}`);
207
+ }
208
+ // Parse status — RFC returns status in STATUS_LINE structure
209
+ // Field names: STATUS_CODE (not CODE), REASON_PHRASE (not REASON)
210
+ const rawCode = resp.STATUS_LINE?.STATUS_CODE || resp.STATUS_LINE?.CODE || 0;
211
+ let statusCode = typeof rawCode === 'string' ? Number.parseInt(rawCode, 10) : rawCode;
212
+ let statusText = resp.STATUS_LINE?.REASON_PHRASE || resp.STATUS_LINE?.REASON || '';
213
+ // Parse response body
214
+ const respBody = resp.MESSAGE_BODY
215
+ ? Buffer.isBuffer(resp.MESSAGE_BODY)
216
+ ? resp.MESSAGE_BODY.toString('utf-8')
217
+ : String(resp.MESSAGE_BODY)
218
+ : '';
219
+ // Parse response headers
220
+ const respHeaders = {};
221
+ const respHeaderFields = resp.HEADER_FIELDS || [];
222
+ for (const field of respHeaderFields) {
223
+ if (field.NAME && field.VALUE !== undefined) {
224
+ respHeaders[field.NAME.toLowerCase()] = field.VALUE;
225
+ }
226
+ }
227
+ // Capture session cookie from LOCK/stateful responses for cookie replay
228
+ if (this.sessionType === 'stateful' && respHeaders['set-cookie']) {
229
+ const setCookieHeader = respHeaders['set-cookie'];
230
+ // Extract cookie name=value pairs (strip attributes like Path, Secure, etc.)
231
+ const cookies = Array.isArray(setCookieHeader)
232
+ ? setCookieHeader
233
+ : [setCookieHeader];
234
+ const cookieValues = cookies
235
+ .map((c) => c.split(';')[0].trim())
236
+ .filter(Boolean);
237
+ if (cookieValues.length > 0) {
238
+ this.sessionCookie = cookieValues.join('; ');
239
+ this.logger?.debug(`RFC: captured session cookie: ${this.sessionCookie}`);
240
+ }
241
+ }
242
+ // On some systems (e.g. BASIS < 7.50), SADT_REST_RFC_ENDPOINT does not
243
+ // populate STATUS_LINE, returning status 0. Detect errors from the
244
+ // response body: ADT exception XML indicates a failed request.
245
+ if (!statusCode && respBody.includes('<exc:exception')) {
246
+ const detected = detectExceptionStatus(respBody);
247
+ statusCode = detected.statusCode;
248
+ statusText = detected.statusText;
249
+ this.logger?.debug(`RFC: STATUS_LINE empty, detected ${statusCode} from exception XML`);
250
+ }
251
+ // Default to 200 OK only when no error was detected
252
+ if (!statusCode) {
253
+ statusCode = 200;
254
+ statusText = statusText || 'OK';
255
+ }
256
+ this.logger?.debug(`RFC ← ${statusCode} ${statusText} (${respBody.length} bytes)`);
257
+ this.logger?.debug(`RFC ← headers: ${JSON.stringify(respHeaders)}`);
258
+ if (respBody)
259
+ this.logger?.debug(`RFC ← body: ${respBody}`);
260
+ const response = {
261
+ data: respBody,
262
+ status: statusCode,
263
+ statusText,
264
+ headers: respHeaders,
265
+ };
266
+ // Throw for error status codes (matching HTTP/axios behavior)
267
+ if (statusCode >= 400) {
268
+ const error = new Error(`Request failed with status ${statusCode}: ${method} ${uri}`);
269
+ error.response = response;
270
+ throw error;
271
+ }
272
+ return response;
273
+ }
274
+ catch (e) {
275
+ // Re-throw our own errors (status >= 400)
276
+ if (e?.response) {
277
+ throw e;
278
+ }
279
+ // RFC-level error
280
+ const msg = e instanceof Error ? e.message : String(e);
281
+ this.logger?.error(`RFC call failed: ${msg}`);
282
+ throw new Error(`RFC call to SADT_REST_RFC_ENDPOINT failed: ${msg}`);
283
+ }
284
+ }
285
+ /**
286
+ * Reset the connection — for RFC this closes the session.
287
+ * Provides interface compatibility with HTTP connections.
288
+ */
289
+ reset() {
290
+ this.close();
291
+ }
292
+ /**
293
+ * Close the RFC connection and release the ABAP session.
294
+ */
295
+ async close() {
296
+ if (this.rfcClient) {
297
+ try {
298
+ await this.rfcClient.close();
299
+ this.logger?.debug('RFC connection closed');
300
+ }
301
+ catch (e) {
302
+ this.logger?.debug(`RFC close error: ${e instanceof Error ? e.message : String(e)}`);
303
+ }
304
+ this.rfcClient = null;
305
+ }
306
+ }
307
+ static validateConfig(config) {
308
+ if (config.connectionType !== 'rfc') {
309
+ throw new Error(`RFC connection expects connectionType "rfc", got "${config.connectionType}"`);
310
+ }
311
+ if (!config.url) {
312
+ throw new Error('RFC connection requires url (hostname is parsed from it)');
313
+ }
314
+ if (!config.username || !config.password) {
315
+ throw new Error('RFC connection requires both username and password');
316
+ }
317
+ if (!config.client) {
318
+ throw new Error('RFC connection requires SAP client');
319
+ }
320
+ }
321
+ }
322
+ exports.RfcAbapConnection = RfcAbapConnection;
@@ -0,0 +1,21 @@
1
+ import type { SapConfig } from '../config/sapConfig.js';
2
+ import type { ILogger } from '../logger.js';
3
+ import { AbstractAbapConnection } from './AbstractAbapConnection.js';
4
+ /**
5
+ * SAML session cookie authentication for SAP systems
6
+ */
7
+ export declare class SamlAbapConnection extends AbstractAbapConnection {
8
+ private sessionCookies;
9
+ constructor(config: SapConfig, logger?: ILogger | null, sessionId?: string, options?: {
10
+ skipSessionType?: boolean;
11
+ });
12
+ /**
13
+ * Connect to SAP system using existing session cookies
14
+ * Fetches CSRF token to establish session context
15
+ */
16
+ connect(): Promise<void>;
17
+ protected buildAuthorizationHeader(): string;
18
+ getAuthHeaders(): Promise<Record<string, string>>;
19
+ private static validateConfig;
20
+ }
21
+ //# sourceMappingURL=SamlAbapConnection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SamlAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/SamlAbapConnection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAErE;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,sBAAsB;IAC5D,OAAO,CAAC,cAAc,CAAS;gBAG7B,MAAM,EAAE,SAAS,EACjB,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,EACvB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE;IAUzC;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAiC9B,SAAS,CAAC,wBAAwB,IAAI,MAAM;IAItC,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAQvD,OAAO,CAAC,MAAM,CAAC,cAAc;CAW9B"}
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SamlAbapConnection = void 0;
4
+ const axios_1 = require("axios");
5
+ const AbstractAbapConnection_js_1 = require("./AbstractAbapConnection.js");
6
+ /**
7
+ * SAML session cookie authentication for SAP systems
8
+ */
9
+ class SamlAbapConnection extends AbstractAbapConnection_js_1.AbstractAbapConnection {
10
+ sessionCookies;
11
+ constructor(config, logger, sessionId, options) {
12
+ SamlAbapConnection.validateConfig(config);
13
+ super(config, logger || null, sessionId, options);
14
+ this.sessionCookies = config.sessionCookies || '';
15
+ if (this.sessionCookies) {
16
+ this.setInitialCookies(this.sessionCookies);
17
+ }
18
+ }
19
+ /**
20
+ * Connect to SAP system using existing session cookies
21
+ * Fetches CSRF token to establish session context
22
+ */
23
+ async connect() {
24
+ const baseUrl = await this.getBaseUrl();
25
+ const discoveryUrl = `${baseUrl}/sap/bc/adt/discovery`;
26
+ this.logger?.debug(`[DEBUG] SamlAbapConnection - Connecting to SAP system: ${discoveryUrl}`);
27
+ try {
28
+ const token = await this.fetchCsrfToken(discoveryUrl, 3, 1000);
29
+ this.setCsrfToken(token);
30
+ this.logger?.debug('Successfully connected to SAP system', {
31
+ hasCsrfToken: !!this.getCsrfToken(),
32
+ hasCookies: !!this.getCookies(),
33
+ cookieLength: this.getCookies()?.length || 0,
34
+ });
35
+ }
36
+ catch (error) {
37
+ const errorMsg = error instanceof Error ? error.message : String(error);
38
+ this.logger?.warn(`[WARN] SamlAbapConnection - Could not connect to SAP system upfront: ${errorMsg}. Will retry on first request.`);
39
+ if (error instanceof axios_1.AxiosError && error.response?.headers) {
40
+ if (this.getCookies()) {
41
+ this.logger?.debug(`[DEBUG] SamlAbapConnection - Cookies extracted from error response during connect (first 100 chars): ${this.getCookies()?.substring(0, 100)}...`);
42
+ }
43
+ }
44
+ }
45
+ }
46
+ buildAuthorizationHeader() {
47
+ return '';
48
+ }
49
+ async getAuthHeaders() {
50
+ const headers = await super.getAuthHeaders();
51
+ if (this.sessionCookies) {
52
+ headers.Cookie = this.sessionCookies;
53
+ }
54
+ return headers;
55
+ }
56
+ static validateConfig(config) {
57
+ if (config.authType !== 'saml') {
58
+ throw new Error(`SAML connection expects authType "saml", got "${config.authType}"`);
59
+ }
60
+ if (!config.sessionCookies) {
61
+ throw new Error('SAML authentication requires sessionCookies');
62
+ }
63
+ }
64
+ }
65
+ exports.SamlAbapConnection = SamlAbapConnection;
@@ -0,0 +1,8 @@
1
+ import type { ITokenRefresher } from '@babamba2/mcp-abap-adt-interfaces';
2
+ import type { SapConfig } from '../config/sapConfig.js';
3
+ import type { ILogger } from '../logger.js';
4
+ import type { AbapConnection } from './AbapConnection.js';
5
+ export declare function createAbapConnection(config: SapConfig, logger?: ILogger | null, sessionId?: string, tokenRefresher?: ITokenRefresher, options?: {
6
+ skipSessionType?: boolean;
7
+ }): AbapConnection;
8
+ //# sourceMappingURL=connectionFactory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connectionFactory.d.ts","sourceRoot":"","sources":["../../src/connection/connectionFactory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAM1D,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,EACvB,SAAS,CAAC,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,eAAe,EAChC,OAAO,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAE,GACtC,cAAc,CAkBhB"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createAbapConnection = createAbapConnection;
4
+ const BaseAbapConnection_js_1 = require("./BaseAbapConnection.js");
5
+ const JwtAbapConnection_js_1 = require("./JwtAbapConnection.js");
6
+ const RfcAbapConnection_js_1 = require("./RfcAbapConnection.js");
7
+ const SamlAbapConnection_js_1 = require("./SamlAbapConnection.js");
8
+ function createAbapConnection(config, logger, sessionId, tokenRefresher, options) {
9
+ // RFC connection type takes priority over auth type
10
+ if (config.connectionType === 'rfc') {
11
+ return new RfcAbapConnection_js_1.RfcAbapConnection(config, logger);
12
+ }
13
+ switch (config.authType) {
14
+ case 'basic':
15
+ return new BaseAbapConnection_js_1.BaseAbapConnection(config, logger, sessionId, options);
16
+ case 'jwt':
17
+ return new JwtAbapConnection_js_1.JwtAbapConnection(config, logger, sessionId, tokenRefresher);
18
+ case 'saml':
19
+ return new SamlAbapConnection_js_1.SamlAbapConnection(config, logger, sessionId, options);
20
+ default:
21
+ throw new Error(`Unsupported SAP authentication type: ${config.authType}`);
22
+ }
23
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * CSRF Token Configuration
3
+ *
4
+ * Centralized constants for CSRF token fetching to ensure consistency
5
+ * across different connection implementations.
6
+ */
7
+ export declare const CSRF_CONFIG: {
8
+ /**
9
+ * Number of retry attempts for CSRF token fetch
10
+ * Default: 3 attempts (total of 4 requests: initial + 3 retries)
11
+ */
12
+ readonly RETRY_COUNT: 3;
13
+ /**
14
+ * Delay between retry attempts (milliseconds)
15
+ * Default: 1000ms (1 second)
16
+ */
17
+ readonly RETRY_DELAY: 1000;
18
+ /**
19
+ * CSRF token endpoint path (primary)
20
+ * Standard SAP ADT core discovery endpoint (newer systems, returns smaller response)
21
+ */
22
+ readonly ENDPOINT: "/sap/bc/adt/core/discovery";
23
+ /**
24
+ * CSRF token endpoint path (fallback)
25
+ * Legacy SAP ADT discovery endpoint for older systems (e.g. BASIS < 7.52)
26
+ * that don't have /sap/bc/adt/core/discovery
27
+ */
28
+ readonly FALLBACK_ENDPOINT: "/sap/bc/adt/discovery";
29
+ /**
30
+ * Required headers for CSRF token fetch
31
+ */
32
+ readonly REQUIRED_HEADERS: {
33
+ readonly 'x-csrf-token': "fetch";
34
+ readonly Accept: "application/atomsvc+xml";
35
+ };
36
+ };
37
+ /**
38
+ * CSRF token error messages
39
+ * Standardized error messages for consistent error reporting
40
+ */
41
+ export declare const CSRF_ERROR_MESSAGES: {
42
+ readonly FETCH_FAILED: (attempts: number, cause: string) => string;
43
+ readonly NOT_IN_HEADERS: "No CSRF token in response headers";
44
+ readonly REQUIRED_FOR_MUTATION: "CSRF token is required for POST/PUT requests but could not be fetched";
45
+ };
46
+ //# sourceMappingURL=csrfConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csrfConfig.d.ts","sourceRoot":"","sources":["../../src/connection/csrfConfig.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,WAAW;IACtB;;;OAGG;;IAGH;;;OAGG;;IAGH;;;OAGG;;IAGH;;;;OAIG;;IAGH;;OAEG;;;;;CAKK,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,mBAAmB;sCACL,MAAM,SAAS,MAAM;;;CAOtC,CAAC"}
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CSRF_ERROR_MESSAGES = exports.CSRF_CONFIG = void 0;
4
+ /**
5
+ * CSRF Token Configuration
6
+ *
7
+ * Centralized constants for CSRF token fetching to ensure consistency
8
+ * across different connection implementations.
9
+ */
10
+ exports.CSRF_CONFIG = {
11
+ /**
12
+ * Number of retry attempts for CSRF token fetch
13
+ * Default: 3 attempts (total of 4 requests: initial + 3 retries)
14
+ */
15
+ RETRY_COUNT: 3,
16
+ /**
17
+ * Delay between retry attempts (milliseconds)
18
+ * Default: 1000ms (1 second)
19
+ */
20
+ RETRY_DELAY: 1000,
21
+ /**
22
+ * CSRF token endpoint path (primary)
23
+ * Standard SAP ADT core discovery endpoint (newer systems, returns smaller response)
24
+ */
25
+ ENDPOINT: '/sap/bc/adt/core/discovery',
26
+ /**
27
+ * CSRF token endpoint path (fallback)
28
+ * Legacy SAP ADT discovery endpoint for older systems (e.g. BASIS < 7.52)
29
+ * that don't have /sap/bc/adt/core/discovery
30
+ */
31
+ FALLBACK_ENDPOINT: '/sap/bc/adt/discovery',
32
+ /**
33
+ * Required headers for CSRF token fetch
34
+ */
35
+ REQUIRED_HEADERS: {
36
+ 'x-csrf-token': 'fetch',
37
+ Accept: 'application/atomsvc+xml',
38
+ },
39
+ };
40
+ /**
41
+ * CSRF token error messages
42
+ * Standardized error messages for consistent error reporting
43
+ */
44
+ exports.CSRF_ERROR_MESSAGES = {
45
+ FETCH_FAILED: (attempts, cause) => `Failed to fetch CSRF token after ${attempts} attempts: ${cause}`,
46
+ NOT_IN_HEADERS: 'No CSRF token in response headers',
47
+ REQUIRED_FOR_MUTATION: 'CSRF token is required for POST/PUT requests but could not be fetched',
48
+ };
@@ -0,0 +1,14 @@
1
+ export type { IWebSocketCloseInfo, IWebSocketConnectOptions, IWebSocketMessageEnvelope, IWebSocketMessageHandler, IWebSocketTransport, } from '@babamba2/mcp-abap-adt-interfaces';
2
+ export type { SapAuthType, SapConfig, SapConnectionType, } from './config/sapConfig.js';
3
+ export { sapConfigSignature } from './config/sapConfig.js';
4
+ export type { AbapConnection, AbapRequestOptions, } from './connection/AbapConnection.js';
5
+ export { BaseAbapConnection, BaseAbapConnection as OnPremAbapConnection, } from './connection/BaseAbapConnection.js';
6
+ export { createAbapConnection } from './connection/connectionFactory.js';
7
+ export { CSRF_CONFIG, CSRF_ERROR_MESSAGES } from './connection/csrfConfig.js';
8
+ export { GenericWebSocketTransport, type IWebSocketFactory, type IWebSocketLike, } from './connection/GenericWebSocketTransport.js';
9
+ export { JwtAbapConnection, JwtAbapConnection as CloudAbapConnection, } from './connection/JwtAbapConnection.js';
10
+ export { RfcAbapConnection } from './connection/RfcAbapConnection.js';
11
+ export { SamlAbapConnection } from './connection/SamlAbapConnection.js';
12
+ export type { ILogger } from './logger.js';
13
+ export { getTimeout, getTimeoutConfig, type TimeoutConfig, } from './utils/timeouts.js';
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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,mCAAmC,CAAC;AAC3C,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;AAGxC,OAAO,EACL,kBAAkB,EAClB,kBAAkB,IAAI,oBAAoB,GAC3C,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAEzE,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EACL,yBAAyB,EACzB,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,2CAA2C,CAAC;AACnD,OAAO,EACL,iBAAiB,EACjB,iBAAiB,IAAI,mBAAmB,GACzC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,KAAK,aAAa,GACnB,MAAM,qBAAqB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ // Types - re-exported from interfaces package with backward compatibility aliases
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.getTimeoutConfig = exports.getTimeout = exports.SamlAbapConnection = exports.RfcAbapConnection = exports.CloudAbapConnection = exports.JwtAbapConnection = exports.GenericWebSocketTransport = exports.CSRF_ERROR_MESSAGES = exports.CSRF_CONFIG = exports.createAbapConnection = exports.OnPremAbapConnection = exports.BaseAbapConnection = exports.sapConfigSignature = void 0;
5
+ // Config utilities
6
+ var sapConfig_js_1 = require("./config/sapConfig.js");
7
+ Object.defineProperty(exports, "sapConfigSignature", { enumerable: true, get: function () { return sapConfig_js_1.sapConfigSignature; } });
8
+ // Connection classes - only final implementations
9
+ // Deprecated aliases for backward compatibility
10
+ var BaseAbapConnection_js_1 = require("./connection/BaseAbapConnection.js");
11
+ Object.defineProperty(exports, "BaseAbapConnection", { enumerable: true, get: function () { return BaseAbapConnection_js_1.BaseAbapConnection; } });
12
+ Object.defineProperty(exports, "OnPremAbapConnection", { enumerable: true, get: function () { return BaseAbapConnection_js_1.BaseAbapConnection; } });
13
+ // Factory
14
+ var connectionFactory_js_1 = require("./connection/connectionFactory.js");
15
+ Object.defineProperty(exports, "createAbapConnection", { enumerable: true, get: function () { return connectionFactory_js_1.createAbapConnection; } });
16
+ // CSRF configuration
17
+ var csrfConfig_js_1 = require("./connection/csrfConfig.js");
18
+ Object.defineProperty(exports, "CSRF_CONFIG", { enumerable: true, get: function () { return csrfConfig_js_1.CSRF_CONFIG; } });
19
+ Object.defineProperty(exports, "CSRF_ERROR_MESSAGES", { enumerable: true, get: function () { return csrfConfig_js_1.CSRF_ERROR_MESSAGES; } });
20
+ var GenericWebSocketTransport_js_1 = require("./connection/GenericWebSocketTransport.js");
21
+ Object.defineProperty(exports, "GenericWebSocketTransport", { enumerable: true, get: function () { return GenericWebSocketTransport_js_1.GenericWebSocketTransport; } });
22
+ var JwtAbapConnection_js_1 = require("./connection/JwtAbapConnection.js");
23
+ Object.defineProperty(exports, "JwtAbapConnection", { enumerable: true, get: function () { return JwtAbapConnection_js_1.JwtAbapConnection; } });
24
+ Object.defineProperty(exports, "CloudAbapConnection", { enumerable: true, get: function () { return JwtAbapConnection_js_1.JwtAbapConnection; } });
25
+ var RfcAbapConnection_js_1 = require("./connection/RfcAbapConnection.js");
26
+ Object.defineProperty(exports, "RfcAbapConnection", { enumerable: true, get: function () { return RfcAbapConnection_js_1.RfcAbapConnection; } });
27
+ var SamlAbapConnection_js_1 = require("./connection/SamlAbapConnection.js");
28
+ Object.defineProperty(exports, "SamlAbapConnection", { enumerable: true, get: function () { return SamlAbapConnection_js_1.SamlAbapConnection; } });
29
+ // Timeouts
30
+ var timeouts_js_1 = require("./utils/timeouts.js");
31
+ Object.defineProperty(exports, "getTimeout", { enumerable: true, get: function () { return timeouts_js_1.getTimeout; } });
32
+ Object.defineProperty(exports, "getTimeoutConfig", { enumerable: true, get: function () { return timeouts_js_1.getTimeoutConfig; } });
@@ -0,0 +1,3 @@
1
+ import type { ILogger } from '@babamba2/mcp-abap-adt-interfaces';
2
+ export type { ILogger };
3
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mCAAmC,CAAC;AAGjE,YAAY,EAAE,OAAO,EAAE,CAAC"}
package/dist/logger.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,5 @@
1
+ import type { ITimeoutConfig } from '@babamba2/mcp-abap-adt-interfaces';
2
+ export type TimeoutConfig = ITimeoutConfig;
3
+ export declare function getTimeoutConfig(): ITimeoutConfig;
4
+ export declare function getTimeout(type?: 'default' | 'csrf' | 'long' | number): number;
5
+ //# sourceMappingURL=timeouts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeouts.d.ts","sourceRoot":"","sources":["../../src/utils/timeouts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAGxE,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"}
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getTimeoutConfig = getTimeoutConfig;
4
+ exports.getTimeout = getTimeout;
5
+ function getTimeoutConfig() {
6
+ const defaultTimeout = parseInt(process.env.SAP_TIMEOUT_DEFAULT || '45000', 10);
7
+ const csrfTimeout = parseInt(process.env.SAP_TIMEOUT_CSRF || '15000', 10);
8
+ const longTimeout = parseInt(process.env.SAP_TIMEOUT_LONG || '60000', 10);
9
+ return {
10
+ default: defaultTimeout,
11
+ csrf: csrfTimeout,
12
+ long: longTimeout,
13
+ };
14
+ }
15
+ function getTimeout(type = 'default') {
16
+ if (typeof type === 'number') {
17
+ return type;
18
+ }
19
+ const config = getTimeoutConfig();
20
+ return config[type];
21
+ }