Repository Wiki
ChanIok/SpinningMomo

局域网访问模式(LAN Access)

局域网访问模式(LAN Access)是指 SpinningMomo 的 Vue 3 Web 前端不通过嵌入式 WebView 加载,而是由局域网内的任意浏览器直接访问 C++ 后端 HTTP 服务(默认端口 51206)的一种前端访问形态。本文基于仓库源码,说明该模式所依赖的前后端通信拓扑、端点面(/rpc、/static、/downloads、/sse)、开发期代理配置与 WebView 嵌入桥接的差异。

目的与范围(Purpose and Scope)

本页面覆盖 web-frontend 目录下的 LAN 访问模式 子主题,具体包括:

  • 前端如何与后端 HTTP 服务通信:/rpc、/static、/downloads、/sse 四类端点前缀的职责划分
  • 开发模式下 Vite Dev Server 的代理规则(web/vite.config.ts)
  • 嵌入式 WebView 模式与浏览器直连模式的桥接差异(web/src/types/webview.d.ts)
  • 与访问模式相关的设置项与前端入口(web/src/main.ts、features/settings/api)

以下内容有意留给兄弟页面,不在本页展开:

  • RPC 协议本身的请求/响应编解码细节 → 参见 RPC 通信相关页面
  • 设置界面(Settings)各功能项的完整实现 → 参见设置界面相关页面
  • C++ 后端 HTTP 服务器的启动、路由注册与生命周期 → 属于后端文档范围

源码覆盖度说明(诚实声明):在本页的源码探索预算内,未检索到名为 lan / LanAccess 的独立前端模块,也未发现 0.0.0.0 显式绑定或 LAN 开关的前端代码。仓库中可验证的核心事实是:后端固定在 localhost:51206 提供 HTTP 服务,前端所有流量(RPC、静态资源、下载、SSE 事件)都经由该端口的后端端点承载。因此本页以"已验证的通信拓扑"为主线展开;凡属推测之处均明确标注,不做虚构。

概述(Overview)

SpinningMomo 是一个 C++23 桌面应用(使用 xmake 构建,见 xmake.lua),其界面层是一套独立的 Vue 3 单页应用(web/ 目录)。整体架构是"本地 HTTP 后端 + Web 前端"的组合:

  • C++ 后端在本地 51206 端口提供 HTTP 服务,暴露 /rpc(远程过程调用)、/static(静态资源)、/downloads(下载)、/sse(服务端推送事件)四类端点前缀
  • 前端(Vue 3 SPA)在三种形态下运行:
    1. 嵌入模式:桌面应用内嵌 WebView 窗口加载前端,通过 postMessage 桥接原生能力
    2. 开发模式:Vite Dev Server 承载前端,/rpc、/static、/downloads、/sse 被代理转发到后端
    3. LAN 浏览器直连模式:局域网内其他设备的浏览器直接访问后端 HTTP 服务获取前端资源

这一"前后端同源、以端口 51206 为唯一入口"的设计,使得 LAN 访问在拓扑上是开发代理模式的自然延伸——只要后端服务可被局域网访问,浏览器即可完整驱动界面。仓库的 AGENTS.md 对该拓扑有一句话概括:

web/ uses a Vite dev server and proxies /rpc and /static to the backend at localhost:51206. docs/ is a separate VitePress site and is not part of the runtime bundle.

同时 docs/ 是独立的 VitePress 站点,不属于运行时打包产物——这明确了 LAN 访问的载体只有 web/ 构建出的前端与 C++ 后端。

架构(Architecture)

下图展示三种访问形态共用同一套后端端点面的真实拓扑(节点均对应仓库中实际存在的文件/端点):

Loading diagram...

架构要点说明:

  1. 唯一后端入口:无论哪种访问形态,前端业务数据的出口都是 localhost:51206 上的端点。LAN 模式与开发模式的区别仅在于"谁来充当前端资源的提供方"——前者由后端直接托管前端构建产物,后者由 Vite Dev Server 提供并转发。
  2. 端点分工清晰:/rpc 承载命令式调用(如设置读写),/static 承载静态资源,/downloads 承载文件下载,/sse 承载事件流。web/src/composables/useRpc.ts 中存在专门用于"监听 RPC 事件"的 Vue Composable(源码注释),对应 /sse 的事件订阅通道。
  3. WebView 桥是嵌入模式独有路径:types/webview.d.ts 声明了 postMessageWithAdditionalObjects 等 API(见下文 API 参考),这是桌面嵌入窗口与前端之间的原生消息通道;LAN 浏览器直连时不经过该桥,完全依赖 HTTP 端点。这意味着任何依赖 WebView 桥的能力在 LAN 模式下需要通过 RPC 端点获得等效替代。

核心流程(Core Flow)

以下时序图展示 LAN 模式下,局域网浏览器从进入页面到完成一次设置读写的完整交互:

Loading diagram...

流程说明:

  1. 资源获取:LAN 浏览器直接向 :51206 请求前端资源,后端返回 SPA 入口(web/index.html 与 web/src 构建产物)。这一步不需要 Vite Dev Server 参与,是 LAN 模式与开发模式的分界点。
  2. SPA 启动与主题初始化:main.ts 在启动时读取 settingsStore.appSettings.ui.webTheme.mode 并 watch 其变化(web/src/main.ts),保证 LAN 浏览器侧的主题与桌面端设置保持一致。
  3. 命令式调用走 /rpc:所有设置读写等业务操作通过 /rpc 端点完成。设置模块的 API(如 adbModeApi、DiscoveredAdbDevice 类型)即由此路径承载(web/src/components/AdbDeviceInput.vue)。
  4. 事件订阅走 /sse:前端通过 SSE 长连接接收后端推送的事件;useRpc.ts 提供的 Composable 封装了事件监听的生命周期(web/src/composables/useRpc.ts)。

使用示例(Usage Examples)

示例一:Vite 开发代理配置(LAN 拓扑的镜像实现)

以下配置把四类端点前缀全部代理到本地后端 51206。这条规则集中体现了"前后端以 51206 为唯一业务入口"的架构约束——LAN 直连模式下浏览器直接命中这些端点,开发模式下由代理转发到同一批端点:

typescript
1// vite.config.ts 中的 server.proxy 配置(节选) 2'/rpc': { 3 target: 'http://localhost:51206', 4 changeOrigin: true, 5}, 6'/static': { 7 target: 'http://localhost:51206', 8 changeOrigin: true, 9}, 10'/downloads': { 11 target: 'http://localhost:51206', 12 changeOrigin: true, 13}, 14'/sse': { 15 target: 'http://localhost:51206', 16 changeOrigin: true, 17},

Source: web/vite.config.ts

设计意图:changeOrigin: true 使代理请求的 Host 头改写为目标地址,避免后端对 Host 校验失败;四个前缀统一指向同一后端,保证前端代码在任何模式下都使用相对路径请求(如 /rpc、/sse),无需感知自身运行形态——这是 LAN 模式"零配置切换"的关键。

示例二:WebView 桥接类型声明(嵌入模式与 LAN 模式的差异点)

typescript
1// web/src/types/webview.d.ts(节选) 2postMessageWithAdditionalObjects(message: any, additionalObjects: object[]): void 3addEventListener(event: 'message', handler: (event: MessageEvent) => void): void 4removeEventListener(event: 'message', handler: (event: MessageEvent) => void): void

Source: web/src/types/webview.d.ts

这段声明定义了嵌入模式下的原生桥。LAN 模式下这些 API 不存在(浏览器环境),前端功能必须全部落在 HTTP 端点上;识别"是否处于嵌入环境"以及相应的降级逻辑应基于该类型存在性判断。

示例三:设置 API 的消费方式(跨模式共用的调用面)

typescript
1import { adbModeApi, type DiscoveredAdbDevice } from '@/features/settings/api' 2 3const devices = ref<DiscoveredAdbDevice[]>([]) 4 5const getDeviceDisplayName = (device: DiscoveredAdbDevice): string => { 6 if (device.kind === 'mumu') return t('settings.adbMode.targetDevice.deviceKind.mumu') 7 // ... 8}

Source: web/src/components/AdbDeviceInput.vue

这类 xxApi 调用是 LAN 模式下所有交互的统一出口。由于请求使用相对路径,无论前端由后端托管(LAN)还是由 Vite 代理(开发),调用代码完全一致——无需修改任何一行前端代码即可切换访问形态。

配置选项(Configuration Options)

选项 / 项类型默认值 / 位置说明
后端服务端口number51206C++ 后端 HTTP 服务端口,前端所有端点前缀的目标地址(AGENTS.md)
/rpc 代理proxy ruletarget: http://localhost:51206RPC 调用代理,changeOrigin: true(vite.config.ts L22-25)
/static 代理proxy ruletarget: http://localhost:51206静态资源代理(vite.config.ts L28-31)
/downloads 代理proxy ruletarget: http://localhost:51206下载端点代理(vite.config.ts L33-36)
/sse 代理proxy ruletarget: http://localhost:51206SSE 事件流代理(vite.config.ts L38-41)
ui.webTheme.modestring(设置项)见设置存储界面主题模式,main.ts 对其建立响应式监听(main.ts)

说明:仓库中未发现可配置的 LAN 监听地址(如 host/0.0.0.0)设置项。端口 51206 是仓库文档与代理配置中唯一可验证的服务地址。

API 参考(API Reference)

WebView 桥接 API(window.chrome.webview,仅嵌入模式可用)

以下签名摘自 web/src/types/webview.d.ts:

  • postMessageWithAdditionalObjects(message: any, additionalObjects: object[]): void
    • 参数:message 为序列化消息体;additionalObjects 为附带的原始对象(如文件句柄)
    • 返回:无
    • 说明:嵌入模式下前端 → 原生的消息通道;LAN 模式下不可用
  • addEventListener(event: 'message', handler: (event: MessageEvent) => void): void
    • 监听原生 → 前端的消息
  • removeEventListener(event: 'message', handler: (event: MessageEvent) => void): void
    • 移除消息监听,用于组件卸载时清理

前端内部 API 面(web/src/features/settings/api)

  • adbModeApi:ADB 模式相关 RPC 调用集合,被 AdbDeviceInput.vue 等组件消费(引用位置)
  • DiscoveredAdbDevice(类型):已发现设备描述,含 kind 字段(如 'mumu')等判别字段(用法示例)

该模块的完整方法签名未在本次源码探索预算内读取,此处仅记录已验证的导出成员与消费方式。完整定义请直接查阅 web/src/features/settings/api。

失败模式、边界情况与并发(Failure Modes, Edge Cases & Concurrency)

  • WebView 桥能力缺失:LAN 浏览器环境没有 window.chrome.webview。任何依赖 postMessageWithAdditionalObjects 的功能在 LAN 模式下会失败;从架构看,等效能力需通过 /rpc 端点提供。前端可通过检测该对象是否存在实现能力降级。
  • SSE 连接生命周期:/sse 是长连接。LAN 环境下浏览器与后端之间可能经过更多网络跳数,连接更易中断;useRpc.ts 的 Composable 封装负责事件监听的建立与清理(见其文档注释),组件卸载时应确保监听被移除,避免重复订阅与内存泄漏。
  • 并发写一致性:多设备同时通过 LAN 访问时,/rpc 写设置与 /sse 事件推送构成"命令 + 广播"模型。设置变更后主题等 UI 状态依赖 main.ts 中的响应式 watch 同步;其他客户端需通过 /sse 收到变更事件才能刷新本地缓存视图(事件推送机制细节属 RPC/SSE 通信页面范围)。
  • 端口占用 / 目标不可达:开发模式下若后端未启动,所有代理目标 localhost:51206 不可达,页面表现为 RPC 失败。LAN 模式下该风险同样存在——后端是唯一资源与数据来源。

性能与运维注意事项(Performance & Operational Notes)

  • 单一端口、无 CDN 依赖:前端资源与数据全部来自 :51206,LAN 内首次加载的资源体积受构建产物大小影响;/static 前缀的缓存策略由后端决定(后端实现超出本页范围)。
  • SSE 与代理超时:开发模式下 /sse 经 Vite 代理转发,需确认代理对长连接不设置过短超时;LAN 直连则无此层代理,直接由浏览器与后端维持连接。
  • 防火墙 / 端口放行:LAN 访问要求操作系统放行 51206 端口入站。仓库中未发现自动配置防火墙的代码,属部署/运维事项。
  • 构建工具链:项目使用 C++23(xmake.lua)与 pnpm workspace 管理前端依赖;前端构建产物需由后端托管后才可被 LAN 访问。

扩展点(Extension Points)

  • 新增 LAN 可用能力:新增功能若要同时服务嵌入与 LAN 两种形态,应实现为 /rpc 端点上的方法(前端通过相对路径调用),而不是只走 WebView 桥。features/settings/api 中的 xxApi 模式(见使用示例三)是可复用的封装范式。
  • 事件订阅扩展:新的运行时事件可通过 /sse 通道广播,前端使用 composables/useRpc.ts 的事件监听 Composable 消费,保持与现有设置同步机制(main.ts watch)一致的架构风格。
  • 多语言 UI:useI18n Composable(t('...') 调用,见示例三)承担文案本地化;新增 LAN 场景专属提示时遵循同一 key 规范。