Skip to content

Commit 8dd1874

Browse files
committed
refactor: 路經處理邏輯優化
* 移除泛路徑支援 * 拆分驗證邏輯至額外函數
1 parent ec1b376 commit 8dd1874

5 files changed

Lines changed: 68 additions & 57 deletions

File tree

src/handlers/webhook.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import type { webhook } from "@line/bot-sdk";
2+
import { hmacSHA256Base64 } from "../utils/hmacSHA256Base64";
3+
import { eventRouter } from "../eventRouter";
4+
import { sendMessage } from "../utils/eventRoutes";
5+
6+
export async function handleWebhook(request: Request, env: Env, ctx: ExecutionContext) {
7+
// 檢查方法合法
8+
if (request.method !== "POST") {
9+
return new Response("Method Not Allowed", { status: 405 });
10+
}
11+
// 取得環境變數
12+
const channelSecret = env.LINE_CHANNEL_SECRET;
13+
const channelAccessToken = env.LINE_CHANNEL_ACCESS_TOKEN;
14+
15+
if (!channelSecret || !channelAccessToken) {
16+
return new Response("Server misconfigured", { status: 500 });
17+
}
18+
19+
// LINE 簽章 header
20+
const lineSignature = request.headers.get("x-line-signature");
21+
if (!lineSignature) {
22+
return new Response("Missing signature", { status: 400 });
23+
}
24+
25+
//讀取 raw body
26+
let body: webhook.CallbackRequest = { destination: "", events: [] };
27+
let rawBody = "";
28+
29+
try {
30+
const clone = request.clone();
31+
body = await clone.json();
32+
rawBody = await request.text();
33+
} catch (e) {
34+
console.error("Failed JSON", { error: e, rawBody });
35+
return new Response("Invalid JSON", { status: 400 });
36+
}
37+
38+
// 計算 HMAC-SHA256 和 base64 並檢驗簽章
39+
const computedSignature = await hmacSHA256Base64(channelSecret, rawBody);
40+
if (computedSignature !== lineSignature) {
41+
return new Response("Invalid signature", { status: 401 });
42+
}
43+
44+
// 解析 JSON body 並派發事件
45+
const events = body.events || [];
46+
for (const event of events) {
47+
const responses = (await eventRouter(event, channelAccessToken, ctx)) ?? [];
48+
if (responses.length > 0 && "replyToken" in event && typeof event.replyToken === "string") {
49+
await sendMessage(channelAccessToken, event.replyToken, responses);
50+
}
51+
}
52+
53+
return new Response("OK");
54+
}

src/router.ts

Lines changed: 5 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,10 @@
1-
import type { webhook } from "@line/bot-sdk";
2-
import { hmacSHA256Base64 } from "./utils/hmacSHA256Base64";
3-
import { eventRouter } from "./eventRouter";
4-
import { sendMessage } from "./utils/eventRoutes";
1+
import { handleWebhook } from "./handlers/webhook";
52

63
export async function router(request: Request, env: Env, ctx: ExecutionContext) {
7-
// 檢查方法合法
8-
if (request.method !== "POST") {
9-
return new Response("Method Not Allowed", { status: 405 });
4+
const pathname = new URL(request.url).pathname;
5+
if (pathname === "/webhook") {
6+
return await handleWebhook(request, env, ctx);
107
}
11-
// 取得環境變數
12-
const channelSecret = env.LINE_CHANNEL_SECRET;
13-
const channelAccessToken = env.LINE_CHANNEL_ACCESS_TOKEN;
148

15-
if (!channelSecret || !channelAccessToken) {
16-
return new Response("Server misconfigured", { status: 500 });
17-
}
18-
19-
// LINE 簽章 header
20-
const lineSignature = request.headers.get("x-line-signature");
21-
if (!lineSignature) {
22-
return new Response("Missing signature", { status: 400 });
23-
}
24-
25-
//讀取 raw body
26-
let body: webhook.CallbackRequest = { destination: "", events: [] };
27-
let rawBody = "";
28-
29-
try {
30-
const clone = request.clone();
31-
body = await clone.json();
32-
rawBody = await request.text();
33-
} catch (e) {
34-
console.error("Failed JSON", { error: e, rawBody });
35-
return new Response("Invalid JSON", { status: 400 });
36-
}
37-
38-
// 計算 HMAC-SHA256 和 base64 並檢驗簽章
39-
const computedSignature = await hmacSHA256Base64(channelSecret, rawBody);
40-
if (computedSignature !== lineSignature) {
41-
return new Response("Invalid signature", { status: 401 });
42-
}
43-
// 解析 JSON body
44-
const events = body.events || [];
45-
for (const event of events) {
46-
const responses = (await eventRouter(event, channelAccessToken, ctx)) ?? [];
47-
if (responses.length > 0 && "replyToken" in event && typeof event.replyToken === "string") {
48-
await sendMessage(channelAccessToken, event.replyToken, responses);
49-
}
50-
}
51-
// 回應 LINE 伺服器
52-
return new Response("OK");
9+
return new Response("Not Found", { status: 404 });
5310
}

test/_index.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import worker from "../src/_index";
1212

1313
describe("worker fetch entry", () => {
1414
it("delegates request handling to router", async () => {
15-
const request = new Request("https://example.com", { method: "POST" });
15+
const request = new Request("https://example.com/webhook", { method: "POST" });
1616
const env = { SOME_ENV: "value" } as any;
1717
const ctx = { waitUntil: vi.fn() } as any;
1818
const expectedResponse = new Response("delegated", { status: 200 });

test/router.extra.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ describe("router extra branches", () => {
4040

4141
it("returns 400 when body is not valid JSON", async () => {
4242
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
43-
const request = new Request("https://example.com", {
43+
const request = new Request("https://example.com/webhook", {
4444
method: "POST",
4545
headers: { "x-line-signature": "sig" },
4646
body: "{",
@@ -62,7 +62,7 @@ describe("router extra branches", () => {
6262
mocks.eventRouter.mockResolvedValueOnce([{ type: "text", text: "first" }]);
6363
mocks.eventRouter.mockResolvedValueOnce([]);
6464

65-
const request = new Request("https://example.com", {
65+
const request = new Request("https://example.com/webhook", {
6666
method: "POST",
6767
headers: { "x-line-signature": signature },
6868
body: rawBody,
@@ -83,7 +83,7 @@ describe("router extra branches", () => {
8383
const rawBody = JSON.stringify({ destination: "" });
8484
const signature = await hmacSHA256Base64("secret", rawBody);
8585

86-
const request = new Request("https://example.com", {
86+
const request = new Request("https://example.com/webhook", {
8787
method: "POST",
8888
headers: { "x-line-signature": signature },
8989
body: rawBody,

test/router.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,23 +26,23 @@ describe("router", () => {
2626
});
2727

2828
it("returns 405 for non-POST methods", async () => {
29-
const request = new Request("https://example.com", { method: "GET" });
29+
const request = new Request("https://example.com/webhook", { method: "GET" });
3030
const response = await router(request, mockEnv as any, {} as any);
3131

3232
expect(response.status).toBe(405);
3333
expect(await response.text()).toBe("Method Not Allowed");
3434
});
3535

3636
it("returns 400 if signature is missing", async () => {
37-
const request = new Request("https://example.com", { method: "POST" });
37+
const request = new Request("https://example.com/webhook", { method: "POST" });
3838
const response = await router(request, mockEnv as any, {} as any);
3939

4040
expect(response.status).toBe(400);
4141
expect(await response.text()).toBe("Missing signature");
4242
});
4343

4444
it("returns 401 if signature is invalid", async () => {
45-
const request = new Request("https://example.com", {
45+
const request = new Request("https://example.com/webhook", {
4646
method: "POST",
4747
headers: { "x-line-signature": "invalid" },
4848
body: JSON.stringify({}),
@@ -55,7 +55,7 @@ describe("router", () => {
5555

5656
it("returns 500 if server is misconfigured", async () => {
5757
mockEnv.LINE_CHANNEL_SECRET = null;
58-
const request = new Request("https://example.com", { method: "POST" });
58+
const request = new Request("https://example.com/webhook", { method: "POST" });
5959
const response = await router(request, mockEnv as any, {} as any);
6060

6161
expect(response.status).toBe(500);
@@ -65,7 +65,7 @@ describe("router", () => {
6565
it("returns 200 for a valid signed request", async () => {
6666
const rawBody = JSON.stringify({ destination: "", events: [] });
6767
const signature = await hmacSHA256Base64("secret", rawBody);
68-
const request = new Request("https://example.com", {
68+
const request = new Request("https://example.com/webhook", {
6969
method: "POST",
7070
headers: { "x-line-signature": signature },
7171
body: rawBody,

0 commit comments

Comments
 (0)