Skip to content

Commit cb619f8

Browse files
kevintsengclaude
andcommitted
feat: complete code review fixes and LINE SDK compatibility
Major Fixes: - Fix Express middleware creating new instance per request (performance) - Fix OAuth error message to include API response content - Add comprehensive tests for push.ts and multicast.ts Code Review Improvements: - All 3 Major issues resolved - 67 tests passing across 10 test files - Build successful (ESM + CJS + DTS) LINE SDK Documentation Research: - Verified implementation against official LINE API docs - Add LINE API Compatibility section to README - Document API versions: Messaging API v2, LINE Login v2.1, LIFF v2 - Document endpoint base URLs and API limits New Files: - packages/messaging/src/push.test.ts (6 tests) - packages/messaging/src/multicast.test.ts (5 tests) - packages/adapters/express/src/index.test.ts (3 tests) - packages/liff/* (placeholder package) - packages/messaging/src/validation.ts (message/recipient limits) - packages/messaging/src/types.ts (Message type definitions) - pnpm-workspace.yaml (monorepo configuration) - tsup.config.ts files for all packages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent ed76b0b commit cb619f8

41 files changed

Lines changed: 3535 additions & 160 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,25 @@ if (login.validateState(savedState, returnedState)) {
8585
- **@linekit/login**: OAuth flow, ID Token verification, CSRF state helpers.
8686
- **@linekit/express**: Adapter for Express.js.
8787

88+
## LINE API Compatibility
89+
90+
This toolkit is built against the following LINE API versions:
91+
92+
| API | Version | Reference |
93+
|-----|---------|-----------|
94+
| Messaging API | v2 | [docs](https://developers.line.biz/en/reference/messaging-api/) |
95+
| LINE Login | v2.1 (OAuth 2.1) | [docs](https://developers.line.biz/en/reference/line-login/) |
96+
| LIFF | v2 | [docs](https://developers.line.biz/en/reference/liff/) |
97+
98+
**Endpoint Base URLs:**
99+
- Messaging API: `https://api.line.me/v2/bot/`
100+
- LINE Login: `https://api.line.me/oauth2/v2.1/`
101+
- Authorization: `https://access.line.me/oauth2/v2.1/`
102+
103+
**API Limits (enforced by linekit):**
104+
- Maximum 5 messages per request
105+
- Maximum 500 recipients per multicast request
106+
88107
## Documentation
89108

90109
📚 **[Full Tutorials & API Reference](https://pcircle-ai.github.io/linekit/)** - Comprehensive guides with flow diagrams

README.zh-TW.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,25 @@ if (login.validateState(savedState, returnedState)) {
8585
- **@linekit/login**: OAuth 流程、ID Token 驗證、CSRF state 工具。
8686
- **@linekit/express**: Express.js 的適配器。
8787

88+
## LINE API 相容性
89+
90+
本工具包基於以下 LINE API 版本開發:
91+
92+
| API | 版本 | 參考文件 |
93+
|-----|------|---------|
94+
| Messaging API | v2 | [文件](https://developers.line.biz/en/reference/messaging-api/) |
95+
| LINE Login | v2.1 (OAuth 2.1) | [文件](https://developers.line.biz/en/reference/line-login/) |
96+
| LIFF | v2 | [文件](https://developers.line.biz/en/reference/liff/) |
97+
98+
**API 端點 Base URL:**
99+
- Messaging API: `https://api.line.me/v2/bot/`
100+
- LINE Login: `https://api.line.me/oauth2/v2.1/`
101+
- 授權: `https://access.line.me/oauth2/v2.1/`
102+
103+
**API 限制(由 linekit 強制執行):**
104+
- 每次請求最多 5 則訊息
105+
- 每次 multicast 請求最多 500 位收件人
106+
88107
## 文件
89108

90109
📚 **[完整教學與 API 參考](https://pcircle-ai.github.io/linekit/index-zh.html)** - 附流程圖的詳細指南

docs/research-invoicekit.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Research Report: Taiwan E-Invoice Development Kit
2+
3+
## 1. Executive Summary
4+
5+
The goal is to create `invoicekit`, a modern, modular, and framework-agnostic TypeScript toolkit for Taiwan Electronic Invoices (e-Invoice/eGUI), similar in philosophy to `linekit`.
6+
7+
Currently, developers usually integrate with **Value Added Service Centers (加值中心)** like ECPay (綠界) or EZpay (簡單付) because connecting directly to the Ministry of Finance (MOF) Turnkey system is technically complex and regulated.
8+
9+
**Opportunity**: There is no single, unified TypeScript SDK that abstracts these different providers (MOF, ECPay, EZpay) into a consistent API. Existing libraries are often outdated, provider-specific, or lack TypeScript support.
10+
11+
## 2. The Ecosystem
12+
13+
### A. Ministry of Finance (MOF) Platform
14+
15+
- **Role**: The central government source.
16+
- **Access**:
17+
- **Turnkey**: For high-volume B2B/B2C issuance. Requires strict certification and MIG 4.0 xml formats.
18+
- **API (AppID)**: For querying invoice details, winning numbers, and verifying carriers (手機條碼). **Not for issuing B2C invoices** directly without a Turnkey or aggregator.
19+
- **Key APIs**: `getInvoiceDetail`, `getWinningNumbers`, `checkMobileBarCode`.
20+
21+
### B. Third-Party Aggregators (Providers)
22+
23+
- **Role**: Intermediaries that handle the Turnkey complexity. Businesses pay them a fee per invoice.
24+
- **Major Players**:
25+
- **ECPay (綠界)**: Dominant market share. Uses AES encryption + HashKey/IV.
26+
- **EZpay (簡單付)**: Another popular option.
27+
- **Others**: NewebPay (藍新), LINE Pay (sometimes bundles).
28+
- **Mechanism**: RESTful-ish APIs (often XML or JSON with specific encryption).
29+
30+
## 3. Proposed Architecture (`invoicekit`)
31+
32+
Drafting a similar monorepo structure to `linekit`:
33+
34+
```text
35+
packages/
36+
core/ # Common types (MIG 4.0), validators, utilities
37+
mof/ # Ministry of Finance Public API (Winning #, Carrier check)
38+
ecpay/ # ECPay specific implementation
39+
ezpay/ # EZpay specific implementation
40+
universal/ # (Optional) Unified Interface for "Issuing"
41+
```
42+
43+
### Module Breakdown
44+
45+
#### 1. `@invoicekit/core`
46+
47+
- **Validators**:
48+
- Uniform Business No (統一編號) validation (Logic 8-digit check).
49+
- Mobile Barcode (手機條碼) regex validation (`/^\/[0-9A-Z.+-]{7}$/`).
50+
- Donate Code (愛心碼) verification.
51+
- **Types**:
52+
- Shared interfaces for `InvoiceItem`, `Customer`, `VatType` (Taxable, Zero-tax).
53+
- **Utils**:
54+
- Random number generators for tracking checking.
55+
56+
#### 2. `@invoicekit/mof` (Public Data)
57+
58+
Direct integration with `api.einvoice.nat.gov.tw`.
59+
60+
- **Features**:
61+
- `getWinningNumbers(term)`: Fetch winning lottery numbers.
62+
- `verifyMobileBarcode(code)`: Check if a user's phone barcode exists.
63+
- `getInvoiceDetail(...)`: Query specific invoice info (B2B mostly).
64+
65+
#### 3. `@invoicekit/ecpay` (Issuance)
66+
67+
Wrapper for ECPay's specific encryption/form-posting flow.
68+
69+
- **Features**:
70+
- `issue(invoiceData)`: Create a new B2C invoice.
71+
- `void(invoiceNumber)`: Cancel an invoice.
72+
- `allowance(invoiceNumber)`: Issue a refund/allowance.
73+
74+
## 4. Example Usage (Concept)
75+
76+
```typescript
77+
// 1. Validation (Core)
78+
import { validateTaxId, validateMobileBarcode } from "@invoicekit/core";
79+
80+
if (!validateMobileBarcode("/AB12345")) {
81+
throw new Error("Invalid barcode");
82+
}
83+
84+
// 2. Fetching Winning Numbers (MOF)
85+
import { mof } from "@invoicekit/mof";
86+
const numbers = await mof.getWinningNumbers("11210"); // Oct 2023
87+
88+
// 3. Issuing Invoice (ECPay Adapter)
89+
import { ECPayClient } from "@invoicekit/ecpay";
90+
91+
const client = new ECPayClient({
92+
merchantId: "...",
93+
hashKey: "...",
94+
hashIv: "..."
95+
});
96+
97+
const result = await client.issue({
98+
orderId: "ORD-001",
99+
amount: 1000,
100+
items: [{ name: "Tech Gadget", count: 1, price: 1000 }],
101+
carrier: { type: "mobile", id: "/AB12345" }
102+
});
103+
```
104+
105+
## 5. Roadmap Recommendation
106+
107+
1. **Phase 1**: Build `@invoicekit/core` with strict validation logic (Tax ID, Barcodes) as these are universal.
108+
2. **Phase 2**: Build `@invoicekit/mof` to query government open data (Winning numbers are a great "Hello World").
109+
3. **Phase 3**: Implement `@invoicekit/ecpay` as it's the most requested feature for commercial use.
110+
111+
This structure allows you to maintain the same "Clean, Modular, Type-Safe" philosophy as `linekit`.

package.json

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
{
22
"name": "linekit-monorepo",
33
"private": true,
4-
"workspaces": [
5-
"packages/*",
6-
"packages/adapters/*",
7-
"examples/*"
8-
],
4+
"packageManager": "pnpm@9.0.0",
95
"scripts": {
10-
"build": "npm run build --workspaces",
11-
"test": "npm run test --workspaces",
12-
"lint": "npm run lint --workspaces"
6+
"build": "pnpm -r run build",
7+
"test": "pnpm -r run test",
8+
"lint": "pnpm -r run lint"
139
},
1410
"devDependencies": {
1511
"typescript": "^5.0.0",
1612
"tsup": "^8.0.0",
17-
"@types/node": "^18.0.0"
13+
"@types/node": "^18.0.0",
14+
"vitest": "^2.1.0"
1815
}
1916
}

packages/adapters/express/package.json

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
11
{
22
"name": "@linekit/express",
33
"version": "0.1.0",
4-
"main": "./dist/index.js",
5-
"module": "./dist/index.mjs",
4+
"type": "module",
5+
"main": "./dist/index.cjs",
6+
"module": "./dist/index.js",
67
"types": "./dist/index.d.ts",
8+
"exports": {
9+
".": {
10+
"import": {
11+
"types": "./dist/index.d.ts",
12+
"default": "./dist/index.js"
13+
},
14+
"require": {
15+
"types": "./dist/index.d.cts",
16+
"default": "./dist/index.cjs"
17+
}
18+
}
19+
},
20+
"files": ["dist"],
721
"scripts": {
8-
"build": "npm exec tsup src/index.ts --format cjs,esm --dts",
9-
"dev": "npm exec tsup src/index.ts --format cjs,esm --dts --watch"
22+
"build": "tsup",
23+
"dev": "tsup --watch",
24+
"test": "vitest run"
1025
},
1126
"keywords": [
1227
"line",
@@ -23,6 +38,7 @@
2338
},
2439
"devDependencies": {
2540
"tsup": "^8.0.0",
26-
"@types/express": "^4.0.0"
41+
"@types/express": "^4.0.0",
42+
"vitest": "^2.1.0"
2743
}
2844
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { describe, it, expect, vi } from "vitest";
2+
import { lineMiddleware } from "./index.js";
3+
4+
// Mock the core module
5+
vi.mock("@linekit/core", () => ({
6+
createWebhookMiddleware: vi.fn(() => {
7+
return vi.fn((_req, _res, next) => next());
8+
}),
9+
}));
10+
11+
describe("@linekit/express", () => {
12+
it("should export lineMiddleware function", () => {
13+
expect(typeof lineMiddleware).toBe("function");
14+
});
15+
16+
it("should create middleware with channel secret config", () => {
17+
const middleware = lineMiddleware({ channelSecret: "test-secret" });
18+
expect(typeof middleware).toBe("function");
19+
});
20+
21+
it("should return a function that accepts req, res, next", () => {
22+
const middleware = lineMiddleware({ channelSecret: "test-secret" });
23+
const req = {} as any;
24+
const res = {} as any;
25+
const next = vi.fn();
26+
27+
middleware(req, res, next);
28+
29+
expect(next).toHaveBeenCalled();
30+
});
31+
});

packages/adapters/express/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ export * from "@linekit/core";
1414
import { createWebhookMiddleware as coreCreateWebhookMiddleware } from "@linekit/core";
1515

1616
export function lineMiddleware(config: { channelSecret: string }) {
17+
// Create middleware instance once during initialization, not per request
18+
const middleware = coreCreateWebhookMiddleware(config);
19+
1720
// linekit core middleware signature matches (req: IncomingMessage, res: ServerResponse, next: ...)
1821
// Express Req/Res extend these.
1922
return (req: Request, res: Response, next: NextFunction) => {
2023
// Ensure rawBody is available if not handled by body-parser
2124
// If the user uses a body parser that swallows stream, core middleware might fail.
2225
// We can add logic here to buffer it if needed, or documentation.
23-
const middleware = coreCreateWebhookMiddleware(config);
2426
return middleware(req, res, next);
2527
};
2628
}

packages/adapters/express/tsconfig.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
"rootDir": "./src"
66
},
77
"include": [
8-
"src/**/*"
8+
"src/**/*.ts"
9+
],
10+
"exclude": [
11+
"node_modules",
12+
"dist",
13+
"**/*.test.ts",
14+
"**/*.spec.ts"
915
]
1016
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { defineConfig } from 'tsup';
2+
3+
export default defineConfig({
4+
entry: ['src/index.ts'],
5+
format: ['cjs', 'esm'],
6+
dts: true,
7+
splitting: false,
8+
clean: true,
9+
treeshake: true,
10+
sourcemap: true,
11+
outExtension({ format }) {
12+
return {
13+
js: format === 'cjs' ? '.cjs' : '.js',
14+
};
15+
},
16+
});

packages/core/package.json

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,27 @@
11
{
22
"name": "@linekit/core",
33
"version": "0.1.0",
4-
"main": "./dist/index.js",
5-
"module": "./dist/index.mjs",
4+
"type": "module",
5+
"main": "./dist/index.cjs",
6+
"module": "./dist/index.js",
67
"types": "./dist/index.d.ts",
8+
"exports": {
9+
".": {
10+
"import": {
11+
"types": "./dist/index.d.ts",
12+
"default": "./dist/index.js"
13+
},
14+
"require": {
15+
"types": "./dist/index.d.cts",
16+
"default": "./dist/index.cjs"
17+
}
18+
}
19+
},
20+
"files": ["dist"],
721
"scripts": {
8-
"build": "npm exec tsup src/index.ts --format cjs,esm --dts",
9-
"dev": "npm exec tsup src/index.ts --format cjs,esm --dts --watch",
10-
"test": "npm exec vitest run"
22+
"build": "tsup",
23+
"dev": "tsup --watch",
24+
"test": "vitest run"
1125
},
1226
"keywords": [
1327
"line",

0 commit comments

Comments
 (0)