diff --git a/fluxer_desktop/src/main/GscordRelease.ts b/fluxer_desktop/src/main/GscordRelease.ts new file mode 100644 index 0000000..54e6593 --- /dev/null +++ b/fluxer_desktop/src/main/GscordRelease.ts @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import type {ManualDesktopFormat} from '@electron/main/ShellDownloadFormats'; +import type {ManualLatestInfo} from '@electron/main/UpdaterDownloads'; + +function isRecord(value: unknown): value is Record { + return value !== null && typeof value === 'object' && !Array.isArray(value); +} + +export function parseGscordRelease( + payload: unknown, + platform: NodeJS.Platform = process.platform, + arch: NodeJS.Architecture = process.arch, +): ManualLatestInfo { + if (!isRecord(payload) || typeof payload.tag_name !== 'string' || !payload.tag_name.trim()) { + throw new Error('GSCORD latest release response missing tag_name'); + } + const version = payload.tag_name.trim().replace(/^v/, ''); + if (!version) throw new Error('GSCORD latest release response missing version'); + const files: ManualLatestInfo['files'] = {}; + const arm = arch === 'arm64'; + const patterns: Array<[ManualDesktopFormat, RegExp]> = platform === 'linux' + ? [ + ['appimage', arm ? /-linux-arm64\.AppImage$/i : /-linux-x86_64\.AppImage$/i], + ['deb', arm ? /-linux-arm64\.deb$/i : /-linux-amd64\.deb$/i], + ['rpm', arm ? /-linux-aarch64\.rpm$/i : /-linux-x86_64\.rpm$/i], + ['tar_gz', arm ? /-linux-arm64\.tar\.gz$/i : /-linux-x64\.tar\.gz$/i], + ] + : platform === 'win32' + ? [['setup', arm ? /-win-arm64\.exe$/i : /-win-x64\.exe$/i]] + : []; + for (const asset of Array.isArray(payload.assets) ? payload.assets : []) { + if (!isRecord(asset) || typeof asset.name !== 'string' || + typeof asset.browser_download_url !== 'string' || !asset.browser_download_url.trim()) continue; + const format = patterns.find(([, pattern]) => pattern.test(asset.name as string))?.[0]; + if (format) files[format] = {url: asset.browser_download_url, sha256: null}; + } + return { + version, + pubDate: typeof payload.published_at === 'string' ? payload.published_at + : typeof payload.created_at === 'string' ? payload.created_at : null, + files, + }; +} diff --git a/fluxer_desktop/src/main/ShellUpdateCapability.ts b/fluxer_desktop/src/main/ShellUpdateCapability.ts index ce31659..465e2e4 100644 --- a/fluxer_desktop/src/main/ShellUpdateCapability.ts +++ b/fluxer_desktop/src/main/ShellUpdateCapability.ts @@ -40,18 +40,13 @@ export function decideShellUpdatePlan(runtime: ShellUpdateRuntime): ShellUpdateP if (runtime.flatpak) { return {capability: ShellUpdateCapability.MANAGED_PACKAGE}; } - if (runtime.portable) { + // GSCORD publishes portable EXE/AppImage/DEB assets, not upstream updater feeds. + if (runtime.portable || runtime.platform === 'win32' || runtime.platform === 'linux') { return {capability: ShellUpdateCapability.MANUAL_DOWNLOAD, reason: 'platform'}; } - if (runtime.platform === 'win32') { - return {capability: ShellUpdateCapability.SELF_UPDATE, updater: 'velopack'}; - } if (runtime.platform === 'darwin') { return {capability: ShellUpdateCapability.SELF_UPDATE, updater: 'electron'}; } - if (runtime.platform === 'linux' && runtime.appImage != null) { - return {capability: ShellUpdateCapability.SELF_UPDATE, updater: 'appimage', target: runtime.appImage}; - } return {capability: ShellUpdateCapability.MANUAL_DOWNLOAD, reason: 'platform'}; } diff --git a/fluxer_desktop/src/main/Updater.ts b/fluxer_desktop/src/main/Updater.ts index 4403ed7..dc0420b 100644 --- a/fluxer_desktop/src/main/Updater.ts +++ b/fluxer_desktop/src/main/Updater.ts @@ -1,13 +1,12 @@ // SPDX-License-Identifier: AGPL-3.0-or-later import {checkDesktopUpdateNow} from '@electron/main/DesktopUpdateGate'; -import {MANUAL_DESKTOP_FORMATS, type ManualDesktopFormat} from '@electron/main/ShellDownloadFormats'; +import {parseGscordRelease} from '@electron/main/GscordRelease'; import {resolveShellUpdatePlan, ShellUpdateCapability} from '@electron/main/ShellUpdateCapability'; import { DOWNLOAD_PAGE_URL, getManualDownloadOptions, getManualDownloadUrl, - type ManualLatestFile, type ManualLatestInfo, UPDATE_BASE_URL, type UpdaterDownloadOption, @@ -48,9 +47,6 @@ function send(win: BrowserWindow | null, event: UpdaterEvent) { win?.webContents.send('updater-event', event); } -function isRecord(value: unknown): value is Record { - return value !== null && typeof value === 'object' && !Array.isArray(value); -} function getErrorMessage(error: unknown): string { if (error instanceof Error) { @@ -89,23 +85,6 @@ function compareVersions(a: string, b: string): number { return aPre < bPre ? -1 : 1; } -function parseManualLatestFiles(value: unknown): Partial> { - if (!isRecord(value)) { - return {}; - } - const files: Partial> = {}; - for (const format of MANUAL_DESKTOP_FORMATS) { - const entry = value[format]; - if (!isRecord(entry) || typeof entry.url !== 'string' || entry.url.trim().length === 0) { - continue; - } - files[format] = { - url: entry.url, - sha256: typeof entry.sha256 === 'string' ? entry.sha256 : null, - }; - } - return files; -} async function fetchManualLatest(options: {forceRefresh?: boolean} = {}): Promise { const now = Date.now(); @@ -123,15 +102,7 @@ async function fetchManualLatest(options: {forceRefresh?: boolean} = {}): Promis if (!response.ok) { throw new Error(`Latest version request failed: ${response.status}`); } - const payload = (await response.json()) as {version?: unknown; pub_date?: unknown; files?: unknown}; - if (typeof payload.version !== 'string' || payload.version.length === 0) { - throw new Error('Latest version response missing version string'); - } - const info: ManualLatestInfo = { - version: payload.version, - pubDate: typeof payload.pub_date === 'string' ? payload.pub_date : null, - files: parseManualLatestFiles(payload.files), - }; + const info = parseGscordRelease(await response.json()); manualLatestCache = {at: now, info}; return info; } diff --git a/fluxer_desktop/src/main/UpdaterDownloads.ts b/fluxer_desktop/src/main/UpdaterDownloads.ts index f63685b..c82ca74 100644 --- a/fluxer_desktop/src/main/UpdaterDownloads.ts +++ b/fluxer_desktop/src/main/UpdaterDownloads.ts @@ -1,11 +1,8 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -import {BUILD_CHANNEL} from '@electron/common/BuildChannel'; -import {DOWNLOAD_PAGE_URLS} from '@electron/common/Constants'; import {DESKTOP_ARTIFACT_PRODUCT_NAME} from '@electron/common/DesktopIdentity'; import { DESKTOP_DOWNLOAD_ARCH, - getUpdateBaseUrl, isLinuxManualDesktopFormat, LINUX_MANUAL_ARCH_TOKENS, LINUX_MANUAL_FORMAT_EXTENSIONS, @@ -21,8 +18,8 @@ export type UpdaterDownloadOption = { sha256?: string | null; }; -export const UPDATE_BASE_URL = getUpdateBaseUrl(); -export const DOWNLOAD_PAGE_URL = DOWNLOAD_PAGE_URLS[BUILD_CHANNEL]; +export const DOWNLOAD_PAGE_URL = 'https://git.gandc.ru/gscord/gscord-desktop/releases'; +export const UPDATE_BASE_URL = 'https://git.gandc.ru/api/v1/repos/gscord/gscord-desktop/releases'; export type ManualLatestFile = {url: string; sha256: string | null}; @@ -53,7 +50,9 @@ const LINUX_MANUAL_FORMAT_LABELS: Record = { }; export function buildManualVersionDownloadUrl(version: string, format: ManualDesktopFormat): string { - return `${UPDATE_BASE_URL}/${version}/${format}`; + void version; + void format; + return DOWNLOAD_PAGE_URL; } function getArtifactProductName(): string { @@ -72,12 +71,13 @@ export function getManualDownloadOptions(info: ManualLatestInfo): Array info.files[format] != null) .map((format) => { const file = info.files[format]; return { format, label: LINUX_MANUAL_FORMAT_LABELS[format], - url: buildManualVersionDownloadUrl(info.version, format), + url: file?.url ?? buildManualVersionDownloadUrl(info.version, format), suggestedName: getManualUpdateSuggestedName(format, info.version), sha256: file?.sha256 ?? null, };