Staging — 僅供部落格預覽。
跳至主要內容

離線可用不是「有網路再碰運氣同步」

排程中

閱讀時間 6 分鐘 作者 NT²

許多應用自稱離線可用,實際上只是唯讀快取,加上一個期待基地台回來的佇列。 本地優先的保管箱先把完整 CRUD 放在裝置上。同步是可選的第二條路。

離線可用不是「有網路再碰運氣同步」

主張:快樂路徑從不索取通訊端

產品文案最愛寫「離線可用」。實務上它常常只代表:應用曾經下載快取、你能瀏覽過期列,寫入則佇列等待連線回來——若佇列還活著、若衝突解析器同意、若伺服器仍認得你的工作階段。

那不是快樂路徑。那是貼了行銷貼紙的降級模式。

NT² Vault 的主張更銳利:

建立、解鎖、讀取、更新、刪除、搜尋、附加檔案,以及匯出可攜備份,都在裝置上完成。日常使用不需要任何 API 呼叫。網路是可選的——NT² Premium 同步與盲目中繼——不是保管箱的擁有者。

離線不是週末功能。它是主要架構。飛機、大使館地下室、強制登入的咖啡館 Wi‑Fi、電信商中斷後的那一小時:這些是正常運作條件,不是只配得上轉圈與聳肩的邊緣案例。

此處的本地優先不是對軟碟的懷舊。它是信任與可用性決策:持有金鑰材料的裝置,同時持有權威的結構化儲存。邊緣可以稍後協助 replica 收斂。它無權決定你今晚能不能新增一筆登入憑證。

約束:「稍後同步」悄悄把雲端放回中心

會殺死離線完整性的捷徑很眼熟。把雲端資料庫當真相來源。再加一個快取上一份 JSON 的 service worker。然後貼上離線標籤。網路一斷,寫入變成「待處理」、搜尋變不完整,解鎖也開始像要打一趟伺服器來回。

三股壓力把團隊推向那裡。

第一,產品速度。 單一 Postgres(或形似 D1)綱要在衝刺裡好推理。用戶端 SQLite、OPFS 耐久性、以及感知衝突的合併,在你把它們當成產品本身之前,都顯得昂貴。

第二,支援敘事。 客服想要「先確認他們是否在線」與「強制同步」。那些腳本假設伺服器看得見足夠多東西才能診斷。盲目邊緣打不開項目;誠實的離線保管箱也不能靠操作員主控台療癒。

第三,身份崩塌。 若「登入」意味著「從 API 抓解鎖材料並列出保管箱」,那麼飛航模式不只是慢——而是鎖死。把本地解鎖與雲端驗證收成同一個手勢,等於重新引入電子郵件帳號、密碼神諭,以及架構本要拒絕的蜜罐。

還有一種較軟的失敗模式:說謊的樂觀 UI。寫入其實只活在記憶體佇列裡,卻顯示綠勾,會教使用者以為保管箱「已儲存」。分頁在刷寫前死去,或重連後佇列因衝突拒絕時,快樂路徑只是虛構。離線 CRUD 必須先在本地提交——在裝置上耐久——再可選地宣告一次 replica 增量。

因此約束是:

  1. 可用性: 核心保管箱操作在 API 端口未設定或不可達時仍須成功。
  2. 誠實: 同步失敗是同步失敗,不是保管箱失敗。
  3. 身份: 解鎖保管箱不得依賴對 Workers 的驗證。

任一條失敗,「離線可用」就只是字體漂亮一點的「有網路再碰運氣同步」。

設計:裝置為主,邊緣可選

執行期拓撲刻意無聊。

路徑執行什麼需要網路?
關鍵路徑建立保管箱、以主密碼解鎖、項目 CRUD、本地搜尋、附件、.nt2backup 匯出/匯入
可選邊緣金鑰 DID 工作階段、replica 批次推送/拉取、附件密文側車、共用/動態中繼、帳單權益——且僅在使用者選擇 NT² Premium 同步/中繼功能時

在裝置上,保管箱是結構化本地資料庫(瀏覽器裡是 Origin Private File System 上的 WASM SQLite,或桌面殼層的原生檔)加上附件密文的獨立儲存。解鎖從主密碼與本地鹽值在記憶體中衍生不可匯出的金鑰。列表在本地分頁與搜尋。上鎖清除金鑰。這一切都不等待 Cloudflare。

在邊緣,啟用 NT² Premium 同步時,用戶端推送與拉取不透明的 replica 批次。Workers 與物件儲存看見密文訊框與帳號詮釋資料——不是項目欄位。離線時同步掛鉤會 no-op 或浮出清楚錯誤;它們不會阻擋你打開已有的筆記。跨裝置鹽值與還原材料走帶外路徑(備份、還原包、replica 交接)——不是「請寄電子郵件給我保管箱」端點。

