[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
223
infrastructure/services/updateService.ts
Normal file
223
infrastructure/services/updateService.ts
Normal file
@@ -0,0 +1,223 @@
|
||||
/**
|
||||
* Update Service
|
||||
*
|
||||
* Combines two update mechanisms:
|
||||
* 1. GitHub API-based version comparison (used by useUpdateCheck for notification banner)
|
||||
* 2. electron-updater bridge (used by SettingsSystemTab for download/install)
|
||||
*/
|
||||
|
||||
import { netcattyBridge } from "./netcattyBridge";
|
||||
|
||||
// ================================
|
||||
// Part 1: GitHub API Version Check
|
||||
// ================================
|
||||
|
||||
const GITHUB_API_URL = 'https://api.github.com/repos/binaricat/Netcatty/releases/latest';
|
||||
const RELEASES_PAGE_URL = 'https://github.com/binaricat/Netcatty/releases';
|
||||
|
||||
export interface ReleaseInfo {
|
||||
version: string; // e.g. "1.0.0" (without 'v' prefix)
|
||||
tagName: string; // e.g. "v1.0.0"
|
||||
name: string; // Release title
|
||||
body: string; // Release notes (markdown)
|
||||
htmlUrl: string; // URL to the release page
|
||||
publishedAt: string; // ISO date string
|
||||
assets: ReleaseAsset[];
|
||||
}
|
||||
|
||||
export interface ReleaseAsset {
|
||||
name: string;
|
||||
browserDownloadUrl: string;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface UpdateCheckResult {
|
||||
hasUpdate: boolean;
|
||||
currentVersion: string;
|
||||
latestRelease: ReleaseInfo | null;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse version string to comparable array
|
||||
* e.g. "1.2.3" -> [1, 2, 3]
|
||||
*/
|
||||
function parseVersion(version: string): number[] {
|
||||
// Remove 'v' prefix if present
|
||||
const clean = version.replace(/^v/i, '');
|
||||
return clean.split('.').map((part) => {
|
||||
const num = parseInt(part, 10);
|
||||
return isNaN(num) ? 0 : num;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two version strings
|
||||
* Returns: 1 if a > b, -1 if a < b, 0 if equal
|
||||
*/
|
||||
export function compareVersions(a: string, b: string): number {
|
||||
const partsA = parseVersion(a);
|
||||
const partsB = parseVersion(b);
|
||||
const maxLen = Math.max(partsA.length, partsB.length);
|
||||
|
||||
for (let i = 0; i < maxLen; i++) {
|
||||
const numA = partsA[i] ?? 0;
|
||||
const numB = partsB[i] ?? 0;
|
||||
if (numA > numB) return 1;
|
||||
if (numA < numB) return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for updates via GitHub API (compares version strings).
|
||||
* Used by useUpdateCheck for the notification banner.
|
||||
*/
|
||||
export async function checkForUpdates(currentVersion: string): Promise<UpdateCheckResult> {
|
||||
try {
|
||||
const response = await fetch(GITHUB_API_URL, {
|
||||
headers: { Accept: 'application/vnd.github.v3+json' },
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`GitHub API returned ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const latestVersion = (data.tag_name as string).replace(/^v/i, '');
|
||||
|
||||
const latestRelease: ReleaseInfo = {
|
||||
version: latestVersion,
|
||||
tagName: data.tag_name,
|
||||
name: data.name || data.tag_name,
|
||||
body: data.body || '',
|
||||
htmlUrl: data.html_url,
|
||||
publishedAt: data.published_at,
|
||||
assets: (data.assets || []).map((a: { name: string; browser_download_url: string; size: number }) => ({
|
||||
name: a.name,
|
||||
browserDownloadUrl: a.browser_download_url,
|
||||
size: a.size,
|
||||
})),
|
||||
};
|
||||
|
||||
const hasUpdate = compareVersions(latestVersion, currentVersion) > 0;
|
||||
|
||||
return { hasUpdate, currentVersion, latestRelease };
|
||||
} catch (error) {
|
||||
return {
|
||||
hasUpdate: false,
|
||||
currentVersion,
|
||||
latestRelease: null,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get release page URL for a specific version
|
||||
*/
|
||||
export function getReleaseUrl(version?: string): string {
|
||||
if (version) {
|
||||
return `${RELEASES_PAGE_URL}/tag/v${version.replace(/^v/i, '')}`;
|
||||
}
|
||||
return RELEASES_PAGE_URL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get download URL for current platform
|
||||
*/
|
||||
export function getDownloadUrlForPlatform(
|
||||
release: ReleaseInfo,
|
||||
platform: string
|
||||
): string | null {
|
||||
const assets = release.assets;
|
||||
|
||||
// Platform-specific file patterns
|
||||
const patterns: Record<string, RegExp[]> = {
|
||||
win32: [/\.exe$/i, /win.*\.zip$/i, /windows/i],
|
||||
darwin: [/\.dmg$/i, /mac.*\.zip$/i, /darwin/i],
|
||||
linux: [/\.AppImage$/i, /\.deb$/i, /linux/i],
|
||||
};
|
||||
|
||||
const platformPatterns = patterns[platform] || [];
|
||||
|
||||
for (const pattern of platformPatterns) {
|
||||
const asset = assets.find((a) => pattern.test(a.name));
|
||||
if (asset) {
|
||||
return asset.browserDownloadUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to release page
|
||||
return null;
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// Part 2: electron-updater Bridge (IPC-based)
|
||||
// =============================================
|
||||
|
||||
export interface ElectronUpdateCheckResult {
|
||||
available: boolean;
|
||||
supported?: boolean;
|
||||
version?: string;
|
||||
releaseNotes?: string;
|
||||
releaseDate?: string | null;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface UpdateDownloadProgress {
|
||||
percent: number;
|
||||
bytesPerSecond: number;
|
||||
transferred: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export async function checkForUpdate(): Promise<ElectronUpdateCheckResult> {
|
||||
const bridge = netcattyBridge.get();
|
||||
if (!bridge?.checkForUpdate) {
|
||||
return { available: false, supported: false, error: "Bridge unavailable" };
|
||||
}
|
||||
try {
|
||||
return await bridge.checkForUpdate();
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : "Unknown error";
|
||||
return { available: false, error: message };
|
||||
}
|
||||
}
|
||||
|
||||
export async function downloadUpdate(): Promise<{ success: boolean; error?: string }> {
|
||||
const bridge = netcattyBridge.get();
|
||||
if (!bridge?.downloadUpdate) {
|
||||
return { success: false, error: "Bridge unavailable" };
|
||||
}
|
||||
return bridge.downloadUpdate();
|
||||
}
|
||||
|
||||
export function installUpdate(): void {
|
||||
const bridge = netcattyBridge.get();
|
||||
bridge?.installUpdate?.();
|
||||
}
|
||||
|
||||
export function onDownloadProgress(
|
||||
cb: (progress: UpdateDownloadProgress) => void,
|
||||
): (() => void) | undefined {
|
||||
return netcattyBridge.get()?.onUpdateDownloadProgress?.(cb);
|
||||
}
|
||||
|
||||
export function onDownloaded(cb: () => void): (() => void) | undefined {
|
||||
return netcattyBridge.get()?.onUpdateDownloaded?.(cb);
|
||||
}
|
||||
|
||||
export function onError(
|
||||
cb: (payload: { error: string }) => void,
|
||||
): (() => void) | undefined {
|
||||
return netcattyBridge.get()?.onUpdateError?.(cb);
|
||||
}
|
||||
|
||||
/** Returns the GitHub Releases page URL, optionally for a specific version tag. */
|
||||
export function getReleasesUrl(version?: string): string {
|
||||
if (version) {
|
||||
return `${RELEASES_PAGE_URL}/tag/v${version}`;
|
||||
}
|
||||
return `${RELEASES_PAGE_URL}/latest`;
|
||||
}
|
||||
Reference in New Issue
Block a user