claude overtook and fixes

This commit is contained in:
JiriUhlir
2026-06-29 10:17:14 +02:00
parent 5281d56998
commit 04fbf692a3
6 changed files with 151 additions and 6 deletions
+19 -2
View File
@@ -55,7 +55,8 @@ export class SessionManager {
CompanyDB: this.config.companyDB,
UserName: this.config.username,
Password: this.config.password,
Language: this.config.language
// Service Layer expects Language as an integer language code (Edm.Int32).
Language: this.normalizeLanguage(this.config.language)
},
{ skipAuth: true }
);
@@ -90,8 +91,15 @@ export class SessionManager {
return;
}
const cookieHeader = this.buildCookieHeader(this.session);
try {
await this.http.post("/Logout", undefined, { skipAuth: false });
// Logout must carry the active session cookie, otherwise the Service Layer
// cannot identify which session to terminate and leaves it open until timeout.
await this.http.post("/Logout", undefined, {
skipAuth: true,
headers: { Cookie: cookieHeader }
});
} finally {
this.session = undefined;
}
@@ -111,6 +119,15 @@ export class SessionManager {
this.session = undefined;
}
private normalizeLanguage(language: string | undefined): number | undefined {
if (language === undefined || language === "") {
return undefined;
}
const numeric = Number(language);
return Number.isInteger(numeric) ? numeric : undefined;
}
private isExpired(session: SapB1Session): boolean {
return session.expiresAt !== undefined && session.expiresAt <= Date.now();
}
+8 -2
View File
@@ -91,6 +91,8 @@ export class SapB1Client {
}
};
let reloginAttempted = false;
for (let attempt = 0; attempt <= this.config.retryCount; attempt += 1) {
try {
if (!requestConfig.skipAuth) {
@@ -106,11 +108,15 @@ export class SapB1Client {
return options.schema ? options.schema.parse(response.data) : (response.data as T);
} catch (error) {
const sapError = this.toSapB1Error(error, method, path);
const canRelogin = sapError.status === 401 && !requestConfig.skipAuth && attempt === 0;
const canRelogin = sapError.status === 401 && !requestConfig.skipAuth && !reloginAttempted;
if (canRelogin) {
// A single re-login on session expiry must not eat into the retry budget,
// so step the counter back and retry the request with a fresh session.
reloginAttempted = true;
this.sessionManager.clearSession();
await this.sessionManager.getSession(true);
attempt -= 1;
continue;
}
@@ -162,7 +168,7 @@ export class SapB1Client {
private normalizeNextLink(nextLink: string): string {
try {
const parsed = new URL(nextLink);
return parsed.pathname.replace(/^.*\/b1s\/v1\/?/, "");
return parsed.pathname.replace(/^.*\/b1s\/v1\/?/, "") + parsed.search;
} catch {
return nextLink.replace(/^\/?b1s\/v1\/?/, "");
}
+6 -1
View File
@@ -610,7 +610,12 @@ app.get("/openapi.json", (_req, res) => {
addResourceEndpoints();
app.use("/docs", swaggerUi.serveFiles(undefined, swaggerOptions("")), swaggerUi.setup(undefined, swaggerOptions("")));
// Behind the AppFactory reverse proxy the container is reached via /apps/<app-id> and
// the prefix is stripped (handle_path) before requests arrive here. The Swagger UI page is
// therefore served at the public /apps/<app-id>/docs, so the OpenAPI document URL and the
// "Try it out" base path must carry ROOT_PATH; otherwise the browser would resolve them
// against the bare origin and bypass the app.
app.use("/docs", swaggerUi.serveFiles(undefined, swaggerOptions(rootPath)), swaggerUi.setup(undefined, swaggerOptions(rootPath)));
if (rootPath) {
app.get(rootPath, (_req, res) => {