mirror of
https://github.com/microsoft/vscode.git
synced 2026-02-14 23:18:36 +00:00
fix: customize user-agent sent via macOS updater path (#294646)
This commit is contained in:
@@ -3,9 +3,11 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as os from 'os';
|
||||
import { IntervalTimer, timeout } from '../../../base/common/async.js';
|
||||
import { CancellationToken, CancellationTokenSource } from '../../../base/common/cancellation.js';
|
||||
import { Emitter, Event } from '../../../base/common/event.js';
|
||||
import { isMacintosh } from '../../../base/common/platform.js';
|
||||
import { IMeteredConnectionService } from '../../meteredConnection/common/meteredConnection.js';
|
||||
import { IConfigurationService } from '../../configuration/common/configuration.js';
|
||||
import { IEnvironmentMainService } from '../../environment/electron-main/environmentMainService.js';
|
||||
@@ -29,6 +31,23 @@ export function createUpdateURL(baseUpdateUrl: string, platform: string, quality
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds common headers for macOS update requests, including those issued
|
||||
* via Electron's auto-updater (e.g. setFeedURL({ url, headers })) and
|
||||
* manual HTTP requests that bypass the auto-updater. On macOS, this includes
|
||||
* the Darwin kernel version which the update server uses for EOL detection.
|
||||
*/
|
||||
export function getUpdateRequestHeaders(productVersion: string): Record<string, string> | undefined {
|
||||
if (isMacintosh) {
|
||||
const darwinVersion = os.release();
|
||||
return {
|
||||
'User-Agent': `Code/${productVersion} Darwin/${darwinVersion}`
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export type UpdateErrorClassification = {
|
||||
owner: 'joaomoreno';
|
||||
messageHash: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The hash of the error message.' };
|
||||
@@ -288,11 +307,16 @@ export abstract class AbstractUpdateService implements IUpdateService {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const headers = getUpdateRequestHeaders(this.productService.version);
|
||||
this.logService.trace('update#isLatestVersion() - checking update server', { url, headers });
|
||||
|
||||
try {
|
||||
const context = await this.requestService.request({ url }, token);
|
||||
const context = await this.requestService.request({ url, headers }, token);
|
||||
const statusCode = context.res.statusCode;
|
||||
this.logService.trace('update#isLatestVersion() - response', { statusCode });
|
||||
// The update server replies with 204 (No Content) when no
|
||||
// update is available - that's all we want to know.
|
||||
return context.res.statusCode === 204;
|
||||
return statusCode === 204;
|
||||
|
||||
} catch (error) {
|
||||
this.logService.error('update#isLatestVersion(): failed to check for updates');
|
||||
|
||||
@@ -18,13 +18,14 @@ import { asJson, IRequestService } from '../../request/common/request.js';
|
||||
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
|
||||
import { AvailableForDownload, IUpdate, State, StateType, UpdateType } from '../common/update.js';
|
||||
import { IMeteredConnectionService } from '../../meteredConnection/common/meteredConnection.js';
|
||||
import { AbstractUpdateService, createUpdateURL, IUpdateURLOptions, UpdateErrorClassification } from './abstractUpdateService.js';
|
||||
import { AbstractUpdateService, createUpdateURL, getUpdateRequestHeaders, IUpdateURLOptions, UpdateErrorClassification } from './abstractUpdateService.js';
|
||||
|
||||
export class DarwinUpdateService extends AbstractUpdateService implements IRelaunchHandler {
|
||||
|
||||
private readonly disposables = new DisposableStore();
|
||||
|
||||
@memoize private get onRawError(): Event<string> { return Event.fromNodeEventEmitter(electron.autoUpdater, 'error', (_, message) => message); }
|
||||
@memoize private get onRawCheckingForUpdate(): Event<void> { return Event.fromNodeEventEmitter<void>(electron.autoUpdater, 'checking-for-update'); }
|
||||
@memoize private get onRawUpdateNotAvailable(): Event<void> { return Event.fromNodeEventEmitter<void>(electron.autoUpdater, 'update-not-available'); }
|
||||
@memoize private get onRawUpdateAvailable(): Event<void> { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-available'); }
|
||||
@memoize private get onRawUpdateDownloaded(): Event<IUpdate> {
|
||||
@@ -68,11 +69,16 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau
|
||||
protected override async initialize(): Promise<void> {
|
||||
await super.initialize();
|
||||
this.onRawError(this.onError, this, this.disposables);
|
||||
this.onRawCheckingForUpdate(this.onCheckingForUpdate, this, this.disposables);
|
||||
this.onRawUpdateAvailable(this.onUpdateAvailable, this, this.disposables);
|
||||
this.onRawUpdateDownloaded(this.onUpdateDownloaded, this, this.disposables);
|
||||
this.onRawUpdateNotAvailable(this.onUpdateNotAvailable, this, this.disposables);
|
||||
}
|
||||
|
||||
private onCheckingForUpdate(): void {
|
||||
this.logService.trace('update#onCheckingForUpdate - Electron autoUpdater is checking for updates');
|
||||
}
|
||||
|
||||
private onError(err: string): void {
|
||||
this.telemetryService.publicLog2<{ messageHash: string }, UpdateErrorClassification>('update:error', { messageHash: String(hash(String(err))) });
|
||||
this.logService.error('UpdateService error:', err);
|
||||
@@ -85,8 +91,10 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau
|
||||
protected buildUpdateFeedUrl(quality: string, commit: string, options?: IUpdateURLOptions): string | undefined {
|
||||
const assetID = this.productService.darwinUniversalAssetId ?? (process.arch === 'x64' ? 'darwin' : 'darwin-arm64');
|
||||
const url = createUpdateURL(this.productService.updateUrl!, assetID, quality, commit, options);
|
||||
const headers = getUpdateRequestHeaders(this.productService.version);
|
||||
try {
|
||||
electron.autoUpdater.setFeedURL({ url });
|
||||
this.logService.trace('update#buildUpdateFeedUrl - setting feed URL for Electron autoUpdater', { url, assetID, quality, commit, headers });
|
||||
electron.autoUpdater.setFeedURL({ url, headers });
|
||||
} catch (e) {
|
||||
// application is very likely not signed
|
||||
this.logService.error('Failed to set update feed URL', e);
|
||||
@@ -126,6 +134,7 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau
|
||||
return;
|
||||
}
|
||||
|
||||
this.logService.trace('update#doCheckForUpdates - using Electron autoUpdater', { url, explicit, background });
|
||||
electron.autoUpdater.checkForUpdates();
|
||||
}
|
||||
|
||||
@@ -134,20 +143,31 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau
|
||||
* Used when connection is metered to show update availability without downloading.
|
||||
*/
|
||||
private async checkForUpdateNoDownload(url: string): Promise<void> {
|
||||
const headers = getUpdateRequestHeaders(this.productService.version);
|
||||
this.logService.trace('update#checkForUpdateNoDownload - checking update server', { url, headers });
|
||||
|
||||
try {
|
||||
const update = await asJson<IUpdate>(await this.requestService.request({ url }, CancellationToken.None));
|
||||
const context = await this.requestService.request({ url, headers }, CancellationToken.None);
|
||||
const statusCode = context.res.statusCode;
|
||||
this.logService.trace('update#checkForUpdateNoDownload - response', { statusCode });
|
||||
|
||||
const update = await asJson<IUpdate>(context);
|
||||
if (!update || !update.url || !update.version || !update.productVersion) {
|
||||
this.logService.trace('update#checkForUpdateNoDownload - no update available');
|
||||
this.setState(State.Idle(UpdateType.Archive));
|
||||
} else {
|
||||
this.logService.trace('update#checkForUpdateNoDownload - update available', { version: update.version, productVersion: update.productVersion });
|
||||
this.setState(State.AvailableForDownload(update));
|
||||
}
|
||||
} catch (err) {
|
||||
this.logService.error(err);
|
||||
this.logService.error('update#checkForUpdateNoDownload - failed to check for update', err);
|
||||
this.setState(State.Idle(UpdateType.Archive));
|
||||
}
|
||||
}
|
||||
|
||||
private onUpdateAvailable(): void {
|
||||
this.logService.trace('update#onUpdateAvailable - Electron autoUpdater reported update available');
|
||||
|
||||
if (this.state.type !== StateType.CheckingForUpdates && this.state.type !== StateType.Overwriting) {
|
||||
return;
|
||||
}
|
||||
@@ -167,6 +187,8 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau
|
||||
}
|
||||
|
||||
private onUpdateNotAvailable(): void {
|
||||
this.logService.trace('update#onUpdateNotAvailable - Electron autoUpdater reported no update available');
|
||||
|
||||
if (this.state.type !== StateType.CheckingForUpdates) {
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user