flowchart LR
  subgraph device [裝置 — 主要]
    UI[保管箱 UI]
    DB[(本地 SQLite + blobs)]
    Key[記憶體中的 CryptoKey]
    UI --> DB
    UI --> Key
  end
  subgraph edge [邊緣 — 可選]
    API[Workers API]
    Hub[Replica 樞紐]
    Store[(密文儲存)]
    API --> Hub
    Hub --> Store
  end
  UI -.->|線上時的 NT² Premium 同步| API

兩個心智模型刻意分開。本地解鎖回答:這個瀏覽器可否解密這份保管箱檔?雲端驗證回答:這個金鑰 DID 可否為已授權帳號推送或拉取 replica 批次?混在一起,離線又會變二等公民。更深的切分寫在解鎖本機保管箱,不等於登入雲端

沒有 NT² Premium 仍可攜帶:.nt2backup、離線 replica 註冊,以及相關交接路徑,在不要求邊緣變成閣樓的前提下,於裝置間複製加密狀態。同步是便利與收斂——不是第二支手機永遠看見保管箱的唯一方式。

取捨:兩層真相,以及可計費的同步

本地優先的離線快樂有我們公開接受的成本。

使用者要學兩層。 免費的日常使用是裝置保管箱。跨裝置自動同步是可選的 NT² Premium。這比「到處登入、我們托管你的人生」更難解釋。它也是唯一與零知識一致的故事:若伺服器從未持有明文,它也不能當唯一的復原櫃檯。

衝突與合併活在用戶端。 盲目 replica 批次依設計收斂,但產品無法提供伺服器端「顯示衝突的護照標題並選贏家」UI。操作員沒有保管箱除錯器。在兩台離線裝置上編輯同一項目的進階使用者,會遇見合併語意,而不是讀欄位的真人客服。

首次畫面仍需要已有保管箱的裝置。 全新瀏覽器設定檔不能憑空發明鹽值與密文,除非匯入、交接,或先前的同步註冊。離線優先不是「零安裝儀式」。它是「保管箱存在於此裝置之後,網路不是依賴」。

NT² Premium 被允許為了同步而需要網路。 那不是偽善。對盲目 replica 傳輸收費,是低 ARPU 產品在不把免費方案變成明文蜜罐的前提下負擔邊緣成本的方式。拒絕的是另一件事:我們不對離線建立項目的權利收費,也不把免費方案降級成唯讀表演。

對應這條邊界的產品故事是預設離線,不代表只能離線:重心在本地,可選的盲目載體。本文是工程那一半——執行期必須成立什麼,那個故事才不是謊言。

我們拒絕什麼

把拒絕說清楚,架構更清楚。

我們拒絕讓 API 成為建立、解鎖、CRUD、搜尋、附件或備份的必要條件。 若快樂路徑需要通訊端,產品就是帶快取的雲端優先。

我們拒絕把解鎖當成登入。 主密碼衍生與本地驗證器檢查留在裝置。雲端註冊與同步工作階段使用金鑰 DID 挑戰-回應;它們不從 D1 抓鹽值或解密神諭。

我們拒絕把「待處理寫入」當成唯一耐久故事。 本地提交在該裝置上是權威。同步是線上且已授權時,密文 replica 的盡力收斂。

我們拒絕伺服器端搜尋機密,以及操作員「檢視保管箱」工具——那些會讓離線完整性變得無關緊要,因為真正的閣樓本來就住在資料中心。

我們拒絕把唯讀快取叫做離線可用。 只能瀏覽上週標題、每筆編輯都被擋下,對結構化保管箱來說不是快樂路徑。

我們拒絕把免費方案收成殘障示範,要等到帳單通過才變成保管箱。 NT² Premium 買的是可選網路功能。它不購買在你自己磁碟上使用 SQLite 的權利。

這些拒絕與威脅模型、邊緣文章並列:殘餘 XSS 與裝置失竊風險維持在地、有時限(別處 XSS 偷工作階段;這裡金鑰不可匯出);Workers 持有權限清單,不是信任演說(Workers 被允許看見什麼);replica 同步保持盲目(在邊緣進行盲目 replica 同步)。

收束:離線是設計,不是道歉

Progressive Web App 能承載認真的保管箱,因為現代瀏覽器已提供零件:私有檔案儲存、WASM SQLite、Web Crypto,以及不必來回伺服器就能畫出表單的 UI。那套堆疊的總覽是為什麼選擇 PWA 本地優先、零伺服器的 Vault。本文是可用性推論:那些零件一旦成為主要路徑,飛航模式就不是失敗模式

誠實做到的離線可用,意味著你在大西洋上空寫下的那筆,在座椅螢幕找到 Wi‑Fi 之前,已經進了你的資料庫。稍後同步——若你要、若你付了盲目載體、若基地台回來——是第二條路。運氣不是合約的一部分。

若這個順序符合你希望私人保管箱的行為——裝置優先、網路可選——可以試試 NT² Vault,或到 nt2.me 多讀一些。

最後更新 2026-11-01

相關故事