approval-md 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/README.md +85 -877
- package/SPEC.md +150 -19
- package/cli.js +66 -7
- package/dist/src/adapters/smtp.d.ts +18 -0
- package/dist/src/adapters/smtp.js +24 -3
- package/dist/src/adapters/smtp.js.map +1 -1
- package/dist/src/channels/contract.d.ts +53 -1
- package/dist/src/channels/contract.js +304 -7
- package/dist/src/channels/contract.js.map +1 -1
- package/dist/src/channels/relay-server.d.ts +90 -0
- package/dist/src/channels/relay-server.js +295 -0
- package/dist/src/channels/relay-server.js.map +1 -0
- package/dist/src/channels/relay.d.ts +273 -0
- package/dist/src/channels/relay.js +564 -0
- package/dist/src/channels/relay.js.map +1 -0
- package/dist/src/channels/tagging.js +6 -1
- package/dist/src/channels/tagging.js.map +1 -1
- package/dist/src/channels/telegram-webhook.d.ts +273 -0
- package/dist/src/channels/telegram-webhook.js +536 -0
- package/dist/src/channels/telegram-webhook.js.map +1 -0
- package/dist/src/channels/telegram.d.ts +394 -12
- package/dist/src/channels/telegram.js +621 -68
- package/dist/src/channels/telegram.js.map +1 -1
- package/dist/src/channels/web.d.ts +9 -0
- package/dist/src/channels/web.js +17 -0
- package/dist/src/channels/web.js.map +1 -1
- package/dist/src/cli/amend.js +564 -34
- package/dist/src/cli/amend.js.map +1 -1
- package/dist/src/cli/attest.d.ts +9 -0
- package/dist/src/cli/attest.js +177 -13
- package/dist/src/cli/attest.js.map +1 -1
- package/dist/src/cli/channel-relay.d.ts +108 -0
- package/dist/src/cli/channel-relay.js +334 -0
- package/dist/src/cli/channel-relay.js.map +1 -0
- package/dist/src/cli/channel-telegram-webhook.d.ts +273 -0
- package/dist/src/cli/channel-telegram-webhook.js +709 -0
- package/dist/src/cli/channel-telegram-webhook.js.map +1 -0
- package/dist/src/cli/channel-telegram.d.ts +347 -28
- package/dist/src/cli/channel-telegram.js +763 -28
- package/dist/src/cli/channel-telegram.js.map +1 -1
- package/dist/src/cli/channel.d.ts +10 -1
- package/dist/src/cli/channel.js +14 -1
- package/dist/src/cli/channel.js.map +1 -1
- package/dist/src/cli/codex-bridge.d.ts +819 -0
- package/dist/src/cli/codex-bridge.js +1607 -0
- package/dist/src/cli/codex-bridge.js.map +1 -0
- package/dist/src/cli/codex.d.ts +1 -1
- package/dist/src/cli/codex.js +304 -7
- package/dist/src/cli/codex.js.map +1 -1
- package/dist/src/cli/daemon.js +41 -6
- package/dist/src/cli/daemon.js.map +1 -1
- package/dist/src/cli/doctor.d.ts +63 -0
- package/dist/src/cli/doctor.js +1322 -122
- package/dist/src/cli/doctor.js.map +1 -1
- package/dist/src/cli/execute.d.ts +17 -1
- package/dist/src/cli/execute.js +366 -22
- package/dist/src/cli/execute.js.map +1 -1
- package/dist/src/cli/exit-codes.d.ts +12 -0
- package/dist/src/cli/exit-codes.js +13 -0
- package/dist/src/cli/exit-codes.js.map +1 -1
- package/dist/src/cli/gate.d.ts +2 -0
- package/dist/src/cli/gate.js +233 -2
- package/dist/src/cli/gate.js.map +1 -1
- package/dist/src/cli/help.d.ts +14 -2
- package/dist/src/cli/help.js +395 -82
- package/dist/src/cli/help.js.map +1 -1
- package/dist/src/cli/hook-codex.d.ts +49 -1
- package/dist/src/cli/hook-codex.js +60 -1
- package/dist/src/cli/hook-codex.js.map +1 -1
- package/dist/src/cli/hook.d.ts +828 -4
- package/dist/src/cli/hook.js +2972 -142
- package/dist/src/cli/hook.js.map +1 -1
- package/dist/src/cli/import.js +1 -1
- package/dist/src/cli/import.js.map +1 -1
- package/dist/src/cli/init.js +16 -7
- package/dist/src/cli/init.js.map +1 -1
- package/dist/src/cli/instructions.js +24 -3
- package/dist/src/cli/instructions.js.map +1 -1
- package/dist/src/cli/log-anchor.d.ts +55 -0
- package/dist/src/cli/log-anchor.js +199 -60
- package/dist/src/cli/log-anchor.js.map +1 -1
- package/dist/src/cli/main.d.ts +0 -12
- package/dist/src/cli/main.js +56 -3
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/cli/muse.d.ts +2 -0
- package/dist/src/cli/muse.js +62 -0
- package/dist/src/cli/muse.js.map +1 -0
- package/dist/src/cli/payload.js +79 -1
- package/dist/src/cli/payload.js.map +1 -1
- package/dist/src/cli/policy-apply.d.ts +195 -0
- package/dist/src/cli/policy-apply.js +573 -0
- package/dist/src/cli/policy-apply.js.map +1 -0
- package/dist/src/cli/policy.js +14 -1
- package/dist/src/cli/policy.js.map +1 -1
- package/dist/src/cli/preflight.d.ts +151 -13
- package/dist/src/cli/preflight.js +398 -41
- package/dist/src/cli/preflight.js.map +1 -1
- package/dist/src/cli/sandbox.js +17 -1
- package/dist/src/cli/sandbox.js.map +1 -1
- package/dist/src/cli/scaffold.d.ts +1 -1
- package/dist/src/cli/scaffold.js +7 -1
- package/dist/src/cli/scaffold.js.map +1 -1
- package/dist/src/cli/serve.d.ts +53 -0
- package/dist/src/cli/serve.js +270 -0
- package/dist/src/cli/serve.js.map +1 -0
- package/dist/src/cli/setup-channel.d.ts +9 -0
- package/dist/src/cli/setup-channel.js +28 -1
- package/dist/src/cli/setup-channel.js.map +1 -1
- package/dist/src/cli/setup-common.d.ts +3 -1
- package/dist/src/cli/setup-common.js +3 -2
- package/dist/src/cli/setup-common.js.map +1 -1
- package/dist/src/cli/setup.d.ts +2 -0
- package/dist/src/cli/setup.js +94 -2
- package/dist/src/cli/setup.js.map +1 -1
- package/dist/src/cli/up.js +134 -51
- package/dist/src/cli/up.js.map +1 -1
- package/dist/src/cli/values.js +3 -4
- package/dist/src/cli/values.js.map +1 -1
- package/dist/src/cli/verb-registry.d.ts +2 -0
- package/dist/src/cli/verb-registry.js +510 -47
- package/dist/src/cli/verb-registry.js.map +1 -1
- package/dist/src/cli/wordmark.d.ts +2 -2
- package/dist/src/cli/wordmark.js +2 -2
- package/dist/src/codex/broker.d.ts +229 -0
- package/dist/src/codex/broker.js +548 -0
- package/dist/src/codex/broker.js.map +1 -0
- package/dist/src/codex/runner.d.ts +178 -0
- package/dist/src/codex/runner.js +231 -0
- package/dist/src/codex/runner.js.map +1 -0
- package/dist/src/codex/serve.d.ts +56 -0
- package/dist/src/codex/serve.js +98 -0
- package/dist/src/codex/serve.js.map +1 -0
- package/dist/src/codex/workspace-commit.d.ts +219 -0
- package/dist/src/codex/workspace-commit.js +549 -0
- package/dist/src/codex/workspace-commit.js.map +1 -0
- package/dist/src/core/advance-cycle.d.ts +51 -0
- package/dist/src/core/advance-cycle.js +66 -2
- package/dist/src/core/advance-cycle.js.map +1 -1
- package/dist/src/core/agents-md.d.ts +20 -18
- package/dist/src/core/agents-md.js +33 -31
- package/dist/src/core/agents-md.js.map +1 -1
- package/dist/src/core/attest-bootstrap.d.ts +95 -0
- package/dist/src/core/attest-bootstrap.js +135 -0
- package/dist/src/core/attest-bootstrap.js.map +1 -0
- package/dist/src/core/attest.d.ts +234 -2
- package/dist/src/core/attest.js +342 -7
- package/dist/src/core/attest.js.map +1 -1
- package/dist/src/core/audit.d.ts +73 -3
- package/dist/src/core/audit.js +65 -5
- package/dist/src/core/audit.js.map +1 -1
- package/dist/src/core/channel-lease.d.ts +322 -0
- package/dist/src/core/channel-lease.js +663 -0
- package/dist/src/core/channel-lease.js.map +1 -0
- package/dist/src/core/channel-owner.d.ts +213 -0
- package/dist/src/core/channel-owner.js +358 -0
- package/dist/src/core/channel-owner.js.map +1 -0
- package/dist/src/core/command-class.d.ts +205 -0
- package/dist/src/core/command-class.js +1433 -28
- package/dist/src/core/command-class.js.map +1 -1
- package/dist/src/core/commit-guard.d.ts +272 -0
- package/dist/src/core/commit-guard.js +424 -0
- package/dist/src/core/commit-guard.js.map +1 -0
- package/dist/src/core/daemon-actor.d.ts +45 -0
- package/dist/src/core/daemon-actor.js +54 -0
- package/dist/src/core/daemon-actor.js.map +1 -0
- package/dist/src/core/daemon-host.d.ts +178 -0
- package/dist/src/core/daemon-host.js +228 -0
- package/dist/src/core/daemon-host.js.map +1 -0
- package/dist/src/core/daemon-identity.d.ts +238 -0
- package/dist/src/core/daemon-identity.js +252 -0
- package/dist/src/core/daemon-identity.js.map +1 -0
- package/dist/src/core/dark-session.d.ts +109 -8
- package/dist/src/core/dark-session.js +266 -82
- package/dist/src/core/dark-session.js.map +1 -1
- package/dist/src/core/decision-refusal.d.ts +23 -2
- package/dist/src/core/decision-refusal.js +24 -2
- package/dist/src/core/decision-refusal.js.map +1 -1
- package/dist/src/core/env-file.d.ts +5 -0
- package/dist/src/core/env-file.js +60 -1
- package/dist/src/core/env-file.js.map +1 -1
- package/dist/src/core/execute.d.ts +15 -2
- package/dist/src/core/execute.js +15 -2
- package/dist/src/core/execute.js.map +1 -1
- package/dist/src/core/gate.d.ts +392 -6
- package/dist/src/core/gate.js +852 -24
- package/dist/src/core/gate.js.map +1 -1
- package/dist/src/core/gesture-refusal.d.ts +166 -0
- package/dist/src/core/gesture-refusal.js +188 -0
- package/dist/src/core/gesture-refusal.js.map +1 -0
- package/dist/src/core/harness-version.d.ts +128 -4
- package/dist/src/core/harness-version.js +181 -12
- package/dist/src/core/harness-version.js.map +1 -1
- package/dist/src/core/harness-wait.d.ts +82 -3
- package/dist/src/core/harness-wait.js +89 -3
- package/dist/src/core/harness-wait.js.map +1 -1
- package/dist/src/core/instance.d.ts +59 -2
- package/dist/src/core/instance.js +113 -0
- package/dist/src/core/instance.js.map +1 -1
- package/dist/src/core/log-subscribe.d.ts +21 -0
- package/dist/src/core/log-subscribe.js +16 -6
- package/dist/src/core/log-subscribe.js.map +1 -1
- package/dist/src/core/log-write-layer.d.ts +55 -0
- package/dist/src/core/log-write-layer.js +91 -0
- package/dist/src/core/log-write-layer.js.map +1 -0
- package/dist/src/core/log.d.ts +89 -22
- package/dist/src/core/log.js +197 -19
- package/dist/src/core/log.js.map +1 -1
- package/dist/src/core/payload-census.d.ts +51 -0
- package/dist/src/core/payload-census.js +125 -2
- package/dist/src/core/payload-census.js.map +1 -1
- package/dist/src/core/payload-store.d.ts +31 -0
- package/dist/src/core/payload-store.js +113 -8
- package/dist/src/core/payload-store.js.map +1 -1
- package/dist/src/core/payload.d.ts +18 -3
- package/dist/src/core/payload.js +20 -4
- package/dist/src/core/payload.js.map +1 -1
- package/dist/src/core/policy-diff.d.ts +10 -0
- package/dist/src/core/policy-diff.js +10 -2
- package/dist/src/core/policy-diff.js.map +1 -1
- package/dist/src/core/policy-explain.d.ts +17 -1
- package/dist/src/core/policy-explain.js +48 -1
- package/dist/src/core/policy-explain.js.map +1 -1
- package/dist/src/core/policy-load.d.ts +65 -1
- package/dist/src/core/policy-load.js +21 -3
- package/dist/src/core/policy-load.js.map +1 -1
- package/dist/src/core/policy-match.d.ts +81 -0
- package/dist/src/core/policy-match.js +87 -0
- package/dist/src/core/policy-match.js.map +1 -1
- package/dist/src/core/policy-proposal.d.ts +75 -0
- package/dist/src/core/policy-proposal.js +110 -4
- package/dist/src/core/policy-proposal.js.map +1 -1
- package/dist/src/core/protected-path-guard.d.ts +117 -4
- package/dist/src/core/protected-path-guard.js +362 -48
- package/dist/src/core/protected-path-guard.js.map +1 -1
- package/dist/src/core/question-preempted.d.ts +141 -0
- package/dist/src/core/question-preempted.js +152 -0
- package/dist/src/core/question-preempted.js.map +1 -0
- package/dist/src/core/read-scope.d.ts +182 -0
- package/dist/src/core/read-scope.js +256 -0
- package/dist/src/core/read-scope.js.map +1 -0
- package/dist/src/core/run-payload.d.ts +160 -0
- package/dist/src/core/run-payload.js +304 -0
- package/dist/src/core/run-payload.js.map +1 -0
- package/dist/src/core/sandbox.d.ts +81 -0
- package/dist/src/core/sandbox.js +190 -1
- package/dist/src/core/sandbox.js.map +1 -1
- package/dist/src/core/sender-identity.d.ts +501 -0
- package/dist/src/core/sender-identity.js +609 -0
- package/dist/src/core/sender-identity.js.map +1 -0
- package/dist/src/core/shlex.d.ts +102 -0
- package/dist/src/core/shlex.js +159 -0
- package/dist/src/core/shlex.js.map +1 -0
- package/dist/src/core/state.d.ts +52 -1
- package/dist/src/core/state.js +52 -6
- package/dist/src/core/state.js.map +1 -1
- package/dist/src/core/telegram-config.d.ts +79 -0
- package/dist/src/core/telegram-config.js +108 -0
- package/dist/src/core/telegram-config.js.map +1 -1
- package/dist/src/core/values.d.ts +18 -8
- package/dist/src/core/values.js +36 -1
- package/dist/src/core/values.js.map +1 -1
- package/dist/src/core/verify.d.ts +23 -1
- package/dist/src/core/verify.js +20 -2
- package/dist/src/core/verify.js.map +1 -1
- package/dist/src/daemon/advance.d.ts +10 -0
- package/dist/src/daemon/advance.js +25 -4
- package/dist/src/daemon/advance.js.map +1 -1
- package/dist/src/daemon/audit.d.ts +80 -5
- package/dist/src/daemon/audit.js +126 -6
- package/dist/src/daemon/audit.js.map +1 -1
- package/dist/src/daemon/daemon.d.ts +174 -2
- package/dist/src/daemon/daemon.js +307 -7
- package/dist/src/daemon/daemon.js.map +1 -1
- package/dist/src/daemon/git-evidence.d.ts +2 -2
- package/dist/src/daemon/git-evidence.js +1 -1
- package/dist/src/daemon/projection.d.ts +13 -2
- package/dist/src/daemon/projection.js +39 -6
- package/dist/src/daemon/projection.js.map +1 -1
- package/dist/src/mcp/server.d.ts +39 -3
- package/dist/src/mcp/server.js +30 -3
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/muse/server.d.ts +16 -0
- package/dist/src/muse/server.js +238 -0
- package/dist/src/muse/server.js.map +1 -0
- package/dist/src/serve/archive.d.ts +158 -0
- package/dist/src/serve/archive.js +354 -0
- package/dist/src/serve/archive.js.map +1 -0
- package/dist/src/serve/arguments.d.ts +93 -0
- package/dist/src/serve/arguments.js +228 -0
- package/dist/src/serve/arguments.js.map +1 -0
- package/dist/src/serve/credentials.d.ts +91 -0
- package/dist/src/serve/credentials.js +143 -0
- package/dist/src/serve/credentials.js.map +1 -0
- package/dist/src/serve/follow.d.ts +86 -0
- package/dist/src/serve/follow.js +101 -0
- package/dist/src/serve/follow.js.map +1 -0
- package/dist/src/serve/hook-thread.d.ts +134 -0
- package/dist/src/serve/hook-thread.js +333 -0
- package/dist/src/serve/hook-thread.js.map +1 -0
- package/dist/src/serve/hook-worker.d.ts +134 -0
- package/dist/src/serve/hook-worker.js +173 -0
- package/dist/src/serve/hook-worker.js.map +1 -0
- package/dist/src/serve/server.d.ts +412 -0
- package/dist/src/serve/server.js +1251 -0
- package/dist/src/serve/server.js.map +1 -0
- package/docs/README-extended.md +766 -0
- package/docs/cli-reference.md +2563 -73
- package/docs/codex-enforced-session.md +75 -2
- package/docs/codex-workspace-broker.md +118 -0
- package/package.json +6 -2
- package/schema/event.schema.json +595 -9
- package/schema/fixtures/event/invalid/approval-granted-sender-hashed-false.json +20 -0
- package/schema/fixtures/event/invalid/approval-granted-sender-hashed-raw-id.json +20 -0
- package/schema/fixtures/event/invalid/audit-gesture-refused-human-actor.json +16 -0
- package/schema/fixtures/event/invalid/audit-gesture-refused-no-actor-no-sender.json +15 -0
- package/schema/fixtures/event/invalid/audit-gesture-refused-unknown-gesture.json +16 -0
- package/schema/fixtures/event/invalid/audit-question-preempted-agent-actor.json +16 -0
- package/schema/fixtures/event/invalid/audit-question-preempted-no-question-id.json +16 -0
- package/schema/fixtures/event/invalid/audit-question-preempted-unknown-source.json +15 -0
- package/schema/fixtures/event/invalid/daemon-id-malformed.json +15 -0
- package/schema/fixtures/event/invalid/execution-started-truncated-policy-sha256.json +18 -0
- package/schema/fixtures/event/invalid/gate-path-signed-off-absolute-path.json +14 -0
- package/schema/fixtures/event/invalid/gate-path-signed-off-agent-actor.json +14 -0
- package/schema/fixtures/event/invalid/gate-path-signed-off-missing-path.json +13 -0
- package/schema/fixtures/event/invalid/harness-cap-at-margin.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-fractional.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-negative.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-string.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-without-harness-execution.json +16 -0
- package/schema/fixtures/event/invalid/harness-cap-zero.json +16 -0
- package/schema/fixtures/event/valid/approval-granted-sender-hashed.json +20 -0
- package/schema/fixtures/event/valid/approval-requested-harness-cap.json +18 -0
- package/schema/fixtures/event/valid/audit-gesture-refused-review-note.json +21 -0
- package/schema/fixtures/event/valid/audit-gesture-refused-sender-key-unavailable.json +19 -0
- package/schema/fixtures/event/valid/audit-gesture-refused.json +19 -0
- package/schema/fixtures/event/valid/audit-question-preempted-no-verdict.json +16 -0
- package/schema/fixtures/event/valid/audit-question-preempted.json +20 -0
- package/schema/fixtures/event/valid/daemon-identity-declared.json +17 -0
- package/schema/fixtures/event/valid/daemon-identity.json +15 -0
- package/schema/fixtures/event/valid/execution-started-policy-sha256.json +18 -0
- package/schema/fixtures/event/valid/gate-path-signed-off.json +14 -0
- package/schema/fixtures/event/valid/harness-kind-claude-code.json +23 -0
- package/schema/fixtures/event/valid/harness-kind-codex.json +23 -0
- package/schema/fixtures/event/valid/harness-kind-cursor.json +23 -0
- package/schema/fixtures/event/valid/harness-kind-grok.json +23 -0
- package/schema/fixtures/event/valid/harness-kind-hermes.json +23 -0
- package/schema/fixtures/event/valid/harness-kind-muse.json +23 -0
- package/schema/fixtures/policy/invalid/daemons-malformed-id.json +10 -0
- package/schema/fixtures/policy/invalid/senders-half-keyed.json +20 -0
- package/schema/fixtures/policy/valid/canonical.json +1 -1
- package/schema/fixtures/policy/valid/daemons-allowlist.json +19 -0
- package/schema/fixtures/policy/valid/senders-keyed.json +24 -0
- package/schema/fixtures/policy-md/invalid/schema-invalid-agent-may-request.md +21 -0
- package/schema/fixtures/policy-md/valid/agent-may-request.md +24 -0
- package/schema/fixtures/policy-md/valid/canonical.md +1 -1
- package/schema/fixtures/policy-md/valid/with-values.md +5 -7
- package/schema/fixtures/values/invalid/class-shaped.json +1 -1
- package/schema/fixtures/values/invalid/duplicate-entry.json +1 -1
- package/schema/fixtures/values/invalid/non-string-item.json +1 -1
- package/schema/fixtures/values/invalid/over-cap.json +1 -1
- package/schema/fixtures/values/invalid/unknown-key.json +1 -1
- package/schema/fixtures/values/invalid/version-float.json +1 -0
- package/schema/fixtures/values/invalid/version-integer.json +1 -0
- package/schema/fixtures/values/invalid/version-wrong-string.json +1 -0
- package/schema/fixtures/values/valid/empty-lists.json +2 -3
- package/schema/fixtures/values/valid/full.json +5 -7
- package/schema/fixtures/values/valid/minimal.json +1 -1
- package/schema/fixtures/values-md/invalid/schema-invalid.md +5 -3
- package/schema/fixtures/values-md/invalid/two-blocks.md +3 -3
- package/schema/fixtures/values-md/invalid/unterminated.md +2 -2
- package/schema/fixtures/values-md/invalid/version-1.md +69 -0
- package/schema/fixtures/values-md/invalid/version-unquoted.md +64 -0
- package/schema/fixtures/values-md/invalid/yaml-error.md +2 -2
- package/schema/fixtures/values-md/valid/absent.md +1 -1
- package/schema/fixtures/values-md/valid/with-values.md +5 -7
- package/schema/policy.schema.json +100 -4
- package/schema/values.schema.json +7 -11
- package/schema/fixtures/values/invalid/version-string.json +0 -1
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Telegram webhook receiver (SPEC.md §10.3, APRV-424).
|
|
3
|
+
*
|
|
4
|
+
* §10.3 has always written the channel interface as `notify(request) ->
|
|
5
|
+
* delivery_id`, `poll()/webhook() -> decision`. Until this task only the first
|
|
6
|
+
* half of the second clause existed: the Telegram channel long-polled
|
|
7
|
+
* `getUpdates`, which needs one always-running process per bot and forbids a
|
|
8
|
+
* second poller on the same token. This is the other half. Telegram posts each
|
|
9
|
+
* update to a URL the runtime registered, and a host that sleeps between
|
|
10
|
+
* requests can therefore serve a gate.
|
|
11
|
+
*
|
|
12
|
+
* ## It decides nothing, and it barely reads
|
|
13
|
+
*
|
|
14
|
+
* This file checks one header, one path, one method and one body size, parses
|
|
15
|
+
* JSON, and hands the object to {@link TelegramChannel.deliverUpdate}. Which
|
|
16
|
+
* account the Bot API attributed the tap to, which approver the attested
|
|
17
|
+
* policy maps that account to, whether the gate will take the answer, and what
|
|
18
|
+
* the message is edited to say afterwards are all decided exactly where they
|
|
19
|
+
* were decided before this task existed — in `channels/telegram.ts`'s
|
|
20
|
+
* `handleUpdate` and in `channels/contract.ts`'s `recordChannelDecision`,
|
|
21
|
+
* reached by the same call the poll loop makes. An update that arrives here is
|
|
22
|
+
* an update; it is not evidence about itself.
|
|
23
|
+
*
|
|
24
|
+
* ## The secret is the whole of the authentication
|
|
25
|
+
*
|
|
26
|
+
* A webhook URL is a public endpoint. Anyone who can route to it can POST a
|
|
27
|
+
* JSON object that LOOKS like a tap from the approver's chat, and nothing in
|
|
28
|
+
* the body could tell the difference: SPEC.md §11.1 invariant 4 — a
|
|
29
|
+
* self-reported field never reduces scrutiny — is exactly the rule that makes
|
|
30
|
+
* a body-only check worthless here. So Telegram's own `secret_token` is
|
|
31
|
+
* REQUIRED rather than optional. It is set on `setWebhook`, echoed on every
|
|
32
|
+
* delivery in `X-Telegram-Bot-Api-Secret-Token`, compared here in constant
|
|
33
|
+
* time, and a post without the matching value is refused before its path, its
|
|
34
|
+
* method or its body is looked at.
|
|
35
|
+
*
|
|
36
|
+
* Three consequences, each deliberate:
|
|
37
|
+
*
|
|
38
|
+
* - **The comparison is over SHA-256 digests under `timingSafeEqual`**, which
|
|
39
|
+
* is constant-time AND length-independent (`timingSafeEqual` throws on a
|
|
40
|
+
* length mismatch, which would itself be an oracle for the secret's length).
|
|
41
|
+
* `serve/credentials.ts` states the same argument for the same reason.
|
|
42
|
+
* - **Duplicate headers are refused rather than resolved.** Node joins
|
|
43
|
+
* repeated headers, so two of them would make the value this server compares
|
|
44
|
+
* differ from the one a reader of the request would name. There is no
|
|
45
|
+
* correct choice; the request is malformed.
|
|
46
|
+
* - **A refusal appends NOTHING to the log.** It is counted, complained about
|
|
47
|
+
* on the runner's stderr and answered with its own machine-readable code,
|
|
48
|
+
* and it never reaches the decision path. This is `TELEGRAM_ANOMALY_KINDS`'s
|
|
49
|
+
* rule, and it matters more here than it does there: writing "somebody
|
|
50
|
+
* posted a forgery" into an append-only approval log would let anyone who
|
|
51
|
+
* can reach this URL grow the record a human is asked to trust. A refusal is
|
|
52
|
+
* a fact about this transport, and this transport reports it where facts
|
|
53
|
+
* about transports belong.
|
|
54
|
+
*
|
|
55
|
+
* ## What is reused from `approval serve`, and what could not be
|
|
56
|
+
*
|
|
57
|
+
* The shape is APRV-421's, deliberately: loopback bind by default with the
|
|
58
|
+
* operator's proxy terminating TLS, the credential checked before the URL is
|
|
59
|
+
* parsed, a refusal carried as a body with a frozen code rather than as a bare
|
|
60
|
+
* status, an oversized body drained before the refusal is written (a response
|
|
61
|
+
* written while the client is still uploading closes the socket under it), and
|
|
62
|
+
* no state a restart loses.
|
|
63
|
+
*
|
|
64
|
+
* Its CODE could not be reused, and the reason is structural rather than
|
|
65
|
+
* stylistic. `src/serve/` imports `src/cli/`, which imports `src/channels/`;
|
|
66
|
+
* an import from here back into `src/serve/` would close an ESM cycle, which
|
|
67
|
+
* is not a compile error but a binding that is `undefined` in one direction on
|
|
68
|
+
* the day module initialisation order changes. `tests/layering.test.ts` pins
|
|
69
|
+
* that direction. The two servers are also different surfaces: that one
|
|
70
|
+
* answers two bearer credentials for two parties across six routes, this one
|
|
71
|
+
* answers Telegram's echoed secret on one.
|
|
72
|
+
*
|
|
73
|
+
* ## One update at a time
|
|
74
|
+
*
|
|
75
|
+
* Deliveries are serialized through a promise chain. `handleUpdate` keeps a
|
|
76
|
+
* single callback-query ack slot and the delivery maps are plain `Map`s, so
|
|
77
|
+
* two updates handled concurrently would interleave in ways nobody ordered.
|
|
78
|
+
* The poll loop gets this for free by awaiting each update in turn; an HTTP
|
|
79
|
+
* server has to say it.
|
|
80
|
+
*
|
|
81
|
+
* ## Stopping waits for the update it is holding
|
|
82
|
+
*
|
|
83
|
+
* A stop closes the listener, drains what is in flight, and only then drops
|
|
84
|
+
* the sockets. The first version of this file did the opposite — it destroyed
|
|
85
|
+
* every socket synchronously and resolved in about a millisecond — so
|
|
86
|
+
* `deleteWebhook`, the "stopped" line and the process's exit all ran while an
|
|
87
|
+
* update was still being handled: an append in progress, a dispatch cycle
|
|
88
|
+
* halfway through a send, and a caller whose response never arrived. The order
|
|
89
|
+
* is now stop accepting, drain, drop, and the verb's own stop path does its
|
|
90
|
+
* `deleteWebhook` after this resolves rather than beside it.
|
|
91
|
+
*
|
|
92
|
+
* ## Redelivery is safe because the gate is
|
|
93
|
+
*
|
|
94
|
+
* Telegram retries a delivery it did not get a 2xx for, so the same tap can
|
|
95
|
+
* arrive twice. Nothing here deduplicates, and nothing needs to: the second
|
|
96
|
+
* one reaches `decide()` through the same path as the first and is refused
|
|
97
|
+
* `already-decided` with the first human answer standing. That is the same
|
|
98
|
+
* property a button pressed twice has had since APRV-26.
|
|
99
|
+
*/
|
|
100
|
+
import { createHash, timingSafeEqual } from "node:crypto";
|
|
101
|
+
import { createServer } from "node:http";
|
|
102
|
+
/**
|
|
103
|
+
* The header Telegram echoes the `secret_token` in, lowercased as Node
|
|
104
|
+
* presents it.
|
|
105
|
+
*/
|
|
106
|
+
export const TELEGRAM_SECRET_HEADER = "x-telegram-bot-api-secret-token";
|
|
107
|
+
/** The path this server answers when the operator names none. */
|
|
108
|
+
export const TELEGRAM_WEBHOOK_DEFAULT_PATH = "/telegram/webhook";
|
|
109
|
+
/** The interface it binds when the operator names none. Loopback, always. */
|
|
110
|
+
export const TELEGRAM_WEBHOOK_DEFAULT_HOST = "127.0.0.1";
|
|
111
|
+
/** The port it binds when the operator names none. */
|
|
112
|
+
export const TELEGRAM_WEBHOOK_DEFAULT_PORT = 4683;
|
|
113
|
+
/**
|
|
114
|
+
* Largest body accepted. A Telegram update is a small JSON object; the
|
|
115
|
+
* largest thing one can carry is a message's 4096-character text.
|
|
116
|
+
*/
|
|
117
|
+
export const TELEGRAM_WEBHOOK_MAX_BODY_BYTES = 256 * 1024;
|
|
118
|
+
/**
|
|
119
|
+
* How much of an oversized body is read and thrown away before the socket is
|
|
120
|
+
* dropped. `serve/server.ts` carries the argument: the refusal cannot be
|
|
121
|
+
* written until the client stops writing, or the response closes the socket
|
|
122
|
+
* under it and the caller sees a transport error instead of a refusal.
|
|
123
|
+
*/
|
|
124
|
+
export const TELEGRAM_WEBHOOK_DRAIN_LIMIT_BYTES = 4 * 1024 * 1024;
|
|
125
|
+
/**
|
|
126
|
+
* How long {@link TelegramWebhookHandle.close} waits for requests already in
|
|
127
|
+
* flight before it destroys their sockets (APRV-424, review finding 2).
|
|
128
|
+
*
|
|
129
|
+
* A stop has to end. What this bounds is the pathological case — a client that
|
|
130
|
+
* opened a request and stopped writing, a handler that will never return — and
|
|
131
|
+
* not the ordinary one, which is an update whose decision is being appended
|
|
132
|
+
* and takes milliseconds. The ordinary one is what the drain exists for: the
|
|
133
|
+
* first version of this file destroyed every socket synchronously and
|
|
134
|
+
* resolved in about a millisecond, so `deleteWebhook`, the "stopped" line and
|
|
135
|
+
* the process's exit all happened while an append and a dispatch were still
|
|
136
|
+
* running.
|
|
137
|
+
*/
|
|
138
|
+
export const TELEGRAM_WEBHOOK_CLOSE_TIMEOUT_MS = 10_000;
|
|
139
|
+
/** How long the close drain waits between checks of the in-flight count. */
|
|
140
|
+
export const TELEGRAM_WEBHOOK_DRAIN_POLL_MS = 5;
|
|
141
|
+
/**
|
|
142
|
+
* Everything this receiver refuses, and nothing else. Frozen, per SPEC.md
|
|
143
|
+
* §11.1 invariant 6: a refusal is machine-readable and distinct.
|
|
144
|
+
*
|
|
145
|
+
* Distinct because the repairs are distinct. A secret mismatch is a forgery or
|
|
146
|
+
* a rotation the registration did not follow; an unknown path is a URL that
|
|
147
|
+
* does not match the one registered; a body too large is a caller that is not
|
|
148
|
+
* Telegram. None of them is a decision, and none of them is the same fact.
|
|
149
|
+
*/
|
|
150
|
+
export const TELEGRAM_WEBHOOK_REFUSAL_CODES = [
|
|
151
|
+
/** No `X-Telegram-Bot-Api-Secret-Token`, or one that is not the registered value. */
|
|
152
|
+
"webhook-secret-mismatch",
|
|
153
|
+
/** More than one secret header. Malformed, and not resolvable by choosing. */
|
|
154
|
+
"webhook-duplicate-secret-header",
|
|
155
|
+
/**
|
|
156
|
+
* The request line and the `Host` header do not form a URL (400).
|
|
157
|
+
*
|
|
158
|
+
* Split from `webhook-unknown-path` in the review of this task: one code for
|
|
159
|
+
* both collapsed "what arrived is not a request this server can parse" into
|
|
160
|
+
* "you are posting to a path this server does not serve", and those two
|
|
161
|
+
* share nothing but a status family — the first is a broken proxy or a
|
|
162
|
+
* hand-written client, the second is a registration that does not match the
|
|
163
|
+
* bind. `src/serve/server.ts` makes the same split, between
|
|
164
|
+
* `serve-malformed-url` and `serve-unknown-path`.
|
|
165
|
+
*/
|
|
166
|
+
"webhook-malformed-request",
|
|
167
|
+
/** A path this server does not serve (404). */
|
|
168
|
+
"webhook-unknown-path",
|
|
169
|
+
/** The right path, the wrong method. Telegram POSTs. */
|
|
170
|
+
"webhook-method-not-allowed",
|
|
171
|
+
/** Larger than {@link TELEGRAM_WEBHOOK_MAX_BODY_BYTES}. */
|
|
172
|
+
"webhook-body-too-large",
|
|
173
|
+
/** The body could not be read off the socket, or is not JSON. */
|
|
174
|
+
"webhook-body-unreadable",
|
|
175
|
+
/** JSON that is not an Update object. */
|
|
176
|
+
"webhook-not-an-update",
|
|
177
|
+
/** The channel is already receiving updates by the other transport. */
|
|
178
|
+
"webhook-transport-conflict",
|
|
179
|
+
/** The channel threw while handling the update. Nothing is claimed about the log. */
|
|
180
|
+
"webhook-handler-failed",
|
|
181
|
+
];
|
|
182
|
+
function emptyRefusals() {
|
|
183
|
+
return {
|
|
184
|
+
"webhook-secret-mismatch": 0,
|
|
185
|
+
"webhook-duplicate-secret-header": 0,
|
|
186
|
+
"webhook-malformed-request": 0,
|
|
187
|
+
"webhook-unknown-path": 0,
|
|
188
|
+
"webhook-method-not-allowed": 0,
|
|
189
|
+
"webhook-body-too-large": 0,
|
|
190
|
+
"webhook-body-unreadable": 0,
|
|
191
|
+
"webhook-not-an-update": 0,
|
|
192
|
+
"webhook-transport-conflict": 0,
|
|
193
|
+
"webhook-handler-failed": 0,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function digest(value) {
|
|
197
|
+
return createHash("sha256").update(value, "utf8").digest();
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Is `offered` the registered secret?
|
|
201
|
+
*
|
|
202
|
+
* Both sides are hashed first, so the comparison is over two equal-length
|
|
203
|
+
* buffers and `timingSafeEqual` can never throw — which is what keeps the
|
|
204
|
+
* length of the real secret out of the answer.
|
|
205
|
+
*/
|
|
206
|
+
export function secretMatches(expected, offered) {
|
|
207
|
+
if (offered === null)
|
|
208
|
+
return false;
|
|
209
|
+
return timingSafeEqual(digest(expected), digest(offered));
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* The one secret header a request offered, `null` for none, and `"duplicate"`
|
|
213
|
+
* for more than one.
|
|
214
|
+
*
|
|
215
|
+
* Read off `rawHeaders` rather than `headers`, because Node joins repeats of a
|
|
216
|
+
* non-special header into one comma-separated value and the join is exactly
|
|
217
|
+
* what hides the second one.
|
|
218
|
+
*/
|
|
219
|
+
export function secretHeaderOf(rawHeaders) {
|
|
220
|
+
const values = [];
|
|
221
|
+
for (let index = 0; index + 1 < rawHeaders.length; index += 2) {
|
|
222
|
+
const name = rawHeaders[index];
|
|
223
|
+
if (name !== undefined && name.toLowerCase() === TELEGRAM_SECRET_HEADER) {
|
|
224
|
+
values.push(rawHeaders[index + 1] ?? "");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (values.length === 0)
|
|
228
|
+
return null;
|
|
229
|
+
if (values.length > 1)
|
|
230
|
+
return "duplicate";
|
|
231
|
+
return values[0] ?? "";
|
|
232
|
+
}
|
|
233
|
+
/** Read one request body, bounded, draining an oversized one before refusing. */
|
|
234
|
+
async function readBody(req) {
|
|
235
|
+
const chunks = [];
|
|
236
|
+
let size = 0;
|
|
237
|
+
let oversized = false;
|
|
238
|
+
const tooLarge = {
|
|
239
|
+
ok: false,
|
|
240
|
+
code: "webhook-body-too-large",
|
|
241
|
+
message: `request body exceeds ${String(TELEGRAM_WEBHOOK_MAX_BODY_BYTES)} bytes`,
|
|
242
|
+
};
|
|
243
|
+
try {
|
|
244
|
+
for await (const chunk of req) {
|
|
245
|
+
const buffer = chunk;
|
|
246
|
+
size += buffer.length;
|
|
247
|
+
if (size > TELEGRAM_WEBHOOK_MAX_BODY_BYTES) {
|
|
248
|
+
// Discarded as it arrives rather than returned here: a response
|
|
249
|
+
// written while the client is still uploading closes the socket under
|
|
250
|
+
// it, and a caller that got a transport error has no refusal to read.
|
|
251
|
+
// Bounded twice over — past the drain limit the socket is simply
|
|
252
|
+
// dropped, because a caller that streams forever is not owed a polite
|
|
253
|
+
// answer.
|
|
254
|
+
oversized = true;
|
|
255
|
+
chunks.length = 0;
|
|
256
|
+
if (size > TELEGRAM_WEBHOOK_DRAIN_LIMIT_BYTES) {
|
|
257
|
+
req.destroy();
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
chunks.push(buffer);
|
|
263
|
+
}
|
|
264
|
+
if (oversized)
|
|
265
|
+
return tooLarge;
|
|
266
|
+
}
|
|
267
|
+
catch (cause) {
|
|
268
|
+
if (oversized)
|
|
269
|
+
return tooLarge;
|
|
270
|
+
return {
|
|
271
|
+
ok: false,
|
|
272
|
+
code: "webhook-body-unreadable",
|
|
273
|
+
message: cause instanceof Error ? cause.message : String(cause),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
return { ok: true, text: Buffer.concat(chunks).toString("utf8") };
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Start the receiver. Resolves once it is bound and its real port is known;
|
|
280
|
+
* rejects when the bind fails, so the verb can report it and exit.
|
|
281
|
+
*
|
|
282
|
+
* The channel's transport is claimed BEFORE the listener exists, for the
|
|
283
|
+
* reason `approval serve` settles its credentials first: a server that bound
|
|
284
|
+
* and then discovered it could not receive would be a server Telegram had
|
|
285
|
+
* already posted to.
|
|
286
|
+
*/
|
|
287
|
+
export async function serveTelegramWebhook(options) {
|
|
288
|
+
const claim = options.channel.claimTransport("webhook");
|
|
289
|
+
if (!claim.ok)
|
|
290
|
+
throw new Error(claim.message);
|
|
291
|
+
const path = options.path ?? TELEGRAM_WEBHOOK_DEFAULT_PATH;
|
|
292
|
+
const host = options.host ?? TELEGRAM_WEBHOOK_DEFAULT_HOST;
|
|
293
|
+
const complain = options.log ??
|
|
294
|
+
((message) => {
|
|
295
|
+
process.stderr.write(`${message}\n`);
|
|
296
|
+
});
|
|
297
|
+
const stats = { requests: 0, updates: 0, refusals: emptyRefusals() };
|
|
298
|
+
const sockets = new Set();
|
|
299
|
+
/**
|
|
300
|
+
* Requests accepted and not yet answered.
|
|
301
|
+
*
|
|
302
|
+
* The serialize queue alone is not the whole of "busy": a request that is
|
|
303
|
+
* still having its body read off the socket, or one whose refusal has been
|
|
304
|
+
* decided and not yet written, is on no queue and would be torn off by a
|
|
305
|
+
* socket destroy. So the drain waits for this to reach zero as well.
|
|
306
|
+
*/
|
|
307
|
+
let inFlight = 0;
|
|
308
|
+
let closed = null;
|
|
309
|
+
// One update at a time, for the reason the module doc gives: `handleUpdate`
|
|
310
|
+
// holds a single ack slot and the delivery maps are not reentrant. A plain
|
|
311
|
+
// promise chain, because the ordering wanted is arrival order and nothing
|
|
312
|
+
// cleverer.
|
|
313
|
+
let queue = Promise.resolve();
|
|
314
|
+
function serialize(work) {
|
|
315
|
+
const next = queue.then(work, work);
|
|
316
|
+
queue = next.then(() => undefined, () => undefined);
|
|
317
|
+
return next;
|
|
318
|
+
}
|
|
319
|
+
function send(res, status, body) {
|
|
320
|
+
const bytes = Buffer.from(JSON.stringify(body), "utf8");
|
|
321
|
+
res.writeHead(status, { "content-type": "application/json", "content-length": bytes.length });
|
|
322
|
+
res.end(bytes);
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* A refusal: counted, said on stderr, and answered with its code.
|
|
326
|
+
*
|
|
327
|
+
* Never an event. See the module doc: an unauthenticated endpoint that could
|
|
328
|
+
* append would be an endpoint anybody could use to pad the log.
|
|
329
|
+
*/
|
|
330
|
+
function refuse(res, status, code, message) {
|
|
331
|
+
stats.refusals[code] += 1;
|
|
332
|
+
complain(`approval: telegram webhook refused a post (${code}): ${message}`);
|
|
333
|
+
send(res, status, { error: { code, message } });
|
|
334
|
+
}
|
|
335
|
+
const http = createServer((req, res) => {
|
|
336
|
+
void (async () => {
|
|
337
|
+
stats.requests += 1;
|
|
338
|
+
inFlight += 1;
|
|
339
|
+
try {
|
|
340
|
+
// THE SECRET FIRST, before the path, the method or the body. A caller
|
|
341
|
+
// without it learns only that something refused: no path is confirmed
|
|
342
|
+
// to exist, no method is named, and no body is read off the socket.
|
|
343
|
+
const offered = secretHeaderOf(req.rawHeaders);
|
|
344
|
+
if (offered === "duplicate") {
|
|
345
|
+
refuse(res, 401, "webhook-duplicate-secret-header", `this request carries more than one ${TELEGRAM_SECRET_HEADER} header. Exactly one is accepted: where there are several, the one this server would check is not the one a reader of the request would name`);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (!secretMatches(options.secret, offered)) {
|
|
349
|
+
refuse(res, 401, "webhook-secret-mismatch", offered === null
|
|
350
|
+
? `no ${TELEGRAM_SECRET_HEADER} header. Every delivery Telegram makes to this URL carries the secret_token the runtime registered; a post without it decides nothing, and is counted and reported here as a refusal rather than written to the log`
|
|
351
|
+
: `the ${TELEGRAM_SECRET_HEADER} header is not the registered secret_token. No decision reached the gate, and nothing was appended`);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
let url;
|
|
355
|
+
try {
|
|
356
|
+
url = new URL(req.url ?? "/", `http://${req.headers.host ?? host}`);
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
refuse(res, 400, "webhook-malformed-request", "the request line and Host header do not form a URL this server can parse");
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (url.pathname !== path) {
|
|
363
|
+
refuse(res, 404, "webhook-unknown-path", `no endpoint at ${url.pathname}`);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const method = req.method ?? "GET";
|
|
367
|
+
if (method !== "POST") {
|
|
368
|
+
refuse(res, 405, "webhook-method-not-allowed", `${path} answers POST, not ${method}`);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const body = await readBody(req);
|
|
372
|
+
if (!body.ok) {
|
|
373
|
+
refuse(res, body.code === "webhook-body-too-large" ? 413 : 400, body.code, body.message);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
let parsed;
|
|
377
|
+
try {
|
|
378
|
+
parsed = JSON.parse(body.text);
|
|
379
|
+
}
|
|
380
|
+
catch (cause) {
|
|
381
|
+
refuse(res, 400, "webhook-body-unreadable", `request body is not JSON: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
385
|
+
refuse(res, 400, "webhook-not-an-update", "request body is not a Telegram Update object");
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
// Handled BEFORE the response, deliberately. Telegram retries a
|
|
389
|
+
// delivery it got no 2xx for, and answering first would mean the retry
|
|
390
|
+
// raced the gate rather than finding a decided request; the redelivery
|
|
391
|
+
// that does happen is refused `already-decided` with the first human
|
|
392
|
+
// answer standing.
|
|
393
|
+
let result;
|
|
394
|
+
try {
|
|
395
|
+
result = await serialize(async () => {
|
|
396
|
+
const handled = await options.channel.deliverUpdate(parsed);
|
|
397
|
+
stats.updates += handled.updates;
|
|
398
|
+
if (options.afterUpdate !== undefined) {
|
|
399
|
+
try {
|
|
400
|
+
await options.afterUpdate(handled);
|
|
401
|
+
}
|
|
402
|
+
catch (cause) {
|
|
403
|
+
complain(`approval: telegram webhook: the dispatch cycle after an update failed (${cause instanceof Error ? cause.message : String(cause)}); the update was handled and the listener is still up`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return handled;
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
catch (cause) {
|
|
410
|
+
// The channel threw. Whatever the gate appended has already
|
|
411
|
+
// happened, so this says where to look rather than what happened —
|
|
412
|
+
// the wording `TELEGRAM_HANDLER_FAILED` uses, for the same reason.
|
|
413
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
414
|
+
// `in` rather than a property read, so a thrown null or a thrown
|
|
415
|
+
// string cannot turn this diagnostic into a second failure.
|
|
416
|
+
const conflict = typeof cause === "object" &&
|
|
417
|
+
cause !== null &&
|
|
418
|
+
"code" in cause &&
|
|
419
|
+
cause.code === "transport-conflict";
|
|
420
|
+
refuse(res, conflict ? 409 : 500, conflict ? "webhook-transport-conflict" : "webhook-handler-failed", message);
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
send(res, 200, {
|
|
424
|
+
ok: true,
|
|
425
|
+
updates: result.updates,
|
|
426
|
+
decisions: result.outcomes.length,
|
|
427
|
+
ignored: result.ignored.length,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
catch (cause) {
|
|
431
|
+
if (!res.headersSent) {
|
|
432
|
+
refuse(res, 500, "webhook-handler-failed", `the request failed: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
res.end();
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
finally {
|
|
439
|
+
inFlight -= 1;
|
|
440
|
+
}
|
|
441
|
+
})();
|
|
442
|
+
});
|
|
443
|
+
http.on("connection", (socket) => {
|
|
444
|
+
sockets.add(socket);
|
|
445
|
+
socket.on("close", () => sockets.delete(socket));
|
|
446
|
+
});
|
|
447
|
+
await new Promise((settle, fail) => {
|
|
448
|
+
const onError = (cause) => fail(cause);
|
|
449
|
+
http.once("error", onError);
|
|
450
|
+
http.listen(options.port ?? TELEGRAM_WEBHOOK_DEFAULT_PORT, host, () => {
|
|
451
|
+
http.off("error", onError);
|
|
452
|
+
settle();
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
const address = http.address();
|
|
456
|
+
const boundPort = typeof address === "object" && address !== null
|
|
457
|
+
? address.port
|
|
458
|
+
: (options.port ?? TELEGRAM_WEBHOOK_DEFAULT_PORT);
|
|
459
|
+
const boundHost = typeof address === "object" && address !== null ? address.address : host;
|
|
460
|
+
/**
|
|
461
|
+
* Wait for the work already accepted, bounded.
|
|
462
|
+
*
|
|
463
|
+
* Two things are waited on, and both are needed. `serialize` empties the
|
|
464
|
+
* update queue, which is where a `deliverUpdate` and the dispatch cycle that
|
|
465
|
+
* follows it run; `inFlight` covers the rest of a request's life, from the
|
|
466
|
+
* body read to the byte the response ends with. A request that never
|
|
467
|
+
* finishes is what the deadline is for: past it the sockets go, because a
|
|
468
|
+
* stop that could be held open forever is not a stop.
|
|
469
|
+
*/
|
|
470
|
+
async function drain(timeoutMs) {
|
|
471
|
+
const deadline = Date.now() + timeoutMs;
|
|
472
|
+
const giveUp = () => {
|
|
473
|
+
complain(`approval: telegram webhook: ${String(inFlight)} request(s) were still in flight after ${String(timeoutMs)}ms; closing anyway`);
|
|
474
|
+
};
|
|
475
|
+
for (;;) {
|
|
476
|
+
const remaining = deadline - Date.now();
|
|
477
|
+
// Giving up leaves a pending handler where it is. Nothing holds the
|
|
478
|
+
// event loop open for it (the sockets are destroyed below, and both
|
|
479
|
+
// timers here are unref'd), and `cli.js` sets `process.exitCode` rather
|
|
480
|
+
// than calling `process.exit()`, so the process ends by the normal path
|
|
481
|
+
// once the loop drains rather than being cut off mid-write.
|
|
482
|
+
if (remaining <= 0)
|
|
483
|
+
return giveUp();
|
|
484
|
+
// The deadline covers the QUEUE WAIT as well, and that is not a detail:
|
|
485
|
+
// a handler that never returns leaves work on the serialize chain
|
|
486
|
+
// forever, so a drain that simply awaited the chain would be the hang it
|
|
487
|
+
// was meant to bound.
|
|
488
|
+
const quiet = await Promise.race([
|
|
489
|
+
serialize(async () => true),
|
|
490
|
+
new Promise((settle) => {
|
|
491
|
+
setTimeout(() => settle(false), remaining).unref?.();
|
|
492
|
+
}),
|
|
493
|
+
]);
|
|
494
|
+
if (!quiet)
|
|
495
|
+
return giveUp();
|
|
496
|
+
if (inFlight === 0)
|
|
497
|
+
return;
|
|
498
|
+
await new Promise((settle) => {
|
|
499
|
+
setTimeout(settle, TELEGRAM_WEBHOOK_DRAIN_POLL_MS).unref?.();
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
async function stop() {
|
|
504
|
+
// 1. Stop accepting. `close` refuses new connections at once and calls
|
|
505
|
+
// back only once every existing one is gone, so the callback is the
|
|
506
|
+
// thing to await and not the thing to race.
|
|
507
|
+
const accepted = new Promise((settle) => {
|
|
508
|
+
http.close(() => settle());
|
|
509
|
+
});
|
|
510
|
+
// 2. Let what is in flight finish. This is the whole of the fix: an update
|
|
511
|
+
// mid-append keeps its socket, writes its response, and only then is
|
|
512
|
+
// anything torn down — so `deleteWebhook` and the "stopped" line, which
|
|
513
|
+
// the caller runs after this resolves, cannot land inside an append.
|
|
514
|
+
await drain(options.closeTimeoutMs ?? TELEGRAM_WEBHOOK_CLOSE_TIMEOUT_MS);
|
|
515
|
+
// 3. Now the idle ones. A keep-alive socket with nothing on it would hold
|
|
516
|
+
// the callback above open until its own timeout, which is a stop that
|
|
517
|
+
// looks like a hang.
|
|
518
|
+
for (const socket of sockets)
|
|
519
|
+
socket.destroy();
|
|
520
|
+
sockets.clear();
|
|
521
|
+
await accepted;
|
|
522
|
+
}
|
|
523
|
+
return {
|
|
524
|
+
host: boundHost,
|
|
525
|
+
port: boundPort,
|
|
526
|
+
path,
|
|
527
|
+
stats: () => ({ ...stats, refusals: { ...stats.refusals } }),
|
|
528
|
+
serialize,
|
|
529
|
+
inFlight: () => inFlight,
|
|
530
|
+
close: () => {
|
|
531
|
+
closed ??= stop();
|
|
532
|
+
return closed;
|
|
533
|
+
},
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
//# sourceMappingURL=telegram-webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telegram-webhook.js","sourceRoot":"","sources":["../../../src/channels/telegram-webhook.ts"],"names":[],"mappings":"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"}
|