reqon-dsl 0.2.0 → 0.4.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/CHANGELOG.md +7 -0
- package/README.md +45 -3
- package/dist/ast/nodes.d.ts +91 -4
- package/dist/ast/nodes.js +14 -0
- package/dist/auth/circuit-breaker.d.ts +11 -0
- package/dist/auth/circuit-breaker.js +90 -18
- package/dist/auth/credentials.d.ts +6 -1
- package/dist/auth/credentials.js +12 -4
- package/dist/auth/oauth2-provider.js +13 -3
- package/dist/auth/rate-limiter.d.ts +12 -1
- package/dist/auth/rate-limiter.js +39 -26
- package/dist/auth/token-store.js +8 -1
- package/dist/cli.d.ts +24 -1
- package/dist/cli.js +149 -10
- package/dist/config/constants.d.ts +152 -0
- package/dist/config/constants.js +139 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.js +4 -0
- package/dist/control/index.d.ts +2 -0
- package/dist/control/index.js +1 -0
- package/dist/control/server.d.ts +105 -0
- package/dist/control/server.js +315 -0
- package/dist/control/types.d.ts +61 -0
- package/dist/control/types.js +7 -0
- package/dist/debug/cli-debugger.d.ts +17 -0
- package/dist/debug/cli-debugger.js +185 -0
- package/dist/debug/controller.d.ts +94 -0
- package/dist/debug/controller.js +45 -0
- package/dist/debug/index.d.ts +6 -0
- package/dist/debug/index.js +5 -0
- package/dist/errors/index.d.ts +67 -0
- package/dist/errors/index.js +89 -1
- package/dist/execution/index.d.ts +1 -1
- package/dist/execution/state.d.ts +24 -0
- package/dist/execution/store.js +2 -2
- package/dist/execution-log/events.d.ts +125 -0
- package/dist/execution-log/events.js +17 -0
- package/dist/execution-log/fold.d.ts +38 -0
- package/dist/execution-log/fold.js +54 -0
- package/dist/execution-log/index.d.ts +18 -0
- package/dist/execution-log/index.js +6 -0
- package/dist/execution-log/postgres-store.d.ts +36 -0
- package/dist/execution-log/postgres-store.js +108 -0
- package/dist/execution-log/resume.d.ts +11 -0
- package/dist/execution-log/resume.js +5 -0
- package/dist/execution-log/sqlite-store.d.ts +16 -0
- package/dist/execution-log/sqlite-store.js +101 -0
- package/dist/execution-log/store.d.ts +72 -0
- package/dist/execution-log/store.js +182 -0
- package/dist/index.d.ts +23 -2
- package/dist/index.js +35 -3
- package/dist/interpreter/context.d.ts +29 -0
- package/dist/interpreter/context.js +18 -0
- package/dist/interpreter/evaluator.d.ts +63 -1
- package/dist/interpreter/evaluator.js +219 -42
- package/dist/interpreter/executor.d.ts +132 -14
- package/dist/interpreter/executor.js +883 -178
- package/dist/interpreter/fetch-handler.d.ts +48 -1
- package/dist/interpreter/fetch-handler.js +216 -38
- package/dist/interpreter/http.d.ts +34 -0
- package/dist/interpreter/http.js +203 -28
- package/dist/interpreter/index.d.ts +5 -3
- package/dist/interpreter/index.js +4 -2
- package/dist/interpreter/pagination.d.ts +12 -3
- package/dist/interpreter/pagination.js +102 -32
- package/dist/interpreter/signals.d.ts +8 -0
- package/dist/interpreter/signals.js +12 -0
- package/dist/interpreter/source-manager.d.ts +75 -0
- package/dist/interpreter/source-manager.js +157 -0
- package/dist/interpreter/step-handlers/apply-handler.d.ts +29 -0
- package/dist/interpreter/step-handlers/apply-handler.js +79 -0
- package/dist/interpreter/step-handlers/for-handler.d.ts +16 -0
- package/dist/interpreter/step-handlers/for-handler.js +89 -7
- package/dist/interpreter/step-handlers/index.d.ts +4 -2
- package/dist/interpreter/step-handlers/index.js +4 -2
- package/dist/interpreter/step-handlers/match-handler.d.ts +9 -0
- package/dist/interpreter/step-handlers/match-handler.js +47 -17
- package/dist/interpreter/step-handlers/pause-handler.d.ts +52 -0
- package/dist/interpreter/step-handlers/pause-handler.js +87 -0
- package/dist/interpreter/step-handlers/store-handler.d.ts +17 -1
- package/dist/interpreter/step-handlers/store-handler.js +61 -20
- package/dist/interpreter/step-handlers/types.d.ts +3 -0
- package/dist/interpreter/step-handlers/validate-handler.d.ts +2 -1
- package/dist/interpreter/step-handlers/validate-handler.js +7 -2
- package/dist/interpreter/step-handlers/webhook-handler.d.ts +4 -0
- package/dist/interpreter/step-handlers/webhook-handler.js +31 -5
- package/dist/interpreter/store-manager.d.ts +46 -0
- package/dist/interpreter/store-manager.js +70 -0
- package/dist/lexer/index.d.ts +11 -4
- package/dist/lexer/index.js +11 -4
- package/dist/lexer/tokens.d.ts +17 -1
- package/dist/lexer/tokens.js +36 -0
- package/dist/loader/index.js +5 -8
- package/dist/mcp/index.d.ts +11 -0
- package/dist/mcp/index.js +11 -0
- package/dist/mcp/sandbox.d.ts +41 -0
- package/dist/mcp/sandbox.js +76 -0
- package/dist/mcp/server.d.ts +17 -0
- package/dist/mcp/server.js +504 -0
- package/dist/oas/index.d.ts +2 -0
- package/dist/oas/index.js +1 -0
- package/dist/oas/loader.d.ts +13 -1
- package/dist/oas/loader.js +25 -3
- package/dist/oas/mock-generator.d.ts +12 -0
- package/dist/oas/mock-generator.js +196 -0
- package/dist/oas/validator.js +45 -5
- package/dist/observability/events.d.ts +248 -0
- package/dist/observability/events.js +85 -0
- package/dist/observability/index.d.ts +15 -0
- package/dist/observability/index.js +12 -0
- package/dist/observability/logger.d.ts +106 -0
- package/dist/observability/logger.js +266 -0
- package/dist/observability/otel.d.ts +143 -0
- package/dist/observability/otel.js +421 -0
- package/dist/parser/action-parser.d.ts +105 -0
- package/dist/parser/action-parser.js +645 -0
- package/dist/parser/base.d.ts +7 -0
- package/dist/parser/base.js +11 -0
- package/dist/parser/expressions.d.ts +14 -0
- package/dist/parser/expressions.js +89 -6
- package/dist/parser/fetch-parser.d.ts +27 -0
- package/dist/parser/fetch-parser.js +280 -0
- package/dist/parser/index.d.ts +17 -0
- package/dist/parser/index.js +17 -0
- package/dist/parser/parser.d.ts +44 -46
- package/dist/parser/parser.js +122 -1070
- package/dist/parser/pipeline-parser.d.ts +12 -0
- package/dist/parser/pipeline-parser.js +52 -0
- package/dist/parser/schedule-parser.d.ts +7 -0
- package/dist/parser/schedule-parser.js +137 -0
- package/dist/parser/source-parser.d.ts +9 -0
- package/dist/parser/source-parser.js +151 -0
- package/dist/pause/index.d.ts +15 -0
- package/dist/pause/index.js +12 -0
- package/dist/pause/log-store.d.ts +33 -0
- package/dist/pause/log-store.js +98 -0
- package/dist/pause/manager.d.ts +130 -0
- package/dist/pause/manager.js +294 -0
- package/dist/pause/state.d.ts +93 -0
- package/dist/pause/state.js +103 -0
- package/dist/pause/store.d.ts +61 -0
- package/dist/pause/store.js +158 -0
- package/dist/plugin.d.ts +9 -12
- package/dist/plugin.js +10 -13
- package/dist/scheduler/cron-parser.d.ts +10 -3
- package/dist/scheduler/cron-parser.js +227 -48
- package/dist/scheduler/scheduler.js +56 -22
- package/dist/stores/factory.d.ts +7 -1
- package/dist/stores/factory.js +14 -3
- package/dist/stores/file.d.ts +26 -0
- package/dist/stores/file.js +67 -21
- package/dist/stores/index.d.ts +16 -1
- package/dist/stores/index.js +16 -1
- package/dist/stores/memory.d.ts +4 -0
- package/dist/stores/memory.js +8 -6
- package/dist/stores/postgrest.d.ts +28 -0
- package/dist/stores/postgrest.js +84 -37
- package/dist/stores/types.d.ts +17 -0
- package/dist/stores/types.js +12 -0
- package/dist/sync/index.d.ts +3 -2
- package/dist/sync/index.js +2 -1
- package/dist/sync/log-store.d.ts +30 -0
- package/dist/sync/log-store.js +45 -0
- package/dist/sync/store.js +1 -1
- package/dist/trace/index.d.ts +18 -0
- package/dist/trace/index.js +13 -0
- package/dist/trace/log-view.d.ts +57 -0
- package/dist/trace/log-view.js +76 -0
- package/dist/trace/recorder.d.ts +75 -0
- package/dist/trace/recorder.js +157 -0
- package/dist/trace/replay.d.ts +132 -0
- package/dist/trace/replay.js +264 -0
- package/dist/trace/state.d.ts +102 -0
- package/dist/trace/state.js +86 -0
- package/dist/trace/store.d.ts +75 -0
- package/dist/trace/store.js +250 -0
- package/dist/utils/deep-merge.d.ts +10 -0
- package/dist/utils/deep-merge.js +23 -0
- package/dist/utils/file.d.ts +13 -4
- package/dist/utils/file.js +70 -12
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/long-timeout.d.ts +19 -0
- package/dist/utils/long-timeout.js +33 -0
- package/dist/utils/path.d.ts +22 -1
- package/dist/utils/path.js +46 -1
- package/dist/utils/redact.d.ts +22 -0
- package/dist/utils/redact.js +42 -0
- package/dist/utils/type-guards.d.ts +58 -0
- package/dist/utils/type-guards.js +92 -0
- package/dist/webhook/server.d.ts +9 -0
- package/dist/webhook/server.js +122 -36
- package/dist/webhook/types.d.ts +9 -1
- package/package.json +76 -9
- package/.claude/settings.local.json +0 -31
- package/.claude/skills/api-integration.md +0 -125
- package/.claude/skills/database-schema.md +0 -51
- package/.claude/skills/dsl-design.md +0 -80
- package/.claude/skills/property-testing.md +0 -143
- package/.claude/skills/reqon/SKILL.md +0 -44
- package/.claude/skills/reqon/references/examples.md +0 -206
- package/.claude/skills/reqon/references/syntax.md +0 -263
- package/.claude/skills/vscode-extension.md +0 -113
- package/.github/dependabot.yml +0 -32
- package/.github/pull_request_template.md +0 -21
- package/.github/workflows/ci.yml +0 -174
- package/.github/workflows/release.yml +0 -73
- package/CLAUDE.md +0 -72
- package/CONTRIBUTING.md +0 -161
- package/TODO.md +0 -51
- package/dist/auth/auth.test.d.ts +0 -1
- package/dist/auth/auth.test.js +0 -255
- package/dist/errors/errors.test.d.ts +0 -1
- package/dist/errors/errors.test.js +0 -165
- package/dist/execution/execution.test.d.ts +0 -1
- package/dist/execution/execution.test.js +0 -246
- package/dist/integration.test.d.ts +0 -1
- package/dist/integration.test.js +0 -168
- package/dist/interpreter/evaluator.test.d.ts +0 -1
- package/dist/interpreter/evaluator.test.js +0 -512
- package/dist/interpreter/http.test.d.ts +0 -1
- package/dist/interpreter/http.test.js +0 -299
- package/dist/interpreter/progress.test.d.ts +0 -1
- package/dist/interpreter/progress.test.js +0 -216
- package/dist/interpreter/schema-matcher.test.d.ts +0 -1
- package/dist/interpreter/schema-matcher.test.js +0 -122
- package/dist/lexer/lexer.d.ts +0 -24
- package/dist/lexer/lexer.js +0 -264
- package/dist/lexer/lexer.test.d.ts +0 -1
- package/dist/lexer/lexer.test.js +0 -259
- package/dist/loader/loader.test.d.ts +0 -1
- package/dist/loader/loader.test.js +0 -287
- package/dist/oas/oas.test.d.ts +0 -1
- package/dist/oas/oas.test.js +0 -218
- package/dist/parser/expressions.test.d.ts +0 -1
- package/dist/parser/expressions.test.js +0 -378
- package/dist/parser/match.test.d.ts +0 -1
- package/dist/parser/match.test.js +0 -254
- package/dist/parser/parser.test.d.ts +0 -1
- package/dist/parser/parser.test.js +0 -333
- package/dist/parser/schedule.test.d.ts +0 -1
- package/dist/parser/schedule.test.js +0 -241
- package/dist/scheduler/cron-parser.test.d.ts +0 -1
- package/dist/scheduler/cron-parser.test.js +0 -188
- package/dist/stores/file.test.d.ts +0 -1
- package/dist/stores/file.test.js +0 -165
- package/dist/stores/memory.test.d.ts +0 -1
- package/dist/stores/memory.test.js +0 -157
- package/dist/stores/stores.test.d.ts +0 -1
- package/dist/stores/stores.test.js +0 -158
- package/dist/sync/sync.test.d.ts +0 -1
- package/dist/sync/sync.test.js +0 -221
- package/docusaurus/README.md +0 -41
- package/docusaurus/docs/advanced/execution-state.md +0 -283
- package/docusaurus/docs/advanced/extending-reqon.md +0 -388
- package/docusaurus/docs/advanced/multi-file-missions.md +0 -250
- package/docusaurus/docs/advanced/parallel-execution.md +0 -353
- package/docusaurus/docs/api-reference.md +0 -443
- package/docusaurus/docs/authentication/api-key.md +0 -339
- package/docusaurus/docs/authentication/basic.md +0 -276
- package/docusaurus/docs/authentication/bearer.md +0 -282
- package/docusaurus/docs/authentication/oauth2.md +0 -317
- package/docusaurus/docs/authentication/overview.md +0 -251
- package/docusaurus/docs/cli.md +0 -229
- package/docusaurus/docs/core-concepts/actions.md +0 -286
- package/docusaurus/docs/core-concepts/missions.md +0 -264
- package/docusaurus/docs/core-concepts/schemas.md +0 -353
- package/docusaurus/docs/core-concepts/sources.md +0 -339
- package/docusaurus/docs/core-concepts/stores.md +0 -332
- package/docusaurus/docs/dsl-syntax/expressions.md +0 -361
- package/docusaurus/docs/dsl-syntax/fetch.md +0 -293
- package/docusaurus/docs/dsl-syntax/for-loops.md +0 -324
- package/docusaurus/docs/dsl-syntax/map.md +0 -345
- package/docusaurus/docs/dsl-syntax/match.md +0 -387
- package/docusaurus/docs/dsl-syntax/pipelines.md +0 -397
- package/docusaurus/docs/dsl-syntax/validate.md +0 -401
- package/docusaurus/docs/error-handling/dead-letter-queues.md +0 -399
- package/docusaurus/docs/error-handling/flow-control.md +0 -337
- package/docusaurus/docs/error-handling/retry-strategies.md +0 -368
- package/docusaurus/docs/examples.md +0 -488
- package/docusaurus/docs/getting-started.md +0 -256
- package/docusaurus/docs/http/circuit-breaker.md +0 -401
- package/docusaurus/docs/http/incremental-sync.md +0 -394
- package/docusaurus/docs/http/pagination.md +0 -361
- package/docusaurus/docs/http/rate-limiting.md +0 -383
- package/docusaurus/docs/http/requests.md +0 -328
- package/docusaurus/docs/http/retry.md +0 -402
- package/docusaurus/docs/intro.md +0 -90
- package/docusaurus/docs/openapi/loading-specs.md +0 -305
- package/docusaurus/docs/openapi/operation-calls.md +0 -314
- package/docusaurus/docs/openapi/overview.md +0 -212
- package/docusaurus/docs/openapi/response-validation.md +0 -344
- package/docusaurus/docs/scheduling/cron.md +0 -305
- package/docusaurus/docs/scheduling/daemon-mode.md +0 -317
- package/docusaurus/docs/scheduling/intervals.md +0 -289
- package/docusaurus/docs/scheduling/overview.md +0 -231
- package/docusaurus/docs/stores/custom-adapters.md +0 -376
- package/docusaurus/docs/stores/file.md +0 -236
- package/docusaurus/docs/stores/memory.md +0 -193
- package/docusaurus/docs/stores/overview.md +0 -274
- package/docusaurus/docs/stores/postgrest.md +0 -316
- package/docusaurus/docusaurus.config.ts +0 -148
- package/docusaurus/package-lock.json +0 -18029
- package/docusaurus/package.json +0 -47
- package/docusaurus/sidebars.ts +0 -155
- package/docusaurus/src/components/HomepageFeatures/index.tsx +0 -105
- package/docusaurus/src/components/HomepageFeatures/styles.module.css +0 -12
- package/docusaurus/src/css/custom.css +0 -169
- package/docusaurus/src/pages/index.module.css +0 -48
- package/docusaurus/src/pages/index.tsx +0 -110
- package/docusaurus/src/pages/markdown-page.md +0 -7
- package/docusaurus/static/.nojekyll +0 -0
- package/docusaurus/static/img/docusaurus-social-card.jpg +0 -0
- package/docusaurus/static/img/docusaurus.png +0 -0
- package/docusaurus/static/img/favicon.ico +0 -0
- package/docusaurus/static/img/logo.svg +0 -10
- package/docusaurus/static/img/undraw_docusaurus_mountain.svg +0 -171
- package/docusaurus/static/img/undraw_docusaurus_react.svg +0 -170
- package/docusaurus/static/img/undraw_docusaurus_tree.svg +0 -40
- package/docusaurus/tsconfig.json +0 -8
- package/examples/README.md +0 -112
- package/examples/error-handling/README.md +0 -150
- package/examples/error-handling/payment-processor.vague +0 -287
- package/examples/github-sync/README.md +0 -74
- package/examples/github-sync/fetch-issues.vague +0 -47
- package/examples/github-sync/fetch-prs.vague +0 -40
- package/examples/github-sync/mission.vague +0 -101
- package/examples/github-sync/normalize.vague +0 -70
- package/examples/jsonplaceholder/README.md +0 -28
- package/examples/jsonplaceholder/posts.vague +0 -48
- package/examples/petstore/README.md +0 -35
- package/examples/petstore/openapi.yaml +0 -97
- package/examples/petstore/sync.vague +0 -52
- package/examples/temporal-comparison/README.md +0 -297
- package/examples/temporal-comparison/reconciliation.vague +0 -355
- package/examples/temporal-comparison/temporal/activities/index.ts +0 -8
- package/examples/temporal-comparison/temporal/activities/shipstation.ts +0 -225
- package/examples/temporal-comparison/temporal/activities/shopify.ts +0 -257
- package/examples/temporal-comparison/temporal/activities/storage.ts +0 -198
- package/examples/temporal-comparison/temporal/activities/stripe.ts +0 -169
- package/examples/temporal-comparison/temporal/activities/validation.ts +0 -205
- package/examples/temporal-comparison/temporal/client/schedule.ts +0 -218
- package/examples/temporal-comparison/temporal/config/retry.ts +0 -63
- package/examples/temporal-comparison/temporal/types/index.ts +0 -129
- package/examples/temporal-comparison/temporal/workers/main.ts +0 -130
- package/examples/temporal-comparison/temporal/workflows/orderReconciliation.ts +0 -262
- package/examples/xero/README.md +0 -88
- package/examples/xero/invoices.vague +0 -189
- package/src/api-integration.test.ts +0 -954
- package/src/ast/index.ts +0 -1
- package/src/ast/nodes.ts +0 -310
- package/src/auth/auth.test.ts +0 -326
- package/src/auth/circuit-breaker.test.ts +0 -390
- package/src/auth/circuit-breaker.ts +0 -379
- package/src/auth/credentials.test.ts +0 -273
- package/src/auth/credentials.ts +0 -246
- package/src/auth/index.ts +0 -40
- package/src/auth/oauth2-provider.ts +0 -177
- package/src/auth/rate-limiter.ts +0 -459
- package/src/auth/token-store.ts +0 -177
- package/src/auth/types.ts +0 -159
- package/src/benchmark/e2e.bench.ts +0 -288
- package/src/benchmark/evaluator.bench.ts +0 -331
- package/src/benchmark/fixtures.ts +0 -295
- package/src/benchmark/index.ts +0 -108
- package/src/benchmark/lexer.bench.ts +0 -69
- package/src/benchmark/parser.bench.ts +0 -103
- package/src/benchmark/resilience.bench.ts +0 -193
- package/src/benchmark/store.bench.ts +0 -147
- package/src/benchmark/utils.ts +0 -230
- package/src/cli.ts +0 -313
- package/src/errors/errors.test.ts +0 -234
- package/src/errors/index.ts +0 -223
- package/src/execution/execution.test.ts +0 -307
- package/src/execution/index.ts +0 -21
- package/src/execution/state.ts +0 -207
- package/src/execution/store.ts +0 -188
- package/src/index.ts +0 -169
- package/src/integration.test.ts +0 -192
- package/src/interpreter/context.ts +0 -57
- package/src/interpreter/evaluator.test.ts +0 -796
- package/src/interpreter/evaluator.ts +0 -245
- package/src/interpreter/executor.ts +0 -946
- package/src/interpreter/fetch-handler.ts +0 -302
- package/src/interpreter/http.test.ts +0 -423
- package/src/interpreter/http.ts +0 -308
- package/src/interpreter/index.ts +0 -32
- package/src/interpreter/pagination.ts +0 -207
- package/src/interpreter/progress.test.ts +0 -276
- package/src/interpreter/schema-matcher.test.ts +0 -160
- package/src/interpreter/schema-matcher.ts +0 -168
- package/src/interpreter/signals.ts +0 -73
- package/src/interpreter/step-handlers/for-handler.ts +0 -65
- package/src/interpreter/step-handlers/index.ts +0 -17
- package/src/interpreter/step-handlers/map-handler.ts +0 -24
- package/src/interpreter/step-handlers/match-handler.ts +0 -101
- package/src/interpreter/step-handlers/store-handler.ts +0 -78
- package/src/interpreter/step-handlers/types.ts +0 -17
- package/src/interpreter/step-handlers/validate-handler.ts +0 -30
- package/src/interpreter/step-handlers/webhook-handler.ts +0 -142
- package/src/lexer/index.ts +0 -18
- package/src/lexer/lexer.test.ts +0 -316
- package/src/lexer/tokens.ts +0 -179
- package/src/loader/index.ts +0 -288
- package/src/loader/loader.test.ts +0 -360
- package/src/oas/index.ts +0 -4
- package/src/oas/loader.ts +0 -126
- package/src/oas/oas.test.ts +0 -254
- package/src/oas/validator.ts +0 -299
- package/src/parser/base.ts +0 -124
- package/src/parser/expressions.test.ts +0 -525
- package/src/parser/expressions.ts +0 -314
- package/src/parser/index.ts +0 -3
- package/src/parser/match.test.ts +0 -296
- package/src/parser/parser.test.ts +0 -739
- package/src/parser/parser.ts +0 -1469
- package/src/parser/schedule.test.ts +0 -287
- package/src/parser/webhook.test.ts +0 -248
- package/src/plugin.ts +0 -83
- package/src/scheduler/cron-parser.test.ts +0 -236
- package/src/scheduler/cron-parser.ts +0 -236
- package/src/scheduler/index.ts +0 -10
- package/src/scheduler/scheduler.ts +0 -443
- package/src/scheduler/types.ts +0 -71
- package/src/stores/factory.ts +0 -104
- package/src/stores/file.test.ts +0 -276
- package/src/stores/file.ts +0 -211
- package/src/stores/index.ts +0 -6
- package/src/stores/memory.test.ts +0 -238
- package/src/stores/memory.ts +0 -63
- package/src/stores/postgrest.test.ts +0 -488
- package/src/stores/postgrest.ts +0 -263
- package/src/stores/stores.test.ts +0 -197
- package/src/stores/types.ts +0 -58
- package/src/sync/index.ts +0 -16
- package/src/sync/state.ts +0 -126
- package/src/sync/store.ts +0 -139
- package/src/sync/sync.test.ts +0 -271
- package/src/utils/async.ts +0 -10
- package/src/utils/file.ts +0 -106
- package/src/utils/index.ts +0 -14
- package/src/utils/logger.ts +0 -53
- package/src/utils/path.ts +0 -47
- package/src/webhook/index.ts +0 -15
- package/src/webhook/server.test.ts +0 -253
- package/src/webhook/server.ts +0 -389
- package/src/webhook/store.ts +0 -239
- package/src/webhook/types.ts +0 -93
- package/tsconfig.json +0 -17
- package/vitest.config.ts +0 -39
package/dist/utils/path.d.ts
CHANGED
|
@@ -7,6 +7,27 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export declare function extractNestedValue(data: Record<string, unknown>, path: string): unknown;
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Error thrown when a path segment would escape its base directory.
|
|
11
11
|
*/
|
|
12
|
+
export declare class PathTraversalError extends Error {
|
|
13
|
+
readonly segment: string;
|
|
14
|
+
readonly baseDir: string;
|
|
15
|
+
constructor(segment: string, baseDir: string);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Safely join an untrusted path segment under a base directory, asserting the
|
|
19
|
+
* result stays confined inside that base. Guards against `../`, absolute paths,
|
|
20
|
+
* and embedded separators in store names / IDs.
|
|
21
|
+
*
|
|
22
|
+
* @throws {PathTraversalError} if the resolved path escapes baseDir
|
|
23
|
+
*/
|
|
24
|
+
export declare function safeJoin(baseDir: string, ...segments: string[]): string;
|
|
25
|
+
/**
|
|
26
|
+
* Sanitize a single untrusted path segment (e.g. a store name or record ID) so
|
|
27
|
+
* it cannot traverse directories. Rejects empty, separator-bearing, or
|
|
28
|
+
* traversal segments rather than silently mangling them.
|
|
29
|
+
*
|
|
30
|
+
* @throws {PathTraversalError} if the segment is unsafe
|
|
31
|
+
*/
|
|
32
|
+
export declare function sanitizeSegment(segment: string, baseDir?: string): string;
|
|
12
33
|
export declare function traversePath(parts: string[], current: unknown, fallbackLookup?: (key: string) => unknown): unknown;
|
package/dist/utils/path.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Utility functions for path traversal and value extraction
|
|
3
3
|
*/
|
|
4
|
+
import { join, resolve, sep } from 'node:path';
|
|
4
5
|
/**
|
|
5
6
|
* Extract a nested value from an object using dot notation path
|
|
6
7
|
* @example extractNestedValue({ a: { b: 1 } }, 'a.b') // => 1
|
|
@@ -19,8 +20,52 @@ export function extractNestedValue(data, path) {
|
|
|
19
20
|
return value;
|
|
20
21
|
}
|
|
21
22
|
/**
|
|
22
|
-
*
|
|
23
|
+
* Error thrown when a path segment would escape its base directory.
|
|
23
24
|
*/
|
|
25
|
+
export class PathTraversalError extends Error {
|
|
26
|
+
segment;
|
|
27
|
+
baseDir;
|
|
28
|
+
constructor(segment, baseDir) {
|
|
29
|
+
super(`Path segment "${segment}" escapes base directory "${baseDir}"`);
|
|
30
|
+
this.segment = segment;
|
|
31
|
+
this.baseDir = baseDir;
|
|
32
|
+
this.name = 'PathTraversalError';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Safely join an untrusted path segment under a base directory, asserting the
|
|
37
|
+
* result stays confined inside that base. Guards against `../`, absolute paths,
|
|
38
|
+
* and embedded separators in store names / IDs.
|
|
39
|
+
*
|
|
40
|
+
* @throws {PathTraversalError} if the resolved path escapes baseDir
|
|
41
|
+
*/
|
|
42
|
+
export function safeJoin(baseDir, ...segments) {
|
|
43
|
+
const base = resolve(baseDir);
|
|
44
|
+
const joined = resolve(base, ...segments);
|
|
45
|
+
// Confined if it equals the base or sits inside it (base + separator prefix).
|
|
46
|
+
if (joined !== base && !joined.startsWith(base + sep)) {
|
|
47
|
+
throw new PathTraversalError(join(...segments), baseDir);
|
|
48
|
+
}
|
|
49
|
+
return joined;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Sanitize a single untrusted path segment (e.g. a store name or record ID) so
|
|
53
|
+
* it cannot traverse directories. Rejects empty, separator-bearing, or
|
|
54
|
+
* traversal segments rather than silently mangling them.
|
|
55
|
+
*
|
|
56
|
+
* @throws {PathTraversalError} if the segment is unsafe
|
|
57
|
+
*/
|
|
58
|
+
export function sanitizeSegment(segment, baseDir = '.') {
|
|
59
|
+
if (segment.length === 0 ||
|
|
60
|
+
segment === '.' ||
|
|
61
|
+
segment === '..' ||
|
|
62
|
+
segment.includes('/') ||
|
|
63
|
+
segment.includes('\\') ||
|
|
64
|
+
segment.includes('\0')) {
|
|
65
|
+
throw new PathTraversalError(segment, baseDir);
|
|
66
|
+
}
|
|
67
|
+
return segment;
|
|
68
|
+
}
|
|
24
69
|
export function traversePath(parts, current, fallbackLookup) {
|
|
25
70
|
let value = current;
|
|
26
71
|
for (let i = 0; i < parts.length; i++) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret redaction for logs, error messages, and durable trace files.
|
|
3
|
+
*
|
|
4
|
+
* Uses a denylist of sensitive key-name patterns: any object property whose
|
|
5
|
+
* key looks like a credential is replaced with {@link REDACTED}. This is a
|
|
6
|
+
* best-effort guard — it cannot catch a raw secret stored under an innocuous
|
|
7
|
+
* key — so callers should also avoid persisting cleartext credentials.
|
|
8
|
+
*/
|
|
9
|
+
export declare const REDACTED = "[REDACTED]";
|
|
10
|
+
/** True when a key name looks like it holds a credential. */
|
|
11
|
+
export declare function isSensitiveKey(key: string): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Return a deep copy of `value` with any credential-looking properties
|
|
14
|
+
* replaced by {@link REDACTED}. Non-objects are returned unchanged. Cycles are
|
|
15
|
+
* handled, and the input is never mutated.
|
|
16
|
+
*/
|
|
17
|
+
export declare function redactSecrets<T>(value: T, seen?: WeakSet<object>): T;
|
|
18
|
+
/**
|
|
19
|
+
* Redact a named value for logging: if the name itself looks sensitive the
|
|
20
|
+
* whole value is hidden; otherwise nested credential properties are redacted.
|
|
21
|
+
*/
|
|
22
|
+
export declare function redactNamedValue(name: string, value: unknown): unknown;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret redaction for logs, error messages, and durable trace files.
|
|
3
|
+
*
|
|
4
|
+
* Uses a denylist of sensitive key-name patterns: any object property whose
|
|
5
|
+
* key looks like a credential is replaced with {@link REDACTED}. This is a
|
|
6
|
+
* best-effort guard — it cannot catch a raw secret stored under an innocuous
|
|
7
|
+
* key — so callers should also avoid persisting cleartext credentials.
|
|
8
|
+
*/
|
|
9
|
+
export const REDACTED = '[REDACTED]';
|
|
10
|
+
/** Key names that should never have their value logged or persisted in clear. */
|
|
11
|
+
const SENSITIVE_KEY = /(pass(word|wd)?|secret|token|auth(orization)?|api[-_]?key|access[-_]?key|client[-_]?secret|credential|cookie|session|bearer|private[-_]?key)/i;
|
|
12
|
+
/** True when a key name looks like it holds a credential. */
|
|
13
|
+
export function isSensitiveKey(key) {
|
|
14
|
+
return SENSITIVE_KEY.test(key);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Return a deep copy of `value` with any credential-looking properties
|
|
18
|
+
* replaced by {@link REDACTED}. Non-objects are returned unchanged. Cycles are
|
|
19
|
+
* handled, and the input is never mutated.
|
|
20
|
+
*/
|
|
21
|
+
export function redactSecrets(value, seen = new WeakSet()) {
|
|
22
|
+
if (value === null || typeof value !== 'object')
|
|
23
|
+
return value;
|
|
24
|
+
if (seen.has(value))
|
|
25
|
+
return value;
|
|
26
|
+
seen.add(value);
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
return value.map((item) => redactSecrets(item, seen));
|
|
29
|
+
}
|
|
30
|
+
const out = {};
|
|
31
|
+
for (const [key, val] of Object.entries(value)) {
|
|
32
|
+
out[key] = isSensitiveKey(key) ? REDACTED : redactSecrets(val, seen);
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Redact a named value for logging: if the name itself looks sensitive the
|
|
38
|
+
* whole value is hidden; otherwise nested credential properties are redacted.
|
|
39
|
+
*/
|
|
40
|
+
export function redactNamedValue(name, value) {
|
|
41
|
+
return isSensitiveKey(name) ? REDACTED : redactSecrets(value);
|
|
42
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type guard utilities to reduce type assertions and improve type safety.
|
|
3
|
+
* These guards provide runtime type checking with TypeScript type narrowing.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Type guard to check if a value is a non-null object (excluding arrays).
|
|
7
|
+
* Narrows the type to Record<string, unknown> for safe property access.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
10
|
+
/**
|
|
11
|
+
* Type guard to check if a value is a non-null object (including arrays).
|
|
12
|
+
* Useful when you need to access properties on any object-like value.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isObject(value: unknown): value is object;
|
|
15
|
+
/**
|
|
16
|
+
* Type guard to check if a value is an array of a specific type.
|
|
17
|
+
* Performs runtime check on each element using the provided guard.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isArrayOf<T>(value: unknown, guard: (item: unknown) => item is T): value is T[];
|
|
20
|
+
/**
|
|
21
|
+
* Type guard to check if a value is a string.
|
|
22
|
+
*/
|
|
23
|
+
export declare function isString(value: unknown): value is string;
|
|
24
|
+
/**
|
|
25
|
+
* Type guard to check if a value is a number.
|
|
26
|
+
*/
|
|
27
|
+
export declare function isNumber(value: unknown): value is number;
|
|
28
|
+
/**
|
|
29
|
+
* Type guard to check if a value is a boolean.
|
|
30
|
+
*/
|
|
31
|
+
export declare function isBoolean(value: unknown): value is boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Type guard to check if a value is defined (not undefined).
|
|
34
|
+
*/
|
|
35
|
+
export declare function isDefined<T>(value: T | undefined): value is T;
|
|
36
|
+
/**
|
|
37
|
+
* Type guard to check if a value is not null or undefined.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isPresent<T>(value: T | null | undefined): value is T;
|
|
40
|
+
/**
|
|
41
|
+
* Safely access a property on an unknown value.
|
|
42
|
+
* Returns undefined if the value is not an object or the property doesn't exist.
|
|
43
|
+
*/
|
|
44
|
+
export declare function getProperty(value: unknown, key: string): unknown;
|
|
45
|
+
/**
|
|
46
|
+
* Safely access a nested property path on an unknown value.
|
|
47
|
+
* Returns undefined if any part of the path is not an object.
|
|
48
|
+
*/
|
|
49
|
+
export declare function getNestedProperty(value: unknown, path: string[]): unknown;
|
|
50
|
+
/**
|
|
51
|
+
* Check if an object has a specific property.
|
|
52
|
+
* Narrows the type to include that property.
|
|
53
|
+
*/
|
|
54
|
+
export declare function hasProperty<K extends string>(value: unknown, key: K): value is Record<K, unknown>;
|
|
55
|
+
/**
|
|
56
|
+
* Check if an object has a specific property with a specific type.
|
|
57
|
+
*/
|
|
58
|
+
export declare function hasTypedProperty<K extends string, T>(value: unknown, key: K, guard: (v: unknown) => v is T): value is Record<K, T>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type guard utilities to reduce type assertions and improve type safety.
|
|
3
|
+
* These guards provide runtime type checking with TypeScript type narrowing.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Type guard to check if a value is a non-null object (excluding arrays).
|
|
7
|
+
* Narrows the type to Record<string, unknown> for safe property access.
|
|
8
|
+
*/
|
|
9
|
+
export function isRecord(value) {
|
|
10
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Type guard to check if a value is a non-null object (including arrays).
|
|
14
|
+
* Useful when you need to access properties on any object-like value.
|
|
15
|
+
*/
|
|
16
|
+
export function isObject(value) {
|
|
17
|
+
return value !== null && typeof value === 'object';
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Type guard to check if a value is an array of a specific type.
|
|
21
|
+
* Performs runtime check on each element using the provided guard.
|
|
22
|
+
*/
|
|
23
|
+
export function isArrayOf(value, guard) {
|
|
24
|
+
return Array.isArray(value) && value.every(guard);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Type guard to check if a value is a string.
|
|
28
|
+
*/
|
|
29
|
+
export function isString(value) {
|
|
30
|
+
return typeof value === 'string';
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Type guard to check if a value is a number.
|
|
34
|
+
*/
|
|
35
|
+
export function isNumber(value) {
|
|
36
|
+
return typeof value === 'number';
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Type guard to check if a value is a boolean.
|
|
40
|
+
*/
|
|
41
|
+
export function isBoolean(value) {
|
|
42
|
+
return typeof value === 'boolean';
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Type guard to check if a value is defined (not undefined).
|
|
46
|
+
*/
|
|
47
|
+
export function isDefined(value) {
|
|
48
|
+
return value !== undefined;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Type guard to check if a value is not null or undefined.
|
|
52
|
+
*/
|
|
53
|
+
export function isPresent(value) {
|
|
54
|
+
return value !== null && value !== undefined;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Safely access a property on an unknown value.
|
|
58
|
+
* Returns undefined if the value is not an object or the property doesn't exist.
|
|
59
|
+
*/
|
|
60
|
+
export function getProperty(value, key) {
|
|
61
|
+
if (isRecord(value)) {
|
|
62
|
+
return value[key];
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Safely access a nested property path on an unknown value.
|
|
68
|
+
* Returns undefined if any part of the path is not an object.
|
|
69
|
+
*/
|
|
70
|
+
export function getNestedProperty(value, path) {
|
|
71
|
+
let current = value;
|
|
72
|
+
for (const key of path) {
|
|
73
|
+
if (!isRecord(current)) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
current = current[key];
|
|
77
|
+
}
|
|
78
|
+
return current;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Check if an object has a specific property.
|
|
82
|
+
* Narrows the type to include that property.
|
|
83
|
+
*/
|
|
84
|
+
export function hasProperty(value, key) {
|
|
85
|
+
return isRecord(value) && key in value;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Check if an object has a specific property with a specific type.
|
|
89
|
+
*/
|
|
90
|
+
export function hasTypedProperty(value, key, guard) {
|
|
91
|
+
return hasProperty(value, key) && guard(value[key]);
|
|
92
|
+
}
|
package/dist/webhook/server.d.ts
CHANGED
|
@@ -45,6 +45,8 @@ export declare class WebhookServer {
|
|
|
45
45
|
* Wait for webhook events
|
|
46
46
|
*/
|
|
47
47
|
waitForEvents(registrationId: string, timeout?: number): Promise<WaitResult>;
|
|
48
|
+
/** Remove a single waiter, dropping the registration's set when empty. */
|
|
49
|
+
private removePendingWait;
|
|
48
50
|
/**
|
|
49
51
|
* Unregister a webhook endpoint
|
|
50
52
|
*/
|
|
@@ -69,6 +71,13 @@ export declare class WebhookServer {
|
|
|
69
71
|
* Read request body
|
|
70
72
|
*/
|
|
71
73
|
private readBody;
|
|
74
|
+
/** True if a host string is a loopback address. */
|
|
75
|
+
private isLoopback;
|
|
76
|
+
/**
|
|
77
|
+
* Validate the shared secret from Authorization bearer, X-Webhook-Token
|
|
78
|
+
* header, or a `token` query param.
|
|
79
|
+
*/
|
|
80
|
+
private authorized;
|
|
72
81
|
/**
|
|
73
82
|
* Extract headers from request
|
|
74
83
|
*/
|
package/dist/webhook/server.js
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
import { createServer } from 'node:http';
|
|
8
8
|
import { parse as parseUrl } from 'node:url';
|
|
9
9
|
import { randomUUID } from 'node:crypto';
|
|
10
|
+
import { setLongTimeout } from '../utils/long-timeout.js';
|
|
10
11
|
import { MemoryWebhookStore } from './store.js';
|
|
12
|
+
import { WEBHOOK_DEFAULTS } from '../config/index.js';
|
|
11
13
|
/**
|
|
12
14
|
* Webhook Server
|
|
13
15
|
*
|
|
@@ -18,16 +20,20 @@ export class WebhookServer {
|
|
|
18
20
|
store;
|
|
19
21
|
callbacks;
|
|
20
22
|
server;
|
|
23
|
+
// Multiple concurrent waiters may await the same registration; each gets its
|
|
24
|
+
// own entry so a second waiter can't clobber the first's timer/promise.
|
|
21
25
|
pendingWaits = new Map();
|
|
22
26
|
cleanupInterval;
|
|
23
27
|
running = false;
|
|
24
28
|
constructor(config = {}, store, callbacks = {}) {
|
|
25
29
|
this.config = {
|
|
26
|
-
port: config.port ??
|
|
27
|
-
host: config.host ??
|
|
28
|
-
baseUrl: config.baseUrl ?? `http://localhost:${config.port ??
|
|
29
|
-
defaultTimeout: config.defaultTimeout ??
|
|
30
|
+
port: config.port ?? WEBHOOK_DEFAULTS.PORT,
|
|
31
|
+
host: config.host ?? WEBHOOK_DEFAULTS.HOST,
|
|
32
|
+
baseUrl: config.baseUrl ?? `http://localhost:${config.port ?? WEBHOOK_DEFAULTS.PORT}`,
|
|
33
|
+
defaultTimeout: config.defaultTimeout ?? WEBHOOK_DEFAULTS.DEFAULT_TIMEOUT_MS,
|
|
30
34
|
verbose: config.verbose ?? false,
|
|
35
|
+
secret: config.secret ?? '',
|
|
36
|
+
maxBodyBytes: config.maxBodyBytes ?? WEBHOOK_DEFAULTS.MAX_BODY_BYTES,
|
|
31
37
|
};
|
|
32
38
|
this.store = store ?? new MemoryWebhookStore();
|
|
33
39
|
this.callbacks = callbacks;
|
|
@@ -38,16 +44,22 @@ export class WebhookServer {
|
|
|
38
44
|
async start() {
|
|
39
45
|
if (this.running)
|
|
40
46
|
return;
|
|
47
|
+
// Warn loudly if exposing an unauthenticated webhook server off-host.
|
|
48
|
+
if (!this.isLoopback(this.config.host) && !this.config.secret) {
|
|
49
|
+
console.warn(`[Webhook] WARNING: binding to ${this.config.host} with no secret — ` +
|
|
50
|
+
`anyone who can reach the port can inject events.`);
|
|
51
|
+
}
|
|
41
52
|
return new Promise((resolve, reject) => {
|
|
42
53
|
this.server = createServer((req, res) => this.handleRequest(req, res));
|
|
54
|
+
this.server.setTimeout(WEBHOOK_DEFAULTS.SOCKET_TIMEOUT_MS);
|
|
43
55
|
this.server.on('error', (error) => {
|
|
44
56
|
reject(error);
|
|
45
57
|
});
|
|
46
58
|
this.server.listen(this.config.port, this.config.host, () => {
|
|
47
59
|
this.running = true;
|
|
48
60
|
this.log(`Webhook server listening on ${this.config.host}:${this.config.port}`);
|
|
49
|
-
// Start cleanup interval
|
|
50
|
-
this.cleanupInterval = setInterval(() => this.cleanup(),
|
|
61
|
+
// Start cleanup interval
|
|
62
|
+
this.cleanupInterval = setInterval(() => this.cleanup(), WEBHOOK_DEFAULTS.CLEANUP_INTERVAL_MS);
|
|
51
63
|
resolve();
|
|
52
64
|
});
|
|
53
65
|
});
|
|
@@ -64,13 +76,15 @@ export class WebhookServer {
|
|
|
64
76
|
this.cleanupInterval = undefined;
|
|
65
77
|
}
|
|
66
78
|
// Cancel all pending waits
|
|
67
|
-
for (const [
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
for (const [, waiters] of this.pendingWaits) {
|
|
80
|
+
for (const pending of waiters) {
|
|
81
|
+
pending.timer.clear();
|
|
82
|
+
pending.resolve({
|
|
83
|
+
success: false,
|
|
84
|
+
events: [],
|
|
85
|
+
error: 'Server shutting down',
|
|
86
|
+
});
|
|
87
|
+
}
|
|
74
88
|
}
|
|
75
89
|
this.pendingWaits.clear();
|
|
76
90
|
// Close server
|
|
@@ -133,10 +147,11 @@ export class WebhookServer {
|
|
|
133
147
|
return { success: true, events };
|
|
134
148
|
}
|
|
135
149
|
// Wait for more events
|
|
136
|
-
const waitTimeout = timeout ??
|
|
150
|
+
const waitTimeout = timeout ?? registration.expiresAt.getTime() - Date.now();
|
|
137
151
|
return new Promise((resolve) => {
|
|
138
|
-
const
|
|
139
|
-
|
|
152
|
+
const pending = { registrationId, resolve, timer: undefined };
|
|
153
|
+
pending.timer = setLongTimeout(() => {
|
|
154
|
+
this.removePendingWait(registrationId, pending);
|
|
140
155
|
this.store.getEvents(registrationId).then((events) => {
|
|
141
156
|
resolve({
|
|
142
157
|
success: events.length >= registration.expectedEvents,
|
|
@@ -145,23 +160,36 @@ export class WebhookServer {
|
|
|
145
160
|
});
|
|
146
161
|
});
|
|
147
162
|
}, waitTimeout);
|
|
148
|
-
this.pendingWaits.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
163
|
+
let waiters = this.pendingWaits.get(registrationId);
|
|
164
|
+
if (!waiters) {
|
|
165
|
+
waiters = new Set();
|
|
166
|
+
this.pendingWaits.set(registrationId, waiters);
|
|
167
|
+
}
|
|
168
|
+
waiters.add(pending);
|
|
153
169
|
});
|
|
154
170
|
}
|
|
171
|
+
/** Remove a single waiter, dropping the registration's set when empty. */
|
|
172
|
+
removePendingWait(registrationId, pending) {
|
|
173
|
+
const waiters = this.pendingWaits.get(registrationId);
|
|
174
|
+
if (!waiters)
|
|
175
|
+
return;
|
|
176
|
+
waiters.delete(pending);
|
|
177
|
+
if (waiters.size === 0) {
|
|
178
|
+
this.pendingWaits.delete(registrationId);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
155
181
|
/**
|
|
156
182
|
* Unregister a webhook endpoint
|
|
157
183
|
*/
|
|
158
184
|
async unregister(registrationId) {
|
|
159
185
|
await this.store.deleteRegistration(registrationId);
|
|
160
186
|
await this.store.deleteEvents(registrationId);
|
|
161
|
-
// Cancel pending
|
|
162
|
-
const
|
|
163
|
-
if (
|
|
164
|
-
|
|
187
|
+
// Cancel any pending waits for this registration.
|
|
188
|
+
const waiters = this.pendingWaits.get(registrationId);
|
|
189
|
+
if (waiters) {
|
|
190
|
+
for (const pending of waiters) {
|
|
191
|
+
pending.timer.clear();
|
|
192
|
+
}
|
|
165
193
|
this.pendingWaits.delete(registrationId);
|
|
166
194
|
}
|
|
167
195
|
this.log(`Unregistered webhook: ${registrationId}`);
|
|
@@ -210,11 +238,31 @@ export class WebhookServer {
|
|
|
210
238
|
await this.store.deleteRegistration(registration.id);
|
|
211
239
|
return;
|
|
212
240
|
}
|
|
213
|
-
//
|
|
241
|
+
// Require the shared secret if one is configured.
|
|
242
|
+
if (this.config.secret && !this.authorized(req, url.query)) {
|
|
243
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
244
|
+
res.end(JSON.stringify({ error: 'Unauthorized' }));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
// Read the request body with a hard size cap to prevent an OOM from a
|
|
248
|
+
// large or slow-drip POST.
|
|
214
249
|
let rawBody = '';
|
|
215
|
-
let body = null;
|
|
216
250
|
try {
|
|
217
251
|
rawBody = await this.readBody(req);
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
if (error.code === 'BODY_TOO_LARGE') {
|
|
255
|
+
res.writeHead(413, { 'Content-Type': 'application/json' });
|
|
256
|
+
res.end(JSON.stringify({ error: 'Request body too large' }));
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
260
|
+
res.end(JSON.stringify({ error: 'Failed to read request body' }));
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
// Parse request body
|
|
264
|
+
let body = null;
|
|
265
|
+
try {
|
|
218
266
|
if (rawBody) {
|
|
219
267
|
const contentType = req.headers['content-type'] ?? '';
|
|
220
268
|
if (contentType.includes('application/json')) {
|
|
@@ -228,7 +276,7 @@ export class WebhookServer {
|
|
|
228
276
|
}
|
|
229
277
|
}
|
|
230
278
|
}
|
|
231
|
-
catch
|
|
279
|
+
catch {
|
|
232
280
|
body = rawBody;
|
|
233
281
|
}
|
|
234
282
|
// Create event
|
|
@@ -252,12 +300,14 @@ export class WebhookServer {
|
|
|
252
300
|
if (registration.receivedEvents >= registration.expectedEvents) {
|
|
253
301
|
const events = await this.store.getEvents(registration.id);
|
|
254
302
|
this.callbacks.onRegistrationComplete?.(registration, events);
|
|
255
|
-
// Resolve pending
|
|
256
|
-
const
|
|
257
|
-
if (
|
|
258
|
-
clearTimeout(pending.timeoutId);
|
|
303
|
+
// Resolve every pending waiter for this registration.
|
|
304
|
+
const waiters = this.pendingWaits.get(registration.id);
|
|
305
|
+
if (waiters) {
|
|
259
306
|
this.pendingWaits.delete(registration.id);
|
|
260
|
-
|
|
307
|
+
for (const pending of waiters) {
|
|
308
|
+
pending.timer.clear();
|
|
309
|
+
pending.resolve({ success: true, events });
|
|
310
|
+
}
|
|
261
311
|
}
|
|
262
312
|
}
|
|
263
313
|
// Send response
|
|
@@ -273,17 +323,53 @@ export class WebhookServer {
|
|
|
273
323
|
* Read request body
|
|
274
324
|
*/
|
|
275
325
|
readBody(req) {
|
|
326
|
+
const limit = this.config.maxBodyBytes;
|
|
276
327
|
return new Promise((resolve, reject) => {
|
|
277
|
-
|
|
328
|
+
const chunks = [];
|
|
329
|
+
let size = 0;
|
|
330
|
+
let aborted = false;
|
|
278
331
|
req.on('data', (chunk) => {
|
|
279
|
-
|
|
332
|
+
if (aborted)
|
|
333
|
+
return;
|
|
334
|
+
size += chunk.length;
|
|
335
|
+
if (size > limit) {
|
|
336
|
+
// Stop accumulating (memory stays bounded) and reject; the handler
|
|
337
|
+
// responds 413. We don't destroy the socket here so the response
|
|
338
|
+
// can flush first.
|
|
339
|
+
aborted = true;
|
|
340
|
+
const err = new Error('Request body too large');
|
|
341
|
+
err.code = 'BODY_TOO_LARGE';
|
|
342
|
+
reject(err);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
chunks.push(chunk);
|
|
280
346
|
});
|
|
281
347
|
req.on('end', () => {
|
|
282
|
-
resolve(
|
|
348
|
+
resolve(Buffer.concat(chunks).toString('utf-8'));
|
|
283
349
|
});
|
|
284
350
|
req.on('error', reject);
|
|
285
351
|
});
|
|
286
352
|
}
|
|
353
|
+
/** True if a host string is a loopback address. */
|
|
354
|
+
isLoopback(host) {
|
|
355
|
+
return host === 'localhost' || host === '127.0.0.1' || host === '::1';
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Validate the shared secret from Authorization bearer, X-Webhook-Token
|
|
359
|
+
* header, or a `token` query param.
|
|
360
|
+
*/
|
|
361
|
+
authorized(req, query) {
|
|
362
|
+
const secret = this.config.secret;
|
|
363
|
+
const auth = req.headers.authorization;
|
|
364
|
+
if (auth === `Bearer ${secret}`)
|
|
365
|
+
return true;
|
|
366
|
+
const tokenHeader = req.headers['x-webhook-token'];
|
|
367
|
+
if (tokenHeader === secret)
|
|
368
|
+
return true;
|
|
369
|
+
if (typeof query.token === 'string' && query.token === secret)
|
|
370
|
+
return true;
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
287
373
|
/**
|
|
288
374
|
* Extract headers from request
|
|
289
375
|
*/
|
package/dist/webhook/types.d.ts
CHANGED
|
@@ -51,7 +51,7 @@ export interface WebhookEvent {
|
|
|
51
51
|
export interface WebhookServerConfig {
|
|
52
52
|
/** Port to listen on (default: 3000) */
|
|
53
53
|
port?: number;
|
|
54
|
-
/** Host to bind to (default: '
|
|
54
|
+
/** Host to bind to (default: '127.0.0.1' — loopback only) */
|
|
55
55
|
host?: string;
|
|
56
56
|
/** Base URL for webhook endpoints (e.g., 'https://example.com/webhooks') */
|
|
57
57
|
baseUrl?: string;
|
|
@@ -59,6 +59,14 @@ export interface WebhookServerConfig {
|
|
|
59
59
|
defaultTimeout?: number;
|
|
60
60
|
/** Enable verbose logging */
|
|
61
61
|
verbose?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Shared secret required on inbound webhook requests. When set, a request
|
|
64
|
+
* must present it via `Authorization: Bearer <secret>`, `X-Webhook-Token`,
|
|
65
|
+
* or a `token` query param, or it is rejected with 401.
|
|
66
|
+
*/
|
|
67
|
+
secret?: string;
|
|
68
|
+
/** Maximum accepted request body size in bytes (default 1 MiB) */
|
|
69
|
+
maxBodyBytes?: number;
|
|
62
70
|
}
|
|
63
71
|
/**
|
|
64
72
|
* Callbacks for webhook server events
|