@mcp-abap-adt/connection 5.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.
Files changed (91) hide show
  1. package/CHANGELOG.md +134 -1
  2. package/README.md +204 -48
  3. package/dist/auth/providers.d.ts +38 -9
  4. package/dist/auth/providers.d.ts.map +1 -1
  5. package/dist/auth/providers.js +46 -3
  6. package/dist/connection/AbstractAbapConnection.d.ts +83 -136
  7. package/dist/connection/AbstractAbapConnection.d.ts.map +1 -1
  8. package/dist/connection/AbstractAbapConnection.js +241 -763
  9. package/dist/connection/AdtCloudConnector.d.ts +12 -7
  10. package/dist/connection/AdtCloudConnector.d.ts.map +1 -1
  11. package/dist/connection/AdtCloudConnector.js +2 -6
  12. package/dist/connection/AdtOnPremConnector.d.ts +20 -7
  13. package/dist/connection/AdtOnPremConnector.d.ts.map +1 -1
  14. package/dist/connection/AdtOnPremConnector.js +2 -6
  15. package/dist/connection/CloudHttpTransport.d.ts +37 -0
  16. package/dist/connection/CloudHttpTransport.d.ts.map +1 -0
  17. package/dist/{session/CloudSecuritySessionStrategy.js → connection/CloudHttpTransport.js} +59 -49
  18. package/dist/connection/CredentialAbapConnection.d.ts +8 -41
  19. package/dist/connection/CredentialAbapConnection.d.ts.map +1 -1
  20. package/dist/connection/CredentialAbapConnection.js +44 -111
  21. package/dist/connection/HttpTransport.d.ts +178 -0
  22. package/dist/connection/HttpTransport.d.ts.map +1 -0
  23. package/dist/connection/HttpTransport.js +402 -0
  24. package/dist/connection/IAdtTransport.d.ts +232 -0
  25. package/dist/connection/IAdtTransport.d.ts.map +1 -0
  26. package/dist/connection/IAdtTransport.js +28 -0
  27. package/dist/connection/LegacyOnPremHttpTransport.d.ts +40 -0
  28. package/dist/connection/LegacyOnPremHttpTransport.d.ts.map +1 -0
  29. package/dist/connection/LegacyOnPremHttpTransport.js +57 -0
  30. package/dist/connection/OnPremHttpTransport.d.ts +45 -0
  31. package/dist/connection/OnPremHttpTransport.d.ts.map +1 -0
  32. package/dist/connection/OnPremHttpTransport.js +91 -0
  33. package/dist/connection/RfcTransport.d.ts +89 -0
  34. package/dist/connection/RfcTransport.d.ts.map +1 -0
  35. package/dist/connection/RfcTransport.js +256 -0
  36. package/dist/connection/rfcConversation.d.ts +44 -0
  37. package/dist/connection/rfcConversation.d.ts.map +1 -0
  38. package/dist/connection/rfcConversation.js +71 -0
  39. package/dist/index.d.ts +7 -8
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +21 -18
  42. package/dist/utils/timeouts.d.ts +6 -19
  43. package/dist/utils/timeouts.d.ts.map +1 -1
  44. package/dist/utils/timeouts.js +6 -22
  45. package/docs/INDEX.md +5 -2
  46. package/docs/INSTALLATION.md +28 -10
  47. package/docs/JWT_AUTH_TOOLS.md +20 -4
  48. package/docs/MIGRATION-6.0.md +359 -0
  49. package/docs/SCOPE.md +1 -1
  50. package/docs/STATEFUL_SESSION_GUIDE.md +86 -17
  51. package/docs/USAGE.md +260 -119
  52. package/examples/basic-connection.js +15 -3
  53. package/examples/jwt-with-token-refresh.js +15 -7
  54. package/examples/saml-connection.js +15 -2
  55. package/package.json +12 -10
  56. package/dist/__tests__/helpers/session.d.ts +0 -15
  57. package/dist/__tests__/helpers/session.d.ts.map +0 -1
  58. package/dist/__tests__/helpers/session.js +0 -19
  59. package/dist/auth/IAuthProvider.d.ts +0 -84
  60. package/dist/auth/IAuthProvider.d.ts.map +0 -1
  61. package/dist/auth/IAuthProvider.js +0 -21
  62. package/dist/connection/BaseAbapConnection.d.ts +0 -29
  63. package/dist/connection/BaseAbapConnection.d.ts.map +0 -1
  64. package/dist/connection/BaseAbapConnection.js +0 -81
  65. package/dist/connection/CertificateAbapConnection.d.ts +0 -35
  66. package/dist/connection/CertificateAbapConnection.d.ts.map +0 -1
  67. package/dist/connection/CertificateAbapConnection.js +0 -91
  68. package/dist/connection/JwtAbapConnection.d.ts +0 -131
  69. package/dist/connection/JwtAbapConnection.d.ts.map +0 -1
  70. package/dist/connection/JwtAbapConnection.js +0 -376
  71. package/dist/connection/KerberosAbapConnection.d.ts +0 -32
  72. package/dist/connection/KerberosAbapConnection.d.ts.map +0 -1
  73. package/dist/connection/KerberosAbapConnection.js +0 -128
  74. package/dist/connection/RfcAbapConnection.d.ts +0 -44
  75. package/dist/connection/RfcAbapConnection.d.ts.map +0 -1
  76. package/dist/connection/RfcAbapConnection.js +0 -324
  77. package/dist/connection/SamlAbapConnection.d.ts +0 -31
  78. package/dist/connection/SamlAbapConnection.d.ts.map +0 -1
  79. package/dist/connection/SamlAbapConnection.js +0 -81
  80. package/dist/connection/connectionFactory.d.ts +0 -25
  81. package/dist/connection/connectionFactory.d.ts.map +0 -1
  82. package/dist/connection/connectionFactory.js +0 -84
  83. package/dist/session/CloudSecuritySessionStrategy.d.ts +0 -32
  84. package/dist/session/CloudSecuritySessionStrategy.d.ts.map +0 -1
  85. package/dist/session/IcfSessionStrategy.d.ts +0 -27
  86. package/dist/session/IcfSessionStrategy.d.ts.map +0 -1
  87. package/dist/session/IcfSessionStrategy.js +0 -62
  88. package/dist/session/SessionStrategy.d.ts +0 -86
  89. package/dist/session/SessionStrategy.d.ts.map +0 -1
  90. package/dist/session/SessionStrategy.js +0 -33
  91. package/docs/superpowers/specs/2026-08-21-platform-connectors.md +0 -108
@@ -0,0 +1,232 @@
1
+ /**
2
+ * What carries an ADT request to the server.
3
+ *
4
+ * The credential says how a request proves who it is; this says what the
5
+ * request travels over. They are independent axes, and on-prem is where both
6
+ * are real choices: the same ADT REST call goes over HTTP, or over RFC to
7
+ * `SADT_REST_RFC_ENDPOINT` — the FM Eclipse ADT itself uses through JCo — on a
8
+ * system where stateful HTTP sessions are not usable. ABAP Cloud has one
9
+ * transport, so its connector takes no such parameter.
10
+ *
11
+ * **Said by the caller, never sniffed.** Which transport is in use is a fact
12
+ * about the deployment, not something to be discovered by trying one and
13
+ * watching it fail — the same rule `SessionStrategy` states for session
14
+ * mechanics.
15
+ */
16
+ /** One request, in the terms every transport can honour. */
17
+ export interface IAdtTransportRequest {
18
+ method: string;
19
+ url: string;
20
+ headers?: Record<string, string>;
21
+ data?: unknown;
22
+ /**
23
+ * Query parameters, unserialised.
24
+ *
25
+ * Part of the request rather than folded into `url` by the caller, because
26
+ * the two transports carry them differently: HTTP hands them to a client
27
+ * that serialises them, RFC has no such step and must write them into the
28
+ * URI itself. A transport that ignored them would send the request without
29
+ * its query — silently, and only on one of the two wires.
30
+ */
31
+ params?: Record<string, unknown>;
32
+ /** Milliseconds; absent means the transport's own default. */
33
+ timeout?: number;
34
+ /**
35
+ * Which statuses resolve rather than throw. Absent means the default: 2xx
36
+ * resolves, everything else throws — which the retry and 401 handling above
37
+ * this seam is written against.
38
+ */
39
+ validateStatus?: (status: number) => boolean;
40
+ }
41
+ /** What came back, in the terms every transport can produce. */
42
+ export interface IAdtTransportResponse {
43
+ status: number;
44
+ statusText?: string;
45
+ /**
46
+ * Deliberately `unknown`: a transport names its own header container, and a
47
+ * caller narrows what it needs. HTTP has axios's, RFC builds one out of
48
+ * `HEADER_FIELDS`.
49
+ */
50
+ headers: unknown;
51
+ data: unknown;
52
+ }
53
+ /** What a transport needs from above to establish itself. */
54
+ /**
55
+ * What a wire needs from the connection to ask for a session, or give one back.
56
+ *
57
+ * The same shape the establishing call takes, minus the retry knobs: opening a
58
+ * session is one request, and one that is retried is one the caller cannot
59
+ * reason about — a second session may already be open by the time the first
60
+ * answer arrives.
61
+ */
62
+ export interface IAdtSessionContext {
63
+ /** The server, for a wire that addresses one. */
64
+ baseUrl: string;
65
+ /** Read once per request: a provider may renew behind the call. */
66
+ authHeaders: () => Promise<Record<string, string>>;
67
+ /** Anything the conversation carries — the ADT connection id, today. */
68
+ extraHeaders?: Record<string, string>;
69
+ /** Where to hand each answer, so the connection sees what the wire saw. */
70
+ observe: (headers: unknown) => void;
71
+ }
72
+ export interface IAdtEstablishContext extends IAdtSessionContext {
73
+ /**
74
+ * Where to hand each answer. The wire folds a response into its own state;
75
+ * whether a new session id is an establishment or a replacement is a question
76
+ * about the session's lifetime, which is decided above.
77
+ */
78
+ observe: (headers: unknown) => void;
79
+ /** How many times to ask again. Absent means the transport's own default. */
80
+ retries?: number;
81
+ /** Milliseconds between attempts. Absent means the transport's own default. */
82
+ retryDelayMs?: number;
83
+ /** Milliseconds per attempt. Absent means the transport's own default. */
84
+ timeoutMs?: number;
85
+ /**
86
+ * Which failures must not be retried.
87
+ *
88
+ * A verdict about the session — "the one we were on is gone" — is not a
89
+ * failed exchange: retrying would observe the SAME new session, read it as
90
+ * unchanged, and lose the replacement for good. The wire cannot tell those
91
+ * apart, so it asks.
92
+ */
93
+ isFatal?: (error: unknown) => boolean;
94
+ }
95
+ export interface IAdtTransport {
96
+ /** For logs, so which transport ran is never inferred from behaviour. */
97
+ readonly kind: string;
98
+ /**
99
+ * Get a session, in whatever way this wire has one.
100
+ *
101
+ * An RFC conversation is opened, and IS the session. ABAP Cloud publishes a
102
+ * session as a resource and this asks for one. On-prem HTTP has neither — its
103
+ * session arrives as a cookie on the establishing call — so its
104
+ * implementation is empty.
105
+ *
106
+ * **Required, not optional.** A wire with nothing to open writes an empty
107
+ * method, which is true of it; the alternative was the connection asking
108
+ * `open?.()` at every teardown — a runtime question about a collaborator it
109
+ * was handed, which is the thing a contract exists to answer instead.
110
+ *
111
+ * This used to be a `SessionStrategy` the connection selected and drove: a
112
+ * second wire abstraction beside this one, in the class every wire shares,
113
+ * describing a mechanism only some of them have.
114
+ */
115
+ open(context: IAdtSessionContext): Promise<void>;
116
+ /**
117
+ * Give the session back.
118
+ *
119
+ * Never throws, and a repeat call finds nothing owed — this is reached from
120
+ * `disconnect()`, which promises to settle. What it does is the wire's: a
121
+ * DELETE on the address the server published, the platform's logoff, or
122
+ * closing the conversation.
123
+ */
124
+ close(context: IAdtSessionContext): Promise<void>;
125
+ /**
126
+ * Issue one request.
127
+ *
128
+ * Throws for a status `validateStatus` does not admit, carrying `response`
129
+ * on the error — that is the shape the request path classifies against, and
130
+ * a transport that resolved instead would turn a 401 into a success with a
131
+ * logon page in the body.
132
+ */
133
+ send(request: IAdtTransportRequest): Promise<IAdtTransportResponse>;
134
+ /**
135
+ * Fold a response into whatever state this wire keeps.
136
+ *
137
+ * Every transport answers, because every transport has an answer — HTTP
138
+ * keeps a cookie jar and the application server it was told about, RFC keeps
139
+ * nothing because its conversation carries the session itself. What the
140
+ * change MEANS is not asked here: whether a new session id is an
141
+ * establishment or a replacement is a question about the session's lifetime,
142
+ * and it is answered above.
143
+ */
144
+ ingest(headers?: Record<string, unknown>): void;
145
+ /** What to put on the `Cookie` header, or `null` for a wire that has none. */
146
+ cookies(): string | null;
147
+ /**
148
+ * Which session this wire is on, as something comparable.
149
+ *
150
+ * Empty means "this wire is on no session" — which for HTTP is a system that
151
+ * issued no `SAP_SESSIONID`, and for RFC never happens while the
152
+ * conversation is open, because the conversation IS the session.
153
+ */
154
+ sessionFingerprint(): Map<string, string>;
155
+ /**
156
+ * Does this wire hold a session it can work over?
157
+ *
158
+ * A verdict, not a fact for somebody else to interpret. The connection used
159
+ * to read a fingerprint and a `skipSessionType` flag and decide for every
160
+ * wire at once — which meant carrying, in the class they all share, a
161
+ * property of one deployment: BASIS 7.40, where the stateful header makes the
162
+ * server store locks in session memory instead of the enqueue table.
163
+ *
164
+ * Each wire answers for itself. HTTP has a session when the server named one.
165
+ * An RFC conversation IS the session. A wire whose system names no session is
166
+ * not a broken HTTP one — it is answering truthfully about itself.
167
+ */
168
+ sessionEstablished(): boolean;
169
+ /** Headers that keep this connection on the server its session lives on. */
170
+ affinityHeaders(): Record<string, string>;
171
+ /**
172
+ * Get this wire ready to carry a mutation.
173
+ *
174
+ * HTTP earns a CSRF token, and the cookies that name the session arrive with
175
+ * it. RFC has nothing to earn: the conversation was opened before this and IS
176
+ * the session, and `SADT_REST_RFC_ENDPOINT` returns neither a token nor a
177
+ * cookie however it is asked — so a shared implementation could only be one
178
+ * that fails on one of the two wires, which is what it did.
179
+ *
180
+ * The context is what the wire cannot know: which server, which credential,
181
+ * and where to hand the answer so the session lifetime above can fence and
182
+ * classify it.
183
+ */
184
+ establish(context: IAdtEstablishContext): Promise<void>;
185
+ /** The CSRF token this wire holds, or `null` for a wire that has none. */
186
+ csrfToken(): string | null;
187
+ /**
188
+ * Take a token earned elsewhere.
189
+ *
190
+ * A SAML credential does the exchange itself — the session cookie it earns is
191
+ * the point of it — and the token still has to reach the wire that will
192
+ * present it. `null` drops one without dropping the session around it.
193
+ */
194
+ adoptCsrfToken(token: string | null): void;
195
+ /** Drop the session state this wire was holding. */
196
+ forgetSession(): void;
197
+ }
198
+ /**
199
+ * The response a refusal carries, whichever client threw it.
200
+ *
201
+ * `IAdtTransport` promises exactly this about a failure and nothing more: the
202
+ * error has a `response`. `HttpTransport` throws an `AxiosError`, which happens
203
+ * to satisfy it; `RfcTransport` throws a plain `Error` with the field, which is
204
+ * what the contract asks for.
205
+ *
206
+ * Read structurally, because `instanceof AxiosError` is a question about which
207
+ * HTTP client is installed — an answer no other wire can give, and the reason
208
+ * observing a failing response, and noticing a dead session in one, were
209
+ * reachable over one wire only.
210
+ */
211
+ /**
212
+ * Which system a wire is for.
213
+ *
214
+ * A marker rather than a class, because a consumer writes their own transports
215
+ * — that is the whole reason the wire is an argument. Constraining a connector
216
+ * to the shipped classes would have made "bring your own wire" impossible while
217
+ * appearing to allow it, since these classes carry private state and so compare
218
+ * nominally.
219
+ *
220
+ * It is still the CALLER who says which system: declaring `system` is that
221
+ * statement, made once by whoever writes the transport rather than at every
222
+ * construction. Nothing reads it at runtime and nothing infers from it — its
223
+ * only job is to stop "ABAP Cloud over RFC" from compiling.
224
+ */
225
+ export interface IOnPremTransport extends IAdtTransport {
226
+ readonly system: 'onprem';
227
+ }
228
+ export interface ICloudTransport extends IAdtTransport {
229
+ readonly system: 'cloud';
230
+ }
231
+ export declare function refusalOf(error: unknown): IAdtTransportResponse | null;
232
+ //# sourceMappingURL=IAdtTransport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IAdtTransport.d.ts","sourceRoot":"","sources":["../../src/connection/IAdtTransport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ /**
3
+ * What carries an ADT request to the server.
4
+ *
5
+ * The credential says how a request proves who it is; this says what the
6
+ * request travels over. They are independent axes, and on-prem is where both
7
+ * are real choices: the same ADT REST call goes over HTTP, or over RFC to
8
+ * `SADT_REST_RFC_ENDPOINT` — the FM Eclipse ADT itself uses through JCo — on a
9
+ * system where stateful HTTP sessions are not usable. ABAP Cloud has one
10
+ * transport, so its connector takes no such parameter.
11
+ *
12
+ * **Said by the caller, never sniffed.** Which transport is in use is a fact
13
+ * about the deployment, not something to be discovered by trying one and
14
+ * watching it fail — the same rule `SessionStrategy` states for session
15
+ * mechanics.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.refusalOf = refusalOf;
19
+ function refusalOf(error) {
20
+ const response = error
21
+ ?.response;
22
+ if (!response || typeof response !== 'object')
23
+ return null;
24
+ const candidate = response;
25
+ return typeof candidate.status === 'number'
26
+ ? response
27
+ : null;
28
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * An on-prem system old enough that the stateful header hurts.
3
+ *
4
+ * On BASIS 7.40, `x-sap-adt-sessiontype: stateful` makes the server keep locks
5
+ * in ABAP session memory instead of the global enqueue table, and the next
6
+ * `PUT` over that lock comes back `423`. Such a system also names no HTTP
7
+ * session, so a connection that insisted on a `SAP_SESSIONID` would refuse to
8
+ * connect to a system that is working fine.
9
+ *
10
+ * Both of those used to be one flag on the connection — `skipSessionType` —
11
+ * carried in the class every wire shares, describing one deployment. It is a
12
+ * deployment, so it is a wire: taking this one is the caller saying which
13
+ * system they are dialling, exactly as taking the cloud or the on-prem wire is.
14
+ */
15
+ import type { IAdtTransportRequest, IAdtTransportResponse } from './IAdtTransport.js';
16
+ import { OnPremHttpTransport } from './OnPremHttpTransport.js';
17
+ export declare class LegacyOnPremHttpTransport extends OnPremHttpTransport {
18
+ readonly kind = "onprem-http-legacy";
19
+ /**
20
+ * The header never goes out, whatever the caller set the session type to.
21
+ *
22
+ * Dropped here rather than refused above: `setSessionType()` records what the
23
+ * caller wants, and what actually travels is the wire's business. A caller
24
+ * that asks for a stateful session on this system gets one — through the RFC
25
+ * conversation, or through this wire's own session — without the header that
26
+ * would move its locks somewhere they cannot be released.
27
+ */
28
+ send(request: IAdtTransportRequest): Promise<IAdtTransportResponse>;
29
+ private withoutSessionType;
30
+ /**
31
+ * This system names no session, and that is not a failure.
32
+ *
33
+ * The rule above — refuse to connect when the server issued no
34
+ * `SAP_SESSIONID` — exists because a connection without one can read and can
35
+ * hold nothing. Here there is nothing to hold that way in the first place, so
36
+ * answering "no session" would refuse a system that works.
37
+ */
38
+ sessionEstablished(): boolean;
39
+ }
40
+ //# sourceMappingURL=LegacyOnPremHttpTransport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LegacyOnPremHttpTransport.d.ts","sourceRoot":"","sources":["../../src/connection/LegacyOnPremHttpTransport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EACV,oBAAoB,EACpB,qBAAqB,EACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAI/D,qBAAa,yBAA0B,SAAQ,mBAAmB;IAChE,SAAkB,IAAI,wBAAwB;IAE9C;;;;;;;;OAQG;IACY,IAAI,CACjB,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAIjC,OAAO,CAAC,kBAAkB;IAY1B;;;;;;;OAOG;IACM,kBAAkB,IAAI,OAAO;CAGvC"}
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ /**
3
+ * An on-prem system old enough that the stateful header hurts.
4
+ *
5
+ * On BASIS 7.40, `x-sap-adt-sessiontype: stateful` makes the server keep locks
6
+ * in ABAP session memory instead of the global enqueue table, and the next
7
+ * `PUT` over that lock comes back `423`. Such a system also names no HTTP
8
+ * session, so a connection that insisted on a `SAP_SESSIONID` would refuse to
9
+ * connect to a system that is working fine.
10
+ *
11
+ * Both of those used to be one flag on the connection — `skipSessionType` —
12
+ * carried in the class every wire shares, describing one deployment. It is a
13
+ * deployment, so it is a wire: taking this one is the caller saying which
14
+ * system they are dialling, exactly as taking the cloud or the on-prem wire is.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.LegacyOnPremHttpTransport = void 0;
18
+ const OnPremHttpTransport_js_1 = require("./OnPremHttpTransport.js");
19
+ const SESSION_TYPE_HEADER = 'x-sap-adt-sessiontype';
20
+ class LegacyOnPremHttpTransport extends OnPremHttpTransport_js_1.OnPremHttpTransport {
21
+ kind = 'onprem-http-legacy';
22
+ /**
23
+ * The header never goes out, whatever the caller set the session type to.
24
+ *
25
+ * Dropped here rather than refused above: `setSessionType()` records what the
26
+ * caller wants, and what actually travels is the wire's business. A caller
27
+ * that asks for a stateful session on this system gets one — through the RFC
28
+ * conversation, or through this wire's own session — without the header that
29
+ * would move its locks somewhere they cannot be released.
30
+ */
31
+ async send(request) {
32
+ return super.send(this.withoutSessionType(request));
33
+ }
34
+ withoutSessionType(request) {
35
+ if (!request.headers)
36
+ return request;
37
+ const headers = {};
38
+ for (const [name, value] of Object.entries(request.headers)) {
39
+ if (name.toLowerCase() === SESSION_TYPE_HEADER)
40
+ continue;
41
+ headers[name] = value;
42
+ }
43
+ return { ...request, headers };
44
+ }
45
+ /**
46
+ * This system names no session, and that is not a failure.
47
+ *
48
+ * The rule above — refuse to connect when the server issued no
49
+ * `SAP_SESSIONID` — exists because a connection without one can read and can
50
+ * hold nothing. Here there is nothing to hold that way in the first place, so
51
+ * answering "no session" would refuse a system that works.
52
+ */
53
+ sessionEstablished() {
54
+ return true;
55
+ }
56
+ }
57
+ exports.LegacyOnPremHttpTransport = LegacyOnPremHttpTransport;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * An on-prem system over HTTP: the wire where the logon IS the session.
3
+ *
4
+ * There is nothing to open. The session arrives as a `SAP_SESSIONID` cookie on
5
+ * the establishing call, which is why SM04 shows those sessions opened by
6
+ * `P=/sap/bc/adt/discovery` — one per `connect()`, and one left behind per
7
+ * `connect()` that never said it was finished.
8
+ *
9
+ * ADT publishes no session resource here — `/sap/bc/adt/core/http/sessions`
10
+ * does not exist — so the goodbye is the platform's own,
11
+ * `/sap/public/bc/icf/logoff`, which answers `200` while expiring the session
12
+ * cookie.
13
+ *
14
+ * **Taking this transport is the consumer saying which system it is dialling.**
15
+ * It does not ask the server which it is, and does not try the cloud mechanism
16
+ * first to see whether it answers.
17
+ */
18
+ import { HttpTransport } from './HttpTransport.js';
19
+ import type { IAdtEstablishContext, IAdtSessionContext, IOnPremTransport } from './IAdtTransport.js';
20
+ export declare class OnPremHttpTransport extends HttpTransport implements IOnPremTransport {
21
+ readonly kind: string;
22
+ /** Which system this wire is for. Read by the compiler, never at runtime. */
23
+ readonly system: "onprem";
24
+ /**
25
+ * Whether an establishment actually succeeded.
26
+ *
27
+ * Cookies alone cannot answer it: a 401 that refuses us can carry a
28
+ * `Set-Cookie`, and that is debris, not a session. Telling the server we are
29
+ * finished with something we never had sends a request nobody asked for —
30
+ * into the middle of an authentication exchange, in the case that found this.
31
+ */
32
+ private established;
33
+ establish(context: IAdtEstablishContext): Promise<void>;
34
+ forgetSession(): void;
35
+ /**
36
+ * Nothing to ask for: the session comes with the establishing request.
37
+ *
38
+ * Stated by being absent rather than by a method that returns `false`. A wire
39
+ * that has no session resource has nothing to say here, and a `false` was
40
+ * only ever read by the connection to decide whether to send a close — a
41
+ * decision that is now this class's, below.
42
+ */
43
+ close(context: IAdtSessionContext): Promise<void>;
44
+ }
45
+ //# sourceMappingURL=OnPremHttpTransport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OnPremHttpTransport.d.ts","sourceRoot":"","sources":["../../src/connection/OnPremHttpTransport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EACV,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,oBAAoB,CAAC;AAI5B,qBAAa,mBACX,SAAQ,aACR,YAAW,gBAAgB;IAE3B,SAAkB,IAAI,EAAE,MAAM,CAAiB;IAE/C,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,EAAG,QAAQ,CAAU;IAEpC;;;;;;;OAOG;IACH,OAAO,CAAC,WAAW,CAAS;IAEb,SAAS,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAK7D,aAAa,IAAI,IAAI;IAK9B;;;;;;;OAOG;IAEY,KAAK,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;CAuCjE"}
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ /**
3
+ * An on-prem system over HTTP: the wire where the logon IS the session.
4
+ *
5
+ * There is nothing to open. The session arrives as a `SAP_SESSIONID` cookie on
6
+ * the establishing call, which is why SM04 shows those sessions opened by
7
+ * `P=/sap/bc/adt/discovery` — one per `connect()`, and one left behind per
8
+ * `connect()` that never said it was finished.
9
+ *
10
+ * ADT publishes no session resource here — `/sap/bc/adt/core/http/sessions`
11
+ * does not exist — so the goodbye is the platform's own,
12
+ * `/sap/public/bc/icf/logoff`, which answers `200` while expiring the session
13
+ * cookie.
14
+ *
15
+ * **Taking this transport is the consumer saying which system it is dialling.**
16
+ * It does not ask the server which it is, and does not try the cloud mechanism
17
+ * first to see whether it answers.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.OnPremHttpTransport = void 0;
21
+ const cookies_js_1 = require("../utils/cookies.js");
22
+ const HttpTransport_js_1 = require("./HttpTransport.js");
23
+ const ICF_LOGOFF_PATH = '/sap/public/bc/icf/logoff';
24
+ class OnPremHttpTransport extends HttpTransport_js_1.HttpTransport {
25
+ kind = 'onprem-http';
26
+ /** Which system this wire is for. Read by the compiler, never at runtime. */
27
+ system = 'onprem';
28
+ /**
29
+ * Whether an establishment actually succeeded.
30
+ *
31
+ * Cookies alone cannot answer it: a 401 that refuses us can carry a
32
+ * `Set-Cookie`, and that is debris, not a session. Telling the server we are
33
+ * finished with something we never had sends a request nobody asked for —
34
+ * into the middle of an authentication exchange, in the case that found this.
35
+ */
36
+ established = false;
37
+ async establish(context) {
38
+ await super.establish(context);
39
+ this.established = true;
40
+ }
41
+ forgetSession() {
42
+ super.forgetSession();
43
+ this.established = false;
44
+ }
45
+ /**
46
+ * Nothing to ask for: the session comes with the establishing request.
47
+ *
48
+ * Stated by being absent rather than by a method that returns `false`. A wire
49
+ * that has no session resource has nothing to say here, and a `false` was
50
+ * only ever read by the connection to decide whether to send a close — a
51
+ * decision that is now this class's, below.
52
+ */
53
+ async close(context) {
54
+ // Read synchronously, for the same reason as the cloud wire: the teardown
55
+ // clears the jar while this is suspended on its first await.
56
+ const cookies = this.cookies();
57
+ const established = this.established;
58
+ // Snapshotted too, and for the same reason as the cookies: the goodbye must
59
+ // reach the application server that holds THIS session, and by the time it
60
+ // goes out the wire may be pinned to the one holding the next.
61
+ const affinity = this.affinityHeaders();
62
+ if (!established || !cookies) {
63
+ // Holding the cookie is the whole permission to end a session, and it is
64
+ // why nothing is sent without one. The session limit is per user and the
65
+ // pool is shared — a SAP GUI logon of the same user sits in the same SM04
66
+ // list — so a connector that tidied up sessions it did not open would
67
+ // eventually close somebody's GUI.
68
+ return;
69
+ }
70
+ try {
71
+ // Read once: a provider may answer differently on a second call, and one
72
+ // request must be built from one credential.
73
+ const auth = await context.authHeaders();
74
+ await this.sendDetached({
75
+ method: 'GET',
76
+ url: `${context.baseUrl}${ICF_LOGOFF_PATH}`,
77
+ headers: {
78
+ ...affinity,
79
+ ...auth,
80
+ ...context.extraHeaders,
81
+ Cookie: (0, cookies_js_1.mergeCookieHeaders)(auth.Cookie, cookies),
82
+ },
83
+ });
84
+ this.logger?.debug('Told the server the session is finished');
85
+ }
86
+ catch (error) {
87
+ this.logger?.debug(`Could not tell the server the session is finished: ${error instanceof Error ? error.message : String(error)}`);
88
+ }
89
+ }
90
+ }
91
+ exports.OnPremHttpTransport = OnPremHttpTransport;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * An ADT request carried over RFC.
3
+ *
4
+ * `SADT_REST_RFC_ENDPOINT` is the standard FM Eclipse ADT uses for on-prem ADT
5
+ * through JCo, and what it carries is a request line, header fields and a body
6
+ * — an HTTP request in all but the wire. So this translates, and translating is
7
+ * the whole of its job.
8
+ *
9
+ * It exists because an RFC conversation is inherently stateful: one ABAP
10
+ * session for the connection's lifetime, which is the way through HTTP 423
11
+ * "invalid lock handle" on legacy systems (BASIS < 7.50) where stateful HTTP
12
+ * sessions are not usable.
13
+ *
14
+ * **What is NOT here.** Cookies, the CSRF token, `x-sap-adt-sessiontype` and
15
+ * the session lifecycle belong to the connection above this seam and are
16
+ * already in `request.headers` by the time `send()` is called. A transport that
17
+ * also captured cookies would be doing that work twice, and the two copies
18
+ * would disagree the first time one of them was cleared.
19
+ */
20
+ import type { ILogger } from '../logger.js';
21
+ import type { IAdtEstablishContext, IAdtTransportRequest, IAdtTransportResponse, IOnPremTransport } from './IAdtTransport.js';
22
+ /** The slice of the native client this needs, so the SDK is not a hard dependency. */
23
+ export interface IRfcConversation {
24
+ open(): Promise<void>;
25
+ close(): Promise<void>;
26
+ call(fm: string, params: Record<string, unknown>): Promise<Record<string, any>>;
27
+ readonly alive: boolean;
28
+ }
29
+ export declare class RfcTransport implements IOnPremTransport {
30
+ private readonly connect;
31
+ private readonly logger;
32
+ readonly kind = "rfc";
33
+ /** Which system this wire is for. Read by the compiler, never at runtime. */
34
+ readonly system: "onprem";
35
+ private conversation;
36
+ /** Names the conversation, and so the ABAP session it carries. */
37
+ private conversationId;
38
+ /**
39
+ * Nothing to fold in. `SADT_REST_RFC_ENDPOINT` answers with two header
40
+ * fields — `~server_protocol` and `content-type` — on every call, including
41
+ * one that asks for a token with `x-csrf-token: fetch`, and on a stateful
42
+ * POST. Measured on E19. There is no ICM in this path, so there is no ICF
43
+ * session to cookie and no application server to be redispatched between.
44
+ */
45
+ ingest(): void;
46
+ /** None, and never any: see `ingest()`. */
47
+ cookies(): null;
48
+ /**
49
+ * Nothing to establish. `open()` made the ABAP session, and there is no token
50
+ * to earn: this endpoint answers `x-csrf-token: fetch` with the same two
51
+ * header fields it answers everything else with. A wire with no
52
+ * cross-site request to forge against needs no token to prove one was not.
53
+ */
54
+ establish(_context: IAdtEstablishContext): Promise<void>;
55
+ /** None: see `establish()`. */
56
+ csrfToken(): null;
57
+ /**
58
+ * Ignored. Nothing on this wire reads a token, and keeping one would be
59
+ * state that never leaves the object.
60
+ */
61
+ adoptCsrfToken(): void;
62
+ /**
63
+ * The conversation, which IS the session — so it is fingerprinted by its own
64
+ * existence rather than by an address the server hands out.
65
+ */
66
+ /** The conversation IS the session, so it exists exactly while that does. */
67
+ sessionEstablished(): boolean;
68
+ sessionFingerprint(): Map<string, string>;
69
+ /** None: there is no dispatcher in front of this wire to stay bound to. */
70
+ affinityHeaders(): Record<string, string>;
71
+ /**
72
+ * Nothing to forget separately. The session ends when the conversation does,
73
+ * and that is `close()` — a wire whose state could be dropped while the
74
+ * conversation stayed open would be claiming a session it had disowned.
75
+ */
76
+ forgetSession(): void;
77
+ /**
78
+ * The client is built by a factory rather than constructed here: the SDK is
79
+ * an optional dependency loaded by `require` at open time, and a transport
80
+ * that reached for it in its constructor could not be built at all on a
81
+ * machine without it.
82
+ */
83
+ constructor(connect: () => IRfcConversation, logger?: ILogger | null);
84
+ open(): Promise<void>;
85
+ /** Never throws, and a repeat call finds nothing owed. */
86
+ close(): Promise<void>;
87
+ send(request: IAdtTransportRequest): Promise<IAdtTransportResponse>;
88
+ }
89
+ //# sourceMappingURL=RfcTransport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RfcTransport.d.ts","sourceRoot":"","sources":["../../src/connection/RfcTransport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EACV,oBAAoB,EAEpB,oBAAoB,EACpB,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,oBAAoB,CAAC;AAE5B,sFAAsF;AACtF,MAAM,WAAW,gBAAgB;IAC/B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,CACF,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAmCD,qBAAa,YAAa,YAAW,gBAAgB;IA+EjD,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IA/EzB,QAAQ,CAAC,IAAI,SAAS;IAEtB,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,EAAG,QAAQ,CAAU;IAEpC,OAAO,CAAC,YAAY,CAAiC;IACrD,kEAAkE;IAClE,OAAO,CAAC,cAAc,CAAM;IAE5B;;;;;;OAMG;IACH,MAAM,IAAI,IAAI;IAEd,2CAA2C;IAC3C,OAAO,IAAI,IAAI;IAIf;;;;;OAKG;IACG,SAAS,CAAC,QAAQ,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAE9D,+BAA+B;IAC/B,SAAS,IAAI,IAAI;IAIjB;;;OAGG;IACH,cAAc,IAAI,IAAI;IAEtB;;;OAGG;IACH,6EAA6E;IAC7E,kBAAkB,IAAI,OAAO;IAI7B,kBAAkB,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IAQzC,2EAA2E;IAC3E,eAAe,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAIzC;;;;OAIG;IACH,aAAa,IAAI,IAAI;IAErB;;;;;OAKG;gBAEgB,OAAO,EAAE,MAAM,gBAAgB,EAC/B,MAAM,GAAE,OAAO,GAAG,IAAW;IAG1C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiB3B,0DAA0D;IACpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAatB,IAAI,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC;CA8H1E"}