專案總覽
墨家書櫃由三個部分組成:一個注入到讀墨網頁的 Chrome 擴充功能(Extension)、一個可在手機瀏覽家庭書櫃的 PWA, 以及一個可自建的 Cloudflare Workers 後端。三者共用同一組 API 與加密協定。
所有核心資料在上傳前都會在使用者瀏覽器端以 AES-256-GCM 加密,伺服器採零知識設計,只儲存密文。
技術架構
| 層級 | 技術 | 說明 |
|---|---|---|
| 前端 | Chrome Extension(React 19 + TypeScript + Vite) | Content Script 注入 Dialog 至讀墨頁面;Manifest V3 |
| 行動端 | PWA(React + TypeScript) | 與 Extension 共用同一後端;無法爬取讀墨書單 |
| 後端 | Cloudflare Workers + Hono | Serverless API,免費額度即可運行,可自建部署 |
| 儲存 | Cloudflare KV | 個人設定(per user)+ 家庭群組(per family) |
| 加密 | Web Crypto API(AES-256-GCM) | 端對端加密,伺服器零知識 |
| 測試 | Vitest + React Testing Library + Playwright + Miniflare | 單元/元件/E2E/整合測試 |
資料模型
採兩層設計,將個人偏好與家庭群組解耦:
1. 個人分享設定(user:{user_id})
- 每位使用者自己的書單與逐本的分享旗標
- 所有書籍預設
is_shared: false;新購入的書也預設不公開 - 變更後需使用者明確「儲存」才會同步至伺服器
- 解綁或更換家庭時,此設定仍保留
2. 家庭群組(family:{family_id})
- 儲存成員的 user ID 清單
- 家庭書櫃是動態聚合查詢,不獨立儲存
- 反向索引
member:{user_id}→family_id用於快速查詢
同步碼格式
同步碼同時傳遞三件事:家庭 ID、加密金鑰,以及(可選的)API 端點。
moo-{family_id_short}-{encryption_key_encoded} # 使用預設 API
moo-{family_id_short}-{encryption_key_encoded}@{host} # 使用自訂 API 端點
當使用自建後端時,@{host} 後綴會自動帶入 API 端點設定,家人收到同步碼後無需手動設定,
確保同一家庭所有成員使用相同的後端。
自建後端(BYO Backend)
不想把資料存在預設伺服器?你可以部署自己的 Cloudflare Worker,完全掌控資料。Cloudflare 的免費額度即可運行。
- Fork 本專案,進入
worker/目錄 - 安裝 Wrangler CLI:
npm install -g wrangler -
登入並部署至你的 Cloudflare 帳號:
cd worker wrangler login wrangler deploy - 在擴充功能或 PWA 的設定頁中,填入你的 Worker URL
- 建立家庭後,同步碼會自動帶入
@{host},把它傳給家人即可
詳細部署步驟請參閱 worker/DEPLOY.md。
Extension 與 PWA 功能對照
手機上無法安裝 Chrome Extension,因此 PWA 僅提供瀏覽與設定功能;爬取讀墨書單只能在桌面端 Extension 完成。
| 功能 | Extension | PWA |
|---|---|---|
| 瀏覽家庭分享書櫃 | ✓ | ✓ |
| 建立/加入家庭 | ✓ | ✓ |
| 個人書櫃分享設定 | ✓ | ✓(針對已同步的書) |
| 從讀墨網頁爬取書單 | ✓ | ✗ |
| 自訂 API 端點 | ✓ | ✓ |
| 離開家庭 | ✓ | ✓ |
API 端點
所有 API 使用 RESTful JSON,統一包在 { data, error } envelope 內,前綴為 /api/。
驗證
| Method | Path | 說明 |
|---|---|---|
| POST | /api/auth/lookup | 以預先雜湊的 userId 查詢家庭歸屬 |
| POST | /api/auth/refresh | 刷新驗證 token |
個人設定
| Method | Path | 說明 |
|---|---|---|
| GET | /api/user/:id/books | 取得個人書單與分享設定 |
| PUT | /api/user/:id/books | 更新分享設定 |
家庭群組
| Method | Path | 說明 |
|---|---|---|
| POST | /api/family | 建立新家庭 |
| POST | /api/family/:id/join | 以同步碼加入家庭 |
| DELETE | /api/family/:id/member/:uid | 離開家庭 |
| GET | /api/family/:id/members | 列出家庭成員 |
| GET | /api/family/:id/bookshelf | 聚合家庭分享書櫃 |
安全與隱私設計
- 端對端加密 — 所有個人書單與分享設定,在瀏覽器端以 AES-256-GCM 加密後才上傳,伺服器只看得到密文
- 零知識伺服器 — 加密金鑰從不傳送至伺服器,開發者與伺服器管理者皆無法解密
- 預設不公開 — 所有書籍預設
is_shared: false,永不自動分享 - 明確儲存 — 變更必須經使用者按下「儲存」才會上傳
- 離開即隔離 — 退出家庭立刻從 member list 移除,其他成員即刻無法存取其資料
- 不收集個資 — 無帳號、無 Email、無追蹤
- 可自建 — Worker 程式碼公開,任何人皆可部署自己的後端
參與開發
歡迎提交 Issue 或 Pull Request。專案採 TypeScript 嚴格模式,核心流程(crypto、API)要求測試覆蓋率 ≥ 80%。
開發環境
pnpm install
pnpm dev # 啟動 Extension 開發伺服器
cd worker && pnpm dev # 啟動 Worker 本地開發(Miniflare)
pnpm test # 執行單元與元件測試
pnpm test:e2e # 執行 E2E 測試(Playwright)
詳細貢獻指南請參閱 CONTRIBUTING.md, 專案文件位於 docs/。