mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-15 15:32:21 +08:00
Adds src/services/bus (the only listen/emit site: one handler per contract event plus the frontend-internal test-all event) and the React context/reducer app store. Migrates the five scattered subscriptions (layout events, pending failures, proxy refresh, profile update/timer, test-all), deletes the old glue (events.ts, use-listen, use-layout-events, LoadingCacheProvider), moves run state into the store, and adds an architecture gate script wired into CI. Behavior is unchanged; the run-state query's poll/focus safety net is preserved by the store provider.
261 lines
8.9 KiB
TypeScript
261 lines
8.9 KiB
TypeScript
import { getCurrentWindow } from '@tauri-apps/api/window'
|
|
|
|
import {
|
|
getCoreStartupError,
|
|
takeDiscardedKeysNotice,
|
|
takeDnsOverrideNotice,
|
|
takeServiceFallbackNotice,
|
|
takeServiceOwnerNotice,
|
|
takeServiceRepairNotice,
|
|
} from '@/services/cmds'
|
|
import { hideNotice, showNotice } from '@/services/notice-service'
|
|
import { requestService } from '@/services/service-request'
|
|
|
|
type NavigateFunction = (path: string, options?: any) => void
|
|
type TranslateFunction = (key: string) => string
|
|
|
|
let shownStartupError: string | null = null
|
|
// Reads can settle out of order; only the newest may act, and a reset counts as one.
|
|
let startupErrorReads = 0
|
|
let settledStartupErrorRead = 0
|
|
let shownOwnerNotice: number | null = null
|
|
|
|
/** A running core has resolved every failure shown before it. */
|
|
export const forgetShownStartupError = () => {
|
|
shownStartupError = null
|
|
settledStartupErrorRead = ++startupErrorReads
|
|
}
|
|
|
|
export const handleNoticeMessage = (
|
|
status: string,
|
|
msg: string,
|
|
t: TranslateFunction,
|
|
navigate: NavigateFunction,
|
|
) => {
|
|
const handlers: Record<string, () => void> = {
|
|
'import_sub_url::ok': () => {
|
|
// 空 msg 传入,我们不希望导致 后端-前端-后端 死循环,这里只做提醒。
|
|
// 未来细分事件通知时,可以考虑传入订阅 ID 或其他标识符
|
|
// navigate("/profile", { state: { current: msg } });
|
|
navigate('/profile')
|
|
showNotice.success(
|
|
'shared.feedback.notifications.importSubscriptionSuccess',
|
|
)
|
|
},
|
|
'import_sub_url::error': () => {
|
|
navigate('/profile')
|
|
showNotice.error(msg)
|
|
},
|
|
'set_config::error': () => showNotice.error(msg),
|
|
'core_start::error': () => {
|
|
const read = ++startupErrorReads
|
|
void getCoreStartupError()
|
|
.then(async (failure) => {
|
|
if (read < settledStartupErrorRead) return
|
|
settledStartupErrorRead = read
|
|
if (!failure) {
|
|
shownStartupError = null
|
|
return
|
|
}
|
|
const window = getCurrentWindow()
|
|
const [visible, minimized] = await Promise.all([
|
|
window.isVisible(),
|
|
window.isMinimized(),
|
|
])
|
|
const shown = `${failure.kind}:${failure.detail}`
|
|
if (
|
|
read === settledStartupErrorRead &&
|
|
visible &&
|
|
!minimized &&
|
|
shownStartupError !== shown
|
|
) {
|
|
shownStartupError = shown
|
|
showNotice.error(
|
|
failure.kind === 'serviceCoreStopped'
|
|
? 'settings.feedback.errors.clash.serviceCoreStopped'
|
|
: 'settings.feedback.errors.clash.startFailed',
|
|
failure.detail,
|
|
)
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error('Failed to read the pending core startup error', error)
|
|
})
|
|
},
|
|
'service_core::repair_required': () => {
|
|
void takeServiceRepairNotice()
|
|
.then((pending) => {
|
|
if (pending) {
|
|
requestService({ reason: 'serviceLocationRefused' })
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error(
|
|
'Failed to read the pending service repair notice',
|
|
error,
|
|
)
|
|
})
|
|
},
|
|
'service_core::app_data_not_owned': () => {
|
|
void takeServiceOwnerNotice()
|
|
.then((command) => {
|
|
if (command) {
|
|
if (shownOwnerNotice !== null) hideNotice(shownOwnerNotice)
|
|
shownOwnerNotice = showNotice.warning(
|
|
'settings.feedback.notifications.clashService.appDataNotOwned',
|
|
{ command },
|
|
0,
|
|
)
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error(
|
|
'Failed to read the pending service owner notice',
|
|
error,
|
|
)
|
|
})
|
|
},
|
|
'service_core::sidecar_fallback': () => {
|
|
void takeServiceFallbackNotice()
|
|
.then((pending) => {
|
|
if (pending) {
|
|
showNotice.warning(
|
|
'settings.feedback.notifications.clashService.sidecarFallback',
|
|
)
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error(
|
|
'Failed to read the pending service fallback notice',
|
|
error,
|
|
)
|
|
})
|
|
},
|
|
'dns_override::auto_disabled': () => {
|
|
void takeDnsOverrideNotice()
|
|
.then((pending) => {
|
|
if (pending) {
|
|
showNotice.info('settings.modals.dns.protection.autoDisabled')
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error('Failed to read the pending DNS override notice', error)
|
|
})
|
|
},
|
|
'enhance::discarded_keys': () => {
|
|
void takeDiscardedKeysNotice()
|
|
.then((keys) => {
|
|
if (keys) {
|
|
showNotice.warning('profiles.page.feedback.notices.discardedKeys', {
|
|
keys,
|
|
})
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error(
|
|
'Failed to read the pending discarded keys notice',
|
|
error,
|
|
)
|
|
})
|
|
},
|
|
'tun_mode::auto_disabled': () =>
|
|
showNotice.info(
|
|
'settings.sections.system.notifications.tunMode.autoDisabled',
|
|
),
|
|
'tun_mode::auto_disable_failed': () =>
|
|
showNotice.error(
|
|
'settings.sections.system.notifications.tunMode.autoDisableFailed',
|
|
),
|
|
'app_restart::core_stop_failed': () =>
|
|
showNotice.error(
|
|
'layout.feedback.errors.restartCoreStopFailed',
|
|
msg || undefined,
|
|
),
|
|
'app_quit::core_stop_failed': () =>
|
|
showNotice.error(
|
|
'layout.feedback.errors.quitCoreStopFailed',
|
|
msg || undefined,
|
|
),
|
|
update_with_clash_proxy: () =>
|
|
showNotice.success(
|
|
'settings.feedback.notifications.updater.withClashProxySuccess',
|
|
msg,
|
|
),
|
|
update_failed_even_with_clash: () =>
|
|
showNotice.error(
|
|
'settings.feedback.notifications.updater.withClashProxyFailed',
|
|
msg,
|
|
),
|
|
'reactivate_profiles::error': () => showNotice.error(msg),
|
|
update_failed: () => showNotice.error(msg),
|
|
'config_validate::boot_error': () =>
|
|
showNotice.error('shared.feedback.validation.config.bootFailed', msg),
|
|
'config_validate::core_change': () =>
|
|
showNotice.error(
|
|
'shared.feedback.validation.config.coreChangeFailed',
|
|
msg,
|
|
),
|
|
'config_validate::error': () =>
|
|
showNotice.error('shared.feedback.validation.config.failed', msg),
|
|
'config_validate::process_terminated': () =>
|
|
showNotice.error('shared.feedback.validation.config.processTerminated'),
|
|
'config_validate::stdout_error': () =>
|
|
showNotice.error('shared.feedback.validation.config.failed', msg),
|
|
'config_validate::script_error': () =>
|
|
showNotice.error('shared.feedback.validation.script.fileError', msg),
|
|
'config_validate::script_syntax_error': () =>
|
|
showNotice.error('shared.feedback.validation.script.syntaxError', msg),
|
|
'config_validate::script_missing_main': () =>
|
|
showNotice.error('shared.feedback.validation.script.missingMain', msg),
|
|
'config_validate::file_not_found': () =>
|
|
showNotice.error('shared.feedback.validation.script.fileNotFound', msg),
|
|
'config_validate::yaml_syntax_error': () =>
|
|
showNotice.error('shared.feedback.validation.yaml.syntaxError', msg),
|
|
'config_validate::yaml_read_error': () =>
|
|
showNotice.error('shared.feedback.validation.yaml.readError', msg),
|
|
'config_validate::yaml_mapping_error': () =>
|
|
showNotice.error('shared.feedback.validation.yaml.mappingError', msg),
|
|
'config_validate::yaml_key_error': () =>
|
|
showNotice.error('shared.feedback.validation.yaml.keyError', msg),
|
|
'config_validate::yaml_error': () =>
|
|
showNotice.error('shared.feedback.validation.yaml.generalError', msg),
|
|
'config_validate::merge_syntax_error': () =>
|
|
showNotice.error('shared.feedback.validation.merge.syntaxError', msg),
|
|
'config_validate::merge_mapping_error': () =>
|
|
showNotice.error('shared.feedback.validation.merge.mappingError', msg),
|
|
'config_validate::merge_key_error': () =>
|
|
showNotice.error('shared.feedback.validation.merge.keyError', msg),
|
|
'config_validate::merge_error': () =>
|
|
showNotice.error('shared.feedback.validation.merge.generalError', msg),
|
|
'config_core::change_success': () =>
|
|
showNotice.success(
|
|
'settings.feedback.notifications.clash.changeSuccess',
|
|
msg,
|
|
),
|
|
'config_core::change_error': () =>
|
|
showNotice.error(
|
|
'settings.feedback.notifications.clash.changeFailed',
|
|
msg,
|
|
),
|
|
'mixed_port::fallback': () => {
|
|
const [oldPort, newPort] = msg.split(',')
|
|
showNotice.info('settings.modals.clashPort.messages.automaticFallback', {
|
|
oldPort,
|
|
newPort,
|
|
})
|
|
},
|
|
'mixed_port::fallback_error': () =>
|
|
showNotice.error(
|
|
'settings.modals.clashPort.messages.automaticFallbackFailed',
|
|
{ error: msg },
|
|
),
|
|
}
|
|
|
|
const handler = handlers[status]
|
|
if (handler) {
|
|
handler()
|
|
} else {
|
|
console.warn(`未处理的通知状态: ${status}`)
|
|
}
|
|
}
|