Skip to content

Commit

Permalink
SDA-4785 AutoUpdate on app launch fix (#2259)
Browse files Browse the repository at this point in the history
  • Loading branch information
sbenmoussati authored Jan 14, 2025
1 parent beb9f53 commit 1a85223
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 46 deletions.
129 changes: 129 additions & 0 deletions spec/autoUpdateUtils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import * as electronFetch from 'electron-fetch';
import { fetchLatestVersion } from '../src/app/auto-update-utils';
import { logger } from '../src/common/logger';

jest.mock('../src/common/logger');
jest.mock('electron-fetch');

const DEFAULT_VERSION_RESPONSE = `
version: 25.1.0-2514
files:
- url: https://123.exe
sha512: 123
size: 206426857
path: https://123.exe
releaseDate: '2024-12-27T10:16:58.947Z'
sha512: 123`;

describe('fetchLatestVersion', () => {
const mockUrl = 'test-url';
const regex = /version: (.*)/;
const mockLogger = logger as jest.Mocked<typeof logger>;

afterEach(() => {
jest.clearAllMocks();
});

it('should fetch the latest version from a URL', async () => {
(
electronFetch as jest.Mocked<typeof electronFetch>
).default.mockResolvedValueOnce({
ok: true,
text: async () => DEFAULT_VERSION_RESPONSE,
});

const version = await fetchLatestVersion(mockUrl, 5000, regex);
expect(version).toBe('25.1.0-2514');
expect(
(electronFetch as jest.Mocked<typeof electronFetch>).default,
).toHaveBeenCalledWith(mockUrl, { signal: expect.anything() });
});

it('should return undefined if the fetch fails', async () => {
(
electronFetch as jest.Mocked<typeof electronFetch>
).default.mockRejectedValueOnce(new Error('Fetch error'));

const version = await fetchLatestVersion(mockUrl, 5000, regex);
expect(version).toBeUndefined();
expect(
(electronFetch as jest.Mocked<typeof electronFetch>).default,
).toHaveBeenCalledTimes(1);
});

it('should return undefined if the response is not ok', async () => {
(
electronFetch as jest.Mocked<typeof electronFetch>
).default.mockResolvedValueOnce({
ok: false,
status: 404,
text: async () => 'Not Found',
});

const version = await fetchLatestVersion(mockUrl, 5000, regex);
expect(version).toBeUndefined();
});

it('should return undefined if the version is not found in the response', async () => {
(
electronFetch as jest.Mocked<typeof electronFetch>
).default.mockResolvedValueOnce({
ok: true,
text: async () => 'Invalid response',
});

const version = await fetchLatestVersion(mockUrl, 5000, regex);
expect(version).toBeUndefined();
});

it('should handle timeout', async () => {
const mockUrl = 'test-url';
const autoUpdateTimeout = 10;

jest.useFakeTimers();

const versionPromise = fetchLatestVersion(
mockUrl,
autoUpdateTimeout,
regex,
);

jest.advanceTimersByTime(autoUpdateTimeout + 1);

const version = await versionPromise;

expect(version).toBeUndefined();
expect(mockLogger.warn).toHaveBeenCalledWith(
'auto-update-handler: fetch aborted due to timeout',
mockUrl,
);

jest.useRealTimers();
});

it('should handle error', async () => {
const mockUrl = 'test-url';
const autoUpdateTimeout = 100;

jest.useFakeTimers();

const versionPromise = fetchLatestVersion(
mockUrl,
autoUpdateTimeout,
regex,
);

jest.advanceTimersByTime(autoUpdateTimeout - 1);

const version = await versionPromise;

expect(version).toBeUndefined();
expect(mockLogger.error).toHaveBeenCalledWith(
'auto-update-handler: error fetching version',
mockUrl,
expect.anything(),
);

jest.useRealTimers();
});
});
54 changes: 8 additions & 46 deletions src/app/auto-update-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { isMac, isWindowsOS } from '../common/env';
import { logger } from '../common/logger';
import { isUrl } from '../common/utils';
import { whitelistHandler } from '../common/whitelist-handler';
import { fetchLatestVersion } from './auto-update-utils';
import { sendAutoUpdateAnalytics } from './bi/auto-update-analytics';
import { InstallActionTypes, InstallTypes } from './bi/interface';
import { config, IConfig } from './config-handler';
Expand Down Expand Up @@ -141,7 +142,13 @@ export class AutoUpdate {
}

logger.info('auto-update-handler: pending update files', files);
const latestVersionFromServer = await this.fetchLatestVersion();
const opts = await this.getGenericServerOptions();
const url = opts.channel ? `${opts.url}/${opts.channel}.yml` : opts.url;
const latestVersionFromServer = await fetchLatestVersion(
url,
FORCE_UPDATE_TIMEOUT,
VERSION_REGEX,
);
if (!latestVersionFromServer) {
logger.info(
'auto-update-handler: no version info from server skipping force auto update',
Expand Down Expand Up @@ -269,51 +276,6 @@ export class AutoUpdate {
return;
};

private fetchLatestVersion = async (): Promise<string | void> => {
return new Promise(async (resolve) => {
const opts = await this.getGenericServerOptions();
const url = opts.channel ? `${opts.url}/${opts.channel}.yml` : opts.url;
logger.info(
'auto-update-handler: fetching latest version info from',
url,
);
const controller = new AbortController();
const signal = controller.signal;
const timeoutId = setTimeout(
() => controller.abort(),
FORCE_UPDATE_TIMEOUT,
);
fetch(url, { signal })
.then((res) => res.blob())
.then((blob) => blob.text())
.then(async (response) => {
clearTimeout(timeoutId);
logger.info(
'auto-update-handler: latest version info from server',
response,
);
const match = VERSION_REGEX.exec(response);
if (match && match.length) {
logger.info('auto-update-handler: version found', match[1]);
resolve(match[1]);
} else {
resolve();
}
})
.catch(async (error) => {
logger.error(
'auto-update-handler: error fetching latest auto-update version from server',
url,
error,
);
resolve();
})
.finally(() => {
clearTimeout(timeoutId);
});
});
};

private updateEventHandler = async (info, eventType: string) => {
logger.info('auto-update-handler: auto update events', info, eventType);
const mainWebContents = windowHandler.mainWebContents;
Expand Down
40 changes: 40 additions & 0 deletions src/app/auto-update-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import fetch from 'electron-fetch';
import { logger } from '../common/logger';

export const fetchLatestVersion = async (
url: string,
autoUpdateTimeout: number,
versionRegexp: RegExp,
): Promise<string | undefined> => {
logger.info('auto-update-handler: fetching latest version info from', url);

const controller = new AbortController();
const signal = controller.signal;
const timeoutId = setTimeout(() => controller.abort(), autoUpdateTimeout);

try {
const response = await fetch(url, { signal });
clearTimeout(timeoutId);
if (!response || !response.ok) {
throw new Error(`HTTP error ${response.status}`);
}

const responseText = await response.text();
logger.info(
'auto-update-handler: latest version info from server',
responseText,
);

const match = versionRegexp.exec(responseText);
return match?.[1];
} catch (error: any) {
if (controller.signal.aborted) {
logger.warn('auto-update-handler: fetch aborted due to timeout', url);
} else {
logger.error('auto-update-handler: error fetching version', url, error);
}
return undefined;
} finally {
clearTimeout(timeoutId);
}
};

0 comments on commit 1a85223

Please sign in to comment.