Add Telegram integration#2
Conversation
- Add logger middleware for structured logging - Introduce error handling middleware for unhandled errors - Update environment validation to use logger - Create logger package with tests and configuration
- Add HttpClient for API requests with error handling - Implement Logger for better debugging and error tracking - Update error handlers to utilize new logging mechanism
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cc977b926e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| await next(); | ||
|
|
||
| // Set the request ID after downstream middleware so cache snapshots never persist a stale ID. | ||
| c.header("X-Request-Id", requestId); |
There was a problem hiding this comment.
Preserve request-finalization in error paths
If any downstream handler throws, execution exits at await next() and the middleware never sets X-Request-Id or emits the request completed log. In those failure scenarios, clients lose the request ID needed for incident correlation and observability misses completion records for errored requests. Move finalization work behind a try/finally so it runs for both success and error responses.
Useful? React with 👍 / 👎.
|
|
||
| const response = await fetch(url, init); | ||
|
|
||
| const json: unknown = await response.json(); |
There was a problem hiding this comment.
Guard JSON parsing for non-JSON error responses
The client parses the body with response.json() before checking response.ok, so upstream 4xx/5xx responses that are empty or non-JSON throw a SyntaxError instead of HttpClientError. This breaks callers that intentionally catch HttpClientError (for example scripts/set-webhook.ts) and prevents graceful error handling/reporting in those failure modes. Parse defensively (or fall back to text) and still raise HttpClientError with status information.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
Adds a Telegram integration to the operator Cloudflare Worker, including shared workspace packages for structured logging and a small Zod-validated HTTP client, plus middleware for request logging and centralized error handling.
Changes:
- Added new workspace packages:
@repo/logger(structured JSON logs) and@repo/http-client(fetch wrapper with Zod parsing + logging). - Implemented Telegram webhook route/controller/service in
apps/operator, plus request logging and error-handling middleware. - Added a
set-webhookscript and updated workspace/docs/gitignore to support new env vars and local/prod vars files.
Reviewed changes
Copilot reviewed 37 out of 39 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-workspace.yaml | Expands workspace to include packages/*. |
| pnpm-lock.yaml | Adds deps for new packages, Vitest, tsx, validator, and Zod. |
| packages/logger/vitest.config.ts | Adds Vitest config for logger package. |
| packages/logger/tsconfig.json | Adds package TS config (incl. vitest globals). |
| packages/logger/src/types.ts | Defines logger types (levels, config, entries). |
| packages/logger/src/logger.ts | Implements structured JSON logger. |
| packages/logger/src/tests/logger.test.ts | Adds unit tests for logger behavior and filtering. |
| packages/logger/package.json | Declares @repo/logger workspace package and scripts. |
| packages/logger/eslint.config.ts | Enables shared ESLint config for logger package. |
| packages/http-client/vitest.config.ts | Adds Vitest config for http-client package. |
| packages/http-client/tsconfig.json | Adds package TS config (incl. vitest globals). |
| packages/http-client/src/types.ts | Defines http-client config and request option types. |
| packages/http-client/src/http-client.ts | Implements fetch wrapper + Zod parsing + logging + error type. |
| packages/http-client/src/tests/http-client.test.ts | Adds unit tests for http-client requests/errors/logging. |
| packages/http-client/package.json | Declares @repo/http-client workspace package and deps. |
| packages/http-client/eslint.config.ts | Enables shared ESLint config for http-client package. |
| docs/plan.md | Adds implementation plan/architecture documentation. |
| apps/operator/src/types/telegram.ts | Adds Zod schemas/types for Telegram payloads and API responses. |
| apps/operator/src/types/env.ts | Adds env schema and typed Hono environment bindings/variables. |
| apps/operator/src/services/telegram.ts | Adds Telegram API service built on @repo/http-client. |
| apps/operator/src/services/telegram.test.ts | Adds tests for Telegram service requests and error handling. |
| apps/operator/src/modules/telegram/routes.ts | Adds /webhook/telegram route with Zod validation. |
| apps/operator/src/modules/telegram/routes.test.ts | Adds route-level tests for auth/validation/echo behavior. |
| apps/operator/src/modules/telegram/controller.ts | Implements webhook auth, allowlist, and echo behavior. |
| apps/operator/src/modules/health/routes.ts | Moves health endpoint into module route. |
| apps/operator/src/modules/health/controller.ts | Implements health controller. |
| apps/operator/src/middleware/logger.ts | Adds request-scoped logger + requestId + request logs. |
| apps/operator/src/middleware/error-handlers.ts | Adds centralized error handler + not-found handler. |
| apps/operator/src/middleware/env.ts | Adds runtime env validation middleware with logging. |
| apps/operator/src/middleware/tests/logger.test.ts | Adds tests for logger middleware and onError handler. |
| apps/operator/src/index.ts | Wires middleware, routes, and handlers into the Hono app. |
| apps/operator/scripts/tsconfig.json | Adds TS config for Node scripts. |
| apps/operator/scripts/set-webhook.ts | Adds CLI script to register Telegram webhook using local vars files. |
| apps/operator/package.json | Switches tests to Vitest; adds deps and set-webhook script. |
| apps/operator/eslint.config.ts | Wraps base ESLint config and ignores scripts/**. |
| apps/operator/README.md | Adds operator setup/dev/prod docs and usage instructions. |
| README.md | Expands root README with stack/structure and links to operator docs. |
| .vscode/settings.json | Updates Copilot enablement settings for dotenv/vars files. |
| .gitignore | Ignores .prod.vars, .env, and .wrangler in addition to .dev.vars. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| await next(); | ||
|
|
||
| // Set the request ID after downstream middleware so cache snapshots never persist a stale ID. | ||
| c.header("X-Request-Id", requestId); | ||
|
|
||
| const duration = Date.now() - start; | ||
| const status = c.res.status; | ||
|
|
||
| logger.info("request completed", { | ||
| requestId, | ||
| method, | ||
| path, | ||
| status, | ||
| duration, | ||
| }); |
There was a problem hiding this comment.
If downstream handlers throw (or an HTTPException is raised), await next() will reject and the middleware won’t reach the X-Request-Id header assignment or the "request completed" log. Wrapping await next() in a try/finally (and optionally logging completion in the finally) will ensure the request ID is always returned and timing/status are logged consistently, including error paths.
| await next(); | |
| // Set the request ID after downstream middleware so cache snapshots never persist a stale ID. | |
| c.header("X-Request-Id", requestId); | |
| const duration = Date.now() - start; | |
| const status = c.res.status; | |
| logger.info("request completed", { | |
| requestId, | |
| method, | |
| path, | |
| status, | |
| duration, | |
| }); | |
| let status: number | undefined; | |
| try { | |
| await next(); | |
| status = c.res.status; | |
| } finally { | |
| // Set the request ID after downstream middleware so cache snapshots never persist a stale ID. | |
| c.header("X-Request-Id", requestId); | |
| const duration = Date.now() - start; | |
| if (status === undefined) { | |
| status = c.res.status; | |
| } | |
| logger.info("request completed", { | |
| requestId, | |
| method, | |
| path, | |
| status, | |
| duration, | |
| }); | |
| } |
|
|
||
| type WebhookInput = { | ||
| in: { json: TelegramUpdate }; | ||
| out: { json: TelegramUpdate }; |
There was a problem hiding this comment.
WebhookInput declares out: { json: TelegramUpdate }, but this handler returns { ok: true } and { error: "Unauthorized" } JSON responses. This output typing is misleading and can break type inference for callers; consider removing the out type entirely or updating it to match the actual response shapes.
| out: { json: TelegramUpdate }; | |
| out: { json: { ok: true } | { error: "Unauthorized" } }; |
| logger.debug("incoming update", { update }); | ||
| logger.debug("chat authorization check", { | ||
| chatId: message?.chat.id, | ||
| allowedChatId: c.env.ALLOWED_CHAT_ID, | ||
| }); | ||
|
|
||
| if (!message?.text) { | ||
| return c.json({ ok: true }); | ||
| } | ||
|
|
||
| const chatId = message.chat.id; | ||
| logger.info("message received", { text: message.text, chatId }); | ||
|
|
There was a problem hiding this comment.
Current logging captures the full Telegram update object and the raw message text. Since the logger defaults to debug level, this will be emitted by default and can leak user-provided content/PII into logs. Consider logging only minimal identifiers (e.g., update_id/chatId) and/or redacting text, and making the minimum log level configurable (e.g., via an env var) so debug payload logs aren’t enabled in production by default.
| const json: unknown = await response.json(); | ||
|
|
There was a problem hiding this comment.
response.json() is called unconditionally. This will throw for empty bodies (e.g., 204) or non-JSON responses, which means callers may get a generic parsing error instead of an HttpClientError with status/body context. Consider parsing defensively (e.g., read text first and JSON-parse conditionally/with try-catch) and include the raw response content in the thrown error/logs when JSON parsing fails.
| const json: unknown = await response.json(); | |
| const text = await response.text(); | |
| let json: unknown = null; | |
| if (text !== "") { | |
| try { | |
| json = JSON.parse(text); | |
| } catch (error) { | |
| this.logger.error("failed to parse JSON response", { | |
| method, | |
| path, | |
| status: response.status, | |
| body: text, | |
| error: | |
| error instanceof Error ? error.message : String(error), | |
| }); | |
| throw new HttpClientError(response.status, text); | |
| } | |
| } |
- Update pnpm action to v5 in GitHub Actions setup - Improve logging in Telegram webhook handler - Add tests for non-JSON error responses in HttpClient
- Clarify package workspaces in AGENTS.md - Enhance README.md with setup instructions and command details
…bhook - Implement verifyTelegramSecret middleware to check secret token - Add enforceTelegramBodyLimit middleware to restrict payload size - Update tests to cover new middleware functionality
What
This update introduces Telegram integration along with a structured logging middleware and error handling for improved debugging. The Telegram service allows sending messages and setting webhooks, while the logger middleware captures request details and errors. The changes enhance the application's robustness and maintainability.
Implemented Telegram service for API interactions.
Added logger middleware for structured logging of requests and errors.
Integrated error handling middleware for unhandled errors.
Updated environment validation to utilize the logger.
How to test
Run
pnpm startto start the application and test the Telegram integration.Use the Telegram bot to send messages and verify the logging output.
Execute
pnpm testto ensure all tests pass.Security review
Secrets / env vars: changed. (New environment variables for Telegram integration.)
Auth / session: not changed.
Network / API calls: changed. (New API calls to Telegram.)
Data handling / PII: changed. (Logging request and error details.)
Dependencies: added. (New dependencies for Telegram and logging functionality.)
Justification:
Introduced new features that require additional environment variables.
Enhanced logging and error handling improves overall security posture.