Skip to content
10 changes: 1 addition & 9 deletions src/bootstrap-esm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@
*--------------------------------------------------------------------------------------------*/

import * as fs from 'fs';
import { createRequire, register } from 'node:module';
import { register } from 'node:module';
import { product, pkg } from './bootstrap-meta.js';
import './bootstrap-node.js';
import * as performance from './vs/base/common/performance.js';
import { INLSConfiguration } from './vs/nls.js';

const require = createRequire(import.meta.url);

// Install a hook to module resolution to map 'fs' to 'original-fs'
if (process.env['ELECTRON_RUN_AS_NODE'] || process.versions['electron']) {
const jsCode = `
Expand All @@ -33,12 +31,6 @@ if (process.env['ELECTRON_RUN_AS_NODE'] || process.versions['electron']) {

// Prepare globals that are needed for running
globalThis._VSCODE_PRODUCT_JSON = { ...product };
if (process.env['VSCODE_DEV']) {
try {
const overrides: unknown = require('../product.overrides.json');
globalThis._VSCODE_PRODUCT_JSON = Object.assign(globalThis._VSCODE_PRODUCT_JSON, overrides);
} catch (error) { /* ignore */ }
}
globalThis._VSCODE_PACKAGE_JSON = { ...pkg };
globalThis._VSCODE_FILE_ROOT = import.meta.dirname;

Expand Down
8 changes: 8 additions & 0 deletions src/bootstrap-meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,13 @@ if (pkgObj['BUILD_INSERT_PACKAGE_CONFIGURATION']) {
pkgObj = require('../package.json'); // Running out of sources
}

let productOverridesObj = {};
if (process.env['VSCODE_DEV']) {
try {
productOverridesObj = require('../product.overrides.json');
productObj = Object.assign(productObj, productOverridesObj);
} catch (error) { /* ignore */ }
}

export const product = productObj;
export const pkg = pkgObj;
1 change: 1 addition & 0 deletions src/vs/base/common/product.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export interface IProductConfiguration {
readonly serviceUrl: string;
readonly controlUrl: string;
readonly mcpUrl: string;
readonly extensionUrlTemplate: string;
readonly resourceUrlTemplate: string;
readonly nlsBaseUrl: string;
readonly accessSKUs?: string[];
Expand Down
130 changes: 112 additions & 18 deletions src/vs/platform/extensionManagement/common/extensionGalleryService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { areApiProposalsCompatible, isEngineValid } from '../../extensions/commo
import { IFileService } from '../../files/common/files.js';
import { ILogService } from '../../log/common/log.js';
import { IProductService } from '../../product/common/productService.js';
import { asJson, asTextOrError, IRequestService, isSuccess } from '../../request/common/request.js';
import { asJson, asTextOrError, IRequestService, isClientError, isServerError, isSuccess } from '../../request/common/request.js';
import { resolveMarketplaceHeaders } from '../../externalServices/common/marketplace.js';
import { IStorageService } from '../../storage/common/storage.js';
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
Expand Down Expand Up @@ -548,6 +548,7 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle
declare readonly _serviceBrand: undefined;

private readonly extensionsControlUrl: string | undefined;
private readonly unpkgResourceApi: string | undefined;

private readonly commonHeadersPromise: Promise<IHeaders>;
private readonly extensionsEnabledWithApiProposalVersion: string[];
Expand All @@ -566,6 +567,7 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle
@IExtensionGalleryManifestService private readonly extensionGalleryManifestService: IExtensionGalleryManifestService,
) {
this.extensionsControlUrl = productService.extensionsGallery?.controlUrl;
this.unpkgResourceApi = productService.extensionsGallery?.extensionUrlTemplate;
this.extensionsEnabledWithApiProposalVersion = productService.extensionsEnabledWithApiProposalVersion?.map(id => id.toLowerCase()) ?? [];
this.commonHeadersPromise = resolveMarketplaceHeaders(
productService.version,
Expand All @@ -592,7 +594,7 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle
const options = CancellationToken.isCancellationToken(arg1) ? {} : arg1 as IExtensionQueryOptions;
const token = CancellationToken.isCancellationToken(arg1) ? arg1 : arg2 as CancellationToken;

const resourceApi = getExtensionGalleryManifestResourceUri(extensionGalleryManifest, ExtensionGalleryResourceType.ExtensionLatestVersionUri);
const resourceApi = await this.getResourceApi(extensionGalleryManifest);
const result = resourceApi
? await this.getExtensionsUsingResourceApi(extensionInfos, options, resourceApi, extensionGalleryManifest, token)
: await this.getExtensionsUsingQueryApi(extensionInfos, options, extensionGalleryManifest, token);
Expand Down Expand Up @@ -624,6 +626,17 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle
return result;
}

private async getResourceApi(extensionGalleryManifest: IExtensionGalleryManifest): Promise<{ uri: string; fallback?: string } | undefined> {
const latestVersionResource = getExtensionGalleryManifestResourceUri(extensionGalleryManifest, ExtensionGalleryResourceType.ExtensionLatestVersionUri);
if (latestVersionResource) {
return {
uri: latestVersionResource,
fallback: this.unpkgResourceApi
};
}
return undefined;
}

private async getExtensionsUsingQueryApi(extensionInfos: ReadonlyArray<IExtensionInfo>, options: IExtensionQueryOptions, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<IGalleryExtension[]> {
const names: string[] = [],
ids: string[] = [],
Expand Down Expand Up @@ -683,7 +696,7 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle
return extensions;
}

private async getExtensionsUsingResourceApi(extensionInfos: ReadonlyArray<IExtensionInfo>, options: IExtensionQueryOptions, resourceApi: string, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<IGalleryExtension[]> {
private async getExtensionsUsingResourceApi(extensionInfos: ReadonlyArray<IExtensionInfo>, options: IExtensionQueryOptions, resourceApi: { uri: string; fallback?: string }, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<IGalleryExtension[]> {

const result: IGalleryExtension[] = [];
const toQuery: IExtensionInfo[] = [];
Expand All @@ -700,7 +713,7 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle
}
}

await Promise.allSettled(toFetchLatest.map(async extensionInfo => {
await Promise.all(toFetchLatest.map(async extensionInfo => {
let galleryExtension: IGalleryExtension | null | 'NOT_FOUND';
try {
galleryExtension = await this.getLatestGalleryExtension(extensionInfo, options, resourceApi, extensionGalleryManifest, token);
Expand All @@ -711,30 +724,40 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle
}
return;
}

if (galleryExtension) {
result.push(galleryExtension);
}

} catch (error) {
if (error instanceof ExtensionGalleryError) {
switch (error.code) {
case ExtensionGalleryErrorCode.Offline:
case ExtensionGalleryErrorCode.Cancelled:
case ExtensionGalleryErrorCode.Timeout:
throw error;
}
}

// fallback to query
this.logService.error(`Error while getting the latest version for the extension ${extensionInfo.id}.`, getErrorMessage(error));
this.telemetryService.publicLog2<
{
extension: string;
preRelease: boolean;
compatible: boolean;
errorCode: string;
},
{
owner: 'sandy081';
comment: 'Report the fallback to the Marketplace query for fetching extensions';
extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension id' };
preRelease: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get pre-release version' };
compatible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get compatible version' };
errorCode: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Error code' };
}>('galleryService:fallbacktoquery', {
extension: extensionInfo.id,
preRelease: !!extensionInfo.preRelease,
compatible: !!options.compatible,
errorCode: error instanceof ExtensionGalleryError ? error.code : 'Unknown'
});
toQuery.push(extensionInfo);
}
Expand All @@ -749,10 +772,8 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle
return result;
}

private async getLatestGalleryExtension(extensionInfo: IExtensionInfo, options: IExtensionQueryOptions, resourceUriTemplate: string, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<IGalleryExtension | null | 'NOT_FOUND'> {
const [publisher, name] = extensionInfo.id.split('.');
const uri = URI.parse(format2(resourceUriTemplate, { publisher, name }));
const rawGalleryExtension = await this.getLatestRawGalleryExtension(extensionInfo.id, uri, token);
private async getLatestGalleryExtension(extensionInfo: IExtensionInfo, options: IExtensionQueryOptions, resourceApi: { uri: string; fallback?: string }, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<IGalleryExtension | null | 'NOT_FOUND'> {
const rawGalleryExtension = await this.getLatestRawGalleryExtensionWithFallback(extensionInfo, resourceApi, token);

if (!rawGalleryExtension) {
return 'NOT_FOUND';
Expand Down Expand Up @@ -1375,11 +1396,76 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle
return value ? new TelemetryTrustedValue(value) : undefined;
}

private async getLatestRawGalleryExtensionWithFallback(extensionInfo: IExtensionInfo, resourceApi: { uri: string; fallback?: string }, token: CancellationToken): Promise<IRawGalleryExtension | null> {
const [publisher, name] = extensionInfo.id.split('.');
let errorCode: string | undefined;
try {
const uri = URI.parse(format2(resourceApi.uri, { publisher, name }));
return await this.getLatestRawGalleryExtension(extensionInfo.id, uri, token);
} catch (error) {
if (error instanceof ExtensionGalleryError) {
errorCode = error.code;
switch (error.code) {
case ExtensionGalleryErrorCode.Offline:
case ExtensionGalleryErrorCode.Cancelled:
case ExtensionGalleryErrorCode.Timeout:
case ExtensionGalleryErrorCode.ClientError:
throw error;
}
} else {
errorCode = 'Unknown';
}
if (!resourceApi.fallback) {
throw error;
}
} finally {
this.telemetryService.publicLog2<
{
extension: string;
errorCode?: string;
},
{
owner: 'sandy081';
comment: 'Report fetching latest version of an extension';
extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the extension' };
errorCode?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error code in case of error' };
}
>('galleryService:getmarketplacelatest', {
extension: extensionInfo.id,
errorCode,
});
}

this.logService.error(`Error while getting the latest version for the extension ${extensionInfo.id} from ${resourceApi.uri}. Trying the fallback ${resourceApi.fallback}`, errorCode);
try {
const uri = URI.parse(format2(resourceApi.fallback, { publisher, name }));
return await this.getLatestRawGalleryExtension(extensionInfo.id, uri, token);
} catch (error) {
errorCode = error instanceof ExtensionGalleryError ? error.code : 'Unknown';
throw error;
} finally {
this.telemetryService.publicLog2<
{
extension: string;
errorCode?: string;
},
{
owner: 'sandy081';
comment: 'Report the fallback to the unpkg service for getting latest extension';
extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension id' };
errorCode?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error code in case of error' };
}>('galleryService:fallbacktounpkg', {
extension: extensionInfo.id,
errorCode,
});
}
}

private async getLatestRawGalleryExtension(extension: string, uri: URI, token: CancellationToken): Promise<IRawGalleryExtension | null> {
let context;
let errorCode: string | undefined;
const stopWatch = new StopWatch();

let context;
try {
const commonHeaders = await this.commonHeadersPromise;
const headers = {
Expand All @@ -1402,7 +1488,6 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle
}

if (context.res.statusCode && context.res.statusCode !== 200) {
errorCode = `GalleryServiceError:` + context.res.statusCode;
throw new Error('Unexpected HTTP response: ' + context.res.statusCode);
}

Expand All @@ -1414,16 +1499,22 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle
}

catch (error) {
let galleryErrorCode: ExtensionGalleryErrorCode;
if (isCancellationError(error)) {
errorCode = ExtensionGalleryErrorCode.Cancelled;
galleryErrorCode = ExtensionGalleryErrorCode.Cancelled;
} else if (isOfflineError(error)) {
errorCode = ExtensionGalleryErrorCode.Offline;
galleryErrorCode = ExtensionGalleryErrorCode.Offline;
} else if (getErrorMessage(error).startsWith('XHR timeout')) {
errorCode = ExtensionGalleryErrorCode.Timeout;
} else if (!errorCode) {
errorCode = ExtensionGalleryErrorCode.Failed;
galleryErrorCode = ExtensionGalleryErrorCode.Timeout;
} else if (context && isClientError(context)) {
galleryErrorCode = ExtensionGalleryErrorCode.ClientError;
} else if (context && isServerError(context)) {
galleryErrorCode = ExtensionGalleryErrorCode.ServerError;
} else {
galleryErrorCode = ExtensionGalleryErrorCode.Failed;
}
throw error;
errorCode = galleryErrorCode;
throw new ExtensionGalleryError(error, galleryErrorCode);
}

finally {
Expand All @@ -1434,6 +1525,7 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle
extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the extension' };
duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Duration in ms for the query' };
errorCode?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error code in case of error' };
statusCode?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The status code in case of error' };
server?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The server of the end point' };
activityId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The activity ID of the request' };
endToEndId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The end-to-end ID of the request' };
Expand All @@ -1443,6 +1535,7 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle
host: string;
duration: number;
errorCode?: string;
statusCode?: string;
server?: TelemetryTrustedValue<string>;
activityId?: TelemetryTrustedValue<string>;
endToEndId?: TelemetryTrustedValue<string>;
Expand All @@ -1452,6 +1545,7 @@ export abstract class AbstractExtensionGalleryService implements IExtensionGalle
host: uri.authority,
duration: stopWatch.elapsed(),
errorCode,
statusCode: context?.res.statusCode && context?.res.statusCode !== 200 ? `${context.res.statusCode}` : undefined,
server: this.getHeaderValue(context?.res.headers, SERVER_HEADER_NAME),
activityId: this.getHeaderValue(context?.res.headers, ACTIVITY_HEADER_NAME),
endToEndId: this.getHeaderValue(context?.res.headers, END_END_ID_HEADER_NAME),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,8 @@ export interface DidUpdateExtensionMetadata {
export const enum ExtensionGalleryErrorCode {
Timeout = 'Timeout',
Cancelled = 'Cancelled',
ClientError = 'ClientError',
ServerError = 'ServerError',
Failed = 'Failed',
DownloadFailedWriting = 'DownloadFailedWriting',
Offline = 'Offline',
Expand Down
2 changes: 2 additions & 0 deletions src/vs/platform/native/common/native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ export interface ICommonNativeHostService {
*/
updateWindowControls(options: INativeHostOptions & { height?: number; backgroundColor?: string; foregroundColor?: string }): Promise<void>;

updateWindowAccentColor(color: 'default' | 'off' | string, inactiveColor: string | undefined): Promise<void>;

setMinimumSize(width: number | undefined, height: number | undefined): Promise<void>;

saveWindowSplash(splash: IPartsSplash): Promise<void>;
Expand Down
36 changes: 36 additions & 0 deletions src/vs/platform/native/electron-main/nativeHostMainService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,42 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
window?.updateWindowControls(options);
}

async updateWindowAccentColor(windowId: number | undefined, color: 'default' | 'off' | string, inactiveColor: string | undefined): Promise<void> {
if (!isWindows) {
return; // windows only
}

const window = this.windowById(windowId);
if (!window) {
return;
}

let activeWindowAccentColor: string | boolean;
let inactiveWindowAccentColor: string | boolean;

if (color === 'default') {
activeWindowAccentColor = true;
inactiveWindowAccentColor = true;
} else if (color === 'off') {
activeWindowAccentColor = false;
inactiveWindowAccentColor = false;
} else {
activeWindowAccentColor = color;
inactiveWindowAccentColor = inactiveColor ?? color;
}

const windows = [window];
for (const auxiliaryWindow of this.auxiliaryWindowsMainService.getWindows()) {
if (auxiliaryWindow.parentId === windowId) {
windows.push(auxiliaryWindow);
}
}

for (const window of windows) {
window.win?.setAccentColor(window.win.isFocused() ? activeWindowAccentColor : inactiveWindowAccentColor);
}
}

async focusWindow(windowId: number | undefined, options?: INativeHostOptions & { mode?: FocusMode }): Promise<void> {
const window = this.windowById(options?.targetWindowId, windowId);
window?.focus({ mode: options?.mode ?? FocusMode.Transfer });
Expand Down
8 changes: 8 additions & 0 deletions src/vs/platform/request/common/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ export function isSuccess(context: IRequestContext): boolean {
return (context.res.statusCode && context.res.statusCode >= 200 && context.res.statusCode < 300) || context.res.statusCode === 1223;
}

export function isClientError(context: IRequestContext): boolean {
return !!context.res.statusCode && context.res.statusCode >= 400 && context.res.statusCode < 500;
}

export function isServerError(context: IRequestContext): boolean {
return !!context.res.statusCode && context.res.statusCode >= 500 && context.res.statusCode < 600;
}

export function hasNoContent(context: IRequestContext): boolean {
return context.res.statusCode === 204;
}
Expand Down
Loading
Loading