# 網頁控制台與 Web Push

> HTML: https://remote.ivjn.us/docs/zh-TW/web-console/

瀏覽器控制台位於 `https://remote.ivjn.us/console/`。它支援透過郵件驗證註冊、帳號/密碼或操作員令牌登入、工作區範圍內的裝置、流式命令、xterm PTY、Android 截圖/點選/導航/文字輸入、指標、審計記錄、裝置建立/刪除，以及主動訂閱的通知。唯讀帳號不能執行命令或檢視 Android 螢幕。離線裝置仍會顯示。

裝置通告 `ubus` 能力時顯示 **ubus** 分頁，可填寫物件、方法與選填的 JSON 物件參數（留空傳送 `null`）。呼叫使用密封工作階段，要求裝置上線、已啟用且具備操作權限。取消只停止等待，無法撤銷裝置已執行的方法；切換裝置或失去權限會清理待處理呼叫與結果。

註冊會建立獨立的工作區。登入前必須使用八位驗證碼驗證郵箱；控制台支援重新傳送驗證碼，並在重新整理後返回驗證流程。Exchange 必須配置 SMTP（`SMTP_HOST`、`SMTP_PORT`、`SMTP_FROM`，以及 `.env.example` 中的認證和安全設定）。郵件服務不可用時，註冊會報告錯誤，不會跳過驗證。

憑證儲存在頁面記憶體中。重新整理後需要重新登入。退出登入會關閉會話，嘗試移除此瀏覽器的推送訂閱，並撤銷透過密碼登入建立的會話。主操作員令牌仍保持原有的有效期。點選通知會聚焦已開啟的控制台，不會重新載入其憑證。

## 建置與部署

```sh
cd website/console
npm ci
npm test
npm run build
```

將 `dist/` 複製到網站的 `console/` 目錄。確保現有的 `website/assets/` 可透過 `/assets/` 訪問，以提供共享字型和標誌。提供 `index.html`、`sw.js` 和清單檔案時，應要求重新驗證或禁用快取。帶雜湊的資源可使用不可變快取。不要快取 `/api/*` 或 `/ws/*`。

控制台使用同源的 `/api/*` 和 `/ws/browser`。將這些路徑反向代理到 Exchange，保留 Host 並允許 WebSocket 升級。不要移除 `/api` 或 `/ws` 字首。Vite 開發伺服器將 API/WS 代理到 `127.0.0.1:18082`。Rust Exchange 預設允許同源瀏覽器存取。透過 `REMOTE_CORS_ORIGINS` 設定精確來源；只有明確的 `*` 才允許所有來源。Worker 使用 `CORS_ORIGINS` 及其自身預設值。Bearer 驗證和一次性 WebSocket 票證仍為必要，不啟用跨來源 Cookie。

瀏覽器認證先使用帶 Bearer 授權的 `POST /api/ws-ticket`，隨後請求 `GET /ws/browser`，併傳送首個 JSON 幀 `{"ticket":"..."}`。票據在 30 秒後過期，只能使用一次，使用時會重新檢查操作員的啟用狀態和憑證有效性。長期有效的憑證不會放入 WebSocket URL。原生客戶端繼續使用 `/ws/operator`。

所有裝置操作都使用原生密封 WebSocket 工作階段。HTTP 提供驗證、裝置中繼資料、金鑰與授權目錄，以及轉送工作階段管理；請參閱 [Agent 操作的 HTTP API](/docs/zh-TW/http-api.md)。

## 瀏覽器裝置面板

選擇瀏覽器裝置後會顯示分頁、頁面和 Cookie 工具。擴充功能為 0.1.45 或更高版本時，面板增加「定位與進階操作」：依定位方式點擊、按兩下、懸停、填寫、輸入、勾選和選擇下拉（最多等待 5 秒，比對到多個元素又沒填序號時回報 `ambiguous`），帶 `wait_until` 的導覽，讀取頁面文字或 HTML，處理待確認的 JavaScript 對話框，請遠端瀏覽器旁的人接手，檢視區預設，下載清單（可把已完成的下載儲存到本機），分頁群組，書籤和紀錄搜尋（讀取紀錄前會先徵求遠端瀏覽器旁的人同意），JSON 批次命令，以及即時畫面。即時畫面會自動確認影格，主控台分頁轉到背景時隱藏畫面。舊版擴充功能只顯示基礎面板。

## 啟用 Web Push

在 Exchange 環境中設定以下兩個變數（兩個都缺失時禁用推送）：

```text
WEB_PUSH_VAPID_PRIVATE_KEY=<base64url-unpadded-32-byte-P256-private-key>
WEB_PUSH_VAPID_SUBJECT=https://remote.ivjn.us
```

使用 `python scripts/generate-vapid-key.py` 生成私鑰。將其儲存在 Git 之外、訪問受限的環境檔案中，重啟時保持不變，並安全備份。公鑰會自動推導。更換金鑰後，瀏覽器需要重新訂閱。

`WEB_PUSH_ALLOWED_ORIGINS` 預設僅包含以下準確的可信源地址：`https://fcm.googleapis.com,https://updates.push.services.mozilla.com,https://web.push.apple.com`。只有驗證了其他瀏覽器廠商的 HTTPS 推送源地址後，才能新增該廠商。客戶端提供的端點不能擴充套件此允許列表。系統不會跟隨重新導向。

