mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-26 22:22:13 +08:00
feat(contract): export wire contract from backend and generate frontend types
Adds a zero-dependency `frontend-contract` bin that serializes the real NoticeStatus variants and FrontendEvent payloads, scripts/gen-contract.mjs which emits the committed src/services/contract.ts, and a CI job that regenerates it and fails on a non-empty diff. events.ts now derives its event map from the generated contract instead of hand-typed shapes.
This commit is contained in:
@@ -85,3 +85,78 @@ jobs:
|
||||
- name: Run Knip
|
||||
if: steps.check_frontend.outputs.frontend == 'true'
|
||||
run: pnpm knip:check
|
||||
|
||||
contract-parity:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Check contract inputs
|
||||
id: check_inputs
|
||||
uses: dorny/paths-filter@v4
|
||||
with:
|
||||
filters: |
|
||||
contract:
|
||||
- 'src/services/contract.ts'
|
||||
- 'scripts/gen-contract.mjs'
|
||||
- 'src-tauri/src/core/notify/**'
|
||||
- 'src-tauri/src/bin/**'
|
||||
- 'src-tauri/src/lib.rs'
|
||||
- '.github/workflows/frontend-check.yml'
|
||||
|
||||
- name: Skip if contract inputs unchanged
|
||||
if: steps.check_inputs.outputs.contract != 'true'
|
||||
run: echo "No contract inputs changed, skipping parity check."
|
||||
|
||||
- name: Install Rust Stable
|
||||
if: steps.check_inputs.outputs.contract == 'true'
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Rust Cache
|
||||
if: steps.check_inputs.outputs.contract == 'true'
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
prefix-key: 'v1-rust'
|
||||
key: 'rust-shared-stable-ubuntu-22.04-x86_64-unknown-linux-gnu-contract'
|
||||
workspaces: |
|
||||
. -> target
|
||||
cache-all-crates: true
|
||||
cache-workspace-crates: true
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.check_inputs.outputs.contract == 'true'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: Install pnpm
|
||||
if: steps.check_inputs.outputs.contract == 'true'
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- uses: actions/setup-node@v7
|
||||
if: steps.check_inputs.outputs.contract == 'true'
|
||||
with:
|
||||
node-version: '24.21.0'
|
||||
cache: 'pnpm'
|
||||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
if: steps.check_inputs.outputs.contract == 'true'
|
||||
|
||||
- name: Provision sidecar placeholders for tauri-build
|
||||
if: steps.check_inputs.outputs.contract == 'true'
|
||||
run: |
|
||||
mkdir -p src-tauri/sidecar src-tauri/resources dist
|
||||
touch dist/index.html
|
||||
for bin in clash-verge-service clash-verge-service-install clash-verge-service-uninstall verge-mihomo verge-mihomo-alpha; do
|
||||
touch "src-tauri/sidecar/${bin}-x86_64-unknown-linux-gnu"
|
||||
done
|
||||
|
||||
- name: Regenerate contract and require empty diff
|
||||
if: steps.check_inputs.outputs.contract == 'true'
|
||||
run: |
|
||||
pnpm gen:contract
|
||||
git diff --exit-code -- src/services/contract.ts
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
"ignoreBinaries": ["rustc", "cc"],
|
||||
"ignoreDependencies": ["lint-staged"],
|
||||
"ignoreIssues": {
|
||||
"src/types/generated/**": ["exports", "types"]
|
||||
"src/types/generated/**": ["exports", "types"],
|
||||
"src/services/contract.ts": ["exports", "types"]
|
||||
},
|
||||
"cycles": {
|
||||
"dynamicImports": true
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"i18n:check": "node scripts/cleanup-unused-i18n.mjs",
|
||||
"i18n:format": "node scripts/cleanup-unused-i18n.mjs --align --apply",
|
||||
"i18n:types": "node scripts/generate-i18n-keys.mjs",
|
||||
"gen:contract": "node scripts/gen-contract.mjs",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
/**
|
||||
* Regenerates src/services/contract.ts from the backend wire contract.
|
||||
* Source of truth: `cargo run -p clash-verge --bin frontend-contract` inside
|
||||
* src-tauri, which serializes the same enum variants and event payloads the
|
||||
* app emits at runtime. CI reruns this and fails on a non-empty diff.
|
||||
*/
|
||||
|
||||
const ROOT = path.resolve(import.meta.dirname, '..')
|
||||
const OUT = path.join(ROOT, 'src/services/contract.ts')
|
||||
|
||||
const contract = JSON.parse(
|
||||
execFileSync(
|
||||
'cargo',
|
||||
['run', '-p', 'clash-verge', '--bin', 'frontend-contract', '-q'],
|
||||
{
|
||||
cwd: path.join(ROOT, 'src-tauri'),
|
||||
encoding: 'utf8',
|
||||
maxBuffer: 16 * 1024 * 1024,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
if (contract.version !== 1) {
|
||||
throw new Error(`unsupported wire contract version: ${contract.version}`)
|
||||
}
|
||||
|
||||
/** TS type per event, derived from the serialized sample payload's JSON shape. */
|
||||
const payloadType = (name, sample) => {
|
||||
if (sample === null) return 'null'
|
||||
if (typeof sample === 'string') return 'string'
|
||||
if (Array.isArray(sample)) {
|
||||
if (name === 'verge://notice-message') return '[NoticeStatus, string]'
|
||||
throw new Error(`unexpected array payload for ${name}`)
|
||||
}
|
||||
if (typeof sample === 'object') {
|
||||
const keys = Object.keys(sample)
|
||||
if (keys.length === 1 && keys[0] === 'uid') return '{ uid: string }'
|
||||
if (name === 'verge://run-state-changed') return 'RunState'
|
||||
throw new Error(`unexpected object payload for ${name}: ${keys.join(',')}`)
|
||||
}
|
||||
throw new Error(`unexpected payload for ${name}`)
|
||||
}
|
||||
|
||||
const needsRunState = contract.events.some(
|
||||
(e) => e.name === 'verge://run-state-changed',
|
||||
)
|
||||
if (!needsRunState)
|
||||
throw new Error('run-state-changed event missing from contract')
|
||||
|
||||
const lines = []
|
||||
lines.push(
|
||||
'// @generated by scripts/gen-contract.mjs from the backend wire contract. DO NOT EDIT.',
|
||||
)
|
||||
lines.push('// Regen: pnpm gen:contract')
|
||||
lines.push('')
|
||||
lines.push("import type { RunState } from './cmds'")
|
||||
lines.push('')
|
||||
lines.push(
|
||||
'/** Wire status strings from NoticeStatus; keep in sync via `pnpm gen:contract`. */',
|
||||
)
|
||||
lines.push('export type NoticeStatus =')
|
||||
for (const status of contract.noticeStatuses) {
|
||||
lines.push(` | ${JSON.stringify(status)}`)
|
||||
}
|
||||
lines.push('')
|
||||
lines.push('/** Event name → payload type for every backend-emitted event. */')
|
||||
lines.push('export interface VergeEventPayloads {')
|
||||
for (const event of contract.events) {
|
||||
const key = JSON.stringify(event.name)
|
||||
lines.push(` ${key}: ${payloadType(event.name, event.sample)}`)
|
||||
}
|
||||
lines.push('}')
|
||||
lines.push('')
|
||||
|
||||
const content = `${lines.join('\n')}\n`
|
||||
fs.writeFileSync(OUT, content)
|
||||
|
||||
// Match the repo's biome style so CI parity (`git diff --exit-code`) is stable.
|
||||
execFileSync(
|
||||
process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm',
|
||||
['exec', 'biome', 'format', '--write', path.relative(ROOT, OUT)],
|
||||
{ cwd: ROOT, stdio: 'ignore' },
|
||||
)
|
||||
|
||||
console.log(
|
||||
`[gen-contract] wrote ${path.relative(ROOT, OUT)}: ${contract.events.length} events, ${contract.noticeStatuses.length} statuses`,
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
//! Prints the frontend wire contract as JSON to stdout. Zero new dependencies:
|
||||
//! the payload is derived from the same serializers the app uses at runtime.
|
||||
//! Consumed by scripts/gen-contract.mjs; CI regenerates and diffs the committed
|
||||
//! TypeScript artifact.
|
||||
|
||||
fn main() {
|
||||
println!("{}", app_lib::frontend_wire_contract());
|
||||
}
|
||||
@@ -3,6 +3,7 @@ pub use announce::{Refresh, after_commit, announce};
|
||||
pub mod desktop;
|
||||
pub mod handle;
|
||||
pub mod notification;
|
||||
pub use notification::frontend_wire_contract;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
|
||||
pub enum NoticeStatus {
|
||||
@@ -106,55 +107,62 @@ pub enum NoticeStatus {
|
||||
SetConfigOk,
|
||||
}
|
||||
|
||||
impl NoticeStatus {
|
||||
/// Every variant in golden order. The wire-contract export and the golden
|
||||
/// test both derive their variant list from here; expected strings stay
|
||||
/// hand-written in the test so renames cannot slip through.
|
||||
pub const ALL: [Self; 41] = [
|
||||
Self::Info,
|
||||
Self::ImportSubUrlOk,
|
||||
Self::ImportSubUrlError,
|
||||
Self::SetConfigError,
|
||||
Self::CoreStartError,
|
||||
Self::ServiceCoreRepairRequired,
|
||||
Self::ServiceCoreAppDataNotOwned,
|
||||
Self::ServiceCoreSidecarFallback,
|
||||
Self::DnsOverrideAutoDisabled,
|
||||
Self::EnhanceDiscardedKeys,
|
||||
Self::TunModeAutoDisabled,
|
||||
Self::TunModeAutoDisableFailed,
|
||||
Self::AppRestartCoreStopFailed,
|
||||
Self::AppQuitCoreStopFailed,
|
||||
Self::UpdateWithClashProxy,
|
||||
Self::UpdateFailedEvenWithClash,
|
||||
Self::ReactivateProfilesError,
|
||||
Self::UpdateFailed,
|
||||
Self::ConfigValidateBootError,
|
||||
Self::ConfigValidateError,
|
||||
Self::ConfigValidateProcessTerminated,
|
||||
Self::ConfigValidateScriptError,
|
||||
Self::ConfigValidateScriptSyntaxError,
|
||||
Self::ConfigValidateScriptMissingMain,
|
||||
Self::ConfigValidateFileNotFound,
|
||||
Self::ConfigValidateYamlSyntaxError,
|
||||
Self::ConfigValidateYamlReadError,
|
||||
Self::ConfigValidateYamlMappingError,
|
||||
Self::ConfigValidateMergeSyntaxError,
|
||||
Self::ConfigValidateMergeMappingError,
|
||||
Self::ConfigCoreChangeSuccess,
|
||||
Self::ConfigCoreChangeError,
|
||||
Self::MixedPortFallback,
|
||||
Self::MixedPortFallbackError,
|
||||
Self::ConfigValidateCoreChange,
|
||||
Self::ConfigValidateStdoutError,
|
||||
Self::ConfigValidateYamlKeyError,
|
||||
Self::ConfigValidateYamlError,
|
||||
Self::ConfigValidateMergeKeyError,
|
||||
Self::ConfigValidateMergeError,
|
||||
Self::SetConfigOk,
|
||||
];
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::NoticeStatus;
|
||||
|
||||
#[test]
|
||||
fn notice_status_wire_contract() -> Result<(), serde_json::Error> {
|
||||
let statuses = [
|
||||
NoticeStatus::Info,
|
||||
NoticeStatus::ImportSubUrlOk,
|
||||
NoticeStatus::ImportSubUrlError,
|
||||
NoticeStatus::SetConfigError,
|
||||
NoticeStatus::CoreStartError,
|
||||
NoticeStatus::ServiceCoreRepairRequired,
|
||||
NoticeStatus::ServiceCoreAppDataNotOwned,
|
||||
NoticeStatus::ServiceCoreSidecarFallback,
|
||||
NoticeStatus::DnsOverrideAutoDisabled,
|
||||
NoticeStatus::EnhanceDiscardedKeys,
|
||||
NoticeStatus::TunModeAutoDisabled,
|
||||
NoticeStatus::TunModeAutoDisableFailed,
|
||||
NoticeStatus::AppRestartCoreStopFailed,
|
||||
NoticeStatus::AppQuitCoreStopFailed,
|
||||
NoticeStatus::UpdateWithClashProxy,
|
||||
NoticeStatus::UpdateFailedEvenWithClash,
|
||||
NoticeStatus::ReactivateProfilesError,
|
||||
NoticeStatus::UpdateFailed,
|
||||
NoticeStatus::ConfigValidateBootError,
|
||||
NoticeStatus::ConfigValidateError,
|
||||
NoticeStatus::ConfigValidateProcessTerminated,
|
||||
NoticeStatus::ConfigValidateScriptError,
|
||||
NoticeStatus::ConfigValidateScriptSyntaxError,
|
||||
NoticeStatus::ConfigValidateScriptMissingMain,
|
||||
NoticeStatus::ConfigValidateFileNotFound,
|
||||
NoticeStatus::ConfigValidateYamlSyntaxError,
|
||||
NoticeStatus::ConfigValidateYamlReadError,
|
||||
NoticeStatus::ConfigValidateYamlMappingError,
|
||||
NoticeStatus::ConfigValidateMergeSyntaxError,
|
||||
NoticeStatus::ConfigValidateMergeMappingError,
|
||||
NoticeStatus::ConfigCoreChangeSuccess,
|
||||
NoticeStatus::ConfigCoreChangeError,
|
||||
NoticeStatus::MixedPortFallback,
|
||||
NoticeStatus::MixedPortFallbackError,
|
||||
NoticeStatus::ConfigValidateCoreChange,
|
||||
NoticeStatus::ConfigValidateStdoutError,
|
||||
NoticeStatus::ConfigValidateYamlKeyError,
|
||||
NoticeStatus::ConfigValidateYamlError,
|
||||
NoticeStatus::ConfigValidateMergeKeyError,
|
||||
NoticeStatus::ConfigValidateMergeError,
|
||||
NoticeStatus::SetConfigOk,
|
||||
];
|
||||
let statuses = NoticeStatus::ALL;
|
||||
let expected = [
|
||||
"info",
|
||||
"import_sub_url::ok",
|
||||
|
||||
@@ -12,7 +12,7 @@ use std::{
|
||||
};
|
||||
use tauri::{AppHandle, Emitter as _, Manager as _, WebviewWindow};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FrontendEvent<'a> {
|
||||
RefreshClash,
|
||||
RefreshVerge,
|
||||
@@ -324,67 +324,7 @@ mod wire_tests {
|
||||
#[test]
|
||||
fn frontend_event_wire_contract() -> Result<(), serde_json::Error> {
|
||||
let uid = String::from("profile");
|
||||
let events = [
|
||||
(
|
||||
FrontendEvent::RefreshClash,
|
||||
"verge://refresh-clash-config",
|
||||
json!("yes"),
|
||||
),
|
||||
(
|
||||
FrontendEvent::RefreshVerge,
|
||||
"verge://refresh-verge-config",
|
||||
json!("yes"),
|
||||
),
|
||||
(FrontendEvent::RefreshProfiles, "verge://refresh-profiles", json!("yes")),
|
||||
(
|
||||
FrontendEvent::RefreshProxyConfig,
|
||||
"verge://refresh-proxy-config",
|
||||
json!(null),
|
||||
),
|
||||
(
|
||||
FrontendEvent::NoticeMessage {
|
||||
status: NoticeStatus::SetConfigOk,
|
||||
message: Arc::from("ok"),
|
||||
},
|
||||
"verge://notice-message",
|
||||
json!(["set_config::ok", "ok"]),
|
||||
),
|
||||
(
|
||||
FrontendEvent::TimerUpdated { profile_index: &uid },
|
||||
"verge://timer-updated",
|
||||
json!("profile"),
|
||||
),
|
||||
(
|
||||
FrontendEvent::RunStateChanged {
|
||||
state: json!({"running": true}),
|
||||
},
|
||||
"verge://run-state-changed",
|
||||
json!({"running": true}),
|
||||
),
|
||||
(
|
||||
FrontendEvent::PendingFailuresChanged,
|
||||
"verge://pending-failures-changed",
|
||||
json!(null),
|
||||
),
|
||||
(
|
||||
FrontendEvent::ProfileChanged {
|
||||
current_profile_id: &uid,
|
||||
},
|
||||
"profile-changed",
|
||||
json!("profile"),
|
||||
),
|
||||
(
|
||||
FrontendEvent::ProfileUpdateStarted { uid: &uid },
|
||||
"profile-update-started",
|
||||
json!({"uid": "profile"}),
|
||||
),
|
||||
(
|
||||
FrontendEvent::ProfileUpdateCompleted { uid: &uid },
|
||||
"profile-update-completed",
|
||||
json!({"uid": "profile"}),
|
||||
),
|
||||
];
|
||||
for (event, name, payload) in events {
|
||||
for (event, name, payload) in super::sample_frontend_events(&uid) {
|
||||
let (actual_name, actual_payload) = NotificationSystem::serialize_event(event);
|
||||
assert_eq!(actual_name, name);
|
||||
assert_eq!(actual_payload?, payload);
|
||||
@@ -392,3 +332,93 @@ mod wire_tests {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// One sample per `FrontendEvent` variant, paired with its wire name and payload.
|
||||
/// Shared by the wire golden test and the contract export so the two cannot drift.
|
||||
fn sample_frontend_events(uid: &String) -> Vec<(FrontendEvent<'_>, &'static str, serde_json::Value)> {
|
||||
vec![
|
||||
(
|
||||
FrontendEvent::RefreshClash,
|
||||
"verge://refresh-clash-config",
|
||||
json!("yes"),
|
||||
),
|
||||
(
|
||||
FrontendEvent::RefreshVerge,
|
||||
"verge://refresh-verge-config",
|
||||
json!("yes"),
|
||||
),
|
||||
(
|
||||
FrontendEvent::RefreshProfiles,
|
||||
"verge://refresh-profiles",
|
||||
json!("yes"),
|
||||
),
|
||||
(
|
||||
FrontendEvent::RefreshProxyConfig,
|
||||
"verge://refresh-proxy-config",
|
||||
json!(null),
|
||||
),
|
||||
(
|
||||
FrontendEvent::NoticeMessage {
|
||||
status: NoticeStatus::SetConfigOk,
|
||||
message: Arc::from("ok"),
|
||||
},
|
||||
"verge://notice-message",
|
||||
json!(["set_config::ok", "ok"]),
|
||||
),
|
||||
(
|
||||
FrontendEvent::TimerUpdated { profile_index: uid },
|
||||
"verge://timer-updated",
|
||||
json!("profile"),
|
||||
),
|
||||
(
|
||||
FrontendEvent::RunStateChanged {
|
||||
state: json!({"running": true}),
|
||||
},
|
||||
"verge://run-state-changed",
|
||||
json!({"running": true}),
|
||||
),
|
||||
(
|
||||
FrontendEvent::PendingFailuresChanged,
|
||||
"verge://pending-failures-changed",
|
||||
json!(null),
|
||||
),
|
||||
(
|
||||
FrontendEvent::ProfileChanged {
|
||||
current_profile_id: uid,
|
||||
},
|
||||
"profile-changed",
|
||||
json!("profile"),
|
||||
),
|
||||
(
|
||||
FrontendEvent::ProfileUpdateStarted { uid },
|
||||
"profile-update-started",
|
||||
json!({"uid": "profile"}),
|
||||
),
|
||||
(
|
||||
FrontendEvent::ProfileUpdateCompleted { uid },
|
||||
"profile-update-completed",
|
||||
json!({"uid": "profile"}),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
/// Machine-readable form of the frontend wire contract: every event name with a
|
||||
/// serialized sample payload, plus every notice status string. The sample
|
||||
/// payload's JSON shape (string / null / array / object) is what the frontend
|
||||
/// generator maps to TypeScript types. `ThemeChanged` is a Tauri built-in
|
||||
/// linux-only window event, not part of this contract.
|
||||
pub fn frontend_wire_contract() -> serde_json::Value {
|
||||
let uid = String::from("sample");
|
||||
let events: Vec<_> = sample_frontend_events(&uid)
|
||||
.into_iter()
|
||||
.map(|(event, _, _)| {
|
||||
let (name, payload) = NotificationSystem::serialize_event(event);
|
||||
json!({ "name": name, "sample": payload.unwrap_or(serde_json::Value::Null) })
|
||||
})
|
||||
.collect();
|
||||
let statuses: Vec<_> = NoticeStatus::ALL
|
||||
.iter()
|
||||
.map(|status| serde_json::to_value(status).expect("NoticeStatus serializes"))
|
||||
.collect();
|
||||
json!({ "version": 1, "events": events, "noticeStatuses": statuses })
|
||||
}
|
||||
|
||||
@@ -25,6 +25,10 @@ use tauri_plugin_autostart::MacosLauncher;
|
||||
use tauri_plugin_deep_link::DeepLinkExt as _;
|
||||
|
||||
pub static APP_HANDLE: OnceCell<AppHandle> = OnceCell::new();
|
||||
|
||||
// Re-exported for src/bin/frontend-contract.rs; the wire contract stays owned
|
||||
// by core::notify.
|
||||
pub use crate::core::notify::frontend_wire_contract;
|
||||
/// Application initialization helper functions
|
||||
mod app_init {
|
||||
use super::*;
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// @generated by scripts/gen-contract.mjs from the backend wire contract. DO NOT EDIT.
|
||||
// Regen: pnpm gen:contract
|
||||
|
||||
import type { RunState } from './cmds'
|
||||
|
||||
/** Wire status strings from NoticeStatus; keep in sync via `pnpm gen:contract`. */
|
||||
export type NoticeStatus =
|
||||
| 'info'
|
||||
| 'import_sub_url::ok'
|
||||
| 'import_sub_url::error'
|
||||
| 'set_config::error'
|
||||
| 'core_start::error'
|
||||
| 'service_core::repair_required'
|
||||
| 'service_core::app_data_not_owned'
|
||||
| 'service_core::sidecar_fallback'
|
||||
| 'dns_override::auto_disabled'
|
||||
| 'enhance::discarded_keys'
|
||||
| 'tun_mode::auto_disabled'
|
||||
| 'tun_mode::auto_disable_failed'
|
||||
| 'app_restart::core_stop_failed'
|
||||
| 'app_quit::core_stop_failed'
|
||||
| 'update_with_clash_proxy'
|
||||
| 'update_failed_even_with_clash'
|
||||
| 'reactivate_profiles::error'
|
||||
| 'update_failed'
|
||||
| 'config_validate::boot_error'
|
||||
| 'config_validate::error'
|
||||
| 'config_validate::process_terminated'
|
||||
| 'config_validate::script_error'
|
||||
| 'config_validate::script_syntax_error'
|
||||
| 'config_validate::script_missing_main'
|
||||
| 'config_validate::file_not_found'
|
||||
| 'config_validate::yaml_syntax_error'
|
||||
| 'config_validate::yaml_read_error'
|
||||
| 'config_validate::yaml_mapping_error'
|
||||
| 'config_validate::merge_syntax_error'
|
||||
| 'config_validate::merge_mapping_error'
|
||||
| 'config_core::change_success'
|
||||
| 'config_core::change_error'
|
||||
| 'mixed_port::fallback'
|
||||
| 'mixed_port::fallback_error'
|
||||
| 'config_validate::core_change'
|
||||
| 'config_validate::stdout_error'
|
||||
| 'config_validate::yaml_key_error'
|
||||
| 'config_validate::yaml_error'
|
||||
| 'config_validate::merge_key_error'
|
||||
| 'config_validate::merge_error'
|
||||
| 'set_config::ok'
|
||||
|
||||
/** Event name → payload type for every backend-emitted event. */
|
||||
export interface VergeEventPayloads {
|
||||
'verge://refresh-clash-config': string
|
||||
'verge://refresh-verge-config': string
|
||||
'verge://refresh-profiles': string
|
||||
'verge://refresh-proxy-config': null
|
||||
'verge://notice-message': [NoticeStatus, string]
|
||||
'verge://timer-updated': string
|
||||
'verge://run-state-changed': RunState
|
||||
'verge://pending-failures-changed': null
|
||||
'profile-changed': string
|
||||
'profile-update-started': { uid: string }
|
||||
'profile-update-completed': { uid: string }
|
||||
}
|
||||
+3
-15
@@ -1,21 +1,9 @@
|
||||
import { listen, type UnlistenFn } from '@tauri-apps/api/event'
|
||||
|
||||
import type { RunState } from './cmds'
|
||||
import type { VergeEventPayloads } from './contract'
|
||||
|
||||
/** Centralizes frontend event names and payload types for the unchecked Rust IPC seam. */
|
||||
interface VergeEvents {
|
||||
'verge://refresh-clash-config': string
|
||||
'verge://refresh-verge-config': string
|
||||
'verge://refresh-profiles': string
|
||||
'verge://refresh-proxy-config': null
|
||||
/** A backend message for the user: `[status, message]`. */
|
||||
'verge://notice-message': [string, string]
|
||||
'verge://timer-updated': string
|
||||
'verge://run-state-changed': RunState
|
||||
'verge://pending-failures-changed': null
|
||||
'profile-changed': string
|
||||
'profile-update-started': { uid?: string }
|
||||
'profile-update-completed': { uid?: string }
|
||||
/** Generated wire contract plus the frontend-internal test-all event. */
|
||||
interface VergeEvents extends VergeEventPayloads {
|
||||
'verge://test-all': null
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user