arc-1 1.6.0 → 1.8.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.
- package/README.md +26 -18
- package/dist/adt/application-log.d.ts +36 -0
- package/dist/adt/application-log.d.ts.map +1 -0
- package/dist/adt/application-log.js +143 -0
- package/dist/adt/application-log.js.map +1 -0
- package/dist/adt/aunit-package.d.ts.map +1 -1
- package/dist/adt/aunit-package.js +9 -1
- package/dist/adt/aunit-package.js.map +1 -1
- package/dist/adt/ci-quality.d.ts.map +1 -1
- package/dist/adt/ci-quality.js +1 -1
- package/dist/adt/ci-quality.js.map +1 -1
- package/dist/adt/class-structure.d.ts +2 -1
- package/dist/adt/class-structure.d.ts.map +1 -1
- package/dist/adt/class-structure.js +28 -10
- package/dist/adt/class-structure.js.map +1 -1
- package/dist/adt/client.d.ts +27 -9
- package/dist/adt/client.d.ts.map +1 -1
- package/dist/adt/client.js +23 -16
- package/dist/adt/client.js.map +1 -1
- package/dist/adt/codeintel.d.ts +9 -23
- package/dist/adt/codeintel.d.ts.map +1 -1
- package/dist/adt/codeintel.js +14 -34
- package/dist/adt/codeintel.js.map +1 -1
- package/dist/adt/crud.d.ts +3 -2
- package/dist/adt/crud.d.ts.map +1 -1
- package/dist/adt/crud.js +13 -3
- package/dist/adt/crud.js.map +1 -1
- package/dist/adt/devtools.d.ts +21 -7
- package/dist/adt/devtools.d.ts.map +1 -1
- package/dist/adt/devtools.js +98 -44
- package/dist/adt/devtools.js.map +1 -1
- package/dist/adt/diagnostics.d.ts +2 -0
- package/dist/adt/diagnostics.d.ts.map +1 -1
- package/dist/adt/diagnostics.js +12 -10
- package/dist/adt/diagnostics.js.map +1 -1
- package/dist/adt/errors.d.ts +5 -1
- package/dist/adt/errors.d.ts.map +1 -1
- package/dist/adt/errors.js +26 -0
- package/dist/adt/errors.js.map +1 -1
- package/dist/adt/feed-schema.d.ts +32 -0
- package/dist/adt/feed-schema.d.ts.map +1 -0
- package/dist/adt/feed-schema.js +123 -0
- package/dist/adt/feed-schema.js.map +1 -0
- package/dist/adt/fm-parameter-docs.d.ts +7 -0
- package/dist/adt/fm-parameter-docs.d.ts.map +1 -0
- package/dist/adt/fm-parameter-docs.js +134 -0
- package/dist/adt/fm-parameter-docs.js.map +1 -0
- package/dist/adt/fm-signature.d.ts +8 -1
- package/dist/adt/fm-signature.d.ts.map +1 -1
- package/dist/adt/fm-signature.js +37 -28
- package/dist/adt/fm-signature.js.map +1 -1
- package/dist/adt/http-deadline.d.ts +2 -0
- package/dist/adt/http-deadline.d.ts.map +1 -1
- package/dist/adt/http-deadline.js +4 -1
- package/dist/adt/http-deadline.js.map +1 -1
- package/dist/adt/http-retry-after.d.ts +20 -0
- package/dist/adt/http-retry-after.d.ts.map +1 -0
- package/dist/adt/http-retry-after.js +37 -0
- package/dist/adt/http-retry-after.js.map +1 -0
- package/dist/adt/http.d.ts +11 -20
- package/dist/adt/http.d.ts.map +1 -1
- package/dist/adt/http.js +104 -90
- package/dist/adt/http.js.map +1 -1
- package/dist/adt/include-expansion.d.ts +143 -0
- package/dist/adt/include-expansion.d.ts.map +1 -0
- package/dist/adt/include-expansion.js +475 -0
- package/dist/adt/include-expansion.js.map +1 -0
- package/dist/adt/internal-data-operations.d.ts +3 -3
- package/dist/adt/internal-data-operations.d.ts.map +1 -1
- package/dist/adt/internal-data-operations.js +9 -2
- package/dist/adt/internal-data-operations.js.map +1 -1
- package/dist/adt/package-browser.d.ts +23 -0
- package/dist/adt/package-browser.d.ts.map +1 -0
- package/dist/adt/package-browser.js +139 -0
- package/dist/adt/package-browser.js.map +1 -0
- package/dist/adt/package-contents.d.ts +2 -0
- package/dist/adt/package-contents.d.ts.map +1 -1
- package/dist/adt/package-contents.js +3 -1
- package/dist/adt/package-contents.js.map +1 -1
- package/dist/adt/pinned-write-policy.d.ts +15 -0
- package/dist/adt/pinned-write-policy.d.ts.map +1 -0
- package/dist/adt/pinned-write-policy.js +111 -0
- package/dist/adt/pinned-write-policy.js.map +1 -0
- package/dist/adt/revision-feed.d.ts +8 -0
- package/dist/adt/revision-feed.d.ts.map +1 -0
- package/dist/adt/revision-feed.js +108 -0
- package/dist/adt/revision-feed.js.map +1 -0
- package/dist/adt/safety.d.ts +11 -0
- package/dist/adt/safety.d.ts.map +1 -1
- package/dist/adt/safety.js +18 -2
- package/dist/adt/safety.js.map +1 -1
- package/dist/adt/security-session-gate.d.ts +23 -0
- package/dist/adt/security-session-gate.d.ts.map +1 -0
- package/dist/adt/security-session-gate.js +56 -0
- package/dist/adt/security-session-gate.js.map +1 -0
- package/dist/adt/source-diff.d.ts +4 -1
- package/dist/adt/source-diff.d.ts.map +1 -1
- package/dist/adt/source-diff.js +5 -3
- package/dist/adt/source-diff.js.map +1 -1
- package/dist/adt/stored-text/chunks.d.ts +8 -0
- package/dist/adt/stored-text/chunks.d.ts.map +1 -0
- package/dist/adt/stored-text/chunks.js +55 -0
- package/dist/adt/stored-text/chunks.js.map +1 -0
- package/dist/adt/stored-text/decoder.d.ts +31 -0
- package/dist/adt/stored-text/decoder.d.ts.map +1 -0
- package/dist/adt/stored-text/decoder.js +319 -0
- package/dist/adt/stored-text/decoder.js.map +1 -0
- package/dist/adt/stored-text/errors.d.ts +10 -0
- package/dist/adt/stored-text/errors.d.ts.map +1 -0
- package/dist/adt/stored-text/errors.js +10 -0
- package/dist/adt/stored-text/errors.js.map +1 -0
- package/dist/adt/stored-text/index.d.ts +7 -0
- package/dist/adt/stored-text/index.d.ts.map +1 -0
- package/dist/adt/stored-text/index.js +6 -0
- package/dist/adt/stored-text/index.js.map +1 -0
- package/dist/adt/stored-text/key.d.ts +3 -0
- package/dist/adt/stored-text/key.d.ts.map +1 -0
- package/dist/adt/stored-text/key.js +23 -0
- package/dist/adt/stored-text/key.js.map +1 -0
- package/dist/adt/stored-text/reader.d.ts +52 -0
- package/dist/adt/stored-text/reader.d.ts.map +1 -0
- package/dist/adt/stored-text/reader.js +142 -0
- package/dist/adt/stored-text/reader.js.map +1 -0
- package/dist/adt/stored-text/source.d.ts +5 -0
- package/dist/adt/stored-text/source.d.ts.map +1 -0
- package/dist/adt/stored-text/source.js +284 -0
- package/dist/adt/stored-text/source.js.map +1 -0
- package/dist/adt/stored-text/types.d.ts +38 -0
- package/dist/adt/stored-text/types.d.ts.map +1 -0
- package/dist/adt/stored-text/types.js +2 -0
- package/dist/adt/stored-text/types.js.map +1 -0
- package/dist/adt/structure-hierarchy.d.ts.map +1 -1
- package/dist/adt/structure-hierarchy.js +11 -27
- package/dist/adt/structure-hierarchy.js.map +1 -1
- package/dist/adt/syntax-batch.d.ts +66 -0
- package/dist/adt/syntax-batch.d.ts.map +1 -0
- package/dist/adt/syntax-batch.js +115 -0
- package/dist/adt/syntax-batch.js.map +1 -0
- package/dist/adt/table-query.d.ts +3 -1
- package/dist/adt/table-query.d.ts.map +1 -1
- package/dist/adt/table-query.js +32 -4
- package/dist/adt/table-query.js.map +1 -1
- package/dist/adt/table-settings.d.ts +3 -0
- package/dist/adt/table-settings.d.ts.map +1 -1
- package/dist/adt/table-settings.js +11 -1
- package/dist/adt/table-settings.js.map +1 -1
- package/dist/adt/text-elements.js +1 -1
- package/dist/adt/text-elements.js.map +1 -1
- package/dist/adt/transport-diff.d.ts +16 -25
- package/dist/adt/transport-diff.d.ts.map +1 -1
- package/dist/adt/transport-diff.js +201 -103
- package/dist/adt/transport-diff.js.map +1 -1
- package/dist/adt/transport-locks.d.ts +7 -0
- package/dist/adt/transport-locks.d.ts.map +1 -0
- package/dist/adt/transport-locks.js +56 -0
- package/dist/adt/transport-locks.js.map +1 -0
- package/dist/adt/transport-review-budget.d.ts +15 -0
- package/dist/adt/transport-review-budget.d.ts.map +1 -0
- package/dist/adt/transport-review-budget.js +57 -0
- package/dist/adt/transport-review-budget.js.map +1 -0
- package/dist/adt/transport.d.ts +5 -2
- package/dist/adt/transport.d.ts.map +1 -1
- package/dist/adt/transport.js +8 -46
- package/dist/adt/transport.js.map +1 -1
- package/dist/adt/types.d.ts +1 -0
- package/dist/adt/types.d.ts.map +1 -1
- package/dist/adt/user-agent.d.ts +1 -1
- package/dist/adt/where-used-native.d.ts +22 -0
- package/dist/adt/where-used-native.d.ts.map +1 -0
- package/dist/adt/where-used-native.js +111 -0
- package/dist/adt/where-used-native.js.map +1 -0
- package/dist/adt/xml-parser.d.ts +2 -3
- package/dist/adt/xml-parser.d.ts.map +1 -1
- package/dist/adt/xml-parser.js +1 -91
- package/dist/adt/xml-parser.js.map +1 -1
- package/dist/aff/validator.d.ts.map +1 -1
- package/dist/aff/validator.js +8 -2
- package/dist/aff/validator.js.map +1 -1
- package/dist/authz/policy.d.ts.map +1 -1
- package/dist/authz/policy.js +10 -2
- package/dist/authz/policy.js.map +1 -1
- package/dist/cache/sqlite-driver.d.ts +24 -0
- package/dist/cache/sqlite-driver.d.ts.map +1 -0
- package/dist/cache/sqlite-driver.js +59 -0
- package/dist/cache/sqlite-driver.js.map +1 -0
- package/dist/cache/sqlite.d.ts +4 -2
- package/dist/cache/sqlite.d.ts.map +1 -1
- package/dist/cache/sqlite.js +5 -4
- package/dist/cache/sqlite.js.map +1 -1
- package/dist/cli-ide.d.ts +11 -0
- package/dist/cli-ide.d.ts.map +1 -0
- package/dist/cli-ide.js +69 -0
- package/dist/cli-ide.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +9 -2
- package/dist/cli.js.map +1 -1
- package/dist/context/grep.d.ts +2 -0
- package/dist/context/grep.d.ts.map +1 -1
- package/dist/context/grep.js +8 -7
- package/dist/context/grep.js.map +1 -1
- package/dist/extract-sap-cookies.js +1 -0
- package/dist/extract-sap-cookies.js.map +1 -1
- package/dist/handlers/activate.d.ts.map +1 -1
- package/dist/handlers/activate.js +3 -0
- package/dist/handlers/activate.js.map +1 -1
- package/dist/handlers/application-log-input.d.ts +32 -0
- package/dist/handlers/application-log-input.d.ts.map +1 -0
- package/dist/handlers/application-log-input.js +34 -0
- package/dist/handlers/application-log-input.js.map +1 -0
- package/dist/handlers/application-log.d.ts +4 -0
- package/dist/handlers/application-log.d.ts.map +1 -0
- package/dist/handlers/application-log.js +14 -0
- package/dist/handlers/application-log.js.map +1 -0
- package/dist/handlers/cds-hints.d.ts +1 -1
- package/dist/handlers/cds-hints.d.ts.map +1 -1
- package/dist/handlers/cds-hints.js +21 -63
- package/dist/handlers/cds-hints.js.map +1 -1
- package/dist/handlers/context.d.ts.map +1 -1
- package/dist/handlers/context.js +7 -3
- package/dist/handlers/context.js.map +1 -1
- package/dist/handlers/diagnose.d.ts.map +1 -1
- package/dist/handlers/diagnose.js +10 -1
- package/dist/handlers/diagnose.js.map +1 -1
- package/dist/handlers/dispatch.d.ts +2 -1
- package/dist/handlers/dispatch.d.ts.map +1 -1
- package/dist/handlers/dispatch.js +63 -12
- package/dist/handlers/dispatch.js.map +1 -1
- package/dist/handlers/dump-filter-inputs.d.ts +38 -0
- package/dist/handlers/dump-filter-inputs.d.ts.map +1 -0
- package/dist/handlers/dump-filter-inputs.js +44 -0
- package/dist/handlers/dump-filter-inputs.js.map +1 -0
- package/dist/handlers/expanded-read.d.ts +24 -0
- package/dist/handlers/expanded-read.d.ts.map +1 -0
- package/dist/handlers/expanded-read.js +56 -0
- package/dist/handlers/expanded-read.js.map +1 -0
- package/dist/handlers/function-parameter-language.d.ts +5 -0
- package/dist/handlers/function-parameter-language.d.ts.map +1 -0
- package/dist/handlers/function-parameter-language.js +19 -0
- package/dist/handlers/function-parameter-language.js.map +1 -0
- package/dist/handlers/function-processing.d.ts +5 -0
- package/dist/handlers/function-processing.d.ts.map +1 -1
- package/dist/handlers/function-processing.js +5 -0
- package/dist/handlers/function-processing.js.map +1 -1
- package/dist/handlers/hyperfocused.d.ts.map +1 -1
- package/dist/handlers/hyperfocused.js +2 -1
- package/dist/handlers/hyperfocused.js.map +1 -1
- package/dist/handlers/long-text-input.d.ts +113 -0
- package/dist/handlers/long-text-input.d.ts.map +1 -0
- package/dist/handlers/long-text-input.js +113 -0
- package/dist/handlers/long-text-input.js.map +1 -0
- package/dist/handlers/long-text.d.ts +5 -0
- package/dist/handlers/long-text.d.ts.map +1 -0
- package/dist/handlers/long-text.js +122 -0
- package/dist/handlers/long-text.js.map +1 -0
- package/dist/handlers/navigate.d.ts.map +1 -1
- package/dist/handlers/navigate.js +8 -3
- package/dist/handlers/navigate.js.map +1 -1
- package/dist/handlers/object-types.d.ts.map +1 -1
- package/dist/handlers/object-types.js +12 -5
- package/dist/handlers/object-types.js.map +1 -1
- package/dist/handlers/query-input.d.ts +50 -0
- package/dist/handlers/query-input.d.ts.map +1 -0
- package/dist/handlers/query-input.js +59 -0
- package/dist/handlers/query-input.js.map +1 -0
- package/dist/handlers/query-tool.d.ts +11 -0
- package/dist/handlers/query-tool.d.ts.map +1 -0
- package/dist/handlers/query-tool.js +99 -0
- package/dist/handlers/query-tool.js.map +1 -0
- package/dist/handlers/query.d.ts +1 -1
- package/dist/handlers/query.d.ts.map +1 -1
- package/dist/handlers/query.js +7 -1
- package/dist/handlers/query.js.map +1 -1
- package/dist/handlers/read.d.ts +1 -0
- package/dist/handlers/read.d.ts.map +1 -1
- package/dist/handlers/read.js +62 -11
- package/dist/handlers/read.js.map +1 -1
- package/dist/handlers/schemas.d.ts +23 -4
- package/dist/handlers/schemas.d.ts.map +1 -1
- package/dist/handlers/schemas.js +82 -9
- package/dist/handlers/schemas.js.map +1 -1
- package/dist/handlers/syntax.d.ts.map +1 -1
- package/dist/handlers/syntax.js +8 -0
- package/dist/handlers/syntax.js.map +1 -1
- package/dist/handlers/tool-descriptions.js +2 -2
- package/dist/handlers/tool-descriptions.js.map +1 -1
- package/dist/handlers/tools.d.ts.map +1 -1
- package/dist/handlers/tools.js +81 -74
- package/dist/handlers/tools.js.map +1 -1
- package/dist/handlers/transport-review.d.ts +5 -0
- package/dist/handlers/transport-review.d.ts.map +1 -0
- package/dist/handlers/transport-review.js +108 -0
- package/dist/handlers/transport-review.js.map +1 -0
- package/dist/handlers/transport.d.ts.map +1 -1
- package/dist/handlers/transport.js +3 -58
- package/dist/handlers/transport.js.map +1 -1
- package/dist/handlers/where-used-details.d.ts +28 -0
- package/dist/handlers/where-used-details.d.ts.map +1 -0
- package/dist/handlers/where-used-details.js +67 -0
- package/dist/handlers/where-used-details.js.map +1 -0
- package/dist/handlers/where-used.d.ts +8 -3
- package/dist/handlers/where-used.d.ts.map +1 -1
- package/dist/handlers/where-used.js +40 -18
- package/dist/handlers/where-used.js.map +1 -1
- package/dist/handlers/write/create.d.ts.map +1 -1
- package/dist/handlers/write/create.js +15 -12
- package/dist/handlers/write/create.js.map +1 -1
- package/dist/handlers/write/pinned-activation.d.ts +8 -0
- package/dist/handlers/write/pinned-activation.d.ts.map +1 -0
- package/dist/handlers/write/pinned-activation.js +68 -0
- package/dist/handlers/write/pinned-activation.js.map +1 -0
- package/dist/handlers/write/pinned.d.ts +10 -0
- package/dist/handlers/write/pinned.d.ts.map +1 -0
- package/dist/handlers/write/pinned.js +202 -0
- package/dist/handlers/write/pinned.js.map +1 -0
- package/dist/handlers/write/update-delete.d.ts.map +1 -1
- package/dist/handlers/write/update-delete.js +5 -3
- package/dist/handlers/write/update-delete.js.map +1 -1
- package/dist/handlers/write-helpers.d.ts +2 -2
- package/dist/handlers/write-helpers.d.ts.map +1 -1
- package/dist/handlers/write-helpers.js +8 -6
- package/dist/handlers/write-helpers.js.map +1 -1
- package/dist/handlers/write.d.ts.map +1 -1
- package/dist/handlers/write.js +4 -1
- package/dist/handlers/write.js.map +1 -1
- package/dist/server/audit.d.ts +1 -0
- package/dist/server/audit.d.ts.map +1 -1
- package/dist/server/audit.js +49 -2
- package/dist/server/audit.js.map +1 -1
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/config.js +29 -1
- package/dist/server/config.js.map +1 -1
- package/dist/server/destination-registry.d.ts +14 -0
- package/dist/server/destination-registry.d.ts.map +1 -1
- package/dist/server/destination-registry.js +43 -4
- package/dist/server/destination-registry.js.map +1 -1
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +25 -5
- package/dist/server/http.js.map +1 -1
- package/dist/server/ide-links.d.ts +14 -0
- package/dist/server/ide-links.d.ts.map +1 -0
- package/dist/server/ide-links.js +146 -0
- package/dist/server/ide-links.js.map +1 -0
- package/dist/server/ide-request.d.ts +13 -0
- package/dist/server/ide-request.d.ts.map +1 -0
- package/dist/server/ide-request.js +14 -0
- package/dist/server/ide-request.js.map +1 -0
- package/dist/server/multi-target-authorization.d.ts +1 -1
- package/dist/server/multi-target-authorization.d.ts.map +1 -1
- package/dist/server/multi-target-authorization.js +5 -3
- package/dist/server/multi-target-authorization.js.map +1 -1
- package/dist/server/multi-target-catalog-enforced.d.ts +6 -1
- package/dist/server/multi-target-catalog-enforced.d.ts.map +1 -1
- package/dist/server/multi-target-catalog-enforced.js +3 -0
- package/dist/server/multi-target-catalog-enforced.js.map +1 -1
- package/dist/server/multi-target-catalog-projection.d.ts.map +1 -1
- package/dist/server/multi-target-catalog-projection.js +1 -0
- package/dist/server/multi-target-catalog-projection.js.map +1 -1
- package/dist/server/multi-target-catalog.d.ts.map +1 -1
- package/dist/server/multi-target-catalog.js +1 -0
- package/dist/server/multi-target-catalog.js.map +1 -1
- package/dist/server/multi-target-destination-config.d.ts +2 -0
- package/dist/server/multi-target-destination-config.d.ts.map +1 -1
- package/dist/server/multi-target-destination-config.js +18 -0
- package/dist/server/multi-target-destination-config.js.map +1 -1
- package/dist/server/multi-target-identity.d.ts.map +1 -1
- package/dist/server/multi-target-identity.js +4 -3
- package/dist/server/multi-target-identity.js.map +1 -1
- package/dist/server/multi-target-request-projection.d.ts +2 -0
- package/dist/server/multi-target-request-projection.d.ts.map +1 -1
- package/dist/server/multi-target-request-projection.js +2 -0
- package/dist/server/multi-target-request-projection.js.map +1 -1
- package/dist/server/multi-target-runtime.d.ts +5 -1
- package/dist/server/multi-target-runtime.d.ts.map +1 -1
- package/dist/server/multi-target-runtime.js +30 -3
- package/dist/server/multi-target-runtime.js.map +1 -1
- package/dist/server/multi-target-server.d.ts.map +1 -1
- package/dist/server/multi-target-server.js +42 -6
- package/dist/server/multi-target-server.js.map +1 -1
- package/dist/server/multi-target-tools.d.ts.map +1 -1
- package/dist/server/multi-target-tools.js +64 -7
- package/dist/server/multi-target-tools.js.map +1 -1
- package/dist/server/multi-target-write-surface.d.ts +9 -0
- package/dist/server/multi-target-write-surface.d.ts.map +1 -0
- package/dist/server/multi-target-write-surface.js +42 -0
- package/dist/server/multi-target-write-surface.js.map +1 -0
- package/dist/server/per-user-transport.d.ts +18 -0
- package/dist/server/per-user-transport.d.ts.map +1 -0
- package/dist/server/per-user-transport.js +72 -0
- package/dist/server/per-user-transport.js.map +1 -0
- package/dist/server/server-instructions.d.ts +2 -0
- package/dist/server/server-instructions.d.ts.map +1 -1
- package/dist/server/server-instructions.js +10 -12
- package/dist/server/server-instructions.js.map +1 -1
- package/dist/server/server.d.ts +3 -0
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +22 -8
- package/dist/server/server.js.map +1 -1
- package/dist/server/tool-auth.d.ts +1 -1
- package/dist/server/tool-auth.d.ts.map +1 -1
- package/dist/server/tool-auth.js +24 -4
- package/dist/server/tool-auth.js.map +1 -1
- package/dist/server/types.d.ts +7 -0
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js +3 -0
- package/dist/server/types.js.map +1 -1
- package/dist/server/ui-state.d.ts.map +1 -1
- package/dist/server/ui-state.js +1 -0
- package/dist/server/ui-state.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +14 -10
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse RFC 7231 `Retry-After` header into a delay in milliseconds.
|
|
3
|
+
*
|
|
4
|
+
* Accepts both forms allowed by the spec:
|
|
5
|
+
* - delta-seconds (e.g. `"5"`)
|
|
6
|
+
* - HTTP-date (e.g. `"Wed, 12 May 2026 14:30:00 GMT"`)
|
|
7
|
+
*
|
|
8
|
+
* On missing, NaN, malformed, past-date, or negative input, falls back to `fallbackMs`.
|
|
9
|
+
* Always clamps to `[0, 60_000]` ms so a misbehaving gateway can't stall us indefinitely
|
|
10
|
+
* and a too-small/past value can't degenerate into a hot retry loop.
|
|
11
|
+
*
|
|
12
|
+
* Returns `{ delayMs, source }` — `source: 'header' | 'fallback'` lets audit events
|
|
13
|
+
* record whether the delay came from the server or from our jitter floor (useful for
|
|
14
|
+
* capacity tuning).
|
|
15
|
+
*/
|
|
16
|
+
export function parseRetryAfter(header, fallbackMs) {
|
|
17
|
+
const clamp = (ms) => Math.max(0, Math.min(60_000, ms));
|
|
18
|
+
if (header == null || header === '') {
|
|
19
|
+
return { delayMs: clamp(fallbackMs), source: 'fallback' };
|
|
20
|
+
}
|
|
21
|
+
const trimmed = header.trim();
|
|
22
|
+
// delta-seconds form: integer (and only digits, optionally signed)
|
|
23
|
+
if (/^-?\d+$/.test(trimmed)) {
|
|
24
|
+
const seconds = Number.parseInt(trimmed, 10);
|
|
25
|
+
if (Number.isFinite(seconds)) {
|
|
26
|
+
return { delayMs: clamp(seconds * 1000), source: 'header' };
|
|
27
|
+
}
|
|
28
|
+
return { delayMs: clamp(fallbackMs), source: 'fallback' };
|
|
29
|
+
}
|
|
30
|
+
// HTTP-date form
|
|
31
|
+
const epochMs = Date.parse(trimmed);
|
|
32
|
+
if (Number.isFinite(epochMs)) {
|
|
33
|
+
return { delayMs: clamp(epochMs - Date.now()), source: 'header' };
|
|
34
|
+
}
|
|
35
|
+
return { delayMs: clamp(fallbackMs), source: 'fallback' };
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=http-retry-after.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-retry-after.js","sourceRoot":"","sources":["../../src/adt/http-retry-after.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAiC,EACjC,UAAkB;IAElB,MAAM,KAAK,GAAG,CAAC,EAAU,EAAU,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;IACxE,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;QACpC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAC5D,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAC9B,mEAAmE;IACnE,IAAI,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAC7C,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7B,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;QAC9D,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAC5D,CAAC;IACD,iBAAiB;IACjB,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACpC,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IACpE,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;AAC5D,CAAC"}
|
package/dist/adt/http.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { BTPProxyConfig } from '@arc-mcp/xsuaa-auth/btp';
|
|
|
9
9
|
import { type AdtRequestOptions } from './http-deadline.js';
|
|
10
10
|
import type { Semaphore } from './semaphore.js';
|
|
11
11
|
export type { AdtRequestOptions } from './http-deadline.js';
|
|
12
|
+
export { parseRetryAfter } from './http-retry-after.js';
|
|
12
13
|
/** Session type for ADT requests */
|
|
13
14
|
export type SessionType = 'stateful' | 'stateless' | undefined;
|
|
14
15
|
/** Configuration for the ADT HTTP client */
|
|
@@ -106,6 +107,8 @@ export declare class AdtHttpClient {
|
|
|
106
107
|
private dbRetryInProgress;
|
|
107
108
|
/** Set after a 401 clears stale cookies — triggers file reload on the next request */
|
|
108
109
|
private cookiesCleared;
|
|
110
|
+
/** Keeps a cold client at one SAP security session (see security-session-gate.ts). */
|
|
111
|
+
private readonly securitySessionGate;
|
|
109
112
|
/** Shared by stateful clones so one rejected Basic credential cannot fan out internally. */
|
|
110
113
|
private readonly authenticationAttemptState;
|
|
111
114
|
constructor(config: AdtHttpConfig, authenticationAttemptState?: AuthenticationAttemptState);
|
|
@@ -136,7 +139,11 @@ export declare class AdtHttpClient {
|
|
|
136
139
|
* Creates a new client instance with the stateful session header,
|
|
137
140
|
* shares the main client's request state, and closes the backend context.
|
|
138
141
|
*/
|
|
139
|
-
withStatefulSession<T>(fn: (client: AdtHttpClient) => Promise<T
|
|
142
|
+
withStatefulSession<T>(fn: (client: AdtHttpClient) => Promise<T>, cleanup?: {
|
|
143
|
+
timeoutMs: number;
|
|
144
|
+
noReplay: boolean;
|
|
145
|
+
onClose: (closed: boolean | undefined) => void;
|
|
146
|
+
}): Promise<T>;
|
|
140
147
|
/** Close the SAP application context without changing the completed operation's result. */
|
|
141
148
|
private closeStatefulSession;
|
|
142
149
|
/** Core request method — wraps requestInner with optional concurrency limiter */
|
|
@@ -221,6 +228,8 @@ export declare class AdtHttpClient {
|
|
|
221
228
|
* `config.cookies` and survive untouched. Returns undefined when empty.
|
|
222
229
|
*/
|
|
223
230
|
private composeCookieHeader;
|
|
231
|
+
/** Read the current pair together after a wait or a response that can rotate the session. */
|
|
232
|
+
private refreshSessionHeaders;
|
|
224
233
|
private storeCookies;
|
|
225
234
|
/** Add SAP context query params unless the caller sends that context in headers. */
|
|
226
235
|
private buildUrl;
|
|
@@ -239,6 +248,7 @@ export declare class AdtHttpClient {
|
|
|
239
248
|
* interface is incompatible with npm undici@8 Agent/ProxyAgent instances.
|
|
240
249
|
*/
|
|
241
250
|
private doFetch;
|
|
251
|
+
private sendRequest;
|
|
242
252
|
/**
|
|
243
253
|
* Execute an HTTP request through the BTP connectivity proxy using standard
|
|
244
254
|
* HTTP proxy protocol (RFC 7230).
|
|
@@ -258,23 +268,4 @@ export declare class AdtHttpClient {
|
|
|
258
268
|
*/
|
|
259
269
|
private doProxyRequest;
|
|
260
270
|
}
|
|
261
|
-
/**
|
|
262
|
-
* Parse RFC 7231 `Retry-After` header into a delay in milliseconds.
|
|
263
|
-
*
|
|
264
|
-
* Accepts both forms allowed by the spec:
|
|
265
|
-
* - delta-seconds (e.g. `"5"`)
|
|
266
|
-
* - HTTP-date (e.g. `"Wed, 12 May 2026 14:30:00 GMT"`)
|
|
267
|
-
*
|
|
268
|
-
* On missing, NaN, malformed, past-date, or negative input, falls back to `fallbackMs`.
|
|
269
|
-
* Always clamps to `[0, 60_000]` ms so a misbehaving gateway can't stall us indefinitely
|
|
270
|
-
* and a too-small/past value can't degenerate into a hot retry loop.
|
|
271
|
-
*
|
|
272
|
-
* Returns `{ delayMs, source }` — `source: 'header' | 'fallback'` lets audit events
|
|
273
|
-
* record whether the delay came from the server or from our jitter floor (useful for
|
|
274
|
-
* capacity tuning).
|
|
275
|
-
*/
|
|
276
|
-
export declare function parseRetryAfter(header: string | null | undefined, fallbackMs: number): {
|
|
277
|
-
delayMs: number;
|
|
278
|
-
source: 'header' | 'fallback';
|
|
279
|
-
};
|
|
280
271
|
//# sourceMappingURL=http.d.ts.map
|
package/dist/adt/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/adt/http.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAU9D,OAAO,EACL,KAAK,iBAAiB,EAOvB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/adt/http.ts"],"names":[],"mappings":"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"}
|
package/dist/adt/http.js
CHANGED
|
@@ -15,10 +15,13 @@ import { resolveCookies } from './cookies.js';
|
|
|
15
15
|
import { resolveAcceptType, resolveContentType } from './discovery.js';
|
|
16
16
|
import { AdtApiError, AdtNetworkError, AdtResponseLimitError } from './errors.js';
|
|
17
17
|
import { awaitWithinRequestBudget, requestBudgetSignal, requestSignal, sleepWithinRequestBudget, throwIfRequestCancelled, withoutResponseBudget, } from './http-deadline.js';
|
|
18
|
+
import { parseRetryAfter } from './http-retry-after.js';
|
|
18
19
|
import { prepareDataPreviewWireBody } from './http-wire-body.js';
|
|
19
20
|
import { legacyContentHandlerFallback, rejectEmptyAdtHtmlResponse } from './legacy-content-handler.js';
|
|
20
21
|
import { fetchWithAttemptBudget } from './request-attempt-budget.js';
|
|
22
|
+
import { hasSecuritySessionCookie, SecuritySessionGate } from './security-session-gate.js';
|
|
21
23
|
import { resolveSapUserAgent } from './user-agent.js';
|
|
24
|
+
export { parseRetryAfter } from './http-retry-after.js';
|
|
22
25
|
/**
|
|
23
26
|
* Opt-in wire-level debug logging.
|
|
24
27
|
*
|
|
@@ -104,6 +107,8 @@ export class AdtHttpClient {
|
|
|
104
107
|
dbRetryInProgress = false;
|
|
105
108
|
/** Set after a 401 clears stale cookies — triggers file reload on the next request */
|
|
106
109
|
cookiesCleared = false;
|
|
110
|
+
/** Keeps a cold client at one SAP security session (see security-session-gate.ts). */
|
|
111
|
+
securitySessionGate = new SecuritySessionGate();
|
|
107
112
|
/** Shared by stateful clones so one rejected Basic credential cannot fan out internally. */
|
|
108
113
|
authenticationAttemptState;
|
|
109
114
|
constructor(config, authenticationAttemptState) {
|
|
@@ -163,7 +168,7 @@ export class AdtHttpClient {
|
|
|
163
168
|
* Creates a new client instance with the stateful session header,
|
|
164
169
|
* shares the main client's request state, and closes the backend context.
|
|
165
170
|
*/
|
|
166
|
-
async withStatefulSession(fn) {
|
|
171
|
+
async withStatefulSession(fn, cleanup) {
|
|
167
172
|
const sessionConfig = {
|
|
168
173
|
...this.config,
|
|
169
174
|
sessionType: 'stateful',
|
|
@@ -172,6 +177,7 @@ export class AdtHttpClient {
|
|
|
172
177
|
// Share CSRF token and cookies so we don't need to re-fetch
|
|
173
178
|
sessionClient.csrfToken = this.csrfToken;
|
|
174
179
|
sessionClient.cookieJar = new Map(this.cookieJar);
|
|
180
|
+
sessionClient.cookieJar.delete('sap-contextid'); // A new write must open its own application context.
|
|
175
181
|
sessionClient.discoveryMap = this.discoveryMap;
|
|
176
182
|
sessionClient.negotiatedHeaders = new Map(this.negotiatedHeaders);
|
|
177
183
|
sessionClient.reuseStatefulProxyClient = true;
|
|
@@ -179,7 +185,14 @@ export class AdtHttpClient {
|
|
|
179
185
|
return await fn(sessionClient);
|
|
180
186
|
}
|
|
181
187
|
finally {
|
|
182
|
-
await sessionClient.closeStatefulSession(
|
|
188
|
+
const closed = await sessionClient.closeStatefulSession(cleanup
|
|
189
|
+
? {
|
|
190
|
+
deadline: Date.now() + cleanup.timeoutMs,
|
|
191
|
+
retryTransientErrors: false,
|
|
192
|
+
noReplay: cleanup.noReplay,
|
|
193
|
+
}
|
|
194
|
+
: undefined);
|
|
195
|
+
cleanup?.onClose(closed);
|
|
183
196
|
try {
|
|
184
197
|
await sessionClient.statefulProxyClient?.close();
|
|
185
198
|
}
|
|
@@ -189,7 +202,7 @@ export class AdtHttpClient {
|
|
|
189
202
|
}
|
|
190
203
|
}
|
|
191
204
|
/** Close the SAP application context without changing the completed operation's result. */
|
|
192
|
-
async closeStatefulSession() {
|
|
205
|
+
async closeStatefulSession(options) {
|
|
193
206
|
const contextId = this.cookieJar.get('sap-contextid');
|
|
194
207
|
if (!contextId || contextId === '0')
|
|
195
208
|
return;
|
|
@@ -198,24 +211,26 @@ export class AdtHttpClient {
|
|
|
198
211
|
this.config.sessionType = 'stateless';
|
|
199
212
|
try {
|
|
200
213
|
try {
|
|
201
|
-
await this.get('/sap/bc/adt/core/http/sessions', { Accept: '*/*', 'sap-adt-purpose': 'close-session', 'sap-contextid': contextId }, { probe: true });
|
|
202
|
-
return;
|
|
214
|
+
await this.get('/sap/bc/adt/core/http/sessions', { Accept: '*/*', 'sap-adt-purpose': 'close-session', 'sap-contextid': contextId }, { probe: true, ...options });
|
|
215
|
+
return true;
|
|
203
216
|
}
|
|
204
217
|
catch (error) {
|
|
205
218
|
if (!(error instanceof AdtApiError && error.statusCode === 404))
|
|
206
219
|
throw error;
|
|
207
220
|
}
|
|
208
221
|
// NW 7.50 lacks the close resource; its stateful-header backport closes on discovery instead.
|
|
209
|
-
await this.head('/sap/bc/adt/core/discovery', { Accept: '*/*' }, { probe: true });
|
|
222
|
+
await this.head('/sap/bc/adt/core/discovery', { Accept: '*/*' }, { probe: true, ...options });
|
|
223
|
+
return true;
|
|
210
224
|
}
|
|
211
225
|
catch (error) {
|
|
212
226
|
// NW 7.50 can return 400 after closing. The reset cookie confirms that cleanup succeeded.
|
|
213
227
|
if (this.cookieJar.get('sap-contextid') === '0')
|
|
214
|
-
return;
|
|
228
|
+
return true;
|
|
215
229
|
// Preserve the write result/error and keep SAP response text out of cleanup warnings.
|
|
216
230
|
logger.warn('Failed to close stateful ADT session.', {
|
|
217
231
|
statusCode: error instanceof AdtApiError ? error.statusCode : undefined,
|
|
218
232
|
});
|
|
233
|
+
return false;
|
|
219
234
|
}
|
|
220
235
|
}
|
|
221
236
|
/** Core request method — wraps requestInner with optional concurrency limiter */
|
|
@@ -236,6 +251,8 @@ export class AdtHttpClient {
|
|
|
236
251
|
}
|
|
237
252
|
if (!options?.signal && options?.deadline === undefined)
|
|
238
253
|
throw error;
|
|
254
|
+
if (options?.signal?.aborted)
|
|
255
|
+
throwIfRequestCancelled(options);
|
|
239
256
|
const cause = error instanceof Error ? error : new Error(String(error));
|
|
240
257
|
throw new AdtNetworkError(cause.message, cause);
|
|
241
258
|
}
|
|
@@ -364,15 +381,13 @@ export class AdtHttpClient {
|
|
|
364
381
|
const url = this.buildUrl(path, sapContextInHeaders);
|
|
365
382
|
const httpStart = Date.now();
|
|
366
383
|
// Per-request guards to prevent infinite retry loops
|
|
367
|
-
const retryTransientErrors = options?.retryTransientErrors !== false;
|
|
384
|
+
const retryTransientErrors = options?.noReplay !== true && options?.retryTransientErrors !== false;
|
|
368
385
|
let negotiationRetried = false;
|
|
369
386
|
let authRetried = false;
|
|
370
387
|
let retried429 = false;
|
|
371
388
|
try {
|
|
372
|
-
let response = await this.doFetch(url, method, headers, wireBody, options);
|
|
389
|
+
let response = await this.doFetch(url, method, headers, wireBody, options, this.config.retryUnauthorized !== false && options?.noReplay !== true);
|
|
373
390
|
let responseBody = await response.text();
|
|
374
|
-
// Persist any Set-Cookie headers from the response
|
|
375
|
-
this.storeCookies(response);
|
|
376
391
|
// Detect broken DB connection on the assigned work process.
|
|
377
392
|
// SAP's ICM routes all requests with the same session cookie to the same
|
|
378
393
|
// work process. If that WP has a broken HANA connection, every request fails
|
|
@@ -414,7 +429,6 @@ export class AdtHttpClient {
|
|
|
414
429
|
}
|
|
415
430
|
const retryResp = await this.doFetch(url, method, headers, wireBody, options);
|
|
416
431
|
const retryBody = await retryResp.text();
|
|
417
|
-
this.storeCookies(retryResp);
|
|
418
432
|
const retryResult = this.handleResponse(retryResp.status, retryResp.headers, retryBody, path, expectBody);
|
|
419
433
|
logger.emitAudit({
|
|
420
434
|
timestamp: new Date().toISOString(),
|
|
@@ -448,9 +462,9 @@ export class AdtHttpClient {
|
|
|
448
462
|
errorBody: `503 Service Unavailable — retrying in ${Math.round(jitterMs)}ms (${source})`,
|
|
449
463
|
});
|
|
450
464
|
await sleepWithinRequestBudget(jitterMs, options);
|
|
465
|
+
this.refreshSessionHeaders(method, headers);
|
|
451
466
|
const retryResp = await this.doFetch(url, method, headers, wireBody, options);
|
|
452
467
|
const retryBody = await retryResp.text();
|
|
453
|
-
this.storeCookies(retryResp);
|
|
454
468
|
logger.emitAudit({
|
|
455
469
|
timestamp: new Date().toISOString(),
|
|
456
470
|
level: retryResp.status === 503 ? 'warn' : 'info',
|
|
@@ -480,9 +494,9 @@ export class AdtHttpClient {
|
|
|
480
494
|
errorBody: `429 Too Many Requests — retrying in ${Math.round(jitterMs)}ms (${source})`,
|
|
481
495
|
});
|
|
482
496
|
await sleepWithinRequestBudget(jitterMs, options);
|
|
497
|
+
this.refreshSessionHeaders(method, headers);
|
|
483
498
|
const retryResp = await this.doFetch(url, method, headers, wireBody, options);
|
|
484
499
|
const retryBody = await retryResp.text();
|
|
485
|
-
this.storeCookies(retryResp);
|
|
486
500
|
logger.emitAudit({
|
|
487
501
|
timestamp: new Date().toISOString(),
|
|
488
502
|
level: retryResp.status === 429 ? 'warn' : 'info',
|
|
@@ -499,7 +513,10 @@ export class AdtHttpClient {
|
|
|
499
513
|
// Uses per-request guard (not instance-level) so concurrent requests each get their own retry.
|
|
500
514
|
// On success, reassigns response/responseBody and falls through to downstream handlers
|
|
501
515
|
// (403 CSRF, 406/415 negotiation) so combined-failure recovery works.
|
|
502
|
-
if (response.status === 401 &&
|
|
516
|
+
if (response.status === 401 &&
|
|
517
|
+
!authRetried &&
|
|
518
|
+
this.config.retryUnauthorized !== false &&
|
|
519
|
+
options?.noReplay !== true) {
|
|
503
520
|
authRetried = true;
|
|
504
521
|
logger.emitAudit({
|
|
505
522
|
timestamp: new Date().toISOString(),
|
|
@@ -511,8 +528,6 @@ export class AdtHttpClient {
|
|
|
511
528
|
durationMs: Date.now() - httpStart,
|
|
512
529
|
errorBody: '401 session expired — resetting session and retrying',
|
|
513
530
|
});
|
|
514
|
-
// Clear session to force fresh authentication
|
|
515
|
-
this.resetSession();
|
|
516
531
|
// Pick up an out-of-band rotated cookie file so the call that observes the
|
|
517
532
|
// expiry can recover; the lazy `cookiesCleared` reload stays the fallback.
|
|
518
533
|
// `cookieFile`, not `isCookieAuthMode()` — cookieString has nothing to re-read,
|
|
@@ -542,7 +557,6 @@ export class AdtHttpClient {
|
|
|
542
557
|
}
|
|
543
558
|
response = await this.doFetch(url, method, headers, wireBody, options);
|
|
544
559
|
responseBody = await response.text();
|
|
545
|
-
this.storeCookies(response);
|
|
546
560
|
logger.emitAudit({
|
|
547
561
|
timestamp: new Date().toISOString(),
|
|
548
562
|
level: 'info',
|
|
@@ -561,7 +575,7 @@ export class AdtHttpClient {
|
|
|
561
575
|
// Fall through to downstream handlers (403/406/415/normal)
|
|
562
576
|
}
|
|
563
577
|
// Handle CSRF token refresh on 403 (modifying requests only)
|
|
564
|
-
if (response.status === 403 && isModifyingMethod(method)) {
|
|
578
|
+
if (options?.noReplay !== true && response.status === 403 && isModifyingMethod(method)) {
|
|
565
579
|
await this.fetchCsrfToken(withoutResponseBudget(options));
|
|
566
580
|
headers['X-CSRF-Token'] = this.csrfToken;
|
|
567
581
|
// Update cookie header after CSRF fetch may have set new cookies. Use the
|
|
@@ -573,12 +587,13 @@ export class AdtHttpClient {
|
|
|
573
587
|
}
|
|
574
588
|
response = await this.doFetch(url, method, headers, wireBody, options);
|
|
575
589
|
responseBody = await response.text();
|
|
576
|
-
this.storeCookies(response);
|
|
577
590
|
// A refreshed session can still need legacy content negotiation below (#996).
|
|
578
591
|
}
|
|
579
592
|
// Retry negotiation once, including the narrowly matched NW 7.40 handler error.
|
|
580
593
|
const legacyFallback = legacyContentHandlerFallback(response.status, responseBody, method, negotiationKey, headers);
|
|
581
|
-
if (
|
|
594
|
+
if (options?.noReplay !== true &&
|
|
595
|
+
(response.status === 406 || response.status === 415 || legacyFallback) &&
|
|
596
|
+
!negotiationRetried) {
|
|
582
597
|
negotiationRetried = true;
|
|
583
598
|
const fallbackHeaders = { ...headers };
|
|
584
599
|
let headersChanged = false;
|
|
@@ -621,14 +636,9 @@ export class AdtHttpClient {
|
|
|
621
636
|
durationMs: Date.now() - httpStart,
|
|
622
637
|
errorBody: `Content negotiation ${response.status} — retrying with fallback headers`,
|
|
623
638
|
});
|
|
639
|
+
this.refreshSessionHeaders(method, fallbackHeaders);
|
|
624
640
|
const retryResp = await this.doFetch(url, method, fallbackHeaders, wireBody, options);
|
|
625
641
|
const retryBody = await retryResp.text();
|
|
626
|
-
this.storeCookies(retryResp);
|
|
627
|
-
// Store CSRF token from retry response
|
|
628
|
-
const retryToken = retryResp.headers.get('x-csrf-token');
|
|
629
|
-
if (retryToken && retryToken !== 'Required') {
|
|
630
|
-
this.csrfToken = retryToken;
|
|
631
|
-
}
|
|
632
642
|
const retryResult = this.handleResponse(retryResp.status, retryResp.headers, retryBody, path, expectBody);
|
|
633
643
|
const currentContentType = contentType ?? headers['Content-Type'];
|
|
634
644
|
const negotiated = {};
|
|
@@ -656,11 +666,6 @@ export class AdtHttpClient {
|
|
|
656
666
|
}
|
|
657
667
|
// No meaningful header change — fall through to normal error handling
|
|
658
668
|
}
|
|
659
|
-
// Store CSRF token from response
|
|
660
|
-
const responseToken = response.headers.get('x-csrf-token');
|
|
661
|
-
if (responseToken && responseToken !== 'Required') {
|
|
662
|
-
this.csrfToken = responseToken;
|
|
663
|
-
}
|
|
664
669
|
const result = this.handleResponse(response.status, response.headers, responseBody, path, expectBody);
|
|
665
670
|
logger.emitAudit({
|
|
666
671
|
timestamp: new Date().toISOString(),
|
|
@@ -711,6 +716,8 @@ export class AdtHttpClient {
|
|
|
711
716
|
}
|
|
712
717
|
if (err instanceof AdtNetworkError || err instanceof AdtResponseLimitError)
|
|
713
718
|
throw err;
|
|
719
|
+
if (options?.signal?.aborted)
|
|
720
|
+
throwIfRequestCancelled(options);
|
|
714
721
|
const message = err instanceof Error ? err.message : String(err);
|
|
715
722
|
throw new AdtNetworkError(message, err instanceof Error ? err : undefined);
|
|
716
723
|
}
|
|
@@ -818,12 +825,6 @@ export class AdtHttpClient {
|
|
|
818
825
|
if (this.cookiesCleared && this.isCookieAuthMode()) {
|
|
819
826
|
this.reloadCookiesFromSource();
|
|
820
827
|
}
|
|
821
|
-
// A token only counts as proof when the response itself succeeded: SAP returns one on
|
|
822
|
-
// failures too, and the old code accepted a token from a 401 — masking the auth error.
|
|
823
|
-
const usableToken = (response) => {
|
|
824
|
-
const token = response.headers.get('x-csrf-token')?.trim();
|
|
825
|
-
return response.ok && token && token.toLowerCase() !== 'required' ? token : undefined;
|
|
826
|
-
};
|
|
827
828
|
// Every probe keeps the same identity, current cookies and caller's request budget.
|
|
828
829
|
const probe = async (method) => {
|
|
829
830
|
const cookieHeader = this.composeCookieHeader();
|
|
@@ -833,7 +834,6 @@ export class AdtHttpClient {
|
|
|
833
834
|
delete headers.Cookie;
|
|
834
835
|
const started = Date.now();
|
|
835
836
|
const response = await this.doFetch(this.buildUrl(path), method, headers, undefined, options);
|
|
836
|
-
this.storeCookies(response);
|
|
837
837
|
logger.emitAudit({
|
|
838
838
|
timestamp: new Date().toISOString(),
|
|
839
839
|
level: 'debug',
|
|
@@ -842,11 +842,11 @@ export class AdtHttpClient {
|
|
|
842
842
|
path,
|
|
843
843
|
statusCode: response.status,
|
|
844
844
|
durationMs: Date.now() - started,
|
|
845
|
-
success: !!
|
|
846
|
-
adtMode: this.config.sessionType ?? 'unspecified',
|
|
847
|
-
hasContext: /(?:^|;\s*)sap-contextid=/.test(
|
|
845
|
+
success: !!usableCsrfToken(response),
|
|
846
|
+
adtMode: headers['X-sap-adt-sessiontype'] === undefined ? 'unspecified' : (this.config.sessionType ?? 'unspecified'),
|
|
847
|
+
hasContext: /(?:^|;\s*)sap-contextid=/.test(headers.Cookie ?? ''),
|
|
848
848
|
});
|
|
849
|
-
if (method === 'GET' && response.status === 200 && !
|
|
849
|
+
if (method === 'GET' && response.status === 200 && !usableCsrfToken(response)) {
|
|
850
850
|
// Check login pages, but preserve the header-only legacy discovery fallback.
|
|
851
851
|
this.handleResponse(response.status, response.headers, await response.text(), path, false);
|
|
852
852
|
}
|
|
@@ -877,16 +877,23 @@ export class AdtHttpClient {
|
|
|
877
877
|
// The tested 7.50/7.58/8.16 systems returned HEAD 400 with a token; GET returned 200.
|
|
878
878
|
// Retry these HEAD refusals or a successful response without a token on the same path.
|
|
879
879
|
// A successful HEAD with a usable token remains the one-request fast path.
|
|
880
|
-
|
|
880
|
+
const headStatus = response.status;
|
|
881
|
+
if ([400, 403, 405].includes(response.status) || (response.ok && !usableCsrfToken(response))) {
|
|
882
|
+
response = await probe('GET');
|
|
883
|
+
}
|
|
884
|
+
// Both stateful discovery probes can fail on affected routes (#1044). Bootstrap once
|
|
885
|
+
// without that header; keep identity/cookies and subsequent lock/write requests stateful.
|
|
886
|
+
if (this.config.sessionType === 'stateful' && headStatus === 400 && response.status === 400) {
|
|
887
|
+
delete headers['X-sap-adt-sessiontype'];
|
|
881
888
|
response = await probe('GET');
|
|
882
889
|
}
|
|
883
890
|
// Older ADT handlers may return an empty 200 for a missing core resource (#817).
|
|
884
891
|
// A real auth/server failure must not be hidden by trying another endpoint.
|
|
885
|
-
if (response.status === 404 || response.status === 405 || (response.ok && !
|
|
892
|
+
if (response.status === 404 || response.status === 405 || (response.ok && !usableCsrfToken(response))) {
|
|
886
893
|
path = '/sap/bc/adt/discovery';
|
|
887
894
|
response = await probe('GET');
|
|
888
895
|
}
|
|
889
|
-
const token =
|
|
896
|
+
const token = usableCsrfToken(response);
|
|
890
897
|
if (!token) {
|
|
891
898
|
if (response.status === 401) {
|
|
892
899
|
if (this.isCookieAuthMode())
|
|
@@ -899,12 +906,13 @@ export class AdtHttpClient {
|
|
|
899
906
|
}
|
|
900
907
|
throw new AdtApiError(`No CSRF token in response (HTTP ${response.status})`, response.status, path);
|
|
901
908
|
}
|
|
902
|
-
this.csrfToken = token;
|
|
903
909
|
return path;
|
|
904
910
|
}
|
|
905
911
|
catch (err) {
|
|
906
912
|
if (err instanceof AdtApiError || err instanceof AdtNetworkError)
|
|
907
913
|
throw err;
|
|
914
|
+
if (options?.signal?.aborted)
|
|
915
|
+
throwIfRequestCancelled(options);
|
|
908
916
|
const message = err instanceof Error ? err.message : String(err);
|
|
909
917
|
throw new AdtNetworkError(`CSRF token fetch failed: ${message}`, err instanceof Error ? err : undefined);
|
|
910
918
|
}
|
|
@@ -943,8 +951,7 @@ export class AdtHttpClient {
|
|
|
943
951
|
*/
|
|
944
952
|
clearCookiesAndMark() {
|
|
945
953
|
this.config.cookies = {};
|
|
946
|
-
this.
|
|
947
|
-
this.csrfToken = '';
|
|
954
|
+
this.resetSession();
|
|
948
955
|
this.cookiesCleared = true;
|
|
949
956
|
}
|
|
950
957
|
/**
|
|
@@ -993,6 +1000,7 @@ export class AdtHttpClient {
|
|
|
993
1000
|
resetSession() {
|
|
994
1001
|
this.cookieJar.clear();
|
|
995
1002
|
this.csrfToken = '';
|
|
1003
|
+
this.securitySessionGate.reset();
|
|
996
1004
|
}
|
|
997
1005
|
/**
|
|
998
1006
|
* Build the `Cookie` request header by merging the static `config.cookies`
|
|
@@ -1025,11 +1033,18 @@ export class AdtHttpClient {
|
|
|
1025
1033
|
return undefined;
|
|
1026
1034
|
return Array.from(merged, ([name, value]) => `${name}=${value}`).join('; ');
|
|
1027
1035
|
}
|
|
1036
|
+
/** Read the current pair together after a wait or a response that can rotate the session. */
|
|
1037
|
+
refreshSessionHeaders(method, headers) {
|
|
1038
|
+
const cookie = this.composeCookieHeader();
|
|
1039
|
+
if (cookie)
|
|
1040
|
+
headers.Cookie = cookie;
|
|
1041
|
+
else
|
|
1042
|
+
delete headers.Cookie;
|
|
1043
|
+
if (isModifyingMethod(method))
|
|
1044
|
+
headers['X-CSRF-Token'] = this.csrfToken;
|
|
1045
|
+
}
|
|
1028
1046
|
storeCookies(response) {
|
|
1029
|
-
const
|
|
1030
|
-
if (!setCookieHeaders || setCookieHeaders.length === 0)
|
|
1031
|
-
return;
|
|
1032
|
-
for (const cookie of setCookieHeaders) {
|
|
1047
|
+
for (const cookie of response.headers.getSetCookie() ?? []) {
|
|
1033
1048
|
// Set-Cookie: name=value; Path=/; HttpOnly; ...
|
|
1034
1049
|
const nameValue = cookie.split(';')[0];
|
|
1035
1050
|
if (!nameValue)
|
|
@@ -1090,7 +1105,37 @@ export class AdtHttpClient {
|
|
|
1090
1105
|
* Node 22's built-in fetch embeds an older undici version whose dispatcher
|
|
1091
1106
|
* interface is incompatible with npm undici@8 Agent/ProxyAgent instances.
|
|
1092
1107
|
*/
|
|
1093
|
-
async doFetch(url, method, headers, body, options) {
|
|
1108
|
+
async doFetch(url, method, headers, body, options, resetOnUnauthorized = false) {
|
|
1109
|
+
const cookieNames = () => [...this.cookieJar.keys(), ...Object.keys(this.config.cookies ?? {})];
|
|
1110
|
+
const response = await this.securitySessionGate.run(() => hasSecuritySessionCookie(cookieNames()), async (waited) => {
|
|
1111
|
+
const generation = this.securitySessionGate.generation;
|
|
1112
|
+
if (waited) {
|
|
1113
|
+
// A preceding response may have replaced both fields while this send waited.
|
|
1114
|
+
// Keep the refreshed pair for this logical request's negotiation/retry paths too.
|
|
1115
|
+
this.refreshSessionHeaders(method, headers);
|
|
1116
|
+
}
|
|
1117
|
+
const response = await this.sendRequest(url, method, headers, body, options);
|
|
1118
|
+
const fetchingCsrf = headers['X-CSRF-Token']?.toLowerCase() === 'fetch';
|
|
1119
|
+
if (generation === this.securitySessionGate.generation) {
|
|
1120
|
+
// Store once at the headers boundary, before releasing the bootstrap owner.
|
|
1121
|
+
this.storeCookies(response);
|
|
1122
|
+
// Ordinary responses (including 404) can carry a token without proving a Fetch probe succeeded.
|
|
1123
|
+
const token = fetchingCsrf ? usableCsrfToken(response) : response.headers.get('x-csrf-token');
|
|
1124
|
+
if (token && token !== 'Required')
|
|
1125
|
+
this.csrfToken = token;
|
|
1126
|
+
// Reset before releasing waiters; a slow 401 body must not clear their new session.
|
|
1127
|
+
if (resetOnUnauthorized && response.status === 401)
|
|
1128
|
+
this.resetSession();
|
|
1129
|
+
}
|
|
1130
|
+
else if (fetchingCsrf) {
|
|
1131
|
+
await response.body?.cancel();
|
|
1132
|
+
throw new AdtNetworkError('SAP session changed during CSRF bootstrap. Retry the request.');
|
|
1133
|
+
}
|
|
1134
|
+
return response;
|
|
1135
|
+
}, options);
|
|
1136
|
+
return prepareBoundedResponse(response, url, options);
|
|
1137
|
+
}
|
|
1138
|
+
async sendRequest(url, method, headers, body, options) {
|
|
1094
1139
|
throwIfRequestCancelled(options);
|
|
1095
1140
|
// Forward the caller's W3C trace context to SAP so one trace spans agent → ARC-1 → SAP.
|
|
1096
1141
|
// Empty unless the MCP client sent a valid `traceparent`; ARC-1 never originates a trace.
|
|
@@ -1117,7 +1162,7 @@ export class AdtHttpClient {
|
|
|
1117
1162
|
...(dispatcher ? { dispatcher } : {}),
|
|
1118
1163
|
}, options?.attemptBudget));
|
|
1119
1164
|
}
|
|
1120
|
-
return
|
|
1165
|
+
return response;
|
|
1121
1166
|
}
|
|
1122
1167
|
/**
|
|
1123
1168
|
* Execute an HTTP request through the BTP connectivity proxy using standard
|
|
@@ -1201,41 +1246,10 @@ export class AdtHttpClient {
|
|
|
1201
1246
|
function isModifyingMethod(method) {
|
|
1202
1247
|
return ['POST', 'PUT', 'DELETE', 'PATCH'].includes(method.toUpperCase());
|
|
1203
1248
|
}
|
|
1204
|
-
/**
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
* - delta-seconds (e.g. `"5"`)
|
|
1209
|
-
* - HTTP-date (e.g. `"Wed, 12 May 2026 14:30:00 GMT"`)
|
|
1210
|
-
*
|
|
1211
|
-
* On missing, NaN, malformed, past-date, or negative input, falls back to `fallbackMs`.
|
|
1212
|
-
* Always clamps to `[0, 60_000]` ms so a misbehaving gateway can't stall us indefinitely
|
|
1213
|
-
* and a too-small/past value can't degenerate into a hot retry loop.
|
|
1214
|
-
*
|
|
1215
|
-
* Returns `{ delayMs, source }` — `source: 'header' | 'fallback'` lets audit events
|
|
1216
|
-
* record whether the delay came from the server or from our jitter floor (useful for
|
|
1217
|
-
* capacity tuning).
|
|
1218
|
-
*/
|
|
1219
|
-
export function parseRetryAfter(header, fallbackMs) {
|
|
1220
|
-
const clamp = (ms) => Math.max(0, Math.min(60_000, ms));
|
|
1221
|
-
if (header == null || header === '') {
|
|
1222
|
-
return { delayMs: clamp(fallbackMs), source: 'fallback' };
|
|
1223
|
-
}
|
|
1224
|
-
const trimmed = header.trim();
|
|
1225
|
-
// delta-seconds form: integer (and only digits, optionally signed)
|
|
1226
|
-
if (/^-?\d+$/.test(trimmed)) {
|
|
1227
|
-
const seconds = Number.parseInt(trimmed, 10);
|
|
1228
|
-
if (Number.isFinite(seconds)) {
|
|
1229
|
-
return { delayMs: clamp(seconds * 1000), source: 'header' };
|
|
1230
|
-
}
|
|
1231
|
-
return { delayMs: clamp(fallbackMs), source: 'fallback' };
|
|
1232
|
-
}
|
|
1233
|
-
// HTTP-date form
|
|
1234
|
-
const epochMs = Date.parse(trimmed);
|
|
1235
|
-
if (Number.isFinite(epochMs)) {
|
|
1236
|
-
return { delayMs: clamp(epochMs - Date.now()), source: 'header' };
|
|
1237
|
-
}
|
|
1238
|
-
return { delayMs: clamp(fallbackMs), source: 'fallback' };
|
|
1249
|
+
/** A token on an unsuccessful response is not proof of authentication. */
|
|
1250
|
+
function usableCsrfToken(response) {
|
|
1251
|
+
const token = response.headers.get('x-csrf-token')?.trim();
|
|
1252
|
+
return response.ok && token && token.toLowerCase() !== 'required' ? token : undefined;
|
|
1239
1253
|
}
|
|
1240
1254
|
/**
|
|
1241
1255
|
* Distinguish a real SAP logon page from a legitimate HTML response payload.
|