Skip to content

HTTP API /api/toggle silently does nothing when Hide at launch is enabled #1038

Description

@zbeosf

System (Mac, Windows 10/11, Linux) / 操作系统

macOS 15 (Apple Silicon)

SwitchHosts Version / SwitchHosts 版本

5.0.1 (also reproduced on master @ 3235e81)

Description / 描述

When Hide at launch is enabled, GET /api/toggle returns ok but nothing happens — the profile is not switched and /etc/hosts is not written. /api/list keeps reporting the old on state.

This also breaks the bundled Alfred workflow, whose action is exactly curl 'http://127.0.0.1:50761/api/toggle?id={query}'.

The endpoint always answers ok, so a caller cannot tell the toggle was dropped.

Why it happens

api_toggle does not change any state itself — it broadcasts toggle_item and lets the main window's onToggleItem run the apply pipeline:

// src-tauri/src/http_api.rs
let _ = state.app.emit("toggle_item", json!({ "_args": [id, !on] }));
"ok"

A Tauri event with no listener is dropped. And with hide_at_launch on, setup skips window creation entirely, so there is no renderer to receive it:

// src-tauri/src/lib.rs
if hide_at_launch && app_state.data_dir_recovery.is_none() {
    // Skip creating the main window ...
    lifecycle::enter_hidden_to_tray_state();
} else {
    let main = lifecycle::create_main_window(&app_handle, app_state.inner())?;

The module doc of http_api.rs states the assumption that no longer holds:

the v5 storage plan noted that doing the apply directly inside the HTTP handler would work even when no renderer is alive, but in our build the main window is created at startup and only ever hidden, so the broadcast always reaches a live listener.

Under hide_at_launch the window is not created at all, so the broadcast reaches nobody.

Once the user opens the main window once from the tray, the renderer exists and the API works for the rest of the session — which makes this look intermittent.

How to reproduce / 重现步骤

  1. Preferences → enable HTTP API, enable Hide at launch.
  2. Quit and relaunch SwitchHosts (do not open the main window).
  3. curl 'http://127.0.0.1:50761/api/list' — works, lists profiles.
  4. curl 'http://127.0.0.1:50761/api/toggle?id=<some-id>' — returns ok.
  5. curl 'http://127.0.0.1:50761/api/list' — the on flag is unchanged, and /etc/hosts is untouched.
  6. Open the main window once from the tray, then repeat step 4 — now it works.

The app log shows the request arriving (toggle: <id>) in both cases, so the request is received and only the broadcast is lost.


中文说明

开启「启动时隐藏窗口」后,GET /api/toggle 返回 ok,但实际上什么都没发生 —— 方案没有切换,/etc/hosts 没有被写入,/api/list 里的 on 状态也保持不变。

自带的 Alfred workflow 也因此失效,因为它的动作正是 curl 'http://127.0.0.1:50761/api/toggle?id={query}'。接口始终返回 ok,调用方无法察觉切换被丢弃了。

原因

api_toggle 本身不修改任何状态,它只是广播 toggle_item 事件,由主窗口的 onToggleItem 执行实际的切换与应用。Tauri 的事件如果没有监听者就会被直接丢弃 —— 而开启「启动时隐藏」后,setup 阶段完全跳过了主窗口的创建,因此没有任何 renderer 能收到这个事件。

http_api.rs 的模块注释里其实已经写明了这个前提:「在我们的构建中主窗口在启动时总是会被创建,只是被隐藏起来,所以广播总能到达存活的监听者」。但在「启动时隐藏」下,窗口根本没有被创建。

从托盘打开一次主窗口之后,renderer 就存在了,本次会话内 API 便恢复正常 —— 这也是为什么这个问题看起来时好时坏。

复现步骤

  1. 偏好设置 → 开启「HTTP API」与「启动时隐藏窗口」
  2. 退出并重新启动 SwitchHosts(不要打开主窗口)
  3. curl 'http://127.0.0.1:50761/api/list' —— 正常返回方案列表
  4. curl 'http://127.0.0.1:50761/api/toggle?id=<某个 id>' —— 返回 ok
  5. 再次 curl .../api/list —— on 状态没有变化,/etc/hosts 也没有被修改
  6. 从托盘打开一次主窗口,再执行第 4 步 —— 这次生效了

两种情况下日志里都能看到 toggle: <id>,说明请求已经收到,只是广播丢失了。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions