Skip to content

SENTRY-Security/SENTRY-Messenger

Repository files navigation

SENTRY Message — 技術筆記

近期進度:雲端硬碟分頁新增容量資訊欄(預設 3GB 配額,隨 Drive 列表即時計算使用率並以進度條呈現);修復 logout→relogin 後分享面板持續顯示「缺少交友金鑰」造成 QR 無法重生的問題,重置 shareState 會清除補貨鎖定並恢復自動補貨;Drive 面板改以使用者資料夾為主並隱藏系統「已傳送 / 已接收」層,避免上傳檔案被困且可再次上傳 / 刪除;訊息附件新增「預覽」動作沿用 Modal 下載流程並於 Playwright 內實際執行 downloadAndDecrypt 驗證 SHA-256 digest,確認接收端確實可還原檔案。

目錄

  1. 簡介與快速開始
  2. 架構概覽
  3. 關鍵流程
  4. 安全預設與環境配置
  5. 營運與部署流程
  6. 測試與自動化
  7. 最新進度與工作項目
  8. 授權條款

簡介與快速開始

  • 目標:驗證「晶片感應 → 零知識登入 → 端對端密訊&媒體」的連貫體驗,同時確保所有祕密僅存於使用者裝置記憶體。實際設計:明文與 MK 僅在瀏覽器記憶體使用,但伺服端會保存密文(訊息/媒體)以及以 MK 包裝的密鑰備份(wrapped_mkwrapped_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_b64account_token 等)與 contactSecrets-v1 快照;App 讀取後立即清空。

資料流摘要

  1. Login 頁完成 SDM + OPAQUE 後解封 MK,僅短暫存於 sessionStorage。
  2. App 頁接手 MK、wrapped_dev、contactSecrets 等 snapshot,並透過 WebSocket / REST 同步資料。
  3. 所有加密/解密在前端記憶體執行;後端只儲存密文與索引。
  4. 雙向訊息採 Double Ratchet;登出時會 flush snapshot,重新登入時由 contactSecrets-v1 還原。

群組聊天擴充(設計筆記)

  • 群組 conversation 採獨立 groupId + conversation_token,不與 1:1 共用 ACL / DR state,原有單聊流程保持原樣。
  • 資料層預計新增 groups / group_members / group_invitesconversation_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 頭像/名稱(連續訊息合併頭像);列表預覽顯示未讀與 @提及徽章;右側/抽屜顯示成員清單與管理操作(靜音/退出/踢人/再發邀請);被踢或離開時覆蓋提示可返回列表。

關鍵流程

登入與主金鑰 (MK)

  1. SDM 感應POST /api/v1/auth/sdm/exchange,Node 端驗證 MAC,並透過 Worker 建立帳號。
  2. OPAQUE:前端 ensureOpaque() 與 Node /api/v1/auth/opaque/*(代理 Worker /d1/opaque/*)互動,不暴露密碼。
  3. MK 處理:若無 MK → 產生並 wrapMKWithPasswordArgon2id/api/v1/mk/store。若已有 → 解封 wrapped_mk
  4. 交棒:登入頁將 mk_b64account_tokenaccount_digestwrapped_devcontactSecrets-v1 放至 sessionStorage / localStorage (contactSecrets-v1-latest),App 取用後清空。

裝置金鑰與 Prekeys

  1. 備份:無備份時產生 IK/SPK + 100 OPKs → /api/v1/keys/publish → 以 MK 包裝後 /api/v1/devkeys/store
  2. 補貨:已備份則解包 wrapped_dev,視需要補 OPKs(每次 20 支),再度包裝並存回。
  3. API 限制/api/v1/devkeys/* 僅接受 accountToken/accountDigest;若只給 token,Node 會自行取 digest 以保護 UID。
  4. Worker/d1/prekeys/publish upsert IK/SPK/OPK,/d1/prekeys/bundle 配發且消耗對方 OPK。

好友邀請與聯絡同步(重構中,單一路徑,無 fallback)

  1. 建立邀請:Owner 端補貨 per-device 預鍵後,產生 QR(僅含 inviteId + owner device/prekey meta,不存 contact snapshot),呼叫 /api/v1/friends/invite 寫入 token hash + owner bundle。
  2. 接受邀請:Guest 掃描後,使用 owner bundle 跑 X3DH 取得會話種子,/api/v1/friends/accept 僅驗證 token 並回傳 owner digest/device + bundle 消耗結果。
  3. 建會話 / DR 初始化:雙方各自以會話種子派生 conversation token(per-device),初始化 DR,後續所有同步只靠 conversation token + DR snapshot。
  4. 聯絡同步/api/v1/friends/contact/share 只收 conversationId + AEAD envelope,ACL 綁 accountDigest+deviceId;WS contact-share 事件帶 sender/targetDeviceId,前端更新 contactSecrets(含 DR snapshot)。
  5. 狀態管理:不再持久化 invite secret 或 session bootstrap,缺資料時直接報錯,不做自動重建 / fallback。

Double Ratchet 訊息傳遞(棄用,重構目標:per-device Signal X3DH/DR,移除 session-init/ack 與 conversation fingerprint 授權,封包 header 標準化)

  1. 設備金鑰交棒ensureDevicePrivAvailable() 僅依賴登入交棒/記憶體;若 sessionStorage 缺件,直接報錯不再自動補建。
  2. 初始化:若 drState 缺失、且 dr_init 可用 → bootstrapDrFromGuestBundle();否則呼叫 prekeysBundle + x3dhInitiate 建立新會話(消耗對方 OPK)。
  3. 傳送訊息drEncryptText 產生 header + ciphertext → /api/v1/messages/secure 儲存 envelope(D1 messages_secure)。
  4. 接收解密
    • listSecureAndDecrypt() 先排序訊息;若為重播情境會利用 prepareDrForMessage 檢查 timestamp / messageId 是否早於 cursor,必要時還原 snapshot。
    • 若 snapshot 缺失會落到 recoverDrState()(可強制使用 guest_bundle),同時記錄 [dr-decrypt-fail-payload]tests/scripts/debug-dr-replay.mjs 重播。
    • 每次成功解密會 recordDrMessageHistory()(包含 messageKey)並 persistDrSnapshot()
  5. DR 恢復判定recoverDrState() 允許 initiator/guest 端只要恢復出有效 ratchet(rk + send/recv 任一鏈與 myRatchetKey 成立)即視為成功,不再硬性要求 responder ckR;避免誤判失敗後強制 reset,實作位於 web/src/app/features/dr-session.js

WebSocket 連線 / presence / 呼叫訊號

  • 單次鑑權:同一 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 控制包)

  1. 邀請建立:Owner 端透過 friendsCreateInvite() 生成 invite,share-controller 會同步:
    • 將 contact snapshot 以 invite secret 包裝後呼叫 friends/invite/contact
    • 使用 setContactSecret() 在本地記錄 { inviteId, secret, role: 'owner', conversation.{token, id, dr_init} }
  2. 好友接受:Guest 掃描邀請後執行 friendsAcceptInvite()
    • Worker 綁定帳號並回傳 guest_bundle + owner contact;
    • Guest 端 storeContactSecretMapping() 儲存對應 invite 資料、預填 conversation.drInit,必要時 bootstrapDrFromGuestBundle() 立即進入 responder 狀態。
  3. Contact Secrets 同步:雙方藉由 /friends/contact/share 更新最新 profile / conversation token,loadContacts() 或 WS contact-share 事件會:
    • 寫入 contactSecrets-v1conversation.{token,id,drInit}drHistory;
    • 對 UI 觸發 contacts:rendered 事件以重建列表。
  4. SecureConversationManager 進場
    • ensureSecureConversationReady() 會先檢查本地 drState,缺失時啟動 deps.prepareDrForMessage()
    • Owner 端若尚未建鏈,會送出 session-init 控制訊息並進入 pending;Guest 端在收到 session-init 後先跑 ensureSecureConversationReady(),成功即回 session-ack
    • 狀態切到 pending 時,Messages Pane 會顯示「建立安全對話」Modal 並鎖住輸入,Ready 後自動解除;failed 則填入錯誤文案方便重試。
  5. 訊息解密 / Replay
    • 前景對話(mutateState=true)使用 live ratchet 並持續寫入 drHistory
    • 其他場景或控制訊息則走 mutateState=false,交給 listSecureAndDecrypt() 以歷史快照與 messageKey_b64 重播,確保非前景同步也能取得最新進度。

媒體、設定與資料夾命名

  • 媒體 / DriveencryptAndPutWithProgress() 用 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=customautoLogoutCustomUrl 通過 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 deploywrangler d1 migrations apply --remotenpm ci && pm2 reload message-apiwrangler pages deploy。可用 --skip-{worker,api,pages} 部分部署;若變更 D1 schema 請保留 --apply-migrations

正式釋出流程(必須)

  1. 本地修正完成後,依 Prompt 規範自行驗證功能(目前已無自動化測試腳本)。
  2. 執行 bash ./scripts/deploy-prod.sh --apply-migrations,不可跳過任何部件(Worker / Node API / Pages 必須同步部署)。
  3. 佈署完成後,於正式環境手動驗證核心流程(登入、交友、訊息、媒體),並記錄結果。

TUN 服務備註:詳細主機資訊與操作規範請參考 docs/internal/tun-host.md(本檔案已被 .gitignore 排除,不隨版本控制分發;需向維運成員索取或於本地自行建立)。

  1. 若任何 Production 測試失敗,需先排除問題並重新部署,直到正式環境也全部通過為止。

D1 Schema 盤點(只讀)

目的:只讀盤點目前 D1 的實際 schema(tables / indexes / triggers / views),禁止任何寫入或 migration。

步驟(建議在 data-worker/ 目錄執行):

  1. 確認 D1 綁定與資料庫名稱:data-worker/wrangler.toml 內的 [[d1_databases]](本專案為 binding = "DB"database_name = "message_db")。
  2. 確認 Wrangler 已登入且帳號可用:wrangler whoami。若有多帳號,請指定 CLOUDFLARE_ACCOUNT_ID
  3. 確認目標 D1 存在:
    CLOUDFLARE_ACCOUNT_ID=<your-account-id> wrangler d1 list --json
  4. 只讀查詢 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_logstokens.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-*.mjstests/e2e/*.spec.mjstests/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.shscripts/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() 早於變數初始化而拋出 ReferenceErrorlogoutInProgress, _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_sigguest_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-panerefreshActivePeerMetadata 重複宣告 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 仍回傳 OperationErrorsetActiveConversation 能進入對話但 #messagesList 不會出現舊訊息),相對應的 test-results/full-flow-* 已保留供除錯。tests/e2e/multi-account-friends.spec.mjs 尚未重新跑。
2025-11-13 16:20 暫時停用視訊通話:移除messagesVideoBtnmediaPermissionOverlay 改為僅要求麥克風授權(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_CALLnpm run test:front:call-audio 通過。
2025-11-13 11:20 修正通話 overlay 顯示錯誤:WebSocketcall-invite 的 metadata 改為帶入本機使用者的暱稱 / 頭像(displayNamecallerDisplayNameavatarUrlcallerAvatarUrl),並同時保留對方資訊於 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-audiotest:prekeys-devkeystest:messages-securetest:friends-messagestest: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_eventsfeatures/calls/signaling.jsmessages-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.swiftshared/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 failedtest:front:login 仍有 full-flowmulti-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 viewportgesturestart/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 OperationErrortests/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.controllerfriends.controller 新增帳號驗證 + 會話 ACL 授權,所有列表 / 刪除 / 建立操作需帶入 uidHexaccountToken/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 完成附件共享金鑰封套驗證,修復接收端預覽維持pendingsendDrMedia() 會為媒體產生共享 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_b64snapshotAfter 寫入 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 導致 OperationErrornpm 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 secureLogoutpurgeLoginStoragehydrateDrStatesFromContactSecrets 整合 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-v1share-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)。若部署於可供他人透過網路存取的服務,請公開對應來源碼與修改內容,以確保社群共享與使用者權益。

Releases

No releases published

Packages

No packages published

Languages