Skip to content

fix(offline): 手動のオフラインモード中は Misskey へ送らないことを、送信の直前で保証する (#1350) - #1391

Merged
hitalin merged 3 commits into
developfrom
fix/offline-egress-gate-1350
Nov 12, 2026
Merged

hitalin merged 3 commits into
developfrom
fix/offline-egress-gate-1350

Conversation

@hitalin

@hitalin hitalin commented Nov 12, 2026

Copy link
Copy Markdown
Collaborator

#1390 の上に積んでいる。#1390 を先にマージすると、この PR の差分は最後の 1 コミットだけになる。

何を直すか

#1350 の設計 (issue のコメント) の本体: 手動のオフラインモード中は Misskey サーバーへ送らないことを、送信の直前で保証する。

呼ぶ側が毎回オフラインを確かめる形では、確かめ忘れた経路が黙って漏れていた (投稿、外部アプリ経由の操作、別プロセスの巡回)。

門 (送信の直前の検査)

  • notecli: egress::EgressGate。クライアントは要求を出すたびに通す。送信は http() を通る 1 か所にまとめた。ストリーミングの接続も同じ検査を通す
  • 判定は、クライアントを作るときに必ず渡す。「渡さなければ常に通す」という既定は無い (渡し忘れた場所が黙って漏れるため)。検査が要らない場面 (コマンドラインの道具、テスト) は AllowAll を明示する
  • notecore: offline::Gate が、手動のオフラインのとき断る。アプリ / notemaid / Android の背景 worker が渡す。エラーの種類は OFFLINE_MODE (再ログインの案内にも、再試行できる通信エラーにも当たらない)
  • キャッシュを読むだけの経路は門に当てない: 照会は接続を飛ばして開き、絵文字の辞書は古くてもキャッシュを返す

利用者から見て変わる点

  • 投稿: オフライン中は送らずに「オフラインモード中です」と出し、入力をフォームに残す。今までは回線があればそのまま送信されていた。送信は楽観的にフォームを閉じてから行い、失敗時の退避先の下書きもサーバーなので、送ってから失敗させると本文を失う。入口で止める
  • リアクションなど: 手動のオフラインでは「オフラインモード中です」と出る (今までは無言で何も起きない)。回線断のとき (自動) は今までどおり
  • ログアウト / 口座のデータの削除: オフライン中は始める前に止めて理由を見せる。サーバー側の後始末 (プッシュ通知の登録の解除など) が通らないまま手元のトークンを消すと、後から後始末できない。ゲストは止めない
  • ログイン: オフライン中は失敗する。失敗の表示に「オフラインモードを解除」を出す (口座が無いと、ほかの解除の入口に届かない)
  • 切替のダイアログの文言: 「すべての通信を停止し…」→「Misskey サーバーとの通信を止め、キャッシュ済みのデータだけを表示します。AI チャットと画像の読み込みは続きます。」

文書

DEVELOPMENT.md の棚卸し表を、門で止まるもの / 門を通さない例外 / 止めないもの (理由つき) に作り直した。

確認

  • 門が閉じていると、API の要求・独自の POST・GET のどの形も 1 つも出ないこと、WebSocket も張らないことをテストで確かめた
  • HTTP クライアントに直に触れる送信が増えていないことを、ソースを読むテストで確かめている
  • 投稿・ログアウトと口座の削除は、テストを先に書いてから実装
  • Rust の workspace 全体のテスト、clippy (workspace)、rustfmt、フロントの全テスト、typecheck、biome、lint:i18n、lint:docs は通過
  • 実機では確認していない。Android の背景 worker は手元でビルドできない (CI の rust-android で初めて確かめられる)。別プロセスの notemaid を実際に動かしての確認もしていない
  • リアクションの表示とログイン画面の操作には、テストを足していない

この PR に入れていないもの

  • Misskey 以外で、押していないのに出る通信 (画像の中継とキャッシュ、プラグインからの外部への取得) と、WebView が直接出す通信。約束を「Misskey サーバーへの要求」に留めているのはこのため。別の issue に分ける
  • 更新確認の失敗時の再試行

Refs #1350

🤖 Generated with Claude Code

hitalin and others added 3 commits November 12, 2026 12:16
手動のオフラインは、別の機械へ持ち運ぶ設定 (settings.json5) に入っていた。
別の機械で入れたオフラインが同期した先の巡回を止めたり、同期で解除
されたりしうる。切替は遅延つきの設定の書き戻しに載っていて、書けない
とき (読み込みに失敗している間は黙って書かない) に、画面はオフラインで
通信は出たまま、になりえた。

- 置き場を state/offline.json に移す。書くのは Rust だけ。起動時に今の
  値を 1 回だけ引き継ぎ、移す前は今までの設定を読む
- 状態が在るのに読めないときはオフラインとして扱う
- 切替は専用の command: 状態を書き、張っているストリーミングの接続を
  すべて切り、全窓へ知らせ、失敗はエラーで返す。フロントは成功して
  から画面に反映する
- 起動時、カラムが繋ぎに行く前に状態を読む
- 解除したら更新確認を頼む (オフラインのまま起動すると確認していない)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
レビューの指摘を反映する。

- 移行: 今までの設定が在るのに読めない / 解釈できないときは移さない。
  「オフラインでない」と保存すると、以後は状態のファイルが優先されて
  元のオフラインが戻らない。設定が無い (初回) ときだけ既定を保存する
- 切替: 口座の一覧に頼らず、管理中の接続をすべて切る
  (StreamingManager::disconnect_all)。一覧を取れないと切らずに成功を
  返していた
- 切替を直列にする。書く → 切る → 知らせるの間に待ちがあり、2 つの窓の
  切替が入れ子になると、古い知らせが新しい状態の後に届いた
- 起動時に状態を取得できなければオフラインとして扱い、読み直せるように
  購読を外す。オンラインのまま進んでいた
- 切替の応答を待つ間に知らせが届いていたら、古い応答で上書きしない

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
呼ぶ側が毎回オフラインを確かめる形では、確かめ忘れた経路が黙って漏れた
(投稿、外部アプリ経由の操作、別プロセスの巡回)。保証を送信の直前に置く。

- notecli: 送信の直前の検査 (egress::EgressGate)。クライアントは要求を
  出すたびに通し、ストリーミングの接続も同じ検査を通す。クライアントを
  作るときに必ず渡す (渡さなければ常に通す、という既定は無い)
- notecore: 手動のオフラインのとき断る門 (offline::Gate)。アプリ /
  notemaid / Android の背景 worker が渡す。エラーの種類は OFFLINE_MODE
- キャッシュを読むだけの経路は門に当てない: 照会は接続を飛ばして開き、
  絵文字の辞書は古くてもキャッシュを返す
- 投稿: 送らずに理由を見せ、入力をフォームに残す。送信は楽観的に
  フォームを閉じてから行い、失敗時の退避先の下書きもサーバーなので、
  送ってから失敗させると本文を失う
- リアクションなど: 手動のオフラインでは理由を見せる (今までは無言)
- ログアウト / 口座のデータの削除: 始める前に止める。サーバー側の
  後始末が通らないまま手元のトークンを消すと、後から後始末できない
- ログインの失敗の表示に「オフラインモードを解除」を出す
- 切替のダイアログの文言を、守っている範囲 (Misskey サーバーとの通信)
  に合わせる。棚卸し表を、門で止まるもの / 例外 / 止めないものに作り直す

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Nov 12, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 17 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 7ecdac6 and 420ad99.

Files ignored due to path filters: 1

  • src/i18n/locale.generated.ts is excluded by !**/*.generated.*
Files selected for processing: 45

  • DESIGN.md
  • DEVELOPMENT.md
  • crates/notecli/src/api.rs
  • crates/notecli/src/commands/auth.rs
  • crates/notecli/src/commands/doctor.rs
  • crates/notecli/src/commands/mod.rs
  • crates/notecli/src/egress.rs
  • crates/notecli/src/lib.rs
  • crates/notecli/src/server_info.rs
  • crates/notecli/src/streaming.rs
  • crates/notecore/locales/en-US.json
  • crates/notecore/locales/ja-JP.json
  • crates/notecore/src/commands/content.rs
  • crates/notecore/src/commands/query.rs
  • crates/notecore/src/commands/streaming.rs
  • crates/notecore/src/context.rs
  • crates/notecore/src/offline.rs
  • crates/notecore/src/stream_mode.rs
  • crates/notemaid/src/daemon/run.rs
  • locales/en-US.source.json
  • locales/en-US.yml
  • locales/ja-JP.yml
  • src-tauri/src/android_worker.rs
  • src-tauri/src/commands/mod.rs
  • src-tauri/src/commands/offline.rs
  • src-tauri/src/lib.rs
  • src/bindings.ts
  • src/components/deck/DeckOfflineColumns.dom.test.ts
  • src/components/window/LoginContent.vue
  • src/composables/useAccountActions.test.ts
  • src/composables/useAccountActions.ts
  • src/composables/useColumnSetup.ts
  • src/composables/useCrossAccountNotes.dom.test.ts
  • src/composables/useDeckResume.dom.test.ts
  • src/composables/useNoteColumn.dom.test.ts
  • src/composables/usePostFormState.test.ts
  • src/composables/usePostFormState.ts
  • src/main.ts
  • src/settings/schema.ts
  • src/stores/offlineMode.test.ts
  • src/stores/offlineMode.ts
  • src/stores/offlineModeSwitch.test.ts
  • src/stores/realtimeMode.test.ts
  • src/stores/settings.test.ts
  • src/utils/errors.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@github-actions

Copy link
Copy Markdown

API surface diff

外部アプリ向け API 面 (src-tauri/openapi.json / src/bindings.ts) が変更されています。
互換性への影響 (#709) をレビューしてください。

src/bindings.ts | 33 +++++++++++++++++++++++++++++++++
 1 file changed, 33 insertions(+)
Full diff
diff --git a/src/bindings.ts b/src/bindings.ts
index 6b78196..a32d72c 100644
--- a/src/bindings.ts
+++ b/src/bindings.ts
@@ -2898,6 +2898,33 @@ async systemStateGet() : Promise<Result<SystemState, { code: string; message: st
     else return { status: "error", error: e  as any };
 }
 },
+/**
+ * 手動のオフラインモードか。状態が在るのに読めないときは true (`notecore::offline::manual`)
+ *
+ * @see src-tauri/src/commands/offline.rs
+ */
+async offlineModeGet() : Promise<Result<boolean, { code: string; message: string; apiCode: string | null; i18n: JsonValue | null; retryAfterMs: number | null }>> {
+    try {
+    return { status: "ok", data: await TAURI_INVOKE("offline_mode_get") };
+} catch (e) {
+    if(e instanceof Error) throw e;
+    else return { status: "error", error: e  as any };
+}
+},
+/**
+ * 手動のオフラインモードを切り替える。入るときは、張っているストリーミングの接続を
+ * すべて切る (フロントの切断に頼らない。窓が 1 つも無くても切れる)
+ *
+ * @see src-tauri/src/commands/offline.rs
+ */
+async offlineModeSet(offline: boolean) : Promise<Result<null, { code: string; message: string; apiCode: string | null; i18n: JsonValue | null; retryAfterMs: number | null }>> {
+    try {
+    return { status: "ok", data: await TAURI_INVOKE("offline_mode_set", { offline }) };
+} catch (e) {
+    if(e instanceof Error) throw e;
+    else return { status: "error", error: e  as any };
+}
+},
 async clientLayerState() : Promise<ClientLayerState> {
     return await TAURI_INVOKE("client_layer_state");
 },
@@ -3251,6 +3278,7 @@ ndAiHeartbeatEvent: HeartbeatEventWire,
 ndSettingsFileChanged: SettingsFileChangedEvent,
 noteCaptureBatch: NoteCaptureBatchEvent,
 notificationClickAddressed: NotificationClickAddressed,
+offlineModeChanged: OfflineModeChanged,
 pipReturnRequested: PipReturnRequested,
 queryDelta: QueryDeltaEvent,
 queryRequest: QueryRequest,
@@ -3275,6 +3303,7 @@ ndAiHeartbeatEvent: "nd:ai-heartbeat-event",
 ndSettingsFileChanged: "nd:settings-file-changed",
 noteCaptureBatch: "note-capture-batch",
 notificationClickAddressed: "notification-click-addressed",
+offlineModeChanged: "offline-mode-changed",
 pipReturnRequested: "pip-return-requested",
 queryDelta: "query-delta",
 queryRequest: "query-request",
@@ -4371,6 +4400,10 @@ export type NotificationClickAddressed = { to: string; click: NotificationClicke
  * 配送は宛先付きの `NotificationClickAddressed` (見ている窓 1 つへ、#1131)
  */
 export type NotificationClicked = { accountId: string; noteId: string | null; userId: string | null }
+/**
+ * 手動のオフラインモードが切り替わった。全窓へ流す (切り替えた窓にも届く)
+ */
+export type OfflineModeChanged = { offline: boolean }
 /**
  * `users/pages` / `pages/show` の 1 件分。本家 packages/backend/src/models/Page.ts。
  * プロフィール一覧で使うのは title / summary / createdAt のみだが、

@github-actions github-actions Bot added 📖Doc Documentation related issue/PR rust Pull requests that update rust code javascript Pull requests that update javascript code labels Nov 12, 2026
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying notedeck with  Cloudflare Pages  Cloudflare Pages

Latest commit: 420ad99
Status: ✅  Deploy successful!
Preview URL: https://32fe81ed.notedeck-d3a.pages.dev
Branch Preview URL: https://fix-offline-egress-gate-1350.notedeck-d3a.pages.dev

View logs

@claude

claude Bot commented Nov 12, 2026

Copy link
Copy Markdown

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

@hitalin hitalin self-assigned this Nov 12, 2026
@hitalin
hitalin merged commit 039f5a5 into develop Nov 12, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📖Doc Documentation related issue/PR javascript Pull requests that update javascript code rust Pull requests that update rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant