2026-07-22 14:51:11 +08:00
# API / 模块合约
MVP 首先提供本地 Go application service 和 CLI;是否增加 loopback HTTP/WebSocket 在 T-203 决策。时间使用 RFC3339,路径使用规范化绝对路径。
## 核心类型
2026-07-25 15:36:23 +08:00
代码权威类型位于 `internal/domain/browser.go` : `BrowserKind` 、`LaunchSpec` 、`InstanceStatus` 、`InstanceView` 、`BrowserEvent` 和稳定错误码。`LaunchSpec.Normalize()` 负责 browser kind、绝对 user data dir、受控的 loopback remote debugging port,以及非空时的 http/https URL 校验;空 URL 表示打开浏览器默认页。
`LaunchSpec` 使用已分配的 `RemoteDebugPort` ;端口分配不属于 UI,也不允许由 `ExtraArgs` 覆盖。`InstanceView` 返回实际关联端口、PID、占用来源和归属。外部关联只公开经 `DevToolsActivePort` 与 `/json/version` 验证的端口,不公开 CDP WebSocket URL。
2026-07-22 14:51:11 +08:00
2026-07-27 09:46:21 +08:00
只读 CDP 目标查看使用 platform `RemoteDebugTargetInspector` :
```go
type RemoteDebugTarget struct {
ID string
Type string
Title string
URL string // 已移除 userinfo、query 与 fragment
}
type RemoteDebugTargetInspector interface {
ListRemoteDebugTargets ( ctx context . Context , kind BrowserKind , port int ) ([] RemoteDebugTarget , error )
}
```
调用者必须提供当前实例已经验证的实际端口;实现仍会复核 `/json/version` 的浏览器类型。该接口只请求 loopback HTTP `/json/list` ,不公开或使用 `webSocketDebuggerUrl` ,也不提供导航、关闭、创建 target 或执行脚本的接口。
2026-07-27 12:00:38 +08:00
UI 编辑使用独立的 `EditableInstance` DTO( `ID` 、名称、浏览器类型、User Data Dir、启动 URL、`ProxyID` ),不将运行时 PID、状态、来源或实际远程调试端口写回配置。`ProxyProfile` DTO 只包含 `ID` 、必填且大小写无关唯一的显示名称、已校验的无认证端点;不提供认证字段。`InstanceRow.ProxyID` 是唯一持久关联,列表中的代理名称是按当前代理 DTO 推导的显示值,不持久化副本。状态刷新使用 `RefreshInstanceStatus(ctx, []InstanceSnapshot) ([]InstanceStatusResult, error)` 形状:每个结果以 `ID` 和快照版本对应,错误可按行返回;实现只能检查调用方提供的实例范围。
2026-07-25 16:21:38 +08:00
2026-07-22 14:51:11 +08:00
## Application service
```go
type BrowserManager interface {
Start ( ctx context . Context , spec LaunchSpec ) ( InstanceView , error )
List ( ctx context . Context ) ([] InstanceView , error )
Get ( ctx context . Context , id string ) ( InstanceView , error )
Stop ( ctx context . Context , id string , force bool ) error
Restart ( ctx context . Context , id string ) ( InstanceView , error )
}
```
`Stop(force=false)` 只执行优雅关闭;强制关闭必须由明确用户动作或 CLI `--force` 触发。
2026-07-22 15:03:21 +08:00
平台实现通过实例级 `StopProfile(userDataDir, force)` 执行关闭;force 只对已注册并经过身份绑定的实例生效。
2026-07-25 16:53:44 +08:00
GUI 将优雅停止封装为异步 `InstanceStopper(ctx, InstanceRow) error` adapter; adapter 只接受当前配置行并执行 `force=false` ,不暴露平台句柄或允许调用方传递任意 PID。
2026-07-25 15:36:23 +08:00
参数数组由 `internal/platform/browser.BuildArgs` 生成;Chrome/Edge executable 由同包 `Discoverer.Resolve` 解析。二者都不接收 shell 命令行字符串。CDP 端点检测和端口分配均通过 platform port 执行,UI 只消费 DTO 结果。
2026-07-22 14:55:35 +08:00
2026-07-27 09:46:21 +08:00
状态刷新、受管进程退出、目录选择和 CDP 页面目标读取必须通过后台 adapter 回到 UI 结果通道;UI frame 不直接做进程、文件或 CDP I/O。受管退出结果至少包含实例 ID、启动代次、受管 PID、退出码和是否为 Chub 请求停止;UI 以实例 ID、代次和 PID 丢弃过期结果。刷新和页面目标读取都不构成外部实例接管授权。
2026-07-25 16:21:38 +08:00
2026-07-27 22:35:59 +08:00
设置页浏览器 executable 选择使用 platform 文件选择契约;它不发现、启动、关闭或检查浏览器进程:
```go
type ExecutablePicker interface {
ChooseExecutable ( ctx context . Context ) ( string , error )
}
```
Windows 实现只返回绝对 `.exe` 路径;用户取消返回可识别的取消错误。UI 在后台调用该契约,并以 `PathField` 和请求代次丢弃过期结果。选择成功才改写对应 Chrome/Edge 字段;同字段的搜索/选择状态和文字反馈必须独立保存,不能依赖页面底部的全局消息。
2026-07-27 22:59:10 +08:00
实例数据删除分成 UI 授权、占用复核和文件删除三个边界:
```go
type InstanceDataDeleter func ( context . Context , InstanceRow ) error
type InstanceDataRemover interface {
RemoveInstanceData ( context . Context , string ) error
}
```
`InstanceDataDeleter` 仅由删除确认框中默认未勾选的“同时删除 User Data Dir 中的数据”触发;它必须先复核 Chub registry 和外部 profile 占用,再调用 `InstanceDataRemover` 。后者只接受绝对、非卷根且非链接的目录,目录不存在可视为已清除;它不根据进程名扫描、停止或接管浏览器。任何错误都使 UI 保留实例配置;只有数据删除成功才发布实例配置删除。
2026-07-27 10:35:32 +08:00
Windows 通知区域使用仅限 platform 的内部 DTO; Gio shell 不直接调用 Shell API:
```go
type TrayCommand uint8
const (
TrayCommandShow TrayCommand = iota
TrayCommandRefresh
TrayCommandExit
)
type TrayState struct {
ManagedRunning int
ExternalLinked int
AttentionCount int
}
```
原生 adapter 通过结果通道发布 `TrayCommand` ,并只接收 `TrayState` 汇总来更新 Tooltip 或图标状态。不得在 DTO 中放入实例名称、路径、URL、代理、PID、CDP 地址或平台句柄;`TrayCommand` 不是实例启动、停止、重启、删除或接管 API。
2026-07-27 11:25:59 +08:00
本地配置入口统一为:
```go
func DefaultPath () ( string , error ) // <dir(os.Executable())>\\config.json
func LegacyPath () ( string , error ) // %APPDATA%\\chub\\config.json,仅用于一次迁移
func OpenDefault () ( * Store , error ) // 目标优先;必要时校验并原子迁移 legacy
2026-07-27 12:00:38 +08:00
func DefaultInstanceUserDataDir ( instanceID string ) ( string , error ) // <dir(os.Executable())>\\user_data_dirs\\<instanceID>
2026-07-27 11:25:59 +08:00
```
2026-07-27 12:00:38 +08:00
调用方不得把当前工作目录作为配置来源,也不得自行复制、删除或合并 legacy 文件。`OpenDefault` 的迁移是文件级配置迁移,不迁移 `UserDataDir` 、日志目录或浏览器文件;目标文件存在、legacy 缺失时均不产生写入。`DefaultInstanceUserDataDir` 只生成绝对默认字符串,不创建目录;调用方必须先分配稳定、唯一的实例 ID。迁移失败返回稳定的配置错误,调用方不得改用 legacy 继续写入。
2026-07-27 11:25:59 +08:00
2026-07-22 15:19:11 +08:00
## CLI 当前合约
2026-07-22 14:51:11 +08:00
```text
chub start --browser chrome --user-data-dir <dir> [--profile-directory <name>] [--url <url>]
chub list
chub stop <instance-id> [--force]
chub restart <instance-id>
2026-07-22 15:19:11 +08:00
chub events
2026-07-22 14:51:11 +08:00
```
退出码:0 成功,2 参数错误,3 profile 占用,4 实例不存在,5 身份校验失败,6 系统权限/进程错误。
2026-07-22 15:19:11 +08:00
当前 `list` 和 `events` 已输出 JSON; `events` 输出本地配置快照 JSON,后续 loopback transport 接入后升级为 JSON Lines 持续流。`start/stop/restart` 已保留稳定命令和错误码,等待 BrowserManager adapter 接入。
2026-07-22 14:51:11 +08:00
## 事件
| 事件 | 触发 | 负载 |
| --- | --- | --- |
| `browser.started` | 新实例完成启动 | InstanceView |
2026-07-25 15:36:23 +08:00
| `browser.status.changed` | 状态变化 | instance_id、status、pid、remote_debug_port、ownership、error_code |
2026-07-25 18:07:57 +08:00
| `browser.exited` | Chub 当前会话受管根进程退出 | instance_id、launch_generation、pid、exit_code、expected_stop |
2026-07-22 14:51:11 +08:00
事件禁止携带密码、Cookie、Token、完整代理认证信息或原始系统堆栈。
2026-07-22 15:19:11 +08:00
应用内 `application.EventBus` 使用带缓冲的订阅通道,发布不阻塞进程监控;UI 通过订阅刷新状态,取消订阅会关闭对应通道。