mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 05:25:40 +09:00
195 lines
6.6 KiB
TypeScript
195 lines
6.6 KiB
TypeScript
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
import {CANARY_RELEASE_CHANNEL_NAME, PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants';
|
|
import type {UpdaterDownloadFormat, UpdaterDownloadOption} from '@app/features/platform/types/Electron';
|
|
|
|
type LinuxManualDownloadFormat = Extract<UpdaterDownloadFormat, 'appimage' | 'deb' | 'rpm' | 'tar_gz'>;
|
|
type LinuxDownloadArch = 'x64' | 'arm64';
|
|
type DesktopDownloadChannel = 'stable' | 'canary';
|
|
|
|
const LINUX_MANUAL_DOWNLOAD_FORMATS: ReadonlyArray<LinuxManualDownloadFormat> = ['appimage', 'deb', 'rpm', 'tar_gz'];
|
|
|
|
const LINUX_MANUAL_FORMAT_LABELS: Record<LinuxManualDownloadFormat, string> = {
|
|
appimage: 'AppImage',
|
|
deb: 'DEB package',
|
|
rpm: 'RPM package',
|
|
tar_gz: 'tar.gz archive',
|
|
};
|
|
|
|
const LINUX_MANUAL_FORMAT_EXTENSIONS: Record<LinuxManualDownloadFormat, string> = {
|
|
appimage: '.AppImage',
|
|
deb: '.deb',
|
|
rpm: '.rpm',
|
|
tar_gz: '.tar.gz',
|
|
};
|
|
|
|
const LINUX_MANUAL_ARCH_TOKENS: Record<LinuxManualDownloadFormat, Record<LinuxDownloadArch, string>> = {
|
|
appimage: {x64: 'x86_64', arm64: 'arm64'},
|
|
deb: {x64: 'amd64', arm64: 'arm64'},
|
|
rpm: {x64: 'x86_64', arm64: 'aarch64'},
|
|
tar_gz: {x64: 'x64', arm64: 'arm64'},
|
|
};
|
|
|
|
const DEFAULT_API_ENDPOINTS: Record<DesktopDownloadChannel, string> = {
|
|
stable: 'https://api.fluxer.app',
|
|
canary: 'https://api.canary.fluxer.app',
|
|
};
|
|
|
|
interface ParsedLinuxDownloadUrl {
|
|
apiEndpoint: string;
|
|
channel: DesktopDownloadChannel;
|
|
arch: LinuxDownloadArch;
|
|
search: string;
|
|
}
|
|
|
|
export interface LinuxManualUpdateOptionsInput {
|
|
downloadUrl?: string | null;
|
|
channel?: string | null;
|
|
arch?: string | null;
|
|
version?: string | null;
|
|
apiEndpoint?: string | null;
|
|
knownOptions?: ReadonlyArray<UpdaterDownloadOption>;
|
|
}
|
|
|
|
function normalizeLinuxDownloadArch(value: string | null | undefined): LinuxDownloadArch {
|
|
const normalized = value?.trim().toLowerCase();
|
|
if (normalized === 'arm64' || normalized === 'aarch64') {
|
|
return 'arm64';
|
|
}
|
|
return 'x64';
|
|
}
|
|
|
|
function normalizeLinuxDownloadArchOrNull(value: string | null | undefined): LinuxDownloadArch | null {
|
|
const normalized = value?.trim();
|
|
if (!normalized) {
|
|
return null;
|
|
}
|
|
return normalizeLinuxDownloadArch(normalized);
|
|
}
|
|
|
|
function normalizeDesktopDownloadChannel(value: string | null | undefined): DesktopDownloadChannel {
|
|
return value?.trim().toLowerCase() === 'canary' ? 'canary' : 'stable';
|
|
}
|
|
|
|
function normalizeApiEndpoint(value: string | null | undefined, channel: DesktopDownloadChannel): string {
|
|
const trimmed = value?.trim();
|
|
if (trimmed) {
|
|
return trimmed.replace(/\/+$/u, '');
|
|
}
|
|
return DEFAULT_API_ENDPOINTS[channel];
|
|
}
|
|
|
|
function parseLinuxDownloadUrl(value: string | null | undefined): ParsedLinuxDownloadUrl | null {
|
|
if (!value) {
|
|
return null;
|
|
}
|
|
try {
|
|
const parsed = new URL(value);
|
|
const segments = parsed.pathname.split('/').filter(Boolean);
|
|
const dlIndex = segments.findIndex((segment, index) => segment === 'dl' && segments[index + 1] === 'desktop');
|
|
if (dlIndex < 0) {
|
|
return null;
|
|
}
|
|
const channel = normalizeDesktopDownloadChannel(segments[dlIndex + 2]);
|
|
const platform = segments[dlIndex + 3];
|
|
const arch = normalizeLinuxDownloadArch(segments[dlIndex + 4]);
|
|
const version = segments[dlIndex + 5];
|
|
const format = segments[dlIndex + 6];
|
|
if (platform !== 'linux' || !version || !format) {
|
|
return null;
|
|
}
|
|
const endpointSegments = segments.slice(0, dlIndex);
|
|
const endpointPath = endpointSegments.length > 0 ? `/${endpointSegments.join('/')}` : '';
|
|
return {
|
|
apiEndpoint: `${parsed.origin}${endpointPath}`,
|
|
channel,
|
|
arch,
|
|
search: parsed.search,
|
|
};
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function getLinuxDownloadArchFromText(value: string | null | undefined): LinuxDownloadArch | null {
|
|
const normalized = value?.trim().toLowerCase();
|
|
if (!normalized) {
|
|
return null;
|
|
}
|
|
if (/(^|[-_/(\s])(?:arm64|aarch64)(?=$|[-_/).\s])/u.test(normalized)) {
|
|
return 'arm64';
|
|
}
|
|
if (/(^|[-_/(\s])(?:x64|x86_64|amd64)(?=$|[-_/).\s])/u.test(normalized)) {
|
|
return 'x64';
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function getLinuxDownloadOptionArch(option: UpdaterDownloadOption): LinuxDownloadArch | null {
|
|
return (
|
|
parseLinuxDownloadUrl(option.url)?.arch ??
|
|
getLinuxDownloadArchFromText(option.suggestedName) ??
|
|
getLinuxDownloadArchFromText(option.label)
|
|
);
|
|
}
|
|
|
|
function getKnownLinuxManualOption(
|
|
options: ReadonlyArray<UpdaterDownloadOption>,
|
|
format: LinuxManualDownloadFormat,
|
|
arch: LinuxDownloadArch,
|
|
): UpdaterDownloadOption | null {
|
|
const formatOptions = options.filter((option) => option.format === format);
|
|
return (
|
|
formatOptions.find((option) => getLinuxDownloadOptionArch(option) === arch) ??
|
|
(formatOptions.length === 1 && getLinuxDownloadOptionArch(formatOptions[0]) === null ? formatOptions[0] : null)
|
|
);
|
|
}
|
|
|
|
function buildLinuxDownloadUrl(params: {
|
|
apiEndpoint: string;
|
|
channel: DesktopDownloadChannel;
|
|
arch: LinuxDownloadArch;
|
|
versionToken: string;
|
|
format: LinuxManualDownloadFormat;
|
|
search: string;
|
|
}): string {
|
|
return `${params.apiEndpoint}/dl/desktop/${params.channel}/linux/${params.arch}/${params.versionToken}/${params.format}${params.search}`;
|
|
}
|
|
|
|
function getModernProductName(channel: DesktopDownloadChannel): string {
|
|
return channel === 'canary' ? CANARY_RELEASE_CHANNEL_NAME : PRODUCT_NAME;
|
|
}
|
|
|
|
function getSuggestedName(
|
|
format: LinuxManualDownloadFormat,
|
|
channel: DesktopDownloadChannel,
|
|
arch: LinuxDownloadArch,
|
|
versionToken: string,
|
|
): string {
|
|
const archToken = LINUX_MANUAL_ARCH_TOKENS[format][arch];
|
|
const extension = LINUX_MANUAL_FORMAT_EXTENSIONS[format];
|
|
return `${getModernProductName(channel)}-${versionToken}-linux-${archToken}${extension}`;
|
|
}
|
|
|
|
export function buildLinuxManualUpdateOptions(input: LinuxManualUpdateOptionsInput): Array<UpdaterDownloadOption> {
|
|
const parsedUrl = parseLinuxDownloadUrl(input.downloadUrl);
|
|
const channel = parsedUrl?.channel ?? normalizeDesktopDownloadChannel(input.channel);
|
|
const arch = normalizeLinuxDownloadArchOrNull(input.arch) ?? parsedUrl?.arch ?? 'x64';
|
|
const apiEndpoint = parsedUrl?.apiEndpoint ?? normalizeApiEndpoint(input.apiEndpoint, channel);
|
|
const search = parsedUrl?.search ?? '';
|
|
const version = input.version?.trim() ?? '';
|
|
const hasVersion = /^\d+\.\d+\.\d+$/u.test(version);
|
|
const versionToken = hasVersion ? version : 'latest';
|
|
const knownOptions = hasVersion ? (input.knownOptions ?? []) : [];
|
|
return LINUX_MANUAL_DOWNLOAD_FORMATS.map((format) => {
|
|
const knownOption = getKnownLinuxManualOption(knownOptions, format, arch);
|
|
return {
|
|
format,
|
|
label: LINUX_MANUAL_FORMAT_LABELS[format],
|
|
url: buildLinuxDownloadUrl({apiEndpoint, channel, arch, versionToken, format, search}),
|
|
suggestedName: knownOption?.suggestedName ?? getSuggestedName(format, channel, arch, versionToken),
|
|
sha256: knownOption?.sha256 ?? null,
|
|
};
|
|
});
|
|
}
|