近期進度:雲端硬碟分頁新增容量資訊欄(預設 3GB 配額,隨 Drive 列表即時計算使用率並以進度條呈現);修復 logout→relogin 後分享面板持續顯示「缺少交友金鑰」造成 QR 無法重生的問題,重置 shareState 會清除補貨鎖定並恢復自動補貨;Drive 面板改以使用者資料夾為主並隱藏系統「已傳送 / 已接收」層,避免上傳檔案被困且可再次上傳 / 刪除;訊息附件新增「預覽」動作沿用 Modal 下載流程並於 Playwright 內實際執行
downloadAndDecrypt驗證 SHA-256 digest,確認接收端確實可還原檔案。
- 目標:驗證「晶片感應 → 零知識登入 → 端對端密訊&媒體」的連貫體驗,
同時確保所有祕密僅存於使用者裝置記憶體。實際設計:明文與 MK 僅在瀏覽器記憶體使用,但伺服端會保存密文(訊息/媒體)以及以 MK 包裝的密鑰備份(wrapped_mk、wrapped_dev、contactSecrets/DR 快照)以支援跨裝置還原。 - 核心堆疊:Node.js (Express + WebSocket) / Cloudflare Worker + D1 / Cloudflare R2 / 前端 ESM。
npm install
NODE_ENV=development node src/server.js # 啟動 API
node scripts/serve-web.mjs # 啟動本機 Pages必要環境變數(摘要):DATA_API_URL, DATA_API_HMAC, WS_TOKEN_SECRET, S3_*, NTAG424_*, OPAQUE_*, ACCOUNT_TOKEN_BYTES, SIGNED_{PUT,GET}_TTL, UPLOAD_MAX_BYTES, CALL_SESSION_TTL_SECONDS, TURN_SHARED_SECRET, TURN_STUN_URIS, TURN_RELAY_URIS, TURN_TTL_SECONDS。細節見安全預設。
開發流程請遵循 Prompt.md:新 session 先閱讀 README 最新進度 → 選定優先事項 → 修改後自跑測試 → 更新此文件紀錄。
權限提醒:Codex 預設擁有 Git 推送、Cloudflare Worker/Pages 部署、D1/R2 清除與各項腳本執行權限。確認操作安全後請直接執行,不需假設權限受限。
.
├─ package.json # Node API
├─ src/ # 伺服端程式碼
│ ├─ routes/ # REST (auth/media/friends/prekeys/...)
│ ├─ controllers/ # 業務邏輯
│ ├─ ws/ # WebSocket presence/contact-share
│ └─ utils/ # HMAC、logger、S3/R2 包裝
├─ data-worker/ # Cloudflare Worker + D1 schema
└─ web/ # 前端(Cloudflare Pages)
├─ src/app/ # ESM 模組(core/crypto/features/ui)
└─ pages/ # login.html / app.html
| 元件 | 職責 |
|---|---|
| 前端 (web) | 管理登入流程、端到端加密、UI;所有敏感資料在瀏覽器記憶體處理。 |
| Node API (src) | 驗證 SDM、代理 OPAQUE、媒體索引、devkeys/prekeys 管理、WebSocket presence。僅接觸密文與索引。 |
| Cloudflare Worker (data-worker) | 以 HMAC 驗證 Node 請求,操作 D1:帳號、邀請、訊息索引、prekey 庫存等。 |
| R2 | 儲存加密媒體/頭像,透過 `/media/sign-put |
| SessionStorage / LocalStorage | 登入→App handoff 用途(mk_b64、account_token 等)與 contactSecrets-v1 快照;App 讀取後立即清空。 |
- Login 頁完成 SDM + OPAQUE 後解封 MK,僅短暫存於 sessionStorage。
- App 頁接手 MK、wrapped_dev、contactSecrets 等 snapshot,並透過 WebSocket / REST 同步資料。
- 所有加密/解密在前端記憶體執行;後端只儲存密文與索引。
- 雙向訊息採 Double Ratchet;登出時會 flush snapshot,重新登入時由
contactSecrets-v1還原。
- 群組 conversation 採獨立
groupId+conversation_token,不與 1:1 共用 ACL / DR state,原有單聊流程保持原樣。 - 資料層預計新增
groups/group_members/group_invites,conversation_acl以成員批次授權;訊息索引需帶 sender 標識(fingerprint / account_digest)。 - API:新增群組 CRUD + 邀請 / 加入 / 退出;
messages.controller送 / 拉訊息時驗證群組成員資格並附帶 sender 資訊。 - 前端:session/contactSecrets 新增 group store 與
type:'group'對話列表;群組訊息渲染以成員表映射 sender 名稱 / 頭像;WS 增補 group-* 事件與 secure-message 分流。 - 加密策略維持不干擾單聊:群組訊息以 per-member fan-out(每成員子封包)或群組共享 key 二選一,不覆寫既有單聊 DR 狀態。
- UI/UX 方案(行動/桌面一致):聊天列表提供「建立群組」入口;建群分步(命名/頭像 → 選好友 → 確認與 QR/連結分享);群組對話標頭顯示名稱+成員數,訊息氣泡附 sender 頭像/名稱(連續訊息合併頭像);列表預覽顯示未讀與 @提及徽章;右側/抽屜顯示成員清單與管理操作(靜音/退出/踢人/再發邀請);被踢或離開時覆蓋提示可返回列表。
- SDM 感應:
POST /api/v1/auth/sdm/exchange,Node 端驗證 MAC,並透過 Worker 建立帳號。 - OPAQUE:前端
ensureOpaque()與 Node/api/v1/auth/opaque/*(代理 Worker/d1/opaque/*)互動,不暴露密碼。 - MK 處理:若無 MK → 產生並
wrapMKWithPasswordArgon2id→/api/v1/mk/store。若已有 → 解封wrapped_mk。 - 交棒:登入頁將
mk_b64、account_token、account_digest、wrapped_dev、contactSecrets-v1放至 sessionStorage / localStorage (contactSecrets-v1-latest),App 取用後清空。
- 備份:無備份時產生 IK/SPK + 100 OPKs →
/api/v1/keys/publish→ 以 MK 包裝後/api/v1/devkeys/store。 - 補貨:已備份則解包
wrapped_dev,視需要補 OPKs(每次 20 支),再度包裝並存回。 - API 限制:
/api/v1/devkeys/*僅接受accountToken/accountDigest;若只給 token,Node 會自行取 digest 以保護 UID。 - Worker:
/d1/prekeys/publishupsert IK/SPK/OPK,/d1/prekeys/bundle配發且消耗對方 OPK。
- 建立邀請:Owner 端補貨 per-device 預鍵後,產生 QR(僅含 inviteId + owner device/prekey meta,不存 contact snapshot),呼叫
/api/v1/friends/invite寫入 token hash + owner bundle。 - 接受邀請:Guest 掃描後,使用 owner bundle 跑 X3DH 取得會話種子,
/api/v1/friends/accept僅驗證 token 並回傳 owner digest/device + bundle 消耗結果。 - 建會話 / DR 初始化:雙方各自以會話種子派生 conversation token(per-device),初始化 DR,後續所有同步只靠 conversation token + DR snapshot。
- 聯絡同步:
/api/v1/friends/contact/share只收 conversationId + AEAD envelope,ACL 綁 accountDigest+deviceId;WScontact-share事件帶 sender/targetDeviceId,前端更新 contactSecrets(含 DR snapshot)。 - 狀態管理:不再持久化 invite secret 或 session bootstrap,缺資料時直接報錯,不做自動重建 / fallback。
Double Ratchet 訊息傳遞(棄用,重構目標:per-device Signal X3DH/DR,移除 session-init/ack 與 conversation fingerprint 授權,封包 header 標準化)
- 設備金鑰交棒:
ensureDevicePrivAvailable()僅依賴登入交棒/記憶體;若 sessionStorage 缺件,直接報錯不再自動補建。 - 初始化:若
drState缺失、且dr_init可用 →bootstrapDrFromGuestBundle();否則呼叫prekeysBundle+x3dhInitiate建立新會話(消耗對方 OPK)。 - 傳送訊息:
drEncryptText產生 header + ciphertext →/api/v1/messages/secure儲存 envelope(D1messages_secure)。 - 接收解密:
listSecureAndDecrypt()先排序訊息;若為重播情境會利用prepareDrForMessage檢查 timestamp / messageId 是否早於 cursor,必要時還原 snapshot。- 若 snapshot 缺失會落到
recoverDrState()(可強制使用guest_bundle),同時記錄[dr-decrypt-fail-payload]供tests/scripts/debug-dr-replay.mjs重播。 - 每次成功解密會
recordDrMessageHistory()(包含 messageKey)並persistDrSnapshot()。
- DR 恢復判定:
recoverDrState()允許 initiator/guest 端只要恢復出有效 ratchet(rk+ send/recv 任一鏈與 myRatchetKey 成立)即視為成功,不再硬性要求 responderckR;避免誤判失敗後強制 reset,實作位於web/src/app/features/dr-session.js。
- 單次鑑權:同一 WebSocket 連線只接受一次
auth;若重送相同帳號 token 會回覆成功並更新 sessionTs,若嘗試切換帳號會先卸載舊帳號的 presence watcher 與 client 註冊後再綁定新帳號,避免一條連線同時掛多個 digest 造成事件外洩(src/ws/index.js)。 - 呼叫鎖復原:
call-invite寫入 call event 失敗時會釋放雙方鎖並回傳CALL_EVENT_FAILED,避免 30~120 秒的 busy 假陽性(src/ws/index.js)。 - 鎖續約與過期清理:
CALL_RENEW_EVENTS續約鎖,CALL_RELEASE_EVENTS釋放;CALL_LOCK_TTL_MS預設 120 秒(下限 30 秒),掃描每 5 秒清除過期,實作同於src/ws/index.js。
安全對話啟動流程(Invite → Contact Secrets → DR Ready → 傳訊)(棄用,重構目標:直接以 per-device DR 建鏈,token 僅作封套密鑰,ACL 改 digest/device,不再依賴 session-init/ack 控制包)
- 邀請建立:Owner 端透過
friendsCreateInvite()生成 invite,share-controller會同步:- 將 contact snapshot 以 invite secret 包裝後呼叫
friends/invite/contact; - 使用
setContactSecret()在本地記錄{ inviteId, secret, role: 'owner', conversation.{token, id, dr_init} }。
- 將 contact snapshot 以 invite secret 包裝後呼叫
- 好友接受:Guest 掃描邀請後執行
friendsAcceptInvite():- Worker 綁定帳號並回傳
guest_bundle+ owner contact; - Guest 端
storeContactSecretMapping()儲存對應 invite 資料、預填conversation.drInit,必要時bootstrapDrFromGuestBundle()立即進入 responder 狀態。
- Worker 綁定帳號並回傳
- Contact Secrets 同步:雙方藉由
/friends/contact/share更新最新 profile / conversation token,loadContacts()或 WScontact-share事件會:- 寫入
contactSecrets-v1的conversation.{token,id,drInit}與drHistory; - 對 UI 觸發
contacts:rendered事件以重建列表。
- 寫入
- SecureConversationManager 進場:
ensureSecureConversationReady()會先檢查本地drState,缺失時啟動deps.prepareDrForMessage();- Owner 端若尚未建鏈,會送出
session-init控制訊息並進入pending;Guest 端在收到session-init後先跑ensureSecureConversationReady(),成功即回session-ack; - 狀態切到
pending時,Messages Pane 會顯示「建立安全對話」Modal 並鎖住輸入,Ready 後自動解除;failed則填入錯誤文案方便重試。
- 訊息解密 / Replay:
- 前景對話(
mutateState=true)使用 live ratchet 並持續寫入drHistory; - 其他場景或控制訊息則走
mutateState=false,交給listSecureAndDecrypt()以歷史快照與messageKey_b64重播,確保非前景同步也能取得最新進度。
- 前景對話(
- 媒體 / Drive:
encryptAndPutWithProgress()用 MK 加密 →/media/sign-put→ R2 上傳;接收端/media/sign-get→ 解密。Drive 系統資料夾命名為drive-<acctDigest>(必要時以 MK-HMAC 分段)。 - 設定:
settings-<acctDigest>以 MK 包裝{ showOnlineStatus, autoLogoutOnBackground, autoLogoutRedirectMode, autoLogoutCustomUrl },所有欄位都以 MK-AEAD 加密儲存;App 啟動時ensureSettings(),更新立即saveSettings()。 - 其餘 envelope:Profile/聯絡人/訊息/媒體皆以 MK 衍生 AES-GCM;儲存層只保存密文。
- 登出清理:
secureLogout()先flushDrSnapshotsBeforeLogout()與persistContactSecrets(),將 JSON 寫入 sessionStorage +contactSecrets-v1-latest,再清除 cache/indexedDB 等。 - 登入頁:
purgeLoginStorage()會挑選最長 snapshot 回填 localStorage,並輸出 checksum(contactSecretsSeed*)供 QA 比對。 - 背景自動登出:
autoLogoutOnBackground(預設 true)在 App 退到背景時觸發secureLogout();若autoLogoutRedirectMode=custom且autoLogoutCustomUrl通過 HTTPS 驗證,登出後會導向指定網址。即使此設定被關閉,只要 App 頁面被重新整理就會立即強制secureLogout()並導向登出頁。 - 環境變數(常用):
NTAG424_*,ACCOUNT_HMAC_KEY,OPAQUE_*,DATA_API_*,S3_*,UPLOAD_MAX_BYTES,SIGNED_{PUT,GET}_TTL,SERVICE_*,ACCOUNT_TOKEN_BYTES,CORS_ORIGIN等。 - 儲值系統(訂閱延展):
PORTAL_API_ORIGIN(例如https://portal.messenger.sentry.red)、PORTAL_HMAC_SECRET必填。Node API 透過/api/v1/subscription/{redeem,validate,status}代理至 Portal,HMAC 計算為HMAC-SHA256(secret, path + "\n" + body);憑證為 Ed25519 簽章、extend_days天數延展,Portal 端負責唯一性與消耗,前端不直接呼叫 Portal。
bash ./scripts/deploy-prod.sh --apply-migrations流程:wrangler deploy → wrangler d1 migrations apply --remote → npm ci && pm2 reload message-api → wrangler pages deploy。可用 --skip-{worker,api,pages} 部分部署;若變更 D1 schema 請保留 --apply-migrations。
- 本地修正完成後,依 Prompt 規範自行驗證功能(目前已無自動化測試腳本)。
- 執行
bash ./scripts/deploy-prod.sh --apply-migrations,不可跳過任何部件(Worker / Node API / Pages 必須同步部署)。 - 佈署完成後,於正式環境手動驗證核心流程(登入、交友、訊息、媒體),並記錄結果。
TUN 服務備註:詳細主機資訊與操作規範請參考
docs/internal/tun-host.md(本檔案已被.gitignore排除,不隨版本控制分發;需向維運成員索取或於本地自行建立)。
- 若任何 Production 測試失敗,需先排除問題並重新部署,直到正式環境也全部通過為止。
目的:只讀盤點目前 D1 的實際 schema(tables / indexes / triggers / views),禁止任何寫入或 migration。
步驟(建議在 data-worker/ 目錄執行):
- 確認 D1 綁定與資料庫名稱:
data-worker/wrangler.toml內的[[d1_databases]](本專案為binding = "DB"、database_name = "message_db")。 - 確認 Wrangler 已登入且帳號可用:
wrangler whoami。若有多帳號,請指定CLOUDFLARE_ACCOUNT_ID。 - 確認目標 D1 存在:
CLOUDFLARE_ACCOUNT_ID=<your-account-id> wrangler d1 list --json
- 只讀查詢 schema(範例):
# 列出所有 tables / indexes / triggers / views(含建立語句) CLOUDFLARE_ACCOUNT_ID=<your-account-id> wrangler d1 execute message_db --remote \ --command "SELECT name, type, sql FROM sqlite_master WHERE type IN ('table','index','trigger','view') ORDER BY type, name;" # 逐表欄位 / index / foreign key CLOUDFLARE_ACCOUNT_ID=<your-account-id> wrangler d1 execute message_db --remote \ --command "PRAGMA table_info('<TABLE_NAME>');" CLOUDFLARE_ACCOUNT_ID=<your-account-id> wrangler d1 execute message_db --remote \ --command "PRAGMA index_list('<TABLE_NAME>');" CLOUDFLARE_ACCOUNT_ID=<your-account-id> wrangler d1 execute message_db --remote \ --command "PRAGMA index_info('<INDEX_NAME>');" CLOUDFLARE_ACCOUNT_ID=<your-account-id> wrangler d1 execute message_db --remote \ --command "PRAGMA foreign_key_list('<TABLE_NAME>');"
備註:部分系統表(例如 _cf_KV)可能回 SQLITE_AUTH,僅需記錄「不可讀取」即可,禁止改用任何寫入或升權方式。
目前無可用的自動化回歸腳本。部署後請人工驗證核心流程(登入 → 交友/建鏈 → 互傳文字與附件 → 登出/重登入)。如需重建 e2e 測試,請先規劃新的腳本與資料夾結構並更新本文件。
舊有 mjs/Playwright 測試已移除,尚未重建新的測試套件。所有變更需自行撰寫/執行對應的手動或自動驗證並在此文件紀錄。
- 目前狀態:所有既有
npm run test:*、Playwright、mjs 腳本已移除,以下時間軸僅保留歷史紀錄(其中提到的測試名稱/指令已不可執行)。現階段需以人工或新建腳本驗證登入/交友/訊息/媒體流程。
- 強化 DR 發送鏈防撞:同鏈單 sender 鎖(跨分頁/多實例)、重送時 header.n 與 counter 同步,送出前校驗 state/peerDeviceId,有異常直接中止並提示重建
| 日期 | 里程碑 |
|---|---|
| 2025-12-10 13:30 | contact-share ACL 調整:撤回自動補寫conversation_acl 的暫時性修補,恢復嚴格 per-device ACL(sender 必須先有授權);後續將追查 ACL 何時未寫入的根因。未執行 npm run test:*(僅調整 ACL 邏輯)。 |
| 2025-12-10 12:00 | Contact Secrets v2 前端收斂:storage key 改為contactSecrets-v2*(PK=accountDigest::deviceId,version=4),snapshot 序列化包含 inviteToken/tokenHash/version 與 peerDeviceId;登入/登出/交棒都以 v2 鍵名讀寫並清除 v1 seeds/meta/checksum,缺 peerDeviceId 可從 WS/會話資料回填;內建 v1→v2 遷移(local/session 挑最佳快照轉存 v2 並刪 v1)。未執行 npm run test:*。 |
| 2025-12-10 02:20 | /d1/friends/bootstrap 補回預鍵 metadata(owner/requester/peer 的 SPK/OPK 可用狀態),Node API cache/轉送同步帶出;/d1/accounts/purge 追加 attachments / device_opks / device_signed_prekeys / devices 清理,避免 Signal 化後殘留。未跑自動化測試。 |
| 2025-12-09 03:00 | 邀請預鍵強制帶 OPK:/d1/friends/invite 的 owner bundle 若無可用 OPK 直接回 PrekeyUnavailable,避免生成缺少預共享金鑰的 QR。仍建議 invite 前先補貨(24 支)確保成功。未執行 npm run test:*(目前無自動化套件)。 |
| 2025-12-09 02:45 | per-device 預鍵補強:prekey publish 現在一併上傳ik_pub(新欄位)且 bundle fetch/好友邀請回傳 IK,確保掃描 QR 不再出現「缺少預共享金鑰」。新增 migration 0015_device_signed_prekeys_ik.sql。未執行 npm run test:*(目前無自動化套件)。 |
| 2025-12-09 02:20 | 好友邀請改用 per-device 預鍵:Worker/d1/friends/invite 改從 device_signed_prekeys/device_opks 抽取指定裝置的 SPK+OPK(支援 deviceId 提供,否則取最新裝置),避免 owner bundle unavailable;保留 OPK 消耗邏輯。未執行 npm run test:*(目前無自動化套件)。 |
| 2025-12-09 02:00 | 修復好友邀請/預鍵補貨偶爾提示「找不到裝置 ID」:deviceId 會寫入 sessionStorage 並在頁面重載後自動恢復,避免 invite 前置檢查抓不到裝置資訊。未執行 npm run test:*(目前無自動化套件)。 |
| 2025-12-09 01:30 | 修復 Signal 重構後 profile/settings/drive 相關呼叫仍走舊版/api/v1/messages 導致 502/404:Node 端將 legacy createMessage 映射到新版 /d1/messages(自寄自收 secure message),並改用 conversationId 查詢 + 回填 ts;保持 per-device ACL 驗證。未執行 npm run test:*(目前無自動化套件)。 |
| 2025-12-09 00:40 | 首次設定 MK 時/api/v1/mk/store 在 session 逾時或多節點遺失記憶體時,會回退使用請求內的 accountToken/accountDigest 避免 401,並延長 SDM 交換 session TTL 至 300 秒;修正 mobile messages-pane 內 peerDeviceId 重複宣告導致的 SyntaxError;重新部署 Worker/Node API/Pages(最新 Pages: https://8453459c.message-web.pages.dev,Worker 版本 958b3e85…)。未執行 npm run test:*(目前無自動化套件)。 |
| 2025-12-08 12:30 | ContactSecrets 讀取時若本機 deviceId 缺快照會自動複製最新裝置的 DR state/history 至本機裝置並持久化,避免新裝置還原失敗;備份事件 detail 附帶 snapshotVersion/generatedAt,備份上傳記錄版本與筆數、下載同步記錄來源 meta。未執行npm run test:*。 |
| 2025-12-08 12:00 | 進一步強化 per-device 會話狀態:message-state 加入 activePeerDeviceId,WS 事件會回填聯絡人/列表的 peer_device_id;toast/列表開啟對話會更新 contactIndex/contactState/convIndex 的 peerDeviceId,conversation list dataset 標注 device;持續維持 WS payload/ACL/deviceId 驗證與 messageKey 媒體下載支持。未執行npm run test:*。 |
| 2025-12-08 10:00 | WS contact-share/contacts-reload/conversation-deleted 加入 sender/targetDeviceId 並在前端過濾、寫入 peerDeviceId;messages-pane/contacts-view 會話索引與 threads 攜帶 peerDeviceId,送訊時回填 receiverDeviceId;Node/Worker conversation authorize 要求 deviceId 並檢查 devices;contact-secrets backup 上傳帶 deviceId;媒體下載支援 messageKey 解密。未執行npm run test:*。 |
| 2025-12-07 18:10 | listSecureAndDecrypt 解密前先強制 recover + ensureDrReceiverState(缺狀態會嘗試 guest bundle)並在 decrypt 失敗時記錄 payload;inbox decrypt 失敗會觸發 recover/force reset 後以 mutate=false replay,同步在回執 send fail 時也嘗試 DR 重建。未執行npm run test:*(目前無自動化測試)。 |
| 2025-12-07 17:10 | WS 鑑權限制為單連線單帳號;跨帳號重認證會先清除舊 presence watcher 與 client 註冊再綁定新帳號,避免一條連線同時收到多個 digest 的事件;call-invite 寫入失敗會釋放鎖並回傳 CALL_EVENT_FAILED 避免 busy 假陽性(src/ws/index.js)。DR 恢復放寬:initiator/guest 端只要恢復出有效 ratchet(有 send/recv 鏈即可)即視為成功,避免誤判後強制 reset(web/src/app/features/dr-session.js)。未執行 npm run test:*。 |
| 2025-12-07 16:50 | 初始暱稱熵提升:擴充暱稱詞庫至 50+ 形容詞 / 50+ 名詞,尾碼改為 3 位 base36(46,656 組合),大幅降低同時註冊撞名機率。未執行npm run test:*(目前無自動化測試)。 |
| 2025-12-07 16:40 | 註冊流程加入「設定初始頭像中」步驟:新帳號在登入初始化時即用 SDM UID 生成 identicon 上傳為預設頭像並在進度列表顯示,避免初始頭像與登入頁預覽不一致。未執行npm run test:*(目前無自動化測試)。 |
| 2025-12-07 16:20 | 初始大頭貼與登入預覽一致化:自動產生頭像時改用 SDM 取得的uidHex 作為 identicon 种子(無 UID 時才回退 account_digest),避免登入頁顯示的 identicon 與首次上傳的預設頭像不一致。未執行 npm run test:*(目前無自動化測試)。 |
| 2025-12-07 17:20(設計) | 訂閱/憑證設計調整(尚未實作):改用本地 RS256 驗簽 + 防重複,不再走 Portal 權威驗證;JWT 以voucherId/jti 為唯一 token,含 durationDays(無上限)、不綁帳號 digest,任何使用者可展期但同一憑證只可使用一次。驗簽公鑰由 .env:PRIVATE_KEY_PUBLIC_PEM 提供,忽略 JWT exp。成功驗證後計算 expires_at = max(now, current) + durationDays*86400,寫入 D1 subscriptions/tokens/extend_logs,tokens.used_by_digest 紀錄實際使用者。後續提供查詢 API:查帳號到期時間、查憑證是否已用/由誰使用。前端 Modal 將顯示剩餘時間倒數、過期紅點,並支援 QR 掃描/圖檔匯入/貼上憑證;金流入口暫放佔位。未跑測試(目前無自動化套件)。 |
| 2025-12-07 16:00 | 修復通話記錄不顯示:messages-pane 內的 call-log 去重邏輯過早將 ID 標記為已送出,導致 sendDrCallLog 永遠不會呼叫、通話紀錄不會寫入對話;已改為僅在送出前標記一次。未執行 npm run test:*(目前無自動化測試)。 |
| 2025-12-07 15:47 | 修復好友邀請接受後 owner 端未收到聯絡同步:API 成功後立即透過 WS 推送contact-share(附 inviteId/envelope)給邀請建立者,確保聯絡人出現並自動關閉分享 Modal;目前無可用自動化測試,未執行 npm run test:*。 |
| 2025-12-07 10:00 | 完成 mobileshare-controller / contacts-view / app-mobile digest-only 事件 payload 清理,移除 ownerUid/peerUid 輸出並統一 conversationIndex/通知欄位;iOS-Development-Guids.md 更新為 accountDigest-only(僅 SDM debug 仍保留 UID)。僅進行靜態檢查,未重跑 npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login}(前端欄位改名,預計後續 digest-only 收尾完畢後一併重驗)。 |
| 2025-12-06 19:33 | 前端 digest-only 清理持續中:DR/session/messages 層改以 account_digest 介面(移除 peerUidHex 參數)、mobile messages-pane 會話列表/通話/傳訊/媒體發送同步改傳 digest、contact loader 儲存 digest 為主並保留 alias、contacts view 事件與刪除流程調整為 digest 索引,dev app UI 輸入欄改為 peerAccountDigest。尚未處理 mobile share-controller,相關測試 (npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login}) 未執行。 |
| 2025-12-06 18:30 | 因本輪重構後既有腳本失效,已移除所有 mjs 測試檔(scripts/test-*.mjs、tests/e2e/*.spec.mjs、tests/unit/messages.test.mjs 等),後續需重建新版測試流程。 |
| 2025-12-06 17:55 | 前端 digest-only 持續清理:Login → App handoff 以 account_digest 為主(app-ui/app-mobile/login-ui)、DR/訊息/通話 UI 去除本端 UID 依賴,remote-console/profile identicon/contacts 渲染改用 digest,測試腳本scripts/test-messages-secure.mjs / tests/e2e/utils.mjs / tests/e2e/multi-account-helpers.mjs 改用 digest 快照。尚未執行 npm run test:*。 |
| 2025-12-06 16:05 | Node API calls/groups digest-only:通話邀請/取消/ACK/metrics/TURN 憑證移除 UID 需求,僅驗證 account_token/account_digest;群組建立/成員增刪/查詢 payload 不再送 UID,僅保留 account_digest 與指紋;好友刪除 WS reload 改以 digest 廣播。未執行npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login}。 |
| 2025-12-06 16:01 | Worker 端 prekeys bundle / call session / call events 改為 digest-only:/d1/prekeys/bundle 僅接受 peer account_digest,不再 hash UID;upsertCallSession / insertCallEvent 移除 UID fallback、要求雙端 digest;Node /api/v1/keys/bundle schema 改為必填 peer_accountDigest。未執行 npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login},待隔離環境補測。 |
| 2025-12-06 15:49 | 前端通話/Presence 流程改為 account_digest 命名與 payload:通話 WS 信令只送/收 targetAccountDigest(移除 targetUid)、媒體層/接聽流程以 digest 追蹤對象;presence 訊息改讀onlineAccountDigests。未執行 npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login}。 |
| 2025-12-06 15:40 | 帳號驗證與 WS token 改為僅接受 account_digest/account_token,/d1/accounts/verify 取消 UID 驗證回傳僅帶 digest;WS token 不再簽 uid,WebSocket 連線/鎖定/presence 改以 accountDigest 運作並移除accountDigestByUid 映射。未執行 npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login}。 |
| 2025-12-06 13:31 | 移除前端 UID fallback:聯絡人/DR/Presence/通話/群組等鍵值全面使用account_digest,缺少 digest 的事件直接跳過;presence WS 訂閱/通知改以 digest 註冊並回傳 onlineAccountDigests,好友刪除 API 改傳 digest;持續未跑 npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login}。 |
| 2025-12-06 13:05 | contact / session 狀態以 account_digest 為主索引:contacts view 載入/新增/刪除、conversationIndex、presence 訂閱改以 digest 儲存並保留 UID fallback,contactState 也記錄 peerAccountDigest;WS/call digest 化同前。未執行npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login},待群組/部署流程完成後一併補跑。 |
| 2025-12-06 12:50 | 前端 peer 鍵值改以 account_digest 為主:core/store DR session 與 contact-secrets 以 digest 為主鍵並維護 UID alias,contacts convId 改優先 contacts-<account_digest>(UID 兼容),features/messages 接受 peerAccountDigest 取得 DR/指紋;WS contact-share/contacts-reload/presence/call 信令帶 digest,通話邀請/接聽與金鑰派生亦改用 digest 主索引。未執行 npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login},待群組/Session digest 化完成後整批補跑。 |
| 2025-12-03 13:45 | 呼叫事件表新增 account_digest 欄位(call_events.from_account_digest/to_account_digest),WS/Node 呼叫事件寫入與查詢同步帶 digest;後續仍需將好友/聯絡人/群組等流程改為僅用 account_digest。 |
| 2025-12-03 13:05 | scripts/cleanup/wipe-all.sh 與 scripts/cleanup/d1-wipe-all.sql 補齊 D1 清除清單,新增 call_* / group_* / contact_secret_backups / conversation_acl / conversations / subscriptions / tokens / extend_logs 等表,確保重置時不殘留新 schema 資料;未執行 npm run test:*。 |
| 2025-12-03 12:26 | 前端加入「建立群組」入口:生成 groupId/conv token/seed 呼叫/api/v1/groups/create,並自動複製群組資訊至剪貼簿、本機列表備查;群組訊息流程仍未串接,待後續擴充。未跑 npm run test:*。 |
| 2025-12-03 12:15 | 執行wrangler d1 migrations apply message_db --remote 套用 0010 群組 schema,並 scripts/deploy-prod.sh --apply-migrations 全套部署(Worker/Node API/Pages);未跑 npm run test:*。 |
| 2025-12-03 12:15 | 群組聊天後端腳手架:新增 D1groups / group_members / group_invites schema,Worker 提供 create/add/remove/get 端點並同步 conversation ACL,Node API 開出 /api/v1/groups/*(create/members add/remove/get);僅程式更新未跑 npm run test:*。 |
| 2025-12-03 12:04 | README 補群組聊天架構與 UI 筆記(區隔 1:1、規劃資料表/ACL/API/前端/WS、群組列表/建群/對話/成員抽屜/邀請 UI),僅文件更新未跑npm run test:*。 |
| 2025-11-29 08:51 | Drive 上傳前後端皆檢查容量:前端以現有使用量 + 待上傳檔案預估超過 3GB 配額即彈窗阻擋;後端media/sign-put 改用 3GB 空間上限(獨立於 500MB 單檔限制),超出直接 413;未執行 npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login},需後續補測。 |
| 2025-11-29 08:49 | Loading modal 去除旋轉圓形圖示,只保留進度條顯示載入狀態;未執行npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login},需後續補測。 |
| 2025-11-29 08:45 | Drive 資料夾點擊/返回上一層會顯示「載入資料夾中…」modal,避免等待列表刷新時無提示;未執行npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login},需後續補測。 |
| 2025-11-29 08:40 | 修正 Drive 重新命名功能未匯入createMessage 導致動作無效,列表改以 obj_key 去重只保留最新版本避免重命名顯示為重複檔案,並移除重命名 modal 的「長按觸發」提示僅保留名稱規則;未執行 npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login},需後續補測。 |
| 2025-11-29 08:36 | Node APImedia/sign-put 改為完全不檢查 Content-Type(忽略 UPLOAD_ALLOWED_TYPES),所有類型都允許;未執行 npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login},需後續補測。 |
| 2025-11-29 08:55 | Drive 上傳前端加入 500MB 單檔尺寸檢查(選擇檔案即阻擋,並在送出/佇列前再次防呆),仍允許任意檔案類型;未執行npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login},需後續補測。 |
| 2025-11-29 08:35 | Drive 容量面板移除外層 card,直接置於檔案列表上方同層呈現,避免雙卡片視覺;UI 調整未重跑npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login},需後續補測。 |
| 2025-11-29 08:21 | Drive 空間使用面板移出檔案列表卡片,獨立同級顯示以避免列表捲動時被頂出;UI 變更未重跑npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login},需後續補測。 |
| 2025-11-29 08:00 | 雲端硬碟頁面加入容量資訊欄與 3GB 配額進度條,依 Drive 列表累計檔案大小(排除佔位與重複 obj key)即時計算已用/剩餘並顯示百分比;前端 UI 變更未重跑npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login},需後續手動覆核 Drive 列表/進度條渲染與登入 e2e。 |
| 2025-11-28 09:18 | 頭像編輯改用 Cropper.js(拖曳 / 滾輪 / 雙指縮放),裁切後直接輸出 512px JPEG 上傳並沿用舊的 profile 儲存 / 廣播流程;裁切 modal UI 改為簡化預覽 + 行動手勢提示,並載入新版 Cropper 樣式。 |
| 2025-11-28 09:07 | 首次登入且尚未上傳頭像時,會直接以晶片 UID 同步生成登入頁 identicon、轉成 512px 圖檔上傳並寫入 profile,未來使用者自訂頭像仍會覆蓋;登入初始化工作清單改為固定高度可捲動容器,步驟完成後會淡出並收合,讓後續項目自動往上移動。 |
| 2025-11-16 13:40 | 修正 reload 強制登出在 Safari/WebKit 上因secureLogout() 早於變數初始化而拋出 ReferenceError(logoutInProgress, _autoLoggedOut, wsConn, presenceManager, SIM_STORAGE_* 等均提前宣告);新增 document.referrer 作為 reload 偵測備援,正式在 tests/e2e/login.spec.mjs(webkit-mobile)驗證「關閉 auto logout → reload 仍導向登出頁」並取得綠燈。尚未跑完整套 npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login}。 |
| 2025-11-16 09:15 | App 頁面重新整理時一定會呼叫secureLogout() 並導向登出頁,即使使用者在設定中關閉背景自動登出也無法繞過;web/src/app/ui/app-mobile.js 新增 reload 偵測與 forceReloadLogout() 流程,document.visibilitychange/pagehide 也會優先檢查 reload。tests/e2e/login.spec.mjs 加入「關閉 auto logout → reload 仍登出」案例,README 補充安全預期。尚未重跑 npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login},需待 WebKit 執行環境可用時再驗證。 |
| 2025-11-15 18:20 | Playwright e2e 調整為 WebKit 預設、Chromium 專責 call-audio:playwright.config.ts 新增 webkit-mobile / chromium-call 雙專案,WebKit 忽略 call-audio;tests/e2e/full-flow.spec.mjs 若環境缺 MediaRecorder 會註記並跳過影片附件,同時延長 timeout、在 context 關閉失敗時記錄 annotation。實際跑 npx playwright test tests/e2e/{contact-backup-cross-device,multi-account-friends}(WebKit)與 tests/e2e/call-audio(Chromium)皆通過,tests/e2e/full-flow 仍於圖片附件驗證階段因 WebKit 緩慢而 timeout,需進一步拆解。 |
| 2025-11-15 15:40 | 修正 WorkerensureDataTables 啟動時會 DROP TABLE prekey_users/prekey_opk/device_backup/friend_invites 的破壞性行為(導致每次冷啟動清空 D1 prekeys/devkeys,直接造成 guest (BD2E…) 無法解密);移除自動 drop 僅保留 idempotent 的 CREATE TABLE IF NOT EXISTS,重新跑 npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow} all green,npm run test:front:login 仍因 tests/e2e/full-flow.spec.mjs 缺泡泡 flake。後續需重新部署 Worker,並協助既有帳號補發 prekeys/devkeys。 |
| 2025-11-15 10:20 | 重新實機測試後,D1 仍只有 owner (E1C6…) 有 prekey_users/prekey_opk/device_backup,guest (BD2E…) 完全沒有任何 IK/SPK/OPK,因此訊息無法解密;PM2 log 顯示前端已觸發 /api/v1/keys/publish fallback(帶 ik_pub/spk_pub/spk_sig),但 Worker/D1 沒留下記錄,下一步需追查 /d1/prekeys/publish 是否未實際 insert 或指向錯誤資料庫。 |
| 2025-11-14 17:40 | 強化好友邀請完整性:Worker/d1/friends/contact/share 找不到 invite 時不再寫入 contacts-* fallback,而是直接 404;Node /api/v1/friends/invite 轉傳上游錯誤,/api/v1/friends/bootstrap-session 若收到缺少 spk_sig 的 guest_bundle 會回報 GuestBundleIncomplete,share-controller 也會攔截並提示重新邀請。同步更新 README TODO,並實際跑 npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow} all green;npm run test:front:login 持續在 tests/e2e/full-flow.spec.mjs flake(缺訊息泡泡 / timeout),待後續調查。 |
| 2025-11-14 14:30 | 針對 iOS Safari / PWA 持續無法關閉麥克風授權 modal 的問題,擴大isIosWebKitLikeBrowser 判斷:除了 Safari UA,也納入 Touch Mac、navigator.userAgentData.platform 含 iOS 以及 standalone PWA,以確保這些環境不會再套用 noiseSuppression。同時透過 navigator.mediaDevices.getSupportedConstraints() 決定是否啟用 echoCancellation/noiseSuppression,動態生成多層約束列表,避免 PWA 因 UA 不含 Safari 又回到舊的 OverconstrainedError。調整完成後執行 scripts/deploy-prod.sh --apply-migrations 全套部署;依指示未跑 npm run test:*。 |
| 2025-11-14 14:15 | 修復messages-pane 內 refreshActivePeerMetadata 重複宣告 avatarData 造成的 Identifier 'avatarData' has already been declared(Safari/Chrome 進入對話即報錯並中斷渲染):改用單一變數並沿用原 avatar snapshot,確保 active thread avatar 與標題更新正常。完成後依流程執行 scripts/deploy-prod.sh --apply-migrations 重新部署 Worker / D1、Node API、Cloudflare Pages;依使用者指示本輪未跑 npm run test:*。 |
| 2025-11-14 14:10 | 調整web/src/app/ui/app-mobile.js 的麥克風授權流程:針對不支援進階音訊約束的瀏覽器(例 iOS Safari)逐步退階 getUserMedia 參數並捕捉 OverconstrainedError,若僅是約束不符仍會視為已授權、顯示警語並收起提示;同時優化錯誤訊息。依照 scripts/deploy-prod.sh --apply-migrations 重新部署 Worker / D1、Node API、Cloudflare Pages,本輪依使用者指示未執行 npm run test:*。 |
| 2025-11-13 19:05 | 完成 NAT / TURN 整合第一階段:後端新增GET /api/v1/calls/network-config 依環境變數帶入 STUN/TURN/頻寬參數;前端 loadCallNetworkConfig() 會優先呼叫此 API、失敗時回退 Pages 內建 JSON,再失敗才使用程式內建預設,並於建立 RTCPeerConnection 時合併靜態 STUN 與即時發出的 TURN 認證。docs/encrypted-calls-network.md 同步記錄新 API/環境變數。尚未在本機跑 npm run test:calls-encryption,原因是缺乏完整 Worker / TURN 佈署,待環境備妥後補測。 |
| 2025-11-13 17:00 | 調整多支 e2e:tests/e2e/call-audio.spec.mjs 於最新語音限定流程下已再度通過;tests/e2e/full-flow.spec.mjs 則仍失敗在重新登入後驗證歷史訊息(Decrypt snapshot 仍回傳 OperationError,setActiveConversation 能進入對話但 #messagesList 不會出現舊訊息),相對應的 test-results/full-flow-* 已保留供除錯。tests/e2e/multi-account-friends.spec.mjs 尚未重新跑。 |
| 2025-11-13 16:20 | 暫時停用視訊通話:移除messagesVideoBtn、mediaPermissionOverlay 改為僅要求麥克風授權(web/src/pages/app.html),並讓媒體授權流程只呼叫 getUserMedia({ audio })、錯誤訊息也改為麥克風專用(web/src/app/ui/app-mobile.js);同時 messages-pane 會強制把「視訊」動作轉為語音並統一提示(web/src/app/ui/mobile/messages-pane.js)。執行 npx playwright test tests/e2e/call-audio.spec.mjs 時因既有 UI 會停留在聯絡人分頁導致 #messagesCallBtn 在小螢幕不可見而逾時(log 見 test-results/call-audio-encrypted-audio-call-with-fake-media-stream-chromium-mobile/),待後續釐清。 |
| 2025-11-13 15:20 | 新增 Playwright 測試tests/e2e/contact-backup-cross-device.spec.mjs,模擬兩支裝置 / 兩顆晶片互登出後交換登入,驗證 contact-secrets 雲端備份會自動解包並還原 Double Ratchet 狀態、可立即解密舊訊息並續傳;本地先啟動 API,再執行 npx playwright test tests/e2e/contact-backup-cross-device.spec.mjs 全數通過。 |
| 2025-11-13 14:40 | App 登入後新增「啟用語音/影像通話」權限提示,使用者需點選確認以授權麥克風/鏡頭並預先解鎖音訊播放(包含 iOS Safari 的背景靜音保護);自動化情境會自動標記為已授權避免測試卡住。完成授權後會記錄於sessionStorage,並在背景播放靜音 + AudioContext resume 來確保遠端音訊可即時播放。此變更已重跑 npm run test:front:call-audio 並重新部署 Worker / Node API / Pages。 |
| 2025-11-13 13:30 | 通話 e2e 測試新增「通話至少 3 秒+雙端音訊振幅」驗證,並於前端加入 WebRTC Offer/Answer 描述與 ICE candidate 正規化、remoteDescription 建立前的候選佇列處理;同步調整事件匯流排僅傳遞detail,讓 media-session 能收到 call-offer/call-answer 訊號。Node API buildCallDetail 現在保留完整 candidate 物件,上述修正經 npm run test:front:call-audio 回歸。 |
| 2025-11-13 12:40 | e2e 語音測試新增「成功接通並開始計時」檢查點:Playwright 會等待兩端 overlay 計時器出現,確保狀態切到「通話中」。為讓 UI 實際達標,call state 在收到 call-accept 時若已完成密鑰協商(CALL_MEDIA_STATE_STATUS.READY)會立即推進為 IN_CALL,同時保留媒體管線事件的自動提升邏輯。npm run test:front:call-audio 通過。 |
| 2025-11-13 12:05 | 修正來電 / 撥出頭像載入失敗與通話狀態卡在「正在接通…」:messages-pane 會正規化聯絡人頭像 URL(避免誤塞整個 avatar 物件造成 <img src=\"[object Object]\">),media-session 則在送出 answer、收到對方 answer、或接收到媒體/ICE 連線時主動推進為 IN_CALL。npm run test:front:call-audio 通過。 |
| 2025-11-13 11:20 | 修正通話 overlay 顯示錯誤:WebSocketcall-invite 的 metadata 改為帶入本機使用者的暱稱 / 頭像(displayName、callerDisplayName、avatarUrl、callerAvatarUrl),並同時保留對方資訊於 peer* 欄位,確保 A 呼叫 B 時,A 看到 B、B 看到 A 的圖像與暱稱。依使用者指示本次未重跑 Playwright / API 測試,後續如需驗證請告知。 |
| 2025-11-13 10:45 | 修正語音通話接起後仍停留在「正在接通…」且雙方無聲的問題:media-session 於 ICE / connection 狀態與 ontrack 事件時主動將 session 推進到 IN_CALL,並在遠端音訊串流掛載時強制呼叫 audio.play()(含未靜音才自動播放),確保計時與提示同步;同時補上 Playback promise 錯誤 logging。Playwright test:front:call-audio、test:prekeys-devkeys、test:messages-secure、test:friends-messages、test:login-flow 本地皆通過。 |
| 2025-11-13 09:30 | 導入 Contact Secrets 雲端加密備份:前端以登入密碼衍生的 MK 將contactSecrets-v1 snapshot 包成 AES-GCM envelope,上傳至 Worker / D1 儲存,僅記錄統計 metadata,伺服端無法解密內容;登入後若本地缺快照會自動下載最新版解包,確保晶片/裝置互換也能還原舊訊息。同步新增 API /api/v1/contact-secrets/backup(POST/GET)與 D1 contact_secret_backups 表,並在登出、定期 flush Contact Secrets 時自動備份。 |
| 2025-11-13 06:04 | 客製化登出網址在實際重導前會先顯示全白遮罩,避免網路延遲時露出原 App 畫面;同時新增npm run test:calls-encryption(API 層)驗證 call invite → ack → session → metrics → cancel,確保 call-key-envelope 會持久化於 session。依使用者指示未重跑 npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login},並重新部署 Worker / Node API / Pages。 |
| 2025-11-13 05:49 | 調整「客製化登出頁面」為獨立 Modal,主設定視窗只保留單選+摘要,勾選或點「設定網址」才會跳出 Modal 供輸入 HTTPS(含常見網址建議);同時移除登入歡迎畫面被選取的文字框線並新增內部捲動。依使用者指示未重跑npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login},並重新部署 Worker / Node API / Pages。 |
| 2025-11-13 05:10 | 系統設定頁在「當畫面不在前台時自動登出」啟用時會顯示「預設 / 客製化」單選與可編輯下拉,支援常見網址選擇、HTTPS 正規化與立即儲存;settings-<acctDigest> 新增 autoLogoutRedirectMode/autoLogoutCustomUrl 仍以 MK-AEAD 加密,secureLogout() 會依設定導向預設頁或自訂網址。同步補強 README 與 CSS,並重新部署 Worker / Node API / Pages;npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login} 全數通過。 |
| 2025-11-12 19:30 | Call Overlay 加入加密狀態、通話計時與靜音/喇叭/掛斷控制,shared/calls/schemas.{js,ts} 新增 controls 結構供 media session 同步,features/calls/media-session.js 暴露靜音 API 並於 Insertable Streams 管線套用;同時把 contactSecrets-v1 名稱空間化(uid/accountDigest),修正同裝置不同晶片交錯測試時的 snapshot 汙染,npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login} 再次全數通過。 |
| 2025-11-12 10:25 | 修正 CallKeyManager 在登入階段反覆resetKeyContext 造成 stack overflow,調整 /shared/* 載入路徑並讓 Playwright test:front:login 再次綠燈;同時完成 sendCallOffer/Answer + TURN Insertable Streams skeleton,npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login} 全數通過後重新部署 Worker / Node API / Pages。 |
| 2025-11-12 15:40 | CallKeyManager 完成:撥號時會以聯絡人祕密派生 CMK、產生 call-key-envelope 並隨 call-invite 傳送,受話端自動驗證 proof/派生音視訊雙向金鑰;Overlay 顯示「建立加密金鑰」與錯誤提示,messages-pane 撥號流程也確保 envelope 成功建立後才送信令。媒體層尚待串接,未重跑 npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login}。 |
| 2025-11-12 12:10 | Node WebSocket 新增call-invite/call-accept/... 信令處理與 120 秒互斥鎖,所有事件寫入 Cloudflare Worker call_events;features/calls/signaling.js 讓 messages-pane 實際透過 WS 發送 call-invite 並在收到訊號時觸發 markIncomingCall(),再以 CALL_EVENT.SIGNAL 廣播供 UI/overlay 使用。README 更新現況與下一步,尚未重跑 npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login}。 |
| 2025-11-12 06:50 | Cloudflare Worker 新增call_sessions / call_events 表與 /d1/calls/{session,events} CRUD,Node API 對應實作 /api/v1/calls/{invite,cancel,ack,report-metrics,turn-credentials} 及 GET /api/v1/calls/:id、TURN 憑證簽發;前端 requestOutgoingCall() 現可寫入 session/event,並將 TURN/ICE 設定集中於環境變數。npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login} 重跑皆綠燈。 |
| 2025-11-12 06:40 | Playwrighttests/e2e/full-flow.spec.mjs 增加 test.setTimeout(240_000)、tests/e2e/multi-account-friends.spec.mjs 增加 test.setTimeout(300_000),確保多帳號 + 媒體壓力流程在 CI 內有足夠時間;同時讓 Node API 在本機常駐後重新跑 npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login} 全數通過,原本的逾時已解除並產生最新 test-results/。 |
| 2025-11-12 05:37 | 實作語音/視訊通話第一階段:建立shared/calls/schemas.{js,ts}、Swift 版 docs/ios/CallSchemas.swift 及 shared/calls/network-config.json;新增 features/calls/{events,state,network-config}.js 讓前端具備 state manager / event bus / TURN 載入,聊天呼叫鈕也改走 requestOutgoingCall() 並預抓 network config。npm run test:{prekeys-devkeys,messages-secure,login-flow} 綠燈,friends-messages 因本機未啟動 API 而 fetch failed,test:front:login 仍有 full-flow、multi-account-friends 逾時(餘 3 項通過,詳細輸出見 test-results/)。 |
| 2025-11-07 00:10 | Login / App 頁加入viewport-fit=cover 與 safe-area padding,修正 iOS Safari 頂/底邊裸露;登入錯誤映射補上 EnvelopeRecoveryError;改密碼流程成功後會即時 re-wrap MK + 重新註冊 OPAQUE,ensureOpaque 偵測 Envelope 錯誤時自動重註冊,E2E login 測試覆蓋「改密碼→新密碼登入→改回原密碼」並留下截圖 (artifacts/e2e/login/change-password-success.png)。npx playwright test tests/e2e/login.spec.mjs 綠燈。 |
| 2025-11-06 23:45 | Login / App handoff 新增wrapped_mk 儲存與還原,App 端變更密碼可直接使用現有 MK;tests/e2e/login.spec.mjs 安插改密碼測試並產生截圖 (artifacts/e2e/login/change-password-success.png),同時在測試內將密碼改回預設值避免影響其他場景。觀察到改密碼後立即以新密碼重新登入仍會觸發 EnvelopeRecoveryError,暫以重新改回原密碼做 workaround,待後續修正。npx playwright test tests/e2e/login.spec.mjs 綠燈。 |
| 2025-11-06 23:00 | 登入頁錯誤映射補上 OPAQUE 密碼錯誤(OpaqueLoginFinishFailed 等)並維持護盾光效調整;設定選單新增「變更密碼」流程(前端 unwrap → rewrap MK、呼叫新 /api/v1/mk/update API)與 UI 表單;跑完 npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login} 後執行 scripts/deploy-prod.sh --apply-migrations 全套部署。 |
| 2025-11-06 22:33 | Login 護盾光帶加粗並提升柔光層次,App 主畫面也加入gesture* 停用 pinch 以配合現有 viewport 設定;依指示直接執行 scripts/deploy-prod.sh --apply-migrations 同步部署 Worker / D1 / Node API(pm2 reload)/ Pages,未重跑 npm run test:*。 |
| 2025-11-06 22:30 | 更新 login 頁meta viewport 與 gesturestart/change/end 事件禁止雙指縮放,確保晶片護盾特效不被縮放干擾;再次執行 scripts/deploy-prod.sh --apply-migrations 同步部署全部部件。依使用者要求仍未重跑 npm run test:*。 |
| 2025-11-06 22:25 | Login 頁新增 3 秒綠色護盾光帶動畫(SVG path + glow + reduced-motion fallback)以陪伴晶片偵測等待,並執行scripts/deploy-prod.sh --apply-migrations 佈署 Cloudflare Worker / D1、Node API(pm2 reload)、Pages。使用者明確要求本輪略過 npm run test:*,沿用前次 2025-11-13 的測試結果。 |
| 2025-11-13 05:40 | 修正x3dhRespond 初始鏈鍵配置(responder 以首段種子作為 ckR)避免 owner 端 decrypt OperationError,tests/e2e/multi-account-friends.spec.mjs 重跑穩定通過;全套 npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login} 皆綠,確認多帳號互邀 / 附件壓力流程與登入迴圈無回歸。 |
| 2025-11-13 02:30 | Playwright 新增tests/e2e/session-bootstrap.spec.mjs 覆蓋「加好友未傳訊息 → 新裝置登入」情境,登入前預注 contact secret snapshot 以驅動 conversation list 與安全 Modal / composer 狀態驗證;前端 fetchWithTimeout 預設 cache: 'no-store' 避免 contacts API 被瀏覽器快取回 304;npm run test:front:login 全套重跑確認四支 E2E 均綠。 |
| 2025-11-12 | 新增SecureConversationManager 集中管理 DR 初始化與 session-init 控制訊息,加入 session-ack 確認、逾時監控與 initiator 自動重送邏輯;Messages / Contacts UI 改為事件驅動顯示安全 Modal 並移除 secureInitBlocked flag。Contact Secrets setter 改為結構化(invite / conversation / dr / session)並提供 getContactSecretSections 方便後續模組引用;補上 Node POST /api/v1/friends/bootstrap-session API(附帶快取)以便缺會話時自動補抓 guest_bundle 並同步 Contact Secrets / sessionStore。npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login} 重跑皆綠。 |
| 2025-11-11 | 好友邀請接受後自動送出隱藏的session-init 封包,同時保留 guest_bundle 強制重建流程並顯示安全提示 Modal,避免雙方首次聊天出現「部分訊息無法解密」。npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login} 通過。 |
| 2025-11-10 | DR / ACL 啟動驗證:登入或掃描後載入訊息會先計算 conversation fingerprint 並帶入/messages/secure,確保 Worker 授權與 DR state 就緒;重新驗證 npm run test:{friends-messages,front:login}。 |
| 2025-11-10 | 好友邀請初始化加強:/friends/invite/contact 會附帶帳號驗證資訊並於缺漏時自動建立 friend_invites 記錄,確保 owner envelope 一定寫入;Node/前端/script 同步更新。npm run test:{friends-messages,front:login} 通過。 |
| 2025-11-10 | 恢復自我聯絡 metadata:loadContacts 會保留自身條目並寫入 contactSecrets / conversationIndex,UI 仍隱藏自我聯絡避免清單出現自己;重跑 npm run test:{friends-messages,front:login} 驗證。 |
| 2025-11-10 | messages.controller 與 friends.controller 新增帳號驗證 + 會話 ACL 授權,所有列表 / 刪除 / 建立操作需帶入 uidHex 與 accountToken/accountDigest;前端 API、DR 流程與測試腳本同步補上憑證與 conversation fingerprint。npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login} 全數通過。 |
| 2025-11-09 | `/media/sign-put |
| 2025-11-08 | 修正 shareState 在 logout 後殘留inviteBlockedDueToKeys 導致 QR 再登入無法生成;Drive 列表隱藏系統「已傳送 / 已接收」層並依使用者資料夾顯示,允許重複上傳與刪除;訊息附件新增預覽按鈕沿用 Modal,Playwright 以 downloadAndDecrypt 驗證 SHA-256 digest;npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login} 全數通過。 |
| 2025-11-07 | 交友金鑰補貨流程自動在PrekeyUnavailable/NotFound 時改用完整 bundle(IK/SPK/OPK)重發,狀態列 spinner 調整為正圓動畫;聯絡人載入時會跳過自己,避免改暱稱後自我條目出現在好友清單;全套 Playwright full-flow 再次確認;重新部署 Worker / Node API / Pages。 |
| 2025-11-06 | Playwright full-flow 完成附件共享金鑰封套驗證,修復接收端預覽維持pending;sendDrMedia() 會為媒體產生共享 key,downloadAndDecrypt() 依 key_type 自動挑選 MK 或共享金鑰;Share controller 會檢查 OPK 補貨 API 回應並於失敗時回報 PrekeyUnavailable,避免出現「缺少交友金鑰」;完成重新部署 Worker、Node API、Pages。 |
| 2025-11-05 | listSecureAndDecrypt() 重播後套用 snapshotAfter,統一媒體物件至 已傳送 / 已接收 系統資料夾並新增 Worker 容量追蹤;DR receiver 重登入時會回溯歷史 snapshot 並重設 processed cache,避免首輪 decrypt 失敗與重複抓取;E2E full-flow 驗證 sign-put payload 與 Drive「已傳送」資料夾顯示;npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login} 全數通過。 |
| 2025-11-04 | 修復重新開啟會話時訊息列表被清空(重置 processed cache 重新導入訊息歷史),新增登出後專用畫面(呼吸紅光 Logo + 提示文案),tests/e2e/full-flow.spec.mjs 新增「返回列表→重進會話」與「聯絡人頁→點選好友」驗證,雙端確認訊息與附件仍存在,再進行刪除;npm run test:front:login 通過。 |
| 2025-11-03 | 重新設計會話與聯絡人列表的刪除介面(固定 delete row + 送出模擬/friends/delete),修正 pointer-events 攔截問題,npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow,front:login} 全數通過。 |
| 2025-11-02 | 新增 DR replay 陣列快取與 skipped message key 快取,listSecureAndDecrypt 支援非 mutate 模式也能 replay;sendDrMedia 攜帶媒體索引並寫入本地預覽。npm run test:{prekeys-devkeys,messages-secure,friends-messages,login-flow} 通過;npm run test:front:login 已可重登入成功解密所有文字訊息與上傳附件,但流程在會話刪除(.item-delete 被 pointer-events 阻擋)與暱稱廣播 fallback(/friends/contact/share 404)卡住,待修。 |
| 2025-11-01 | Worker/d1/friends/contact/share 新增 fallback:當 invite_id 不存在但仍提供 myUid/peerUid 時,直接寫入目標聯絡人信箱並標記 fallback=invite_missing;登入流程若備份 404,會優先回填 handoff 的 wrapped_dev,必要時再重建。前端送訊端會連同 message_key_b64 與 snapshotAfter 寫入 DR 歷史,讀取端也能以 replay 優先解密。npm run test:prekeys-devkeys / test:messages-secure / test:friends-messages / test:login-flow 通過;npm run test:front:login 仍於 tests/e2e/full-flow.spec.mjs 失敗:最新 run 中訊息 d27fb152-3093-43d3-84c7-232a82358203 replay 後 DR state 的 Nr 未同步至 header n=2,導致再次 OperationError,重播後續的媒體預覽流程亦受阻。 |
| 2025-10-31 | 新增drHistory.messageKey_b64 儲存每則訊息的派生金鑰,listSecureAndDecrypt() 在重新登入、初次載入時會優先使用快照中的 message key 進行重播解密,避免重複 ratchet 導致 OperationError。npm run test:prekeys-devkeys / test:messages-secure / test:friends-messages / test:login-flow 均通過;npm run test:front:login 仍在 tests/e2e/full-flow.spec.mjs 卡關:A 端更新暱稱時 /friends/contact/share 回 404,導致 B 端重新登入流程出現 Device backup missing(/devkeys/fetch 404)。需先修復 contact share / devkeys 取得問題,再重跑 full-flow 驗證 decrypt 是否恢復正常。 |
| 2025-10-30 | 新增flushDrSnapshotsBeforeLogout(),logout 前將記憶體 DR state 寫回 contactSecrets 並記錄 checksum;登入頁 purgeLoginStorage() 會挑選最長 snapshot 回填。recoverDrState() 支援 forceGuestBundle,並針對 Automation 模式輸出 dr-debug 與重播腳本。prepareDrForMessage() 加入 historyMatchBy 日誌。 |
| 2025-10-29 | secureLogout、purgeLoginStorage、hydrateDrStatesFromContactSecrets 整合 snapshot 摘要/SHA-256 checksum,確保 QA 可比對 handoff;pullLatestSnapshot() 會將 sessionStorage 較新的資料回填 localStorage。 |
| 2025-10-28 | messages.js 對背景預覽改用 mutateState=false,避免覆寫 DR snapshot;新增 duplicate guard 與去重快取;ensureDevicePrivAvailable() 只接受登入交棒;full-flow 仍卡在重登入第一則訊息 OperationError。 |
| 2025-10-26 | Login 頁清除 localStorage 前會回寫contactSecrets-v1;share-controller 不再覆寫既有角色;dr-session.js / messages.js 增加 snapshot 還原與 dr-debug log。 |
| 2025-10-10 | 裝置私鑰備援流程:若備份缺失,會重新發佈預共享金鑰並儲存wrapped_dev,避免 DR 初始化因 404 中斷。 |
本專案採用 GNU Affero General Public License v3.0(AGPL-3.0-only)。若部署於可供他人透過網路存取的服務,請公開對應來源碼與修改內容,以確保社群共享與使用者權益。