Skip to content

Commit 96671b4

Browse files
committed
Refactor bot worker routing with Hono
1 parent 7e36609 commit 96671b4

4 files changed

Lines changed: 132 additions & 79 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
## Worker Notes
107107

108108
- `packages/bot-worker/src/index.js` is intentionally broad: webhook routing, Telegram response flow, admin routes, uploads, and report redirects.
109+
- Worker HTTP routing is Hono-based. Keep route declarations near the top of `index.js`, use `context.env` and `context.executionCtx`, and leave heavy business logic in existing handler modules/functions.
109110
- Keep remote APK URL/range preview logic in `apk-url-preview.js`.
110111
- Remote URL preview is not the same as full local package analysis. APKS/APKM/XAPK containers may need inner APK extraction; range parsing works best for direct APKs or stored inner APK entries. For deflated inner APKs, return a clear unsupported/limited diagnostic rather than a misleading "missing AndroidManifest.xml" report.
111112
- Keep Worker HTML page rendering in `upload-view.js` and `report-viewer.js`.

package-lock.json

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/bot-worker/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,8 @@
2323
},
2424
"devDependencies": {
2525
"wrangler": "^4.103.0"
26+
},
27+
"dependencies": {
28+
"hono": "^4.12.27"
2629
}
2730
}

packages/bot-worker/src/index.js

Lines changed: 116 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Hono } from "hono";
12
import { readAndroidPackageInfo } from "../../shared/src/apk.js";
23
import { assertTelegramApkReport } from "../../shared/src/contracts.js";
34
import { buildFeatureIconUrl, buildSdkIconUrl, handleIconRequest } from "./icons.js";
@@ -36,81 +37,132 @@ let sdkRuleAnnotatorPromise = null;
3637
let telegraphModulePromise = null;
3738
let uploadViewModulePromise = null;
3839

39-
export default {
40-
async fetch(request, env, ctx) {
41-
const url = new URL(request.url);
42-
const telemetry = createRequestTelemetryContext(request, url, env);
40+
const app = new Hono();
4341

44-
const iconResponse = handleIconRequest(url.pathname);
45-
if (iconResponse) {
46-
return await iconResponse;
47-
}
42+
app.use("*", async (context, next) => {
43+
const request = context.req.raw;
44+
const url = new URL(request.url);
45+
const iconResponse = handleIconRequest(url.pathname);
46+
if (iconResponse) {
47+
return await iconResponse;
48+
}
4849

49-
logInfoEvent(env, telemetry, "worker.request.received", {
50-
result: "received",
51-
content_length: parseContentLengthHeader(request.headers.get("content-length")),
52-
}, { analytics: false });
50+
const telemetry = createRequestTelemetryContext(request, url, context.env);
51+
context.set("url", url);
52+
context.set("telemetry", telemetry);
5353

54-
if (request.method === "GET" && url.pathname === "/") {
55-
logInfoEvent(env, telemetry, "worker.status_viewed", {
56-
result: "success",
57-
http_status: 200,
58-
});
59-
return new Response(
60-
"Telegram APK info bot is running on Cloudflare Workers. Send Telegram webhook updates to /webhook. Admin endpoints: GET /admin/webhook, POST /admin/webhook/set, POST /admin/webhook/delete.",
61-
{
62-
headers: TEXT_CONTENT_HEADERS,
63-
},
64-
);
65-
}
54+
logInfoEvent(context.env, telemetry, "worker.request.received", {
55+
result: "received",
56+
content_length: parseContentLengthHeader(request.headers.get("content-length")),
57+
}, { analytics: false });
6658

67-
if (request.method === "GET" && url.pathname === "/report") {
68-
const startedAt = Date.now();
69-
const reportPath = url.searchParams.get("path") || null;
70-
const { handleReportRequest } = await loadReportViewerModule();
71-
const response = await handleReportRequest(url, env);
72-
const logFields = {
73-
result: response.ok ? "success" : "error",
74-
http_status: response.status,
75-
report_path: reportPath,
76-
duration_ms: Date.now() - startedAt,
77-
};
59+
await next();
60+
});
7861

79-
if (response.ok) {
80-
logInfoEvent(env, telemetry, "report.viewed", logFields);
81-
} else {
82-
logWarnEvent(env, telemetry, "report.view_failed", logFields);
83-
}
62+
app.get("/", (context) => {
63+
const telemetry = getRequestTelemetry(context);
64+
logInfoEvent(context.env, telemetry, "worker.status_viewed", {
65+
result: "success",
66+
http_status: 200,
67+
});
68+
return new Response(
69+
"Telegram APK info bot is running on Cloudflare Workers. Send Telegram webhook updates to /webhook. Admin endpoints: GET /admin/webhook, POST /admin/webhook/set, POST /admin/webhook/delete.",
70+
{
71+
headers: TEXT_CONTENT_HEADERS,
72+
},
73+
);
74+
});
8475

85-
return response;
86-
}
76+
app.get("/report", async (context) => {
77+
const url = getRequestUrl(context);
78+
const telemetry = getRequestTelemetry(context);
79+
const startedAt = Date.now();
80+
const reportPath = url.searchParams.get("path") || null;
81+
const { handleReportRequest } = await loadReportViewerModule();
82+
const response = await handleReportRequest(url, context.env);
83+
const logFields = {
84+
result: response.ok ? "success" : "error",
85+
http_status: response.status,
86+
report_path: reportPath,
87+
duration_ms: Date.now() - startedAt,
88+
};
8789

88-
if ((request.method === "GET" || request.method === "POST") && url.pathname === "/upload") {
89-
return handleUploadRequest(request, env, url, telemetry);
90-
}
90+
if (response.ok) {
91+
logInfoEvent(context.env, telemetry, "report.viewed", logFields);
92+
} else {
93+
logWarnEvent(context.env, telemetry, "report.view_failed", logFields);
94+
}
9195

92-
if (isAdminPath(url.pathname)) {
93-
return handleAdminRequest(request, env, url, telemetry);
94-
}
96+
return response;
97+
});
9598

96-
if (request.method === "POST" && isWebhookPath(url.pathname)) {
97-
return handleWebhookRequest(request, env, ctx, url.origin, telemetry);
98-
}
99+
app.on(["GET", "POST"], "/upload", (context) => {
100+
return handleUploadRequest(
101+
context.req.raw,
102+
context.env,
103+
getRequestUrl(context),
104+
getRequestTelemetry(context),
105+
);
106+
});
99107

100-
logWarnEvent(
101-
env,
102-
telemetry,
103-
"worker.route_not_found",
104-
{
105-
result: "not_found",
106-
http_status: 404,
107-
},
108-
{ analytics: false },
109-
);
108+
app.all("/admin/webhook", handleAdminRoute);
109+
app.all("/admin/webhook/set", handleAdminRoute);
110+
app.all("/admin/webhook/delete", handleAdminRoute);
111+
app.all("/admin/commands", handleAdminRoute);
112+
app.all("/admin/commands/set", handleAdminRoute);
113+
app.all("/admin/commands/delete", handleAdminRoute);
110114

111-
return new Response("Not Found", { status: 404 });
112-
},
113-
};
115+
app.post("/", handleWebhookRoute);
116+
app.post("/webhook", handleWebhookRoute);
117+
118+
app.notFound((context) => {
119+
logWarnEvent(
120+
context.env,
121+
getRequestTelemetry(context),
122+
"worker.route_not_found",
123+
{
124+
result: "not_found",
125+
http_status: 404,
126+
},
127+
{ analytics: false },
128+
);
129+
130+
return new Response("Not Found", { status: 404 });
131+
});
132+
133+
export default app;
134+
135+
function handleAdminRoute(context) {
136+
return handleAdminRequest(
137+
context.req.raw,
138+
context.env,
139+
getRequestUrl(context),
140+
getRequestTelemetry(context),
141+
);
142+
}
143+
144+
function handleWebhookRoute(context) {
145+
const url = getRequestUrl(context);
146+
return handleWebhookRequest(
147+
context.req.raw,
148+
context.env,
149+
context.executionCtx,
150+
url.origin,
151+
getRequestTelemetry(context),
152+
);
153+
}
154+
155+
function getRequestUrl(context) {
156+
return context.get("url") || new URL(context.req.raw.url);
157+
}
158+
159+
function getRequestTelemetry(context) {
160+
return context.get("telemetry") || createRequestTelemetryContext(
161+
context.req.raw,
162+
getRequestUrl(context),
163+
context.env,
164+
);
165+
}
114166

115167
async function handleWebhookRequest(request, env, ctx, requestOrigin, telemetry) {
116168
if (!env.BOT_TOKEN) {
@@ -1334,21 +1386,6 @@ async function analyzeApkUrl(env, message, apkUrl, requestOrigin, telemetry, loc
13341386
}
13351387
}
13361388

1337-
function isWebhookPath(pathname) {
1338-
return pathname === "/" || pathname === "/webhook";
1339-
}
1340-
1341-
function isAdminPath(pathname) {
1342-
return (
1343-
pathname === "/admin/webhook" ||
1344-
pathname === "/admin/webhook/set" ||
1345-
pathname === "/admin/webhook/delete" ||
1346-
pathname === "/admin/commands" ||
1347-
pathname === "/admin/commands/set" ||
1348-
pathname === "/admin/commands/delete"
1349-
);
1350-
}
1351-
13521389
function isWebhookSecretValid(request, env) {
13531390
const expected = env.TELEGRAM_WEBHOOK_SECRET?.trim();
13541391
if (!expected) {

0 commit comments

Comments
 (0)