インターネット接続状態(オンライン)を監視する

Recipe ID: net-012

ユーザーのデバイスがインターネットに接続されているかどうかを監視・判定する方法を解説します。
簡単な UI 表示の切り替えであれば JavaScript の標準機能で十分ですが、実際に通信可能かどうかを厳密に判定するには Rust 側でのチェックが有効です。

方法 1: JavaScript 標準 API (簡易チェック)

Web 標準の navigator.onLine プロパティと online/offline イベントを使用します。
これは最も手軽な方法ですが、「ルーターに繋がっているが、インターネットには繋がっていない」場合でも true (Online) を返す という特性があります。

* 用途: UI の表示切り替え(「オフラインモードです」の表示など)
* 信頼性: 低(LANケーブルが刺さっているかどうかの判定に近い)

実装例

// ネットワーク状態の変化を監視する
function initConnectivityListener() {
    const updateStatus = () => {
        const isOnline = navigator.onLine;
        console.log(isOnline ? '接続されました' : '切断されました');
        
    };

    window.addEventListener('online', updateStatus);
    window.addEventListener('offline', updateStatus);
    
    // 初期状態の反映
    updateStatus();
}

// アプリ起動時などに呼び出す
// initConnectivityListener();

---

方法 2: Rust による疎通確認 (厳密なチェック)

「本当にインターネット上のサーバーにアクセスできるか」を知るためには、実際にパケットを飛ばしてみるのが確実です。
Rust 側で定期的に信頼できるエンドポイント(Google DNS 8.8.8.8 や Cloudflare 1.1.1.1、または自社サーバー)に対して接続試行を行います。

1. 依存関係 (src-tauri/Cargo.toml)

[dependencies]
# 特別な追加依存関係は不要ですが、HTTPでチェックしたい場合は reqwest が便利です
# ここでは標準ライブラリの TcpStream を使い軽量に実装します

2. Rust 実装 (src-tauri/src/lib.rs)

TcpStream::connect を使って、特定の IP アドレスのポート 53 (DNS) や 80 (HTTP) に接続できるかを確認するコマンドを作成します。

use tauri::command;
use std::net::TcpStream;
use std::time::Duration;

#[command]
async fn check_internet_connectivity() -> bool {
    // Cloudflare Public DNS (1.1.1.1) のポート 53 に接続を試みる
    // タイムアウトを短く設定 (例: 1500ms)
    // ※ TCP接続を確立するだけで、実際のデータは送らない
    let addr = "1.1.1.1:53";
    let timeout = Duration::from_millis(1500);

    // std::net::TcpStream を使う場合はブロッキングするため、
    // 厳密には spawn_blocking するか、非同期版 (tokio::net::TcpStream) を使うのがベターです。
    // ここでは Tokio 環境下でも動作しやすいよう、tokio::net::TcpStream を使う例にします
    match tokio::net::TcpStream::connect(addr).await {
        Ok(_) => true,
        Err(_) => false,
    }
}

// 登録関数
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![
            check_internet_connectivity
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

3. JavaScript 実装

重要な操作(課金処理や同期処理など)の直前にこのコマンドを呼び出して確認します。

import { invoke } from '@tauri-apps/api/core';

async function performSensitiveAction(): Promise<void> {
    // まずブラウザの判定でフィルタ
    if (!navigator.onLine) {
        alert('オフラインです。ネットワークを確認してください。');
        return;
    }

    // 念のため実際に繋がるか確認
    const hasInternet = await invoke<boolean>('check_internet_connectivity');
    
    if (!hasInternet) {
        alert('インターネット接続が確認できません。ファイアウォール等の設定を確認してください。');
        return;
    }

    // 処理実行...
    console.log('Action executed!');
}

---

方法 3: バックグラウンドでの定期監視

Rust 側で定期的にチェックを行い、状態が変化したタイミングでイベントを発行する実装です。
常に最新の接続状態を UI に反映させたい場合に有効です。

Rust 側 (src-tauri/src/lib.rs)

use tauri::{AppHandle, Emitter, Manager};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::time::{sleep, Duration};

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .setup(|app| {
            let app_handle = app.handle().clone();
            
            // 現在の状態を保持
            let is_online = Arc::new(AtomicBool::new(true)); // 初期値
            
            tauri::async_runtime::spawn(async move {
                loop {
                    // 5秒ごとにチェック
                    sleep(Duration::from_secs(5)).await;

                    // 接続確認 (非同期)
                    let current_status = tokio::net::TcpStream::connect("8.8.8.8:53").await.is_ok();
                    
                    // 前回の状態と比較
                    let prev_status = is_online.load(Ordering::Relaxed);
                    
                    if current_status != prev_status {
                        is_online.store(current_status, Ordering::Relaxed);
                        println!("Internet status changed: {}", current_status);
                        
                        // イベント送信
                        let _ = app_handle.emit("network-status-changed", current_status);
                    }
                }
            });

            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

JavaScript 側 (イベント受信)

import { listen } from '@tauri-apps/api/event';

async function startNetworkMonitor() {
    console.log("ネットワーク監視を開始します...");

    // イベントリスナーの登録
    const unlisten = await listen<boolean>('network-status-changed', (event) => {
        const isOnline = event.payload;
        console.log(`詳細な接続状態が変更されました: ${isOnline ? 'Online' : 'Offline'}`);

        if (!isOnline) {
            console.warn("インターネット接続が失われました (Rust側判定)");
            // ここでトースト通知などを表示する
        } else {
            console.info("インターネット接続が復帰しました");
        }
    });

    // 監視を停止したい場合は unlisten() を呼び出します
    // unlisten(); 
}

// 起動時に監視を開始
// startNetworkMonitor();