catalyst-relay 0.4.2 → 0.4.4
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/dist/index.d.mts +36 -7
- package/dist/index.d.ts +36 -7
- package/dist/index.js +142 -160
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +142 -160
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -64,6 +64,15 @@ interface SsoAuthConfig {
|
|
|
64
64
|
* Union of all auth configurations
|
|
65
65
|
*/
|
|
66
66
|
type AuthConfig = BasicAuthConfig | SamlAuthConfig | SsoAuthConfig;
|
|
67
|
+
/**
|
|
68
|
+
* Auto-refresh configuration for session keepalive
|
|
69
|
+
*/
|
|
70
|
+
interface AutoRefreshConfig {
|
|
71
|
+
/** Enable automatic session refresh (default: true) */
|
|
72
|
+
enabled: boolean;
|
|
73
|
+
/** Refresh interval in milliseconds (default: 7200000 = 2 hours) */
|
|
74
|
+
intervalMs?: number;
|
|
75
|
+
}
|
|
67
76
|
/**
|
|
68
77
|
* Client configuration for connecting to SAP ADT
|
|
69
78
|
*/
|
|
@@ -78,6 +87,8 @@ interface ClientConfig {
|
|
|
78
87
|
timeout?: number;
|
|
79
88
|
/** Skip SSL verification (dev only) */
|
|
80
89
|
insecure?: boolean;
|
|
90
|
+
/** Auto-refresh configuration for session keepalive (default: enabled with 2-hour interval) */
|
|
91
|
+
autoRefresh?: AutoRefreshConfig;
|
|
81
92
|
}
|
|
82
93
|
|
|
83
94
|
/**
|
|
@@ -187,6 +198,27 @@ interface Session {
|
|
|
187
198
|
expiresAt: number;
|
|
188
199
|
}
|
|
189
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Session Refresh via Reentrance Ticket
|
|
203
|
+
*
|
|
204
|
+
* Fetches a reentrance ticket from SAP ADT to keep the session alive.
|
|
205
|
+
* Eclipse ADT uses this mechanism to maintain sessions across extended periods.
|
|
206
|
+
*
|
|
207
|
+
* Endpoint: GET /sap/bc/adt/security/reentranceticket
|
|
208
|
+
* - Returns a base64-encoded SSO ticket
|
|
209
|
+
* - Refreshes server-side session cookies (MYSAPSSO2)
|
|
210
|
+
*/
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Result of a session refresh operation
|
|
214
|
+
*/
|
|
215
|
+
interface RefreshResult {
|
|
216
|
+
/** Base64-encoded reentrance ticket */
|
|
217
|
+
ticket: string;
|
|
218
|
+
/** Updated session expiration timestamp (ms since epoch) */
|
|
219
|
+
expiresAt: number;
|
|
220
|
+
}
|
|
221
|
+
|
|
190
222
|
interface ObjectConfig {
|
|
191
223
|
/** ADT endpoint path (e.g., 'ddic/ddl/sources') */
|
|
192
224
|
endpoint: string;
|
|
@@ -273,16 +305,11 @@ interface FolderNode {
|
|
|
273
305
|
displayName: string;
|
|
274
306
|
numContents: number;
|
|
275
307
|
}
|
|
276
|
-
interface ApiState {
|
|
277
|
-
useInCloudDevelopment: boolean;
|
|
278
|
-
useInCloudDvlpmntActive: boolean;
|
|
279
|
-
useInKeyUserApps: boolean;
|
|
280
|
-
}
|
|
281
308
|
interface ObjectNode {
|
|
282
309
|
name: string;
|
|
283
310
|
objectType: string;
|
|
284
311
|
extension: string;
|
|
285
|
-
|
|
312
|
+
description?: string;
|
|
286
313
|
}
|
|
287
314
|
|
|
288
315
|
/**
|
|
@@ -487,6 +514,7 @@ interface ADTClient {
|
|
|
487
514
|
readonly session: Session | null;
|
|
488
515
|
login(): AsyncResult<Session>;
|
|
489
516
|
logout(): AsyncResult<void>;
|
|
517
|
+
refreshSession(): AsyncResult<RefreshResult>;
|
|
490
518
|
read(objects: ObjectRef[]): AsyncResult<ObjectWithContent[]>;
|
|
491
519
|
create(object: ObjectContent, packageName: string, transport?: string): AsyncResult<void>;
|
|
492
520
|
update(object: ObjectContent, transport?: string): AsyncResult<void>;
|
|
@@ -496,6 +524,7 @@ interface ADTClient {
|
|
|
496
524
|
getPackages(filter?: string): AsyncResult<Package[]>;
|
|
497
525
|
getTree(query: TreeQuery): AsyncResult<TreeResponse>;
|
|
498
526
|
getPackageStats(packageName: string): AsyncResult<PackageNode>;
|
|
527
|
+
getPackageStats(packageNames: string[]): AsyncResult<PackageNode[]>;
|
|
499
528
|
getTransports(packageName: string): AsyncResult<Transport[]>;
|
|
500
529
|
previewData(query: PreviewSQL): AsyncResult<DataFrame>;
|
|
501
530
|
getDistinctValues(objectName: string, parameters: Parameter[], column: string, objectType?: 'table' | 'view'): AsyncResult<DistinctResult>;
|
|
@@ -508,4 +537,4 @@ interface ADTClient {
|
|
|
508
537
|
}
|
|
509
538
|
declare function createClient(config: ClientConfig): Result<ADTClient, Error>;
|
|
510
539
|
|
|
511
|
-
export { type ADTClient, type ActivationMessage, type ActivationResult, type Aggregation, type ApiResponse, type
|
|
540
|
+
export { type ADTClient, type ActivationMessage, type ActivationResult, type Aggregation, type ApiResponse, type AsyncResult, type AuthConfig, type AuthType, type BasicAuthConfig, type BasicFilter, type BetweenFilter, type ClientConfig, type ColumnInfo, type DataFrame, type DataPreviewQuery, type Dependency, type DiffResult, type DistinctResult, type ErrorCode, type ErrorResponse, type FolderNode, type ListFilter, type ObjectConfig, type ObjectContent, type ObjectMetadata, type ObjectNode, type ObjectRef, type ObjectWithContent, type Package, type PackageNode, type Parameter, type PreviewSQL, type QueryFilter, type Result, type SamlAuthConfig, type SearchResult, type Session, type Sorting, type SsoAuthConfig, type SuccessResponse, type Transport, type TransportConfig, type TreeQuery, type TreeResponse, type UpsertResult, buildSQLQuery, createClient, err, ok };
|
package/dist/index.d.ts
CHANGED
|
@@ -64,6 +64,15 @@ interface SsoAuthConfig {
|
|
|
64
64
|
* Union of all auth configurations
|
|
65
65
|
*/
|
|
66
66
|
type AuthConfig = BasicAuthConfig | SamlAuthConfig | SsoAuthConfig;
|
|
67
|
+
/**
|
|
68
|
+
* Auto-refresh configuration for session keepalive
|
|
69
|
+
*/
|
|
70
|
+
interface AutoRefreshConfig {
|
|
71
|
+
/** Enable automatic session refresh (default: true) */
|
|
72
|
+
enabled: boolean;
|
|
73
|
+
/** Refresh interval in milliseconds (default: 7200000 = 2 hours) */
|
|
74
|
+
intervalMs?: number;
|
|
75
|
+
}
|
|
67
76
|
/**
|
|
68
77
|
* Client configuration for connecting to SAP ADT
|
|
69
78
|
*/
|
|
@@ -78,6 +87,8 @@ interface ClientConfig {
|
|
|
78
87
|
timeout?: number;
|
|
79
88
|
/** Skip SSL verification (dev only) */
|
|
80
89
|
insecure?: boolean;
|
|
90
|
+
/** Auto-refresh configuration for session keepalive (default: enabled with 2-hour interval) */
|
|
91
|
+
autoRefresh?: AutoRefreshConfig;
|
|
81
92
|
}
|
|
82
93
|
|
|
83
94
|
/**
|
|
@@ -187,6 +198,27 @@ interface Session {
|
|
|
187
198
|
expiresAt: number;
|
|
188
199
|
}
|
|
189
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Session Refresh via Reentrance Ticket
|
|
203
|
+
*
|
|
204
|
+
* Fetches a reentrance ticket from SAP ADT to keep the session alive.
|
|
205
|
+
* Eclipse ADT uses this mechanism to maintain sessions across extended periods.
|
|
206
|
+
*
|
|
207
|
+
* Endpoint: GET /sap/bc/adt/security/reentranceticket
|
|
208
|
+
* - Returns a base64-encoded SSO ticket
|
|
209
|
+
* - Refreshes server-side session cookies (MYSAPSSO2)
|
|
210
|
+
*/
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Result of a session refresh operation
|
|
214
|
+
*/
|
|
215
|
+
interface RefreshResult {
|
|
216
|
+
/** Base64-encoded reentrance ticket */
|
|
217
|
+
ticket: string;
|
|
218
|
+
/** Updated session expiration timestamp (ms since epoch) */
|
|
219
|
+
expiresAt: number;
|
|
220
|
+
}
|
|
221
|
+
|
|
190
222
|
interface ObjectConfig {
|
|
191
223
|
/** ADT endpoint path (e.g., 'ddic/ddl/sources') */
|
|
192
224
|
endpoint: string;
|
|
@@ -273,16 +305,11 @@ interface FolderNode {
|
|
|
273
305
|
displayName: string;
|
|
274
306
|
numContents: number;
|
|
275
307
|
}
|
|
276
|
-
interface ApiState {
|
|
277
|
-
useInCloudDevelopment: boolean;
|
|
278
|
-
useInCloudDvlpmntActive: boolean;
|
|
279
|
-
useInKeyUserApps: boolean;
|
|
280
|
-
}
|
|
281
308
|
interface ObjectNode {
|
|
282
309
|
name: string;
|
|
283
310
|
objectType: string;
|
|
284
311
|
extension: string;
|
|
285
|
-
|
|
312
|
+
description?: string;
|
|
286
313
|
}
|
|
287
314
|
|
|
288
315
|
/**
|
|
@@ -487,6 +514,7 @@ interface ADTClient {
|
|
|
487
514
|
readonly session: Session | null;
|
|
488
515
|
login(): AsyncResult<Session>;
|
|
489
516
|
logout(): AsyncResult<void>;
|
|
517
|
+
refreshSession(): AsyncResult<RefreshResult>;
|
|
490
518
|
read(objects: ObjectRef[]): AsyncResult<ObjectWithContent[]>;
|
|
491
519
|
create(object: ObjectContent, packageName: string, transport?: string): AsyncResult<void>;
|
|
492
520
|
update(object: ObjectContent, transport?: string): AsyncResult<void>;
|
|
@@ -496,6 +524,7 @@ interface ADTClient {
|
|
|
496
524
|
getPackages(filter?: string): AsyncResult<Package[]>;
|
|
497
525
|
getTree(query: TreeQuery): AsyncResult<TreeResponse>;
|
|
498
526
|
getPackageStats(packageName: string): AsyncResult<PackageNode>;
|
|
527
|
+
getPackageStats(packageNames: string[]): AsyncResult<PackageNode[]>;
|
|
499
528
|
getTransports(packageName: string): AsyncResult<Transport[]>;
|
|
500
529
|
previewData(query: PreviewSQL): AsyncResult<DataFrame>;
|
|
501
530
|
getDistinctValues(objectName: string, parameters: Parameter[], column: string, objectType?: 'table' | 'view'): AsyncResult<DistinctResult>;
|
|
@@ -508,4 +537,4 @@ interface ADTClient {
|
|
|
508
537
|
}
|
|
509
538
|
declare function createClient(config: ClientConfig): Result<ADTClient, Error>;
|
|
510
539
|
|
|
511
|
-
export { type ADTClient, type ActivationMessage, type ActivationResult, type Aggregation, type ApiResponse, type
|
|
540
|
+
export { type ADTClient, type ActivationMessage, type ActivationResult, type Aggregation, type ApiResponse, type AsyncResult, type AuthConfig, type AuthType, type BasicAuthConfig, type BasicFilter, type BetweenFilter, type ClientConfig, type ColumnInfo, type DataFrame, type DataPreviewQuery, type Dependency, type DiffResult, type DistinctResult, type ErrorCode, type ErrorResponse, type FolderNode, type ListFilter, type ObjectConfig, type ObjectContent, type ObjectMetadata, type ObjectNode, type ObjectRef, type ObjectWithContent, type Package, type PackageNode, type Parameter, type PreviewSQL, type QueryFilter, type Result, type SamlAuthConfig, type SearchResult, type Session, type Sorting, type SsoAuthConfig, type SuccessResponse, type Transport, type TransportConfig, type TreeQuery, type TreeResponse, type UpsertResult, buildSQLQuery, createClient, err, ok };
|
package/dist/index.js
CHANGED
|
@@ -237,7 +237,11 @@ var clientConfigSchema = import_zod.z.object({
|
|
|
237
237
|
})
|
|
238
238
|
]),
|
|
239
239
|
timeout: import_zod.z.number().positive().optional(),
|
|
240
|
-
insecure: import_zod.z.boolean().optional()
|
|
240
|
+
insecure: import_zod.z.boolean().optional(),
|
|
241
|
+
autoRefresh: import_zod.z.object({
|
|
242
|
+
enabled: import_zod.z.boolean(),
|
|
243
|
+
intervalMs: import_zod.z.number().positive().optional()
|
|
244
|
+
}).optional()
|
|
241
245
|
});
|
|
242
246
|
|
|
243
247
|
// src/core/session/types.ts
|
|
@@ -351,6 +355,33 @@ async function sessionReset(state, request3) {
|
|
|
351
355
|
return ok(void 0);
|
|
352
356
|
}
|
|
353
357
|
|
|
358
|
+
// src/core/session/refresh.ts
|
|
359
|
+
var REENTRANCE_TICKET_PATH = "/sap/bc/adt/security/reentranceticket";
|
|
360
|
+
async function refreshSession(state, request3) {
|
|
361
|
+
if (!state.session) {
|
|
362
|
+
return err(new Error("Not logged in"));
|
|
363
|
+
}
|
|
364
|
+
debug("Fetching reentrance ticket to refresh session...");
|
|
365
|
+
const [response, reqErr] = await request3({
|
|
366
|
+
method: "GET",
|
|
367
|
+
path: REENTRANCE_TICKET_PATH,
|
|
368
|
+
headers: { "Accept": "text/plain" }
|
|
369
|
+
});
|
|
370
|
+
if (reqErr) {
|
|
371
|
+
return err(new Error(`Session refresh failed: ${reqErr.message}`));
|
|
372
|
+
}
|
|
373
|
+
if (!response.ok) {
|
|
374
|
+
const text = await response.text();
|
|
375
|
+
return err(new Error(`Session refresh failed (${response.status}): ${text}`));
|
|
376
|
+
}
|
|
377
|
+
const ticket = await response.text();
|
|
378
|
+
debug(`Received reentrance ticket: ${ticket.substring(0, 20)}...`);
|
|
379
|
+
const timeout = getSessionTimeout(state.config.auth.type);
|
|
380
|
+
state.session.expiresAt = Date.now() + timeout;
|
|
381
|
+
debug(`Session refreshed, new expiration: ${new Date(state.session.expiresAt).toISOString()}`);
|
|
382
|
+
return ok({ ticket, expiresAt: state.session.expiresAt });
|
|
383
|
+
}
|
|
384
|
+
|
|
354
385
|
// src/core/adt/types.ts
|
|
355
386
|
var OBJECT_CONFIG_MAP = {
|
|
356
387
|
"asddls": {
|
|
@@ -745,14 +776,6 @@ async function getPackages(client, filter = "*") {
|
|
|
745
776
|
return ok(packages);
|
|
746
777
|
}
|
|
747
778
|
|
|
748
|
-
// src/core/adt/discovery/tree/types.ts
|
|
749
|
-
var API_FOLDERS = [
|
|
750
|
-
"NOT_RELEASED",
|
|
751
|
-
"USE_IN_CLOUD_DEVELOPMENT",
|
|
752
|
-
"USE_IN_CLOUD_DVLPMNT_ACTIVE",
|
|
753
|
-
"USE_IN_KEY_USER_APPS"
|
|
754
|
-
];
|
|
755
|
-
|
|
756
779
|
// src/core/adt/discovery/tree/parsers.ts
|
|
757
780
|
function buildQueryFromPath(packageName, path) {
|
|
758
781
|
const query = {
|
|
@@ -795,13 +818,14 @@ function constructTreeBody(query, searchPattern) {
|
|
|
795
818
|
const specifiedXml = Object.entries(specified).map(([facet, name]) => ` <vfs:preselection facet="${facet.toLowerCase()}">
|
|
796
819
|
<vfs:value>${name}</vfs:value>
|
|
797
820
|
</vfs:preselection>`).join("\n");
|
|
798
|
-
const
|
|
821
|
+
const atObjectLevel = query.PACKAGE && query.GROUP && query.TYPE;
|
|
822
|
+
const facetorderXml = atObjectLevel || facets.length === 0 ? " <vfs:facetorder/>" : ` <vfs:facetorder>
|
|
823
|
+
${facets.map((f) => ` <vfs:facet>${f.toLowerCase()}</vfs:facet>`).join("\n")}
|
|
824
|
+
</vfs:facetorder>`;
|
|
799
825
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
800
826
|
<vfs:virtualFoldersRequest xmlns:vfs="http://www.sap.com/adt/ris/virtualFolders" objectSearchPattern="${searchPattern}">
|
|
801
827
|
${specifiedXml}
|
|
802
|
-
|
|
803
|
-
${facetsXml}
|
|
804
|
-
</vfs:facetorder>
|
|
828
|
+
${facetorderXml}
|
|
805
829
|
</vfs:virtualFoldersRequest>`;
|
|
806
830
|
}
|
|
807
831
|
function parseTreeXml(xml) {
|
|
@@ -842,11 +866,14 @@ function parseTreeXml(xml) {
|
|
|
842
866
|
if (!name || !type) continue;
|
|
843
867
|
const config = getConfigByType(type);
|
|
844
868
|
if (!config) continue;
|
|
845
|
-
|
|
869
|
+
const text = obj.getAttribute("text");
|
|
870
|
+
const parsedObj = {
|
|
846
871
|
name,
|
|
847
872
|
objectType: config.label,
|
|
848
873
|
extension: config.extension
|
|
849
|
-
}
|
|
874
|
+
};
|
|
875
|
+
if (text) parsedObj.description = text;
|
|
876
|
+
objects.push(parsedObj);
|
|
850
877
|
}
|
|
851
878
|
return ok({ folders, objects });
|
|
852
879
|
}
|
|
@@ -870,11 +897,15 @@ function transformToTreeResponse(parsed, queryPackage) {
|
|
|
870
897
|
});
|
|
871
898
|
}
|
|
872
899
|
}
|
|
873
|
-
const objects = parsed.objects.map((obj) =>
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
900
|
+
const objects = parsed.objects.map((obj) => {
|
|
901
|
+
const node = {
|
|
902
|
+
name: obj.name,
|
|
903
|
+
objectType: obj.objectType,
|
|
904
|
+
extension: obj.extension
|
|
905
|
+
};
|
|
906
|
+
if (obj.description) node.description = obj.description;
|
|
907
|
+
return node;
|
|
908
|
+
});
|
|
878
909
|
return { packages, folders, objects };
|
|
879
910
|
}
|
|
880
911
|
|
|
@@ -938,58 +969,6 @@ async function fetchVirtualFolders(client, query) {
|
|
|
938
969
|
const text = await response.text();
|
|
939
970
|
return parseTreeXml(text);
|
|
940
971
|
}
|
|
941
|
-
async function fetchObjectsWithApiState(client, packageName, pathSegments, apiFolders) {
|
|
942
|
-
const group = pathSegments[0];
|
|
943
|
-
const type = pathSegments[1];
|
|
944
|
-
if (!group || !type) return ok([]);
|
|
945
|
-
const apiQueries = apiFolders.map((apiFolder) => ({
|
|
946
|
-
apiFolder,
|
|
947
|
-
query: {
|
|
948
|
-
PACKAGE: { name: `..${packageName}`, hasChildrenOfSameFacet: false },
|
|
949
|
-
GROUP: { name: group, hasChildrenOfSameFacet: false },
|
|
950
|
-
TYPE: { name: type, hasChildrenOfSameFacet: false },
|
|
951
|
-
API: { name: apiFolder, hasChildrenOfSameFacet: false }
|
|
952
|
-
}
|
|
953
|
-
}));
|
|
954
|
-
const results = await Promise.all(
|
|
955
|
-
apiQueries.map(async ({ apiFolder, query }) => {
|
|
956
|
-
const [parsed, parseErr] = await fetchVirtualFolders(client, query);
|
|
957
|
-
if (parseErr) return { apiFolder, objects: [], error: parseErr };
|
|
958
|
-
return { apiFolder, objects: parsed.objects, error: null };
|
|
959
|
-
})
|
|
960
|
-
);
|
|
961
|
-
const errors = results.filter((r) => r.error !== null);
|
|
962
|
-
if (errors.length === results.length) {
|
|
963
|
-
return err(errors[0].error);
|
|
964
|
-
}
|
|
965
|
-
const objectMap = /* @__PURE__ */ new Map();
|
|
966
|
-
for (const { apiFolder, objects } of results) {
|
|
967
|
-
for (const obj of objects) {
|
|
968
|
-
let node = objectMap.get(obj.name);
|
|
969
|
-
if (!node) {
|
|
970
|
-
node = {
|
|
971
|
-
name: obj.name,
|
|
972
|
-
objectType: obj.objectType,
|
|
973
|
-
extension: obj.extension,
|
|
974
|
-
apiState: {
|
|
975
|
-
useInCloudDevelopment: false,
|
|
976
|
-
useInCloudDvlpmntActive: false,
|
|
977
|
-
useInKeyUserApps: false
|
|
978
|
-
}
|
|
979
|
-
};
|
|
980
|
-
objectMap.set(obj.name, node);
|
|
981
|
-
}
|
|
982
|
-
if (apiFolder === "USE_IN_CLOUD_DEVELOPMENT") {
|
|
983
|
-
node.apiState.useInCloudDevelopment = true;
|
|
984
|
-
} else if (apiFolder === "USE_IN_CLOUD_DVLPMNT_ACTIVE") {
|
|
985
|
-
node.apiState.useInCloudDvlpmntActive = true;
|
|
986
|
-
} else if (apiFolder === "USE_IN_KEY_USER_APPS") {
|
|
987
|
-
node.apiState.useInKeyUserApps = true;
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
return ok(Array.from(objectMap.values()));
|
|
992
|
-
}
|
|
993
972
|
|
|
994
973
|
// src/core/adt/discovery/tree/index.ts
|
|
995
974
|
async function getTree(client, query = {}) {
|
|
@@ -1019,70 +998,61 @@ async function getTree(client, query = {}) {
|
|
|
1019
998
|
const internalQuery = buildQueryFromPath(query.package, query.path);
|
|
1020
999
|
const [parsed, parseErr] = await fetchVirtualFolders(client, internalQuery);
|
|
1021
1000
|
if (parseErr) return err(parseErr);
|
|
1022
|
-
const pathSegments = query.path?.split("/").filter((s) => s.length > 0) ?? [];
|
|
1023
|
-
const hasApiFolders = parsed.folders.length > 0 && parsed.folders.every((f) => f.facet === "API");
|
|
1024
|
-
if (pathSegments.length >= 2 && hasApiFolders) {
|
|
1025
|
-
const [objects, objErr] = await fetchObjectsWithApiState(
|
|
1026
|
-
client,
|
|
1027
|
-
query.package,
|
|
1028
|
-
pathSegments,
|
|
1029
|
-
API_FOLDERS
|
|
1030
|
-
);
|
|
1031
|
-
if (objErr) return err(objErr);
|
|
1032
|
-
return ok({
|
|
1033
|
-
packages,
|
|
1034
|
-
folders: [],
|
|
1035
|
-
objects
|
|
1036
|
-
});
|
|
1037
|
-
}
|
|
1038
1001
|
const result = transformToTreeResponse(parsed, query.package);
|
|
1039
1002
|
result.packages = packages;
|
|
1040
1003
|
return ok(result);
|
|
1041
1004
|
}
|
|
1042
1005
|
|
|
1043
1006
|
// src/core/adt/discovery/tree/packageStats.ts
|
|
1044
|
-
|
|
1045
|
-
const [
|
|
1046
|
-
|
|
1047
|
-
path: `/sap/bc/adt/packages/${packageName.toLowerCase()}`,
|
|
1048
|
-
headers: {
|
|
1049
|
-
"Accept": "application/vnd.sap.adt.packages.v1+xml"
|
|
1050
|
-
}
|
|
1051
|
-
});
|
|
1052
|
-
if (requestErr) return err(requestErr);
|
|
1053
|
-
if (!response.ok) {
|
|
1054
|
-
const text = await response.text();
|
|
1055
|
-
const errorMsg = extractError(text);
|
|
1056
|
-
return err(new Error(`Package metadata fetch failed: ${errorMsg}`));
|
|
1057
|
-
}
|
|
1058
|
-
const xml = await response.text();
|
|
1059
|
-
const [doc, parseErr] = safeParseXml(xml);
|
|
1060
|
-
if (parseErr) return err(parseErr);
|
|
1061
|
-
const packageElements = doc.getElementsByTagName("pak:package");
|
|
1062
|
-
if (packageElements.length === 0) {
|
|
1063
|
-
return err(new Error(`Package ${packageName} not found`));
|
|
1064
|
-
}
|
|
1065
|
-
const pkgEl = packageElements[0];
|
|
1066
|
-
const name = pkgEl.getAttribute("adtcore:name") || pkgEl.getAttributeNS("http://www.sap.com/adt/core", "name") || packageName.toUpperCase();
|
|
1067
|
-
const description = pkgEl.getAttribute("adtcore:description") || pkgEl.getAttributeNS("http://www.sap.com/adt/core", "description");
|
|
1068
|
-
const result = { name };
|
|
1069
|
-
if (description) result.description = description;
|
|
1070
|
-
return ok(result);
|
|
1071
|
-
}
|
|
1072
|
-
function constructCountRequestBody(packageName) {
|
|
1007
|
+
function constructPackageStatsBody(packageNames) {
|
|
1008
|
+
const names = packageNames.length === 1 ? [...packageNames, "SRIS_TEST_DATA_VFS_EMPTY"] : packageNames;
|
|
1009
|
+
const values = names.map((name) => ` <vfs:value>${name}</vfs:value>`).join("\n");
|
|
1073
1010
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
1074
1011
|
<vfs:virtualFoldersRequest xmlns:vfs="http://www.sap.com/adt/ris/virtualFolders" objectSearchPattern="*">
|
|
1075
1012
|
<vfs:preselection facet="package">
|
|
1076
|
-
|
|
1013
|
+
${values}
|
|
1077
1014
|
</vfs:preselection>
|
|
1078
|
-
<vfs:facetorder
|
|
1015
|
+
<vfs:facetorder>
|
|
1016
|
+
<vfs:facet>package</vfs:facet>
|
|
1017
|
+
<vfs:facet>group</vfs:facet>
|
|
1018
|
+
<vfs:facet>type</vfs:facet>
|
|
1019
|
+
</vfs:facetorder>
|
|
1079
1020
|
</vfs:virtualFoldersRequest>`;
|
|
1080
1021
|
}
|
|
1081
|
-
|
|
1082
|
-
const
|
|
1022
|
+
function parsePackageStats(xml) {
|
|
1023
|
+
const [doc, parseErr] = safeParseXml(xml);
|
|
1024
|
+
if (parseErr) return [];
|
|
1025
|
+
const packages = [];
|
|
1026
|
+
const virtualFolders = doc.getElementsByTagName("vfs:virtualFolder");
|
|
1027
|
+
for (let i = 0; i < virtualFolders.length; i++) {
|
|
1028
|
+
const vf = virtualFolders[i];
|
|
1029
|
+
if (!vf) continue;
|
|
1030
|
+
const facet = vf.getAttribute("facet")?.toUpperCase();
|
|
1031
|
+
if (facet !== "PACKAGE") continue;
|
|
1032
|
+
const name = vf.getAttribute("name");
|
|
1033
|
+
if (!name) continue;
|
|
1034
|
+
const countAttr = vf.getAttribute("counter");
|
|
1035
|
+
const count = countAttr ? parseInt(countAttr, 10) : 0;
|
|
1036
|
+
const description = vf.getAttribute("text");
|
|
1037
|
+
const pkg = {
|
|
1038
|
+
name,
|
|
1039
|
+
numContents: count
|
|
1040
|
+
};
|
|
1041
|
+
if (description) pkg.description = description;
|
|
1042
|
+
packages.push(pkg);
|
|
1043
|
+
}
|
|
1044
|
+
return packages;
|
|
1045
|
+
}
|
|
1046
|
+
async function getPackageStats(client, packageNames) {
|
|
1047
|
+
const isSingle = typeof packageNames === "string";
|
|
1048
|
+
const names = isSingle ? [packageNames] : packageNames;
|
|
1049
|
+
if (names.length === 0) {
|
|
1050
|
+
return ok([]);
|
|
1051
|
+
}
|
|
1052
|
+
const body = constructPackageStatsBody(names);
|
|
1083
1053
|
const [response, requestErr] = await client.request({
|
|
1084
1054
|
method: "POST",
|
|
1085
|
-
path: "/sap/bc/adt/repository/informationsystem/virtualfolders",
|
|
1055
|
+
path: "/sap/bc/adt/repository/informationsystem/virtualfolders/contents",
|
|
1086
1056
|
headers: {
|
|
1087
1057
|
"Content-Type": "application/vnd.sap.adt.repository.virtualfolders.request.v1+xml",
|
|
1088
1058
|
"Accept": "application/vnd.sap.adt.repository.virtualfolders.result.v1+xml"
|
|
@@ -1093,41 +1063,17 @@ async function fetchContentCount(client, packageName) {
|
|
|
1093
1063
|
if (!response.ok) {
|
|
1094
1064
|
const text = await response.text();
|
|
1095
1065
|
const errorMsg = extractError(text);
|
|
1096
|
-
return err(new Error(`Package
|
|
1066
|
+
return err(new Error(`Package stats fetch failed: ${errorMsg}`));
|
|
1097
1067
|
}
|
|
1098
1068
|
const xml = await response.text();
|
|
1099
|
-
const
|
|
1100
|
-
if (
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
const resultEl = resultElements[0];
|
|
1106
|
-
const objectCountAttr = resultEl.getAttribute("objectCount");
|
|
1107
|
-
if (!objectCountAttr) {
|
|
1108
|
-
return err(new Error("Invalid virtualfolders response: missing objectCount attribute"));
|
|
1109
|
-
}
|
|
1110
|
-
const count = parseInt(objectCountAttr, 10);
|
|
1111
|
-
if (isNaN(count)) {
|
|
1112
|
-
return err(new Error(`Invalid objectCount value: ${objectCountAttr}`));
|
|
1069
|
+
const packages = parsePackageStats(xml).filter((pkg) => pkg.name !== "SRIS_TEST_DATA_VFS_EMPTY");
|
|
1070
|
+
if (isSingle) {
|
|
1071
|
+
if (packages.length === 0) {
|
|
1072
|
+
return err(new Error(`Package ${packageNames} not found`));
|
|
1073
|
+
}
|
|
1074
|
+
return ok(packages[0]);
|
|
1113
1075
|
}
|
|
1114
|
-
return ok(
|
|
1115
|
-
}
|
|
1116
|
-
async function getPackageStats(client, packageName) {
|
|
1117
|
-
const [metadataResult, countResult] = await Promise.all([
|
|
1118
|
-
fetchPackageMetadata(client, packageName),
|
|
1119
|
-
fetchContentCount(client, packageName)
|
|
1120
|
-
]);
|
|
1121
|
-
const [metadata, metaErr] = metadataResult;
|
|
1122
|
-
if (metaErr) return err(metaErr);
|
|
1123
|
-
const [numContents, countErr] = countResult;
|
|
1124
|
-
if (countErr) return err(countErr);
|
|
1125
|
-
const result = {
|
|
1126
|
-
name: metadata.name,
|
|
1127
|
-
numContents
|
|
1128
|
-
};
|
|
1129
|
-
if (metadata.description) result.description = metadata.description;
|
|
1130
|
-
return ok(result);
|
|
1076
|
+
return ok(packages);
|
|
1131
1077
|
}
|
|
1132
1078
|
|
|
1133
1079
|
// src/core/adt/transports/transports.ts
|
|
@@ -2338,6 +2284,7 @@ async function httpsRequest2(url, options) {
|
|
|
2338
2284
|
req.end();
|
|
2339
2285
|
});
|
|
2340
2286
|
}
|
|
2287
|
+
var DEFAULT_REFRESH_INTERVAL = 30 * 60 * 1e3;
|
|
2341
2288
|
function buildParams(baseParams, clientNum) {
|
|
2342
2289
|
const params = new URLSearchParams();
|
|
2343
2290
|
if (baseParams) {
|
|
@@ -2362,6 +2309,8 @@ var ADTClientImpl = class {
|
|
|
2362
2309
|
requestor;
|
|
2363
2310
|
// Store SSO certificates for mTLS authentication
|
|
2364
2311
|
ssoCerts;
|
|
2312
|
+
// Auto-refresh timer handle
|
|
2313
|
+
refreshTimer = null;
|
|
2365
2314
|
constructor(config) {
|
|
2366
2315
|
const authOptions = {
|
|
2367
2316
|
config: config.auth,
|
|
@@ -2399,6 +2348,22 @@ var ADTClientImpl = class {
|
|
|
2399
2348
|
if (this.state.cookies.size === 0) return null;
|
|
2400
2349
|
return Array.from(this.state.cookies.entries()).map(([name, value]) => `${name}=${value}`).join("; ");
|
|
2401
2350
|
}
|
|
2351
|
+
startAutoRefresh(intervalMs) {
|
|
2352
|
+
this.stopAutoRefresh();
|
|
2353
|
+
this.refreshTimer = setInterval(async () => {
|
|
2354
|
+
if (!this.state.session) return;
|
|
2355
|
+
const [, refreshErr] = await this.refreshSession();
|
|
2356
|
+
if (refreshErr) {
|
|
2357
|
+
debug(`Auto-refresh failed: ${refreshErr.message}`);
|
|
2358
|
+
}
|
|
2359
|
+
}, intervalMs);
|
|
2360
|
+
}
|
|
2361
|
+
stopAutoRefresh() {
|
|
2362
|
+
if (this.refreshTimer) {
|
|
2363
|
+
clearInterval(this.refreshTimer);
|
|
2364
|
+
this.refreshTimer = null;
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2402
2367
|
// Core HTTP request function with CSRF token injection and automatic retry on 403 errors
|
|
2403
2368
|
async request(options) {
|
|
2404
2369
|
const { method, path, params, headers: customHeaders, body } = options;
|
|
@@ -2490,9 +2455,9 @@ var ADTClientImpl = class {
|
|
|
2490
2455
|
async login() {
|
|
2491
2456
|
const { authStrategy } = this.state;
|
|
2492
2457
|
if (authStrategy.performLogin) {
|
|
2493
|
-
const [,
|
|
2494
|
-
if (
|
|
2495
|
-
return err(
|
|
2458
|
+
const [, loginErr2] = await authStrategy.performLogin(fetch);
|
|
2459
|
+
if (loginErr2) {
|
|
2460
|
+
return err(loginErr2);
|
|
2496
2461
|
}
|
|
2497
2462
|
}
|
|
2498
2463
|
if (authStrategy.type === "saml" && authStrategy.getCookies) {
|
|
@@ -2512,11 +2477,28 @@ var ADTClientImpl = class {
|
|
|
2512
2477
|
debug("Stored mTLS certificates for SSO authentication");
|
|
2513
2478
|
}
|
|
2514
2479
|
}
|
|
2515
|
-
|
|
2480
|
+
const [session, loginErr] = await login(this.state, this.request.bind(this));
|
|
2481
|
+
if (loginErr) {
|
|
2482
|
+
return err(loginErr);
|
|
2483
|
+
}
|
|
2484
|
+
const autoRefresh = this.state.config.autoRefresh ?? { enabled: true };
|
|
2485
|
+
if (autoRefresh.enabled) {
|
|
2486
|
+
const interval = autoRefresh.intervalMs ?? DEFAULT_REFRESH_INTERVAL;
|
|
2487
|
+
this.startAutoRefresh(interval);
|
|
2488
|
+
debug(`Auto-refresh started with ${interval}ms interval`);
|
|
2489
|
+
}
|
|
2490
|
+
return ok(session);
|
|
2516
2491
|
}
|
|
2517
2492
|
async logout() {
|
|
2493
|
+
this.stopAutoRefresh();
|
|
2518
2494
|
return logout(this.state, this.request.bind(this));
|
|
2519
2495
|
}
|
|
2496
|
+
async refreshSession() {
|
|
2497
|
+
if (!this.state.session) {
|
|
2498
|
+
return err(new Error("Not logged in"));
|
|
2499
|
+
}
|
|
2500
|
+
return refreshSession(this.state, this.request.bind(this));
|
|
2501
|
+
}
|
|
2520
2502
|
// --- CRAUD Operations ---
|
|
2521
2503
|
async read(objects) {
|
|
2522
2504
|
if (!this.state.session) return err(new Error("Not logged in"));
|
|
@@ -2626,9 +2608,9 @@ var ADTClientImpl = class {
|
|
|
2626
2608
|
if (!this.state.session) return err(new Error("Not logged in"));
|
|
2627
2609
|
return getTree(this.requestor, query);
|
|
2628
2610
|
}
|
|
2629
|
-
async getPackageStats(
|
|
2611
|
+
async getPackageStats(packageNames) {
|
|
2630
2612
|
if (!this.state.session) return err(new Error("Not logged in"));
|
|
2631
|
-
return getPackageStats(this.requestor,
|
|
2613
|
+
return getPackageStats(this.requestor, packageNames);
|
|
2632
2614
|
}
|
|
2633
2615
|
async getTransports(packageName) {
|
|
2634
2616
|
if (!this.state.session) return err(new Error("Not logged in"));
|