需要認證的路由：

| 方法與路徑 | 用途 |
| --- | --- |
| GET `/api/webpush/config` | 啟用狀態及公開的應用伺服器金鑰 |
| GET/POST `/api/webpush/subscriptions` | 列出自己的訂閱後設資料，或訂閱/更新插入 |
| DELETE `/api/webpush/subscriptions/{id}` | 移除自己的訂閱 |
| POST `/api/webpush/test` | 向呼叫者的訂閱佇列加入一條可見的測試通知 |
| POST `/api/webpush/messages` | 傳給自己，或由管理員傳給同一工作區的其他帳號 |
| POST `/api/webpush/devices/{id}/messages` | 裝置向有管理權限且已訂閱的管理員傳送訊息 |

訂閱使用瀏覽器的 `PushSubscription.toJSON()`、`application_server_key` 和 `triggers`。`triggers` 最多包含一個 `online` 物件和一個 `offline` 物件。每個物件包含 `kind`、`tick_count`（預設 3，範圍 1–100），以及可選的 `device_ids`。`offline` 在連續 `tick_count` 次未收到響應後通知；`online` 在連續 `tick_count` 次成功後通知。`device_ids` 可以是單個裝置 ID（字串）、裝置 ID 列表，或省略/設為 `null` 以涵蓋工作區中的所有裝置。空的 `triggers` 陣列會訂閱瀏覽器，但不接收自動上線/離線通知。不接受 `missed_count` 和 `success_count`。訊息使用可選的 `operator_id` 和 `notification`，後者包含 `title`、`body`，以及可選的同源相對 `url` 和 `tag`。`/webpush/` 是最小參考客戶端；生產控制台使用自己的 worker，作用域為 `/console/`。

遷移 `0005_web_push.sql` 新增訂閱和持久化發件箱。`0008_online_offline_triggers.sql` 將裝置通知遷移到 `triggers`，並移除 `missed_count` / `success_count`。`202` 表示已入隊，不代表已送達或顯示。任務在 15 分鐘後過期。網路錯誤、408、429 和 5xx 會重試，總計最多嘗試五次；404/410 會移除對應版本的訂閱。90 秒租約允許 worker 故障後恢復待處理工作。投遞保證至少一次，瀏覽器是否顯示取決於廠商投遞、許可權和作業系統策略。持久化入隊前的裝置事件在過載時盡力處理；已入隊的事件在重啟後仍保留。限制：每帳號 20 個訂閱，每訂閱 32 條排隊訊息，每帳號每分鐘 30 次變更操作。

iOS/iPadOS 可能要求先將控制台新增到主螢幕，才能啟用通知。瀏覽器推送服務的可達性與 Exchange 的連線情況是兩回事。

## 驗證

執行 `cargo test --workspace`、`cargo clippy --workspace --all-targets -- -D warnings`，以及控制台的 npm 測試和建置。進行 PostgreSQL/HTTPS 整合測試時，先安裝 `psycopg[binary]`，將 `WEB_PUSH_TEST_ADMIN_URL` 設定為具有 CREATE DATABASE 許可權的測試 PostgreSQL 連線，再執行 `python scripts/test-webpush.py`。該指令碼會建立並刪除臨時資料庫；切勿指向具有非預期許可權的資料庫角色。

## 從各端傳送

Web Console 的瀏覽器通知面板可填寫訊息；行動 Console 設定提供 Web Push 傳送、測試和開啟 Web Console 入口。桌面 Agent 本機設定、Android Agent 設定和瀏覽器擴充功能設定頁均可使用裝置憑證傳送。無介面/OpenWrt Agent 使用以下命令。接收端在 Web Console/PWA 啟用通知；此功能不新增原生 FCM/APNs 接收器。

```sh
remote-cli webpush status
remote-cli webpush subscriptions
remote-cli webpush test
remote-cli webpush send --title "Backup ready" --body "Completed"
remote-cli webpush send --operator-id ACCOUNT_UUID --title "Notice" --body "Hello"
remote-agent --config /path/to/config.json webpush --title "Device ready" --body "Completed"
```

```json
{"notification":{"title":"Backup ready","body":"Completed","url":"/console/","device_id":"edge-1"}}
```

POST `/api/webpush/messages` 使用操作員 Bearer 憑證。省略 `operator_id` 表示傳給自己；其他接收者需要管理員權限，且必須屬於同一工作區。上方 JSON 也可透過 `remote-cli webpush send --json-file message.json` 傳送。將裝置狀態觸發器設為空陣列可關閉自動上線/離線提醒，同時保留通用訊息接收。

POST `/api/webpush/devices/{id}/messages` 使用該裝置的 Bearer 權杖，直接接收通知物件（`title`、`body`、選填 `tag`）。只有已啟用且管理該裝置的管理員的有效訂閱會接收。伺服器固定 `device_id`、`event=device.message`、`occurred_at` 和 `/console/`，傳送者不能指定帳號或冒充其他裝置。裝置傳送共用每工作區每分鐘 30 次的額度，單次最多 256 個訂閱；帳號訂閱數和佇列限制仍生效。沒有有效接收者回傳 `202 {"queued":0}`；`queued` 是訂閱數，不是送達數。
