Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ These are absolute rules — never violate them:
- **React SPA input masking**: For dialog inputs in React/Vue SPAs, do NOT set `visibility: visible` after masking — the framework overwrites `input.value` but keeps our inline styles, exposing plaintext. Instead, keep inputs hidden by manifest CSS.
- **AWS dual-key capture**: AWS has Access Key ID (`AKIA...`, DOM scan) + Secret Access Key (no prefix, 40-char base64, clipboard-only capture via `isAwsConsolePage()` in `handleClipboardText()`).
- **Toast stacking**: Multiple consecutive captures show stacked toasts (each calculates top offset from existing toasts) instead of replacing the previous one.
- **NMH dual-path IPC**: NativeMessagingHost supports both `get_config` (direct ipc.json read) and WS relay (`get_state`, `submit_captured_key`, `toggle_demo_mode`). WS relay uses `URLSessionWebSocketTask` short-lived connection (~20-60ms). clientType `"nmh"` — Core skips NMH in broadcast. `sendRequest()` in service-worker.ts tries WS first, then NMH fallback.
- **NMH no plaintext queue**: When both WS and NMH fail for `submit_captured_key`, the key is NOT queued to `chrome.storage.local`. Storing plaintext API keys in browser storage violates security red line #1. The key is lost and must be re-captured.
- **NMH isConnected semantics**: NMH relay success does NOT set `state.isConnected = true` — NMH is a one-shot relay, not a persistent connection. Popup shows "Connected (NMH)" via `connectionPath` field only, while WS reconnect continues independently.
- **NMHInstaller**: Core auto-installs NMH binary + Chrome manifest from app bundle Resources on startup. Uses binary file size comparison for version checking. `install.sh` retained as manual fallback.

## Documentation

Expand Down
12 changes: 7 additions & 5 deletions docs/01-product-spec/implementation-status.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 實作狀態追蹤

> 最後更新:2026-03-17
> 最後更新:2026-03-18

## 狀態圖例

Expand All @@ -19,7 +19,7 @@
| KeychainService | ✅ | store / retrieve / delete 完成 |
| ClipboardEngine | ✅ | copy + autoClear + detectKeys 完成 |
| MaskingCoordinator | ✅ | isDemoMode / activeContext / pattern 匹配完成 |
| IPCServer (WebSocket) | ✅ | handshake / state_changed / pattern_cache_sync / toggle_demo_mode |
| IPCServer (WebSocket) | ✅ | handshake / state_changed / pattern_cache_sync / toggle_demo_mode / nmh clientType |
| HotkeyManager | ✅ | `⌃⌥⌘D` toggle、`⌃⌥Space` hold 偵測、`⌃⌥[1-9]` paste、flagsChanged 監聽 |
| Floating Toolbox (HUD) | ✅ | NSPanel 浮動視窗、hold-to-search、Scheme B 鎖定、↑↓ 導航 |
| ToolboxState (ViewModel) | ✅ | 搜尋過濾、選取狀態、release/confirm/dismiss 邏輯 |
Expand Down Expand Up @@ -67,14 +67,16 @@

| 功能 | 狀態 | 備註 |
|------|------|------|
| Background Service Worker | ✅ | WebSocket 連線、state 管理、reconnect |
| Popup UI | ✅ | 連線狀態、Demo Mode、Context、Patterns |
| Background Service Worker | ✅ | WebSocket 連線、NMH fallback 雙路分派、state 管理、reconnect |
| Popup UI | ✅ | 連線狀態(WebSocket/NMH/Offline)、Demo Mode、Context、Patterns |
| Toggle Demo Mode | ✅ | Popup → Background → Core → broadcast |
| Content Script DOM masking | ✅ | TreeWalker + CSS overlay + MutationObserver |
| Content Script unmask | ✅ | 退出 Demo Mode 恢復原始文字 |
| Options 頁面 | ✅ | Pattern cache 管理 + Dev IPC Config |
| Dev IPC Config (workaround) | ✅ | 替代 Native Messaging Host |
| Native Messaging Host | ❌ | Swift binary 未編譯部署 |
| Native Messaging Host | ✅ | get_config + WS relay(get_state / submit_captured_key / toggle_demo_mode) |
| NMH 雙路 IPC | ✅ | WS primary + NMH fallback,popup 顯示連線路徑 |
| NMHInstaller(Core 自動安裝) | ✅ | Core 啟動時從 bundle Resources 安裝 binary + manifest |
| Active Key Capture | ✅ | 4 層偵測:DOM scan → attribute → clipboard → platform selectors |
| capture-patterns.ts (SSoT) | ✅ | 11 平台 pattern 定義,單一檔案維護 |
| Pre-hide anti-flash | ✅ | 三層:manifest CSS → pre-hide.ts → instant MutationObserver |
Expand Down
63 changes: 47 additions & 16 deletions docs/02-technical-architecture/chrome-extension-architecture.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Chrome Extension 架構

> 狀態:✅ 核心功能完成(WebSocket 連線、Content Script masking、Popup UI)
> 尚未完成:Native Messaging Host 部署、Smart Extract
> 狀態:✅ 核心功能完成(WebSocket 連線、NMH 雙路 IPC、Content Script masking、Popup UI)
> 尚未完成:Smart Extract

---

Expand Down Expand Up @@ -43,6 +43,7 @@
**職責**:
- 維持與 Swift Core 的 WebSocket 連線(含指數退避重連)
- 透過 Native Messaging Host 取得 IPC 連線資訊
- **雙路分派**:WS primary → NMH fallback(get_state / submit_captured_key / toggle_demo_mode)
- 接收 Core 事件並轉發至 Content Scripts
- 回應 Popup 的狀態查詢
- 持久化 pattern cache 至 `chrome.storage.local`
Expand All @@ -53,6 +54,12 @@
3. 發送 handshake(clientType: 'chrome', token, version)
4. 收到 success → 標記 connected,開始接收事件

**雙路分派(Dual-path Dispatch)**:
- `sendRequest(action, payload)` 統一入口
- WS 連線中 → 透過 WS 發送(含 request-response correlation + 5s timeout)
- WS 斷線且 action 為 relay 清單 → 透過 NMH fallback
- 雙路皆失敗 → log warning(不將明文 key 存入 chrome.storage,遵守安全紅線)

**Dev Fallback**:
- Native Host 不可用時,從 `chrome.storage.local` 讀取手動設定的 port/token
- Options 頁面提供 Dev IPC Config 輸入介面
Expand Down Expand Up @@ -108,11 +115,12 @@ chrome.runtime.sendMessage({ type: 'get_state' }, (response) => {
### Popup (`popup.ts` + `popup.html`)

**顯示資訊**:
- Connection 狀態(綠點 Connected / 紅點 Offline)
- Connection 狀態與路徑(綠點 WebSocket / 藍點 NMH / 紅點 Offline)
- Mode(Normal / Demo)
- Active Context 名稱
- Pattern 數量
- Toggle Demo Mode 按鈕
- Capture Mode 控制

### Options (`options.ts` + `options.html`)

Expand All @@ -126,45 +134,68 @@ chrome.runtime.sendMessage({ type: 'get_state' }, (response) => {

## Native Messaging Host

### 架構
### 架構(雙路 IPC)

NMH 同時支援兩種模式:**config 查詢**和 **WS relay**。

```
Chrome Extension → chrome.runtime.sendNativeMessage('com.demosafe.nmh', ...)
↓ stdin (4-byte length prefix + JSON)
NativeMessagingHost (Swift binary)
↓ 讀取 ~/.demosafe/ipc.json
├─ action: get_config → 讀取 ~/.demosafe/ipc.json → 回傳 {port, token}
└─ action: get_state / submit_captured_key / toggle_demo_mode
→ 讀取 ipc.json 取得 port/token
→ 建立短暫 WS 連線至 Core(URLSessionWebSocketTask)
→ handshake (clientType: "nmh") → 送 request → 收 response → 斷線
→ stdout 回傳 Core 的 response
↓ stdout (4-byte length prefix + JSON)
Chrome Extension ← { port: 55535, token: "..." }
Chrome Extension ← response
```

**WS Relay 特性**:
- 短暫連線:connect → handshake → 1 request → 1 response → close(~20-60ms)
- clientType `"nmh"`:Core 不會對 NMH 連線推送 events(broadcast 時跳過)
- 5 秒 timeout,失敗回傳 `{"error": "core_unreachable" | "auth_failed" | "timeout"}`
- 會自動跳過 handshake 後 Core 推送的 event messages(如 pattern_cache_sync)

### 支援的 Actions

| Action | 模式 | 說明 |
|--------|------|------|
| `get_config` | 直讀 ipc.json | 回傳 `{port, token}`,原有行為 |
| `get_state` | WS relay | 回傳 isDemoMode、activeContext、patternCacheVersion |
| `submit_captured_key` | WS relay | 提交截取到的 API key |
| `toggle_demo_mode` | WS relay | 切換 Demo Mode |

### 安裝路徑

| 檔案 | 路徑 |
|------|------|
| Swift binary | `/Applications/DemoSafe.app/Contents/Helpers/demosafe-nmh` |
| Swift binary | `~/.demosafe/bin/demosafe-nmh` |
| Host manifest | `~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.demosafe.nmh.json` |

### 自動安裝(NMHInstaller)

Core 啟動時自動檢查並安裝 NMH:
1. 檢查 `~/.demosafe/bin/demosafe-nmh` 是否存在(比對 binary size)
2. 檢查 Chrome NMH manifest 是否存在且 allowed_origins 正確
3. 缺少或版本不符 → 從 app bundle Resources 複製 binary + 寫入 manifest
4. `install.sh` 保留作為手動備援

### Host Manifest (`com.demosafe.nmh.json`)

```json
{
"name": "com.demosafe.nmh",
"description": "DemoSafe Native Messaging Host",
"path": "/Applications/DemoSafe.app/Contents/Helpers/demosafe-nmh",
"description": "DemoSafe Native Messaging Host — relay for Chrome Extension",
"path": "/Users/<user>/.demosafe/bin/demosafe-nmh",
"type": "stdio",
"allowed_origins": [
"chrome-extension://ACTUAL_EXTENSION_ID/"
]
}
```

### 部署步驟(尚未自動化)

1. 編譯 `native-host/NativeMessagingHost.swift` 為 binary
2. 放置至 `/Applications/DemoSafe.app/Contents/Helpers/demosafe-nmh`
3. 將 `com.demosafe.nmh.json` 複製至 NativeMessagingHosts 目錄
4. 替換 `EXTENSION_ID_HERE` 為實際 Chrome Extension ID

---

## Content Script 目標網站
Expand Down
16 changes: 9 additions & 7 deletions docs/02-technical-architecture/extension-boundaries.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,22 @@
### 連線架構

- Background Service Worker 維護與 Core 的 WebSocket 連線
- Native Messaging Host(Swift helper)讀取 `ipc.json` 以輔助探索
- Native Messaging Host(Swift helper)提供 config 查詢 + WS relay 雙路備援

### Native Messaging Host 規格

Chrome Extension 無法直接讀取檔案系統(`~/.demosafe/ipc.json`),因此需要 Native Messaging Host 作為橋接
Chrome Extension 無法直接讀取檔案系統(`~/.demosafe/ipc.json`),因此需要 Native Messaging Host 作為橋接。NMH 同時作為 WS 斷線時的 fallback relay。

| 項目 | 說明 |
|------|------|
| 實作語言 | Swift(macOS helper binary) |
| 安裝位置 | `~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.demosafe.nmh.json` |
| 職責 | 讀取 `ipc.json` → 回傳 `{port, token}` 給 Background Service Worker |
| 實作語言 | Swift(macOS helper binary,standalone swiftc 編譯,~93KB) |
| 安裝位置 | binary: `~/.demosafe/bin/demosafe-nmh`,manifest: `~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.demosafe.nmh.json` |
| 職責 | (1) 讀取 `ipc.json` → 回傳 `{port, token}`;(2) WS relay:get_state / submit_captured_key / toggle_demo_mode |
| 通訊協定 | Chrome Native Messaging(stdin/stdout JSON) |
| 觸發時機 | Extension 啟動時呼叫一次取得連線資訊;Core 重啟後重新呼叫 |
| 安全性 | manifest 限定 `allowed_origins` 僅允許本 Extension ID |
| WS Relay | 短暫連線(connect → handshake → 1 request → 1 response → close),clientType: `"nmh"`,5s timeout |
| 觸發時機 | Extension 啟動時取連線資訊;WS 斷線時 fallback relay |
| 安裝方式 | Core 啟動時 NMHInstaller 自動安裝;`install.sh` 手動備援 |
| 安全性 | manifest 限定 `allowed_origins`;NMH 不儲存任何資料,僅轉發 |

---

Expand Down
16 changes: 15 additions & 1 deletion docs/05-ipc-protocol/protocol-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

| Action | Payload | Response |
|--------|---------|----------|
| `handshake` | `clientType`, `token`, `version` | port, pid, patternCache version, 連線狀態 |
| `handshake` | `clientType`(`vscode` / `chrome` / `accessibility` / `nmh`), `token`, `version` | port, pid, patternCache version, 連線狀態 |
| `get_state` | (無) | isDemoMode, activeContext, patternCache, version |
| `request_paste` | `keyId` | status (`success` \| `denied` \| `offline`) |
| `request_paste_group` | `groupId`, `fieldIndex`(可選) | status, groupId |
Expand Down Expand Up @@ -159,11 +159,25 @@ Core 維護 `patternCacheVersion`,每次 pattern 變更時遞增。Extension

---

## NMH Relay(Native Messaging Host 短暫連線)

clientType `"nmh"` 為 Chrome Native Messaging Host 透過短暫 WS 連線轉發請求。

| 特性 | 說明 |
|------|------|
| 連線生命週期 | connect → handshake → 1 request → 1 response → close(~20-60ms) |
| 支援的 action | `get_state`、`submit_captured_key`、`toggle_demo_mode` |
| broadcast 排除 | Core broadcast events 時自動跳過 `.nmh` clients |
| 使用場景 | Chrome Extension WS 斷線時的 fallback 路徑 |

---

## 安全約束

| 規則 | 說明 |
|------|------|
| WebSocket 僅 127.0.0.1 | 禁止綁定外部介面 |
| Handshake 認證 | 需要 `ipc.json` 中的 token |
| **明文永不經過 IPC** | 僅遮蔽表示和參照流過網路 |
| **明文不存入 chrome.storage** | submit_captured_key 失敗時不 queue,遵守安全紅線 |
| `ipc.json` 權限 600 | 僅限使用者讀寫 |
12 changes: 7 additions & 5 deletions docs/en/01-product-spec/implementation-status.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Implementation Status Tracking

> Last updated: 2026-03-17
> Last updated: 2026-03-18

## Status Legend

Expand All @@ -19,7 +19,7 @@
| KeychainService | ✅ | store / retrieve / delete completed |
| ClipboardEngine | ✅ | copy + autoClear + detectKeys completed |
| MaskingCoordinator | ✅ | isDemoMode / activeContext / pattern matching completed |
| IPCServer (WebSocket) | ✅ | handshake / state_changed / pattern_cache_sync / toggle_demo_mode |
| IPCServer (WebSocket) | ✅ | handshake / state_changed / pattern_cache_sync / toggle_demo_mode / nmh clientType |
| HotkeyManager | ✅ | `⌃⌥⌘D` toggle, `⌃⌥Space` hold detection, `⌃⌥[1-9]` paste, flagsChanged listener |
| Floating Toolbox (HUD) | ✅ | NSPanel floating window, hold-to-search, Scheme B lock, ↑↓ navigation |
| ToolboxState (ViewModel) | ✅ | Search filtering, selection state, release/confirm/dismiss logic |
Expand Down Expand Up @@ -67,14 +67,16 @@

| Feature | Status | Notes |
|---------|--------|-------|
| Background Service Worker | ✅ | WebSocket connection, state management, reconnect |
| Popup UI | ✅ | Connection status, Demo Mode, Context, Patterns |
| Background Service Worker | ✅ | WebSocket connection, NMH fallback dual-path dispatch, state management, reconnect |
| Popup UI | ✅ | Connection status (WebSocket/NMH/Offline), Demo Mode, Context, Patterns |
| Toggle Demo Mode | ✅ | Popup → Background → Core → broadcast |
| Content Script DOM masking | ✅ | TreeWalker + CSS overlay + MutationObserver |
| Content Script unmask | ✅ | Restore original text when exiting Demo Mode |
| Options page | ✅ | Pattern cache management + Dev IPC Config |
| Dev IPC Config (workaround) | ✅ | Alternative to Native Messaging Host |
| Native Messaging Host | ❌ | Swift binary not compiled/deployed |
| Native Messaging Host | ✅ | get_config + WS relay (get_state / submit_captured_key / toggle_demo_mode) |
| Dual-path IPC (NMH fallback) | ✅ | WS primary + NMH fallback, popup shows connection path |
| NMHInstaller (Core auto-install) | ✅ | Core installs binary + manifest from bundle Resources on startup |
| Active Key Capture | ✅ | 4-layer detection: DOM scan → attribute → clipboard → platform selectors |
| capture-patterns.ts (SSoT) | ✅ | 11 platform pattern definitions, single file maintenance |
| Pre-hide anti-flash | ✅ | 3 layers: manifest CSS → pre-hide.ts → instant MutationObserver |
Expand Down
Loading
Loading