mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat(app-update): add in-app update flow with native e2e coverage
This commit is contained in:
@@ -0,0 +1,284 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const testState = vi.hoisted(() => ({
|
||||
androidDownloadAndInstallUpdateMock: vi.fn(),
|
||||
capacitorPlatform: 'web',
|
||||
electronDownloadAndInstallUpdateMock: vi.fn(),
|
||||
electronGetPlatformMock: vi.fn(),
|
||||
fetchMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@capacitor/core', () => ({
|
||||
Capacitor: {
|
||||
getPlatform: () => testState.capacitorPlatform,
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../plugins/app-updater', () => ({
|
||||
default: {
|
||||
downloadAndInstallUpdate: (options: unknown) => testState.androidDownloadAndInstallUpdateMock(options),
|
||||
},
|
||||
}));
|
||||
|
||||
const createFetchResponse = (body: unknown, ok = true, status = 200) => ({
|
||||
ok,
|
||||
status,
|
||||
json: vi.fn().mockResolvedValue(body),
|
||||
});
|
||||
|
||||
const originalLocation = window.location;
|
||||
const originalElectronApi = window.electronApi;
|
||||
const originalServiceWorker = navigator.serviceWorker;
|
||||
|
||||
const loadModule = async () => {
|
||||
vi.resetModules();
|
||||
return import('../app-update');
|
||||
};
|
||||
|
||||
describe('app-update', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
testState.androidDownloadAndInstallUpdateMock.mockReset();
|
||||
testState.capacitorPlatform = 'web';
|
||||
testState.electronDownloadAndInstallUpdateMock.mockReset();
|
||||
testState.electronGetPlatformMock.mockReset();
|
||||
testState.fetchMock.mockReset();
|
||||
vi.stubGlobal('fetch', testState.fetchMock);
|
||||
window.electronApi = undefined;
|
||||
Object.defineProperty(navigator, 'serviceWorker', {
|
||||
configurable: true,
|
||||
value: {
|
||||
getRegistration: vi.fn().mockResolvedValue({
|
||||
update: vi.fn().mockResolvedValue(undefined),
|
||||
}),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
vi.unstubAllEnvs();
|
||||
window.electronApi = originalElectronApi;
|
||||
Object.defineProperty(window, 'location', {
|
||||
configurable: true,
|
||||
value: originalLocation,
|
||||
});
|
||||
Object.defineProperty(navigator, 'serviceWorker', {
|
||||
configurable: true,
|
||||
value: originalServiceWorker,
|
||||
});
|
||||
});
|
||||
|
||||
it('resolves a web update when version metadata is newer', async () => {
|
||||
testState.fetchMock.mockResolvedValueOnce(createFetchResponse({ version: '9.9.9' }));
|
||||
|
||||
const { resolveAvailableAppUpdate } = await loadModule();
|
||||
const result = await resolveAvailableAppUpdate();
|
||||
|
||||
expect(result).toEqual({
|
||||
runtime: 'web',
|
||||
targetVersion: '9.9.9',
|
||||
releaseUrl: 'https://github.com/bitsocialnet/5chan/releases/tag/v9.9.9',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns no web update when the installed version is already current', async () => {
|
||||
testState.fetchMock.mockResolvedValueOnce(createFetchResponse({ version: '0.7.2' }));
|
||||
|
||||
const { resolveAvailableAppUpdate } = await loadModule();
|
||||
const result = await resolveAvailableAppUpdate();
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('selects the matching electron release asset for the current desktop platform', async () => {
|
||||
window.electronApi = {
|
||||
isElectron: true,
|
||||
getPlatform: () => testState.electronGetPlatformMock(),
|
||||
downloadAndInstallUpdate: (options) => testState.electronDownloadAndInstallUpdateMock(options),
|
||||
copyToClipboard: vi.fn(),
|
||||
automateUploadMedia: vi.fn(),
|
||||
} as Window['electronApi'];
|
||||
testState.electronGetPlatformMock.mockResolvedValue({
|
||||
platform: 'linux',
|
||||
arch: 'x64',
|
||||
version: 'v20.0.0',
|
||||
});
|
||||
testState.fetchMock.mockResolvedValueOnce(
|
||||
createFetchResponse({
|
||||
tag_name: 'v9.9.9',
|
||||
assets: [
|
||||
{
|
||||
name: '5chan-9.9.9-arm64.AppImage',
|
||||
browser_download_url: 'https://github.com/bitsocialnet/5chan/releases/download/v9.9.9/5chan-9.9.9-arm64.AppImage',
|
||||
},
|
||||
{
|
||||
name: '5chan-9.9.9-x64.AppImage',
|
||||
browser_download_url: 'https://github.com/bitsocialnet/5chan/releases/download/v9.9.9/5chan-9.9.9-x64.AppImage',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
const { resolveAvailableAppUpdate } = await loadModule();
|
||||
const result = await resolveAvailableAppUpdate();
|
||||
|
||||
expect(result).toEqual({
|
||||
runtime: 'electron',
|
||||
targetVersion: '9.9.9',
|
||||
assetName: '5chan-9.9.9-x64.AppImage',
|
||||
downloadUrl: 'https://github.com/bitsocialnet/5chan/releases/download/v9.9.9/5chan-9.9.9-x64.AppImage',
|
||||
releaseUrl: 'https://github.com/bitsocialnet/5chan/releases/tag/v9.9.9',
|
||||
});
|
||||
});
|
||||
|
||||
it('prefers the matching mac zip asset for packaged electron updates', async () => {
|
||||
window.electronApi = {
|
||||
isElectron: true,
|
||||
getPlatform: () => testState.electronGetPlatformMock(),
|
||||
downloadAndInstallUpdate: (options) => testState.electronDownloadAndInstallUpdateMock(options),
|
||||
copyToClipboard: vi.fn(),
|
||||
automateUploadMedia: vi.fn(),
|
||||
} as Window['electronApi'];
|
||||
testState.electronGetPlatformMock.mockResolvedValue({
|
||||
platform: 'darwin',
|
||||
arch: 'arm64',
|
||||
version: 'v20.0.0',
|
||||
});
|
||||
testState.fetchMock.mockResolvedValueOnce(
|
||||
createFetchResponse({
|
||||
tag_name: 'v9.9.9',
|
||||
assets: [
|
||||
{
|
||||
name: '5chan-9.9.9-arm64.zip',
|
||||
browser_download_url: 'https://github.com/bitsocialnet/5chan/releases/download/v9.9.9/5chan-9.9.9-arm64.zip',
|
||||
},
|
||||
{
|
||||
name: '5chan-9.9.9-arm64.dmg',
|
||||
browser_download_url: 'https://github.com/bitsocialnet/5chan/releases/download/v9.9.9/5chan-9.9.9-arm64.dmg',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
const { resolveAvailableAppUpdate } = await loadModule();
|
||||
const result = await resolveAvailableAppUpdate();
|
||||
|
||||
expect(result).toEqual({
|
||||
runtime: 'electron',
|
||||
targetVersion: '9.9.9',
|
||||
assetName: '5chan-9.9.9-arm64.zip',
|
||||
downloadUrl: 'https://github.com/bitsocialnet/5chan/releases/download/v9.9.9/5chan-9.9.9-arm64.zip',
|
||||
releaseUrl: 'https://github.com/bitsocialnet/5chan/releases/tag/v9.9.9',
|
||||
});
|
||||
});
|
||||
|
||||
it('selects the latest android apk release asset', async () => {
|
||||
testState.capacitorPlatform = 'android';
|
||||
testState.fetchMock.mockResolvedValueOnce(
|
||||
createFetchResponse({
|
||||
tag_name: 'v9.9.9',
|
||||
assets: [
|
||||
{
|
||||
name: '5chan-9.9.9.apk',
|
||||
browser_download_url: 'https://github.com/bitsocialnet/5chan/releases/download/v9.9.9/5chan-9.9.9.apk',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
const { resolveAvailableAppUpdate } = await loadModule();
|
||||
const result = await resolveAvailableAppUpdate();
|
||||
|
||||
expect(result).toEqual({
|
||||
runtime: 'android',
|
||||
targetVersion: '9.9.9',
|
||||
assetName: '5chan-9.9.9.apk',
|
||||
downloadUrl: 'https://github.com/bitsocialnet/5chan/releases/download/v9.9.9/5chan-9.9.9.apk',
|
||||
releaseUrl: 'https://github.com/bitsocialnet/5chan/releases/tag/v9.9.9',
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts configured local test asset hosts for native e2e builds', async () => {
|
||||
vi.stubEnv('VITE_APP_UPDATE_ALLOWED_DOWNLOAD_HOSTS', '10.0.2.2');
|
||||
testState.capacitorPlatform = 'android';
|
||||
testState.fetchMock.mockResolvedValueOnce(
|
||||
createFetchResponse({
|
||||
tag_name: 'v9.9.9',
|
||||
html_url: 'http://10.0.2.2:4010/releases/v9.9.9',
|
||||
assets: [
|
||||
{
|
||||
name: '5chan-9.9.9.apk',
|
||||
browser_download_url: 'http://10.0.2.2:4010/assets/5chan-9.9.9.apk',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
const { resolveAvailableAppUpdate } = await loadModule();
|
||||
const result = await resolveAvailableAppUpdate();
|
||||
|
||||
expect(result).toEqual({
|
||||
runtime: 'android',
|
||||
targetVersion: '9.9.9',
|
||||
assetName: '5chan-9.9.9.apk',
|
||||
downloadUrl: 'http://10.0.2.2:4010/assets/5chan-9.9.9.apk',
|
||||
releaseUrl: 'http://10.0.2.2:4010/releases/v9.9.9',
|
||||
});
|
||||
});
|
||||
|
||||
it('reloads the page when applying a web update', async () => {
|
||||
const reloadMock = vi.fn();
|
||||
Object.defineProperty(window, 'location', {
|
||||
configurable: true,
|
||||
value: {
|
||||
...originalLocation,
|
||||
reload: reloadMock,
|
||||
},
|
||||
});
|
||||
|
||||
const { applyAvailableAppUpdate } = await loadModule();
|
||||
await applyAvailableAppUpdate({
|
||||
runtime: 'web',
|
||||
targetVersion: '9.9.9',
|
||||
releaseUrl: 'https://github.com/bitsocialnet/5chan/releases/tag/v9.9.9',
|
||||
});
|
||||
|
||||
expect(reloadMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('routes native update installs through the matching platform bridge', async () => {
|
||||
window.electronApi = {
|
||||
isElectron: true,
|
||||
getPlatform: () => testState.electronGetPlatformMock(),
|
||||
downloadAndInstallUpdate: (options) => testState.electronDownloadAndInstallUpdateMock(options),
|
||||
copyToClipboard: vi.fn(),
|
||||
automateUploadMedia: vi.fn(),
|
||||
} as Window['electronApi'];
|
||||
|
||||
const { applyAvailableAppUpdate } = await loadModule();
|
||||
await applyAvailableAppUpdate({
|
||||
runtime: 'electron',
|
||||
targetVersion: '9.9.9',
|
||||
assetName: '5chan-9.9.9.Setup.exe',
|
||||
downloadUrl: 'https://github.com/bitsocialnet/5chan/releases/download/v9.9.9/5chan-9.9.9.Setup.exe',
|
||||
releaseUrl: 'https://github.com/bitsocialnet/5chan/releases/tag/v9.9.9',
|
||||
});
|
||||
await applyAvailableAppUpdate({
|
||||
runtime: 'android',
|
||||
targetVersion: '9.9.9',
|
||||
assetName: '5chan-9.9.9.apk',
|
||||
downloadUrl: 'https://github.com/bitsocialnet/5chan/releases/download/v9.9.9/5chan-9.9.9.apk',
|
||||
releaseUrl: 'https://github.com/bitsocialnet/5chan/releases/tag/v9.9.9',
|
||||
});
|
||||
|
||||
expect(testState.electronDownloadAndInstallUpdateMock).toHaveBeenCalledWith({
|
||||
url: 'https://github.com/bitsocialnet/5chan/releases/download/v9.9.9/5chan-9.9.9.Setup.exe',
|
||||
fileName: '5chan-9.9.9.Setup.exe',
|
||||
});
|
||||
expect(testState.androidDownloadAndInstallUpdateMock).toHaveBeenCalledWith({
|
||||
url: 'https://github.com/bitsocialnet/5chan/releases/download/v9.9.9/5chan-9.9.9.apk',
|
||||
fileName: '5chan-9.9.9.apk',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
const DEFAULT_RELEASE_API_URL = 'https://api.github.com/repos/bitsocialnet/5chan/releases/latest';
|
||||
const DEFAULT_RELEASES_BASE_URL = 'https://github.com/bitsocialnet/5chan/releases/tag/';
|
||||
|
||||
const parseConfiguredHosts = (value: string | undefined): Set<string> =>
|
||||
new Set(
|
||||
`${value || ''}`
|
||||
.split(',')
|
||||
.map((host) => host.trim().toLowerCase())
|
||||
.filter(Boolean),
|
||||
);
|
||||
|
||||
const configuredDownloadHosts = parseConfiguredHosts(import.meta.env.VITE_APP_UPDATE_ALLOWED_DOWNLOAD_HOSTS);
|
||||
|
||||
const getReleaseApiUrl = (): string => {
|
||||
const configuredUrl = import.meta.env.VITE_APP_UPDATE_RELEASE_API_URL;
|
||||
return typeof configuredUrl === 'string' && configuredUrl.trim().length > 0 ? configuredUrl.trim() : DEFAULT_RELEASE_API_URL;
|
||||
};
|
||||
|
||||
const getDefaultReleaseUrl = (version: string): string => `${DEFAULT_RELEASES_BASE_URL}v${version.trim().replace(/^v/i, '').split('-')[0]}`;
|
||||
|
||||
const isAllowedDownloadUrl = (url: string): boolean => {
|
||||
try {
|
||||
const parsedUrl = new URL(url);
|
||||
const hostname = parsedUrl.hostname.toLowerCase();
|
||||
|
||||
if (parsedUrl.protocol === 'https:' && hostname === 'github.com') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return configuredDownloadHosts.has(hostname) && (parsedUrl.protocol === 'https:' || parsedUrl.protocol === 'http:');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export { getDefaultReleaseUrl, getReleaseApiUrl, isAllowedDownloadUrl };
|
||||
+206
-6
@@ -1,11 +1,86 @@
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
import AppUpdater from '../plugins/app-updater';
|
||||
import { currentAppVersion } from './app-version';
|
||||
import { getDefaultReleaseUrl, getReleaseApiUrl, isAllowedDownloadUrl } from './app-update-config';
|
||||
|
||||
const isElectron = window.electronApi?.isElectron === true;
|
||||
|
||||
type AppRuntime = 'web' | 'electron' | 'android';
|
||||
|
||||
interface GitHubReleaseAsset {
|
||||
name: string;
|
||||
browser_download_url: string;
|
||||
}
|
||||
|
||||
interface GitHubLatestReleaseResponse {
|
||||
tag_name?: string;
|
||||
html_url?: string;
|
||||
assets?: GitHubReleaseAsset[];
|
||||
}
|
||||
|
||||
interface WebAppUpdateInfo {
|
||||
runtime: 'web';
|
||||
targetVersion: string;
|
||||
releaseUrl: string;
|
||||
}
|
||||
|
||||
interface NativeAppUpdateInfo {
|
||||
runtime: 'electron' | 'android';
|
||||
targetVersion: string;
|
||||
assetName: string;
|
||||
downloadUrl: string;
|
||||
releaseUrl: string;
|
||||
}
|
||||
|
||||
type AvailableAppUpdate = WebAppUpdateInfo | NativeAppUpdateInfo;
|
||||
|
||||
const getAppRuntime = (): AppRuntime => {
|
||||
if (isElectron) {
|
||||
return 'electron';
|
||||
}
|
||||
|
||||
return Capacitor.getPlatform() === 'android' ? 'android' : 'web';
|
||||
};
|
||||
|
||||
const normalizeVersion = (version: string): string => version.trim().replace(/^v/i, '').split('-')[0];
|
||||
|
||||
const compareVersions = (left: string, right: string): number => {
|
||||
const leftParts = normalizeVersion(left)
|
||||
.split('.')
|
||||
.map((part) => Number.parseInt(part, 10) || 0);
|
||||
const rightParts = normalizeVersion(right)
|
||||
.split('.')
|
||||
.map((part) => Number.parseInt(part, 10) || 0);
|
||||
const maxLength = Math.max(leftParts.length, rightParts.length);
|
||||
|
||||
for (let index = 0; index < maxLength; index += 1) {
|
||||
const diff = (leftParts[index] || 0) - (rightParts[index] || 0);
|
||||
if (diff !== 0) {
|
||||
return diff > 0 ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
const fetchJson = async <T>(url: string): Promise<T> => {
|
||||
const response = await fetch(url, {
|
||||
cache: 'no-store',
|
||||
headers: {
|
||||
Accept: 'application/vnd.github+json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`request failed with status ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json() as Promise<T>;
|
||||
};
|
||||
|
||||
const fetchLatestStableVersion = async (): Promise<string> => {
|
||||
const versionUrl = isElectron
|
||||
? 'https://raw.githubusercontent.com/bitsocialnet/5chan/master/package.json'
|
||||
: new URL(`/version.json?t=${Date.now()}`, window.location.origin).toString();
|
||||
const packageRes = await fetch(versionUrl, { cache: 'no-store' });
|
||||
const packageData = await packageRes.json();
|
||||
const versionUrl = new URL(`/version.json?t=${Date.now()}`, window.location.origin).toString();
|
||||
const packageData = await fetchJson<{ version?: string }>(versionUrl);
|
||||
|
||||
if (typeof packageData?.version !== 'string') {
|
||||
throw new Error('invalid version payload');
|
||||
@@ -23,4 +98,129 @@ const refreshServiceWorkerRegistration = async (): Promise<void> => {
|
||||
await registration?.update();
|
||||
};
|
||||
|
||||
export { fetchLatestStableVersion, isElectron, refreshServiceWorkerRegistration };
|
||||
const hasArmArchitecture = (value: string): boolean => {
|
||||
const normalized = value.toLowerCase();
|
||||
return normalized.includes('arm64') || normalized.includes('aarch64');
|
||||
};
|
||||
|
||||
const hasX64Architecture = (value: string): boolean => !hasArmArchitecture(value);
|
||||
|
||||
const getReleaseUrl = (version: string): string => getDefaultReleaseUrl(normalizeVersion(version));
|
||||
|
||||
const findMatchingElectronAsset = async (assets: GitHubReleaseAsset[]): Promise<GitHubReleaseAsset | null> => {
|
||||
const platformInfo = await window.electronApi?.getPlatform();
|
||||
if (!platformInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { platform, arch } = platformInfo;
|
||||
const prefersArm = hasArmArchitecture(arch);
|
||||
const prefersX64 = hasX64Architecture(arch);
|
||||
|
||||
if (platform === 'darwin') {
|
||||
return (
|
||||
assets.find((asset) => asset.name.endsWith('.zip') && ((prefersArm && hasArmArchitecture(asset.name)) || (prefersX64 && hasX64Architecture(asset.name)))) ||
|
||||
assets.find((asset) => asset.name.endsWith('.zip')) ||
|
||||
assets.find((asset) => asset.name.endsWith('.dmg') && ((prefersArm && hasArmArchitecture(asset.name)) || (prefersX64 && hasX64Architecture(asset.name)))) ||
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
if (platform === 'linux') {
|
||||
return (
|
||||
assets.find((asset) => asset.name.endsWith('.AppImage') && ((prefersArm && hasArmArchitecture(asset.name)) || (prefersX64 && hasX64Architecture(asset.name)))) ||
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
if (platform === 'win32') {
|
||||
return (
|
||||
assets.find(
|
||||
(asset) =>
|
||||
asset.name.toLowerCase().endsWith('.exe') &&
|
||||
asset.name.toLowerCase().includes('setup') &&
|
||||
((prefersArm && hasArmArchitecture(asset.name)) || (prefersX64 && hasX64Architecture(asset.name))),
|
||||
) || null
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const fetchLatestReleaseUpdate = async (runtime: Extract<AppRuntime, 'electron' | 'android'>): Promise<NativeAppUpdateInfo | null> => {
|
||||
const releaseData = await fetchJson<GitHubLatestReleaseResponse>(getReleaseApiUrl());
|
||||
const targetVersion = typeof releaseData.tag_name === 'string' ? normalizeVersion(releaseData.tag_name) : '';
|
||||
|
||||
if (!targetVersion || compareVersions(targetVersion, currentAppVersion) <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const assets = Array.isArray(releaseData.assets)
|
||||
? releaseData.assets.filter((asset) => typeof asset?.name === 'string' && typeof asset?.browser_download_url === 'string')
|
||||
: [];
|
||||
const matchedAsset = runtime === 'android' ? assets.find((asset) => asset.name.toLowerCase().endsWith('.apk')) || null : await findMatchingElectronAsset(assets);
|
||||
|
||||
if (!matchedAsset || !isAllowedDownloadUrl(matchedAsset.browser_download_url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
runtime,
|
||||
targetVersion,
|
||||
assetName: matchedAsset.name,
|
||||
downloadUrl: matchedAsset.browser_download_url,
|
||||
releaseUrl:
|
||||
typeof releaseData.html_url === 'string' && releaseData.html_url.trim().length > 0 ? releaseData.html_url : getReleaseUrl(releaseData.tag_name || targetVersion),
|
||||
};
|
||||
};
|
||||
|
||||
const resolveAvailableAppUpdate = async (): Promise<AvailableAppUpdate | null> => {
|
||||
const runtime = getAppRuntime();
|
||||
|
||||
if (runtime === 'web') {
|
||||
await refreshServiceWorkerRegistration();
|
||||
|
||||
const latestStableVersion = await fetchLatestStableVersion();
|
||||
if (compareVersions(latestStableVersion, currentAppVersion) > 0) {
|
||||
return {
|
||||
runtime: 'web',
|
||||
targetVersion: latestStableVersion,
|
||||
releaseUrl: getReleaseUrl(latestStableVersion),
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return fetchLatestReleaseUpdate(runtime);
|
||||
};
|
||||
|
||||
const applyAvailableAppUpdate = async (update: AvailableAppUpdate): Promise<void> => {
|
||||
if (update.runtime === 'web') {
|
||||
await refreshServiceWorkerRegistration().catch((error) => {
|
||||
console.error('Failed to refresh service worker registration', error);
|
||||
});
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
if (update.runtime === 'electron') {
|
||||
if (!window.electronApi?.downloadAndInstallUpdate) {
|
||||
throw new Error('Electron updater is unavailable');
|
||||
}
|
||||
|
||||
await window.electronApi.downloadAndInstallUpdate({
|
||||
url: update.downloadUrl,
|
||||
fileName: update.assetName,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await AppUpdater.downloadAndInstallUpdate({
|
||||
url: update.downloadUrl,
|
||||
fileName: update.assetName,
|
||||
});
|
||||
};
|
||||
|
||||
export type { AppRuntime, AvailableAppUpdate, NativeAppUpdateInfo, WebAppUpdateInfo };
|
||||
export { applyAvailableAppUpdate, fetchLatestStableVersion, getAppRuntime, isElectron, refreshServiceWorkerRegistration, resolveAvailableAppUpdate };
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import packageJson from '../../package.json';
|
||||
|
||||
const resolveCurrentAppVersion = (): string => {
|
||||
const configuredVersion = import.meta.env.VITE_APP_VERSION;
|
||||
|
||||
if (typeof configuredVersion === 'string' && configuredVersion.trim().length > 0) {
|
||||
return configuredVersion.trim();
|
||||
}
|
||||
|
||||
return packageJson.version;
|
||||
};
|
||||
|
||||
const currentAppVersion = resolveCurrentAppVersion();
|
||||
|
||||
export { currentAppVersion };
|
||||
Reference in New Issue
Block a user