@mcp-abap-adt/connection 0.1.12 → 0.1.14
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/README.md +116 -5
- package/dist/config/sapConfig.d.ts +4 -14
- package/dist/config/sapConfig.d.ts.map +1 -1
- package/dist/connection/AbapConnection.d.ts +3 -23
- package/dist/connection/AbapConnection.d.ts.map +1 -1
- package/dist/connection/AbstractAbapConnection.d.ts.map +1 -1
- package/dist/connection/AbstractAbapConnection.js +13 -9
- package/dist/connection/csrfConfig.d.ts +40 -0
- package/dist/connection/csrfConfig.d.ts.map +1 -0
- package/dist/connection/csrfConfig.js +42 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/logger.d.ts +3 -66
- package/dist/logger.d.ts.map +1 -1
- package/dist/utils/timeouts.d.ts +3 -6
- package/dist/utils/timeouts.d.ts.map +1 -1
- package/dist/utils/tokenRefresh.d.ts +3 -5
- package/dist/utils/tokenRefresh.d.ts.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -46,6 +46,58 @@ The package uses a clean separation of concerns:
|
|
|
46
46
|
- Permission vs auth error detection
|
|
47
47
|
- Suitable for SAP BTP ABAP Environment
|
|
48
48
|
|
|
49
|
+
## Responsibilities and Design Principles
|
|
50
|
+
|
|
51
|
+
### Core Development Principle
|
|
52
|
+
|
|
53
|
+
**Interface-Only Communication**: This package follows a fundamental development principle: **all interactions with external dependencies happen ONLY through interfaces**. The code knows **NOTHING beyond what is defined in the interfaces**.
|
|
54
|
+
|
|
55
|
+
This means:
|
|
56
|
+
- Does not know about concrete implementation classes from other packages
|
|
57
|
+
- Does not know about internal data structures or methods not defined in interfaces
|
|
58
|
+
- Does not make assumptions about implementation behavior beyond interface contracts
|
|
59
|
+
- Does not access properties or methods not explicitly defined in interfaces
|
|
60
|
+
|
|
61
|
+
This principle ensures:
|
|
62
|
+
- **Loose coupling**: Connection classes are decoupled from concrete implementations in other packages
|
|
63
|
+
- **Flexibility**: New implementations can be added without modifying connection classes
|
|
64
|
+
- **Testability**: Easy to mock dependencies for testing
|
|
65
|
+
- **Maintainability**: Changes to implementations don't affect connection classes
|
|
66
|
+
|
|
67
|
+
### Package Responsibilities
|
|
68
|
+
|
|
69
|
+
This package is responsible for:
|
|
70
|
+
|
|
71
|
+
1. **HTTP communication with SAP systems**: Makes HTTP requests to SAP ABAP systems via ADT protocol
|
|
72
|
+
2. **Authentication handling**: Supports Basic Auth and JWT/OAuth2 authentication methods
|
|
73
|
+
3. **Session management**: Manages cookies, CSRF tokens, and session state
|
|
74
|
+
4. **Token refresh**: Automatically refreshes expired JWT tokens (for `JwtAbapConnection`)
|
|
75
|
+
5. **Error handling**: Distinguishes between authentication errors and permission errors
|
|
76
|
+
|
|
77
|
+
#### What This Package Does
|
|
78
|
+
|
|
79
|
+
- **Provides connection abstraction**: `AbapConnection` interface for interacting with SAP systems
|
|
80
|
+
- **Handles HTTP requests**: Makes requests to SAP ADT endpoints with proper headers and authentication
|
|
81
|
+
- **Manages sessions**: Handles cookies, CSRF tokens, and session state persistence
|
|
82
|
+
- **Refreshes tokens**: Automatically refreshes expired JWT tokens when detected
|
|
83
|
+
- **Validates tokens**: Detects expired tokens by analyzing HTTP response codes (401/403)
|
|
84
|
+
|
|
85
|
+
#### What This Package Does NOT Do
|
|
86
|
+
|
|
87
|
+
- **Does NOT obtain tokens**: Token acquisition is handled by `@mcp-abap-adt/auth-providers` and `@mcp-abap-adt/auth-broker`
|
|
88
|
+
- **Does NOT store tokens**: Token storage is handled by `@mcp-abap-adt/auth-stores`
|
|
89
|
+
- **Does NOT orchestrate authentication**: Token lifecycle management is handled by `@mcp-abap-adt/auth-broker`
|
|
90
|
+
- **Does NOT know about destinations**: Destination-based authentication is handled by consumers
|
|
91
|
+
- **Does NOT handle OAuth2 flows**: OAuth2 flows are handled by token providers
|
|
92
|
+
|
|
93
|
+
### External Dependencies
|
|
94
|
+
|
|
95
|
+
This package interacts with external packages **ONLY through interfaces**:
|
|
96
|
+
|
|
97
|
+
- **Logger interface**: Uses `ILogger` interface for logging - does not know about concrete logger implementation
|
|
98
|
+
- **Session storage interface**: Uses `ISessionStorage` interface for session persistence - does not know about concrete storage implementation
|
|
99
|
+
- **No direct dependencies on auth packages**: All token-related operations are handled through configuration (`SapConfig`) passed by consumers
|
|
100
|
+
|
|
49
101
|
## Documentation
|
|
50
102
|
|
|
51
103
|
- 📦 **[Installation Guide](./docs/INSTALLATION.md)** - Setup and installation instructions
|
|
@@ -371,6 +423,66 @@ function createAbapConnection(
|
|
|
371
423
|
): AbapConnection;
|
|
372
424
|
```
|
|
373
425
|
|
|
426
|
+
#### `CSRF_CONFIG` and `CSRF_ERROR_MESSAGES`
|
|
427
|
+
|
|
428
|
+
**New in 0.1.13+:** Exported constants for consistent CSRF token handling across different connection implementations.
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
import { CSRF_CONFIG, CSRF_ERROR_MESSAGES } from '@mcp-abap-adt/connection';
|
|
432
|
+
|
|
433
|
+
// CSRF_CONFIG contains:
|
|
434
|
+
// - RETRY_COUNT: number (default: 3)
|
|
435
|
+
// - RETRY_DELAY: number (default: 1000ms)
|
|
436
|
+
// - ENDPOINT: string (default: '/sap/bc/adt/core/discovery')
|
|
437
|
+
// - REQUIRED_HEADERS: { 'x-csrf-token': 'fetch', 'Accept': 'application/atomsvc+xml' }
|
|
438
|
+
|
|
439
|
+
// CSRF_ERROR_MESSAGES contains:
|
|
440
|
+
// - FETCH_FAILED(attempts: number, cause: string): string
|
|
441
|
+
// - NOT_IN_HEADERS: string
|
|
442
|
+
// - REQUIRED_FOR_MUTATION: string
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
**Use case:** When implementing custom connection classes (e.g., Cloud SDK-based), you can use these constants to ensure consistent CSRF token handling:
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
import { CSRF_CONFIG, CSRF_ERROR_MESSAGES } from '@mcp-abap-adt/connection';
|
|
449
|
+
|
|
450
|
+
async function fetchCsrfToken(baseUrl: string): Promise<string> {
|
|
451
|
+
const csrfUrl = `${baseUrl}${CSRF_CONFIG.ENDPOINT}`;
|
|
452
|
+
|
|
453
|
+
for (let attempt = 0; attempt <= CSRF_CONFIG.RETRY_COUNT; attempt++) {
|
|
454
|
+
try {
|
|
455
|
+
const response = await yourHttpClient.get(csrfUrl, {
|
|
456
|
+
headers: CSRF_CONFIG.REQUIRED_HEADERS
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
const token = response.headers['x-csrf-token'];
|
|
460
|
+
if (!token) {
|
|
461
|
+
if (attempt < CSRF_CONFIG.RETRY_COUNT) {
|
|
462
|
+
await new Promise(resolve => setTimeout(resolve, CSRF_CONFIG.RETRY_DELAY));
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
throw new Error(CSRF_ERROR_MESSAGES.NOT_IN_HEADERS);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return token;
|
|
469
|
+
} catch (error) {
|
|
470
|
+
if (attempt >= CSRF_CONFIG.RETRY_COUNT) {
|
|
471
|
+
throw new Error(
|
|
472
|
+
CSRF_ERROR_MESSAGES.FETCH_FAILED(
|
|
473
|
+
CSRF_CONFIG.RETRY_COUNT + 1,
|
|
474
|
+
error instanceof Error ? error.message : String(error)
|
|
475
|
+
)
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
await new Promise(resolve => setTimeout(resolve, CSRF_CONFIG.RETRY_DELAY));
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
See [PR Proposal](./PR_PROPOSAL_CSRF_CONFIG.md) for more details.
|
|
485
|
+
|
|
374
486
|
## Requirements
|
|
375
487
|
|
|
376
488
|
- Node.js >= 18.0.0
|
|
@@ -380,11 +492,10 @@ function createAbapConnection(
|
|
|
380
492
|
|
|
381
493
|
See [CHANGELOG.md](./CHANGELOG.md) for detailed version history and breaking changes.
|
|
382
494
|
|
|
383
|
-
**Latest version: 0.1.
|
|
384
|
-
-
|
|
385
|
-
-
|
|
386
|
-
-
|
|
387
|
-
- Fixed documentation structure and links
|
|
495
|
+
**Latest version: 0.1.13**
|
|
496
|
+
- Added `CSRF_CONFIG` and `CSRF_ERROR_MESSAGES` exports for consistent CSRF token handling
|
|
497
|
+
- Updated CSRF token endpoint to `/sap/bc/adt/core/discovery` (lighter response, available on all systems)
|
|
498
|
+
- See [CHANGELOG.md](./CHANGELOG.md) for full details
|
|
388
499
|
|
|
389
500
|
## Documentation
|
|
390
501
|
|
|
@@ -1,15 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
authType: SapAuthType;
|
|
6
|
-
username?: string;
|
|
7
|
-
password?: string;
|
|
8
|
-
jwtToken?: string;
|
|
9
|
-
refreshToken?: string;
|
|
10
|
-
uaaUrl?: string;
|
|
11
|
-
uaaClientId?: string;
|
|
12
|
-
uaaClientSecret?: string;
|
|
13
|
-
}
|
|
14
|
-
export declare function sapConfigSignature(config: SapConfig): string;
|
|
1
|
+
import type { ISapConfig, SapAuthType } from '@mcp-abap-adt/interfaces';
|
|
2
|
+
export type { SapAuthType };
|
|
3
|
+
export type SapConfig = ISapConfig;
|
|
4
|
+
export declare function sapConfigSignature(config: ISapConfig): string;
|
|
15
5
|
//# sourceMappingURL=sapConfig.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sapConfig.d.ts","sourceRoot":"","sources":["../../src/config/sapConfig.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sapConfig.d.ts","sourceRoot":"","sources":["../../src/config/sapConfig.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGxE,YAAY,EAAE,WAAW,EAAE,CAAC;AAC5B,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC;AAEnC,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAmB7D"}
|
|
@@ -1,24 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export interface AbapRequestOptions {
|
|
5
|
-
url: string;
|
|
6
|
-
method: string;
|
|
7
|
-
timeout: number;
|
|
8
|
-
data?: any;
|
|
9
|
-
params?: any;
|
|
10
|
-
headers?: Record<string, string>;
|
|
11
|
-
}
|
|
12
|
-
export interface AbapConnection {
|
|
13
|
-
getConfig(): SapConfig;
|
|
14
|
-
getBaseUrl(): Promise<string>;
|
|
15
|
-
getAuthHeaders(): Promise<Record<string, string>>;
|
|
16
|
-
getSessionId(): string | null;
|
|
17
|
-
setSessionType(type: "stateful" | "stateless"): void;
|
|
18
|
-
makeAdtRequest(options: AbapRequestOptions): Promise<AxiosResponse>;
|
|
19
|
-
connect(): Promise<void>;
|
|
20
|
-
reset(): void;
|
|
21
|
-
getSessionState(): SessionState | null;
|
|
22
|
-
setSessionState(state: SessionState): void;
|
|
23
|
-
}
|
|
1
|
+
import type { IAbapRequestOptions, IAbapConnection } from '@mcp-abap-adt/interfaces';
|
|
2
|
+
export type AbapRequestOptions = IAbapRequestOptions;
|
|
3
|
+
export type AbapConnection = IAbapConnection;
|
|
24
4
|
//# sourceMappingURL=AbapConnection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/AbapConnection.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/AbapConnection.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAGrF,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,CAAC;AACrD,MAAM,MAAM,cAAc,GAAG,eAAe,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AbstractAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/AbstractAbapConnection.ts"],"names":[],"mappings":"AAAA,OAAc,EAAiD,aAAa,EAAE,MAAM,OAAO,CAAC;AAG5F,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEtE,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"AbstractAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/AbstractAbapConnection.ts"],"names":[],"mappings":"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"}
|
|
@@ -38,6 +38,7 @@ const axios_1 = __importStar(require("axios"));
|
|
|
38
38
|
const https_1 = require("https");
|
|
39
39
|
const crypto_1 = require("crypto");
|
|
40
40
|
const timeouts_js_1 = require("../utils/timeouts.js");
|
|
41
|
+
const csrfConfig_js_1 = require("./csrfConfig.js");
|
|
41
42
|
class AbstractAbapConnection {
|
|
42
43
|
config;
|
|
43
44
|
logger;
|
|
@@ -464,15 +465,19 @@ class AbstractAbapConnection {
|
|
|
464
465
|
* Fetch CSRF token from SAP system
|
|
465
466
|
* Protected method for use by concrete implementations in their connect() method
|
|
466
467
|
*/
|
|
467
|
-
async fetchCsrfToken(url, retryCount =
|
|
468
|
+
async fetchCsrfToken(url, retryCount = csrfConfig_js_1.CSRF_CONFIG.RETRY_COUNT, retryDelay = csrfConfig_js_1.CSRF_CONFIG.RETRY_DELAY) {
|
|
468
469
|
let csrfUrl = url;
|
|
470
|
+
// Build CSRF endpoint URL from base URL
|
|
469
471
|
if (!url.includes("/sap/bc/adt/")) {
|
|
470
|
-
|
|
472
|
+
// If URL doesn't contain ADT path, append endpoint
|
|
473
|
+
csrfUrl = url.endsWith("/") ? `${url}${csrfConfig_js_1.CSRF_CONFIG.ENDPOINT.slice(1)}` : `${url}${csrfConfig_js_1.CSRF_CONFIG.ENDPOINT}`;
|
|
471
474
|
}
|
|
472
|
-
else if (!url.includes(
|
|
475
|
+
else if (!url.includes(csrfConfig_js_1.CSRF_CONFIG.ENDPOINT)) {
|
|
476
|
+
// If URL contains ADT path but not our endpoint, extract base and append endpoint
|
|
473
477
|
const base = url.split("/sap/bc/adt")[0];
|
|
474
|
-
csrfUrl = `${base}
|
|
478
|
+
csrfUrl = `${base}${csrfConfig_js_1.CSRF_CONFIG.ENDPOINT}`;
|
|
475
479
|
}
|
|
480
|
+
// If URL already contains the endpoint, use it as is
|
|
476
481
|
if (this.logger.csrfToken) {
|
|
477
482
|
this.logger.csrfToken("fetch", `Fetching CSRF token from: ${csrfUrl}`);
|
|
478
483
|
}
|
|
@@ -484,8 +489,7 @@ class AbstractAbapConnection {
|
|
|
484
489
|
const authHeaders = await this.getAuthHeaders();
|
|
485
490
|
const headers = {
|
|
486
491
|
...authHeaders,
|
|
487
|
-
|
|
488
|
-
Accept: "application/atomsvc+xml"
|
|
492
|
+
...csrfConfig_js_1.CSRF_CONFIG.REQUIRED_HEADERS
|
|
489
493
|
};
|
|
490
494
|
// Always add cookies if available - they are needed for session continuity
|
|
491
495
|
// Even on first attempt, if we have cookies from previous session or error response, use them
|
|
@@ -517,7 +521,7 @@ class AbstractAbapConnection {
|
|
|
517
521
|
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
518
522
|
continue;
|
|
519
523
|
}
|
|
520
|
-
throw new Error(
|
|
524
|
+
throw new Error(csrfConfig_js_1.CSRF_ERROR_MESSAGES.NOT_IN_HEADERS);
|
|
521
525
|
}
|
|
522
526
|
if (response.headers["set-cookie"]) {
|
|
523
527
|
this.updateCookiesFromResponse(response.headers);
|
|
@@ -606,7 +610,7 @@ class AbstractAbapConnection {
|
|
|
606
610
|
// Re-throw the original AxiosError to preserve response information
|
|
607
611
|
throw error;
|
|
608
612
|
}
|
|
609
|
-
throw new Error(
|
|
613
|
+
throw new Error(csrfConfig_js_1.CSRF_ERROR_MESSAGES.FETCH_FAILED(retryCount + 1, error instanceof Error ? error.message : String(error)));
|
|
610
614
|
}
|
|
611
615
|
}
|
|
612
616
|
throw new Error("CSRF token fetch failed unexpectedly");
|
|
@@ -699,7 +703,7 @@ class AbstractAbapConnection {
|
|
|
699
703
|
catch (error) {
|
|
700
704
|
// fetchCsrfToken already handles auth errors and auto-refresh
|
|
701
705
|
// Just re-throw the error with minimal logging to avoid duplicate error messages
|
|
702
|
-
const errorMsg = error instanceof Error ? error.message :
|
|
706
|
+
const errorMsg = error instanceof Error ? error.message : csrfConfig_js_1.CSRF_ERROR_MESSAGES.REQUIRED_FOR_MUTATION;
|
|
703
707
|
// Only log at DEBUG level to avoid duplicate error messages
|
|
704
708
|
// (fetchCsrfToken already logged the error at ERROR level if auth failed)
|
|
705
709
|
this.logger.debug(`[DEBUG] BaseAbapConnection - ensureFreshCsrfToken failed: ${errorMsg}`);
|
|
@@ -0,0 +1,40 @@
|
|
|
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
|
|
20
|
+
* Standard SAP ADT core discovery endpoint (available on all systems, returns smaller response)
|
|
21
|
+
*/
|
|
22
|
+
readonly ENDPOINT: "/sap/bc/adt/core/discovery";
|
|
23
|
+
/**
|
|
24
|
+
* Required headers for CSRF token fetch
|
|
25
|
+
*/
|
|
26
|
+
readonly REQUIRED_HEADERS: {
|
|
27
|
+
readonly 'x-csrf-token': "fetch";
|
|
28
|
+
readonly Accept: "application/atomsvc+xml";
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* CSRF token error messages
|
|
33
|
+
* Standardized error messages for consistent error reporting
|
|
34
|
+
*/
|
|
35
|
+
export declare const CSRF_ERROR_MESSAGES: {
|
|
36
|
+
readonly FETCH_FAILED: (attempts: number, cause: string) => string;
|
|
37
|
+
readonly NOT_IN_HEADERS: "No CSRF token in response headers";
|
|
38
|
+
readonly REQUIRED_FOR_MUTATION: "CSRF token is required for POST/PUT requests but could not be fetched";
|
|
39
|
+
};
|
|
40
|
+
//# 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;;OAEG;;;;;CAKK,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,mBAAmB;sCACL,MAAM,SAAS,MAAM;;;CAMtC,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
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
|
|
23
|
+
* Standard SAP ADT core discovery endpoint (available on all systems, returns smaller response)
|
|
24
|
+
*/
|
|
25
|
+
ENDPOINT: '/sap/bc/adt/core/discovery',
|
|
26
|
+
/**
|
|
27
|
+
* Required headers for CSRF token fetch
|
|
28
|
+
*/
|
|
29
|
+
REQUIRED_HEADERS: {
|
|
30
|
+
'x-csrf-token': 'fetch',
|
|
31
|
+
'Accept': 'application/atomsvc+xml'
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* CSRF token error messages
|
|
36
|
+
* Standardized error messages for consistent error reporting
|
|
37
|
+
*/
|
|
38
|
+
exports.CSRF_ERROR_MESSAGES = {
|
|
39
|
+
FETCH_FAILED: (attempts, cause) => `Failed to fetch CSRF token after ${attempts} attempts: ${cause}`,
|
|
40
|
+
NOT_IN_HEADERS: 'No CSRF token in response headers',
|
|
41
|
+
REQUIRED_FOR_MUTATION: 'CSRF token is required for POST/PUT requests but could not be fetched'
|
|
42
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -10,4 +10,5 @@ export { JwtAbapConnection as CloudAbapConnection } from "./connection/JwtAbapCo
|
|
|
10
10
|
export { createAbapConnection } from "./connection/connectionFactory.js";
|
|
11
11
|
export { sapConfigSignature } from "./config/sapConfig.js";
|
|
12
12
|
export { getTimeout, getTimeoutConfig, type TimeoutConfig } from "./utils/timeouts.js";
|
|
13
|
+
export { CSRF_CONFIG, CSRF_ERROR_MESSAGES } from "./connection/csrfConfig.js";
|
|
13
14
|
//# 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":"AACA,YAAY,EACV,SAAS,EACT,WAAW,GACZ,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAGzE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAG1E,OAAO,EAAE,kBAAkB,EAAE,KAAK,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAGnG,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAGtE,OAAO,EAAE,kBAAkB,IAAI,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAChG,OAAO,EAAE,iBAAiB,IAAI,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAG7F,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAGzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG3D,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,SAAS,EACT,WAAW,GACZ,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAGzE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAG1E,OAAO,EAAE,kBAAkB,EAAE,KAAK,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAGnG,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAGtE,OAAO,EAAE,kBAAkB,IAAI,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAChG,OAAO,EAAE,iBAAiB,IAAI,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAG7F,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAGzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG3D,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGvF,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getTimeoutConfig = exports.getTimeout = exports.sapConfigSignature = exports.createAbapConnection = exports.CloudAbapConnection = exports.OnPremAbapConnection = exports.JwtAbapConnection = exports.BaseAbapConnection = exports.FileSessionStorage = void 0;
|
|
3
|
+
exports.CSRF_ERROR_MESSAGES = exports.CSRF_CONFIG = exports.getTimeoutConfig = exports.getTimeout = exports.sapConfigSignature = exports.createAbapConnection = exports.CloudAbapConnection = exports.OnPremAbapConnection = exports.JwtAbapConnection = exports.BaseAbapConnection = exports.FileSessionStorage = void 0;
|
|
4
4
|
// Session storage implementations
|
|
5
5
|
var FileSessionStorage_js_1 = require("./utils/FileSessionStorage.js");
|
|
6
6
|
Object.defineProperty(exports, "FileSessionStorage", { enumerable: true, get: function () { return FileSessionStorage_js_1.FileSessionStorage; } });
|
|
@@ -24,3 +24,7 @@ Object.defineProperty(exports, "sapConfigSignature", { enumerable: true, get: fu
|
|
|
24
24
|
var timeouts_js_1 = require("./utils/timeouts.js");
|
|
25
25
|
Object.defineProperty(exports, "getTimeout", { enumerable: true, get: function () { return timeouts_js_1.getTimeout; } });
|
|
26
26
|
Object.defineProperty(exports, "getTimeoutConfig", { enumerable: true, get: function () { return timeouts_js_1.getTimeoutConfig; } });
|
|
27
|
+
// CSRF configuration
|
|
28
|
+
var csrfConfig_js_1 = require("./connection/csrfConfig.js");
|
|
29
|
+
Object.defineProperty(exports, "CSRF_CONFIG", { enumerable: true, get: function () { return csrfConfig_js_1.CSRF_CONFIG; } });
|
|
30
|
+
Object.defineProperty(exports, "CSRF_ERROR_MESSAGES", { enumerable: true, get: function () { return csrfConfig_js_1.CSRF_ERROR_MESSAGES; } });
|
package/dist/logger.d.ts
CHANGED
|
@@ -1,67 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*/
|
|
5
|
-
export interface ILogger {
|
|
6
|
-
/**
|
|
7
|
-
* Log informational message
|
|
8
|
-
*/
|
|
9
|
-
info(message: string, meta?: any): void;
|
|
10
|
-
/**
|
|
11
|
-
* Log error message
|
|
12
|
-
*/
|
|
13
|
-
error(message: string, meta?: any): void;
|
|
14
|
-
/**
|
|
15
|
-
* Log warning message
|
|
16
|
-
*/
|
|
17
|
-
warn(message: string, meta?: any): void;
|
|
18
|
-
/**
|
|
19
|
-
* Log debug message
|
|
20
|
-
*/
|
|
21
|
-
debug(message: string, meta?: any): void;
|
|
22
|
-
/**
|
|
23
|
-
* Log CSRF token operations
|
|
24
|
-
* @param action - Type of CSRF operation: "fetch", "retry", "success", or "error"
|
|
25
|
-
* @param message - Log message
|
|
26
|
-
* @param meta - Additional metadata
|
|
27
|
-
*/
|
|
28
|
-
csrfToken?(action: "fetch" | "retry" | "success" | "error", message: string, meta?: any): void;
|
|
29
|
-
/**
|
|
30
|
-
* Log TLS configuration
|
|
31
|
-
* @param rejectUnauthorized - Whether TLS certificate validation is enabled
|
|
32
|
-
*/
|
|
33
|
-
tlsConfig?(rejectUnauthorized: boolean): void;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Session state interface for stateful connections
|
|
37
|
-
* Contains cookies and CSRF token that need to be preserved across requests
|
|
38
|
-
*/
|
|
39
|
-
export interface SessionState {
|
|
40
|
-
cookies: string | null;
|
|
41
|
-
csrfToken: string | null;
|
|
42
|
-
cookieStore: Record<string, string>;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Interface for storing and retrieving session state
|
|
46
|
-
* Allows connection layer to persist session state (cookies, CSRF token) externally
|
|
47
|
-
*/
|
|
48
|
-
export interface ISessionStorage {
|
|
49
|
-
/**
|
|
50
|
-
* Save session state for a given session ID
|
|
51
|
-
* @param sessionId - Unique session identifier
|
|
52
|
-
* @param state - Session state to save
|
|
53
|
-
*/
|
|
54
|
-
save(sessionId: string, state: SessionState): Promise<void>;
|
|
55
|
-
/**
|
|
56
|
-
* Load session state for a given session ID
|
|
57
|
-
* @param sessionId - Unique session identifier
|
|
58
|
-
* @returns Session state or null if not found
|
|
59
|
-
*/
|
|
60
|
-
load(sessionId: string): Promise<SessionState | null>;
|
|
61
|
-
/**
|
|
62
|
-
* Delete session state for a given session ID
|
|
63
|
-
* @param sessionId - Unique session identifier
|
|
64
|
-
*/
|
|
65
|
-
delete(sessionId: string): Promise<void>;
|
|
66
|
-
}
|
|
1
|
+
import type { ILogger, ISessionStorage, ISessionState } from '@mcp-abap-adt/interfaces';
|
|
2
|
+
export type { ILogger, ISessionStorage };
|
|
3
|
+
export type SessionState = ISessionState;
|
|
67
4
|
//# sourceMappingURL=logger.d.ts.map
|
package/dist/logger.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAGxF,YAAY,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC;AACzC,MAAM,MAAM,YAAY,GAAG,aAAa,CAAC"}
|
package/dist/utils/timeouts.d.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
long: number;
|
|
5
|
-
}
|
|
6
|
-
export declare function getTimeoutConfig(): TimeoutConfig;
|
|
1
|
+
import type { ITimeoutConfig } from '@mcp-abap-adt/interfaces';
|
|
2
|
+
export type TimeoutConfig = ITimeoutConfig;
|
|
3
|
+
export declare function getTimeoutConfig(): ITimeoutConfig;
|
|
7
4
|
export declare function getTimeout(type?: "default" | "csrf" | "long" | number): number;
|
|
8
5
|
//# sourceMappingURL=timeouts.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeouts.d.ts","sourceRoot":"","sources":["../../src/utils/timeouts.ts"],"names":[],"mappings":"AAAA,
|
|
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,CAUjD;AAED,wBAAgB,UAAU,CAAC,IAAI,GAAE,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,MAAkB,GAAG,MAAM,CAOzF"}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Token refresh utilities for JWT authentication
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
refreshToken?: string;
|
|
7
|
-
}
|
|
4
|
+
import type { ITokenRefreshResult } from '@mcp-abap-adt/interfaces';
|
|
5
|
+
export type TokenRefreshResult = ITokenRefreshResult;
|
|
8
6
|
/**
|
|
9
7
|
* Refreshes the access token using refresh token
|
|
10
8
|
* @param refreshToken Refresh token
|
|
@@ -13,5 +11,5 @@ export interface TokenRefreshResult {
|
|
|
13
11
|
* @param clientSecret UAA client secret
|
|
14
12
|
* @returns Promise that resolves to new tokens
|
|
15
13
|
*/
|
|
16
|
-
export declare function refreshJwtToken(refreshToken: string, uaaUrl: string, clientId: string, clientSecret: string): Promise<
|
|
14
|
+
export declare function refreshJwtToken(refreshToken: string, uaaUrl: string, clientId: string, clientSecret: string): Promise<ITokenRefreshResult>;
|
|
17
15
|
//# sourceMappingURL=tokenRefresh.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokenRefresh.d.ts","sourceRoot":"","sources":["../../src/utils/tokenRefresh.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"tokenRefresh.d.ts","sourceRoot":"","sources":["../../src/utils/tokenRefresh.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAGpE,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,CAAC;AAErD;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,mBAAmB,CAAC,CAqC9B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-abap-adt/connection",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "ABAP connection layer for MCP ABAP ADT server",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"node": ">=18.0.0"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
+
"@mcp-abap-adt/interfaces": "^0.1.0",
|
|
46
47
|
"axios": "^1.11.0",
|
|
47
48
|
"commander": "^14.0.2",
|
|
48
49
|
"express": "^5.1.0",
|