dirs.test.ts asserts on values produced by `path.join`, but compares
them against hardcoded POSIX literals such as
'/tmp/app-data/mihomo-party-dev'. On Windows `path.join` returns
'\tmp\app-data\mihomo-party-dev', so two of the three cases fail on a
clean checkout:
× isolates an unpackaged local development app
× keeps portable userData precedence over local development isolation
The fs mock had the same problem: it detected the portable marker with
`value.endsWith('/PORTABLE')`, which never matches the backslash path
that `path.join` produces on Windows.
dirs.ts itself is correct; only the test encodes a platform assumption.
Build the expected values with `path.join` and match the portable
marker with `path.basename`, so the suite is platform-agnostic.
Full suite now passes on Windows 11: 20 files, 176 tests.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix: treat HTTP errors as failures in the updater and fix the pkg install path
1. tryDownload returned the first response it got. chromeRequest only
rejects on transport errors, so an HTTP 404/502 from a mirror was
returned as success and the remaining mirrors were never tried.
checkUpdate then ran `parse()` over an HTML error page; the result has
no `version`, so compareVersions calls `.replace` on undefined and
throws a TypeError instead of reporting that the check failed. Check
the status code and throw so the fallback loop keeps going.
2. downloadAndInstallUpdate only cleared updateInstallPromise when
installUpdate rejected. The macOS .pkg branch catches an osascript
failure and falls back to `shell.openPath`, which resolves normally
and leaves the app running. From then on updateInstallPromise held a
settled promise, so every later attempt returned it immediately and
the update button did nothing. Clear it in `finally`.
3. The pkg command escaped spaces with `.replace(' ', '\\\\ ')`. A string
pattern replaces only the first occurrence, and the macOS data
directory ("~/Library/Application Support/...") contains more than
one space, so the installer command was still split. Quote the path
once and escape it for the AppleScript string literal, and invoke
osascript through execFile so the outer command is not shell-parsed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: validate latest.yml and skip the proxy when the mixed port is off
- checkUpdate parsed latest.yml and used the result without checking it.
A proxy error page can still parse as YAML (`404: Not Found` is a valid
mapping), so `latest.version` was undefined and compareVersions threw a
TypeError on `a.replace` instead of reporting a failed check.
- Both the update check and the download built the proxy from the mixed
port through a destructuring default, which only covers undefined.
Turning the port off stores 0, so every request went to 127.0.0.1:0 and
failed. Go direct when no port is enabled.
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Turning the traffic display off sent trayIconUpdate with enabled=false,
but the handler ignores that flag and always applies the supplied data
URL, so the tray kept showing the renderer-composed image until the app
was restarted. Ask the main process to redraw from its own resources
afterwards.
Refs #1080
This covers the "does not recover" half of the report. The colours being
wrong while the traffic display is on has the same root cause as #1143 -
the handler calls setTemplateImage(true) unconditionally, so macOS renders
alpha only - and needs a change in tray.ts.
The scheduled updater calls addProfileItem, which never sends
profileConfigUpdated. The subscription list revalidates on that event, so
the "updated N minutes ago" line and the traffic figures stayed stale
until the window regained focus - which reads exactly like the interval
not working. The plugin update path already notifies; only the remote one
did not.
Refs #1570
This addresses the stale UI half of the report. A scheduled update of the
profile that is currently active still writes the new YAML without
reloading it into the running core, because changeCurrentProfile returns
early when the id is unchanged; that part is left for a separate change.
Two independent robustness defects in mihomoApi.
1. In mihomoTraffic(), `JSON.parse(data)` sat outside the try block,
unlike the memory, logs and connections streams where it is inside.
The handler was also `async`, so a non-JSON frame from the core threw
inside a promise with no rejection handler, producing an
unhandledRejection instead of being ignored like the other three
streams. Move the parse inside the try and drop the unnecessary
`async`.
2. SysProxyStatus() read `appConfig.sysProxy.enable` with no optional
chaining, while the neighbouring TunStatus() already guards with
`config?.tun?.enable`. If the app config is missing or truncated,
sysProxy is undefined and the property access throws. That rejection
propagates through getTrayIconStatus(), so the tray icon stops
reflecting the real state. Guard it the same way TunStatus does.
Closes#1286
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>