feat: apply patch after setting up subtree
This commit is contained in:
241
lib/vscode/src/vs/server/browser/client.ts
Normal file
241
lib/vscode/src/vs/server/browser/client.ts
Normal file
@@ -0,0 +1,241 @@
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { INodeProxyService, NodeProxyChannelClient } from 'vs/server/common/nodeProxy';
|
||||
import { TelemetryChannelClient } from 'vs/server/common/telemetry';
|
||||
import 'vs/workbench/contrib/localizations/browser/localizations.contribution';
|
||||
import { LocalizationsService } from 'vs/workbench/services/localizations/electron-browser/localizationsService';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { Options } from 'vs/server/ipc.d';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import * as path from 'vs/base/common/path';
|
||||
|
||||
class TelemetryService extends TelemetryChannelClient {
|
||||
public constructor(
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
) {
|
||||
super(remoteAgentService.getConnection()!.getChannel('telemetry'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove extra slashes in a URL.
|
||||
*/
|
||||
export const normalize = (url: string, keepTrailing = false): string => {
|
||||
return url.replace(/\/\/+/g, '/').replace(/\/+$/, keepTrailing ? '/' : '');
|
||||
};
|
||||
|
||||
/**
|
||||
* Get options embedded in the HTML.
|
||||
*/
|
||||
export const getOptions = <T extends Options>(): T => {
|
||||
try {
|
||||
return JSON.parse(document.getElementById('coder-options')!.getAttribute('data-settings')!);
|
||||
} catch (error) {
|
||||
return {} as T;
|
||||
}
|
||||
};
|
||||
|
||||
const options = getOptions();
|
||||
|
||||
const TELEMETRY_SECTION_ID = 'telemetry';
|
||||
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
|
||||
'id': TELEMETRY_SECTION_ID,
|
||||
'order': 110,
|
||||
'type': 'object',
|
||||
'title': localize('telemetryConfigurationTitle', 'Telemetry'),
|
||||
'properties': {
|
||||
'telemetry.enableTelemetry': {
|
||||
'type': 'boolean',
|
||||
'description': localize('telemetry.enableTelemetry', 'Enable usage data and errors to be sent to a Microsoft online service.'),
|
||||
'default': !options.disableTelemetry,
|
||||
'tags': ['usesOnlineServices']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
class NodeProxyService extends NodeProxyChannelClient implements INodeProxyService {
|
||||
private readonly _onClose = new Emitter<void>();
|
||||
public readonly onClose = this._onClose.event;
|
||||
private readonly _onDown = new Emitter<void>();
|
||||
public readonly onDown = this._onDown.event;
|
||||
private readonly _onUp = new Emitter<void>();
|
||||
public readonly onUp = this._onUp.event;
|
||||
|
||||
public constructor(
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
) {
|
||||
super(remoteAgentService.getConnection()!.getChannel('nodeProxy'));
|
||||
remoteAgentService.getConnection()!.onDidStateChange((state) => {
|
||||
switch (state.type) {
|
||||
case PersistentConnectionEventType.ConnectionGain:
|
||||
return this._onUp.fire();
|
||||
case PersistentConnectionEventType.ConnectionLost:
|
||||
return this._onDown.fire();
|
||||
case PersistentConnectionEventType.ReconnectionPermanentFailure:
|
||||
return this._onClose.fire();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(ILocalizationsService, LocalizationsService);
|
||||
registerSingleton(INodeProxyService, NodeProxyService);
|
||||
registerSingleton(ITelemetryService, TelemetryService);
|
||||
|
||||
/**
|
||||
* This is called by vs/workbench/browser/web.main.ts after the workbench has
|
||||
* been initialized so we can initialize our own client-side code.
|
||||
*/
|
||||
export const initialize = async (services: ServiceCollection): Promise<void> => {
|
||||
const event = new CustomEvent('ide-ready');
|
||||
window.dispatchEvent(event);
|
||||
|
||||
if (parent) {
|
||||
// Tell the parent loading has completed.
|
||||
parent.postMessage({ event: 'loaded' }, window.location.origin);
|
||||
|
||||
// Proxy or stop proxing events as requested by the parent.
|
||||
const listeners = new Map<string, (event: Event) => void>();
|
||||
window.addEventListener('message', (parentEvent) => {
|
||||
const eventName = parentEvent.data.bind || parentEvent.data.unbind;
|
||||
if (eventName) {
|
||||
const oldListener = listeners.get(eventName);
|
||||
if (oldListener) {
|
||||
document.removeEventListener(eventName, oldListener);
|
||||
}
|
||||
}
|
||||
|
||||
if (parentEvent.data.bind && parentEvent.data.prop) {
|
||||
const listener = (event: Event) => {
|
||||
parent.postMessage({
|
||||
event: parentEvent.data.event,
|
||||
[parentEvent.data.prop]: event[parentEvent.data.prop as keyof Event]
|
||||
}, window.location.origin);
|
||||
};
|
||||
listeners.set(parentEvent.data.bind, listener);
|
||||
document.addEventListener(parentEvent.data.bind, listener);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!window.isSecureContext) {
|
||||
(services.get(INotificationService) as INotificationService).notify({
|
||||
severity: Severity.Warning,
|
||||
message: 'code-server is being accessed over an insecure domain. Web views, the clipboard, and other functionality will not work as expected.',
|
||||
actions: {
|
||||
primary: [{
|
||||
id: 'understand',
|
||||
label: 'I understand',
|
||||
tooltip: '',
|
||||
class: undefined,
|
||||
enabled: true,
|
||||
checked: true,
|
||||
dispose: () => undefined,
|
||||
run: () => {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}],
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const logService = (services.get(ILogService) as ILogService);
|
||||
const storageService = (services.get(IStorageService) as IStorageService);
|
||||
const updateCheckEndpoint = path.join(options.base, '/update/check');
|
||||
const getUpdate = async (): Promise<void> => {
|
||||
logService.debug('Checking for update...');
|
||||
|
||||
const response = await fetch(updateCheckEndpoint, {
|
||||
headers: { 'Accept': 'application/json' },
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
const json = await response.json();
|
||||
if (json.error) {
|
||||
throw new Error(json.error);
|
||||
}
|
||||
if (json.isLatest) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lastNoti = storageService.getNumber('csLastUpdateNotification', StorageScope.GLOBAL);
|
||||
if (lastNoti) {
|
||||
// Only remind them again after 1 week.
|
||||
const timeout = 1000*60*60*24*7;
|
||||
const threshold = lastNoti + timeout;
|
||||
if (Date.now() < threshold) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
storageService.store('csLastUpdateNotification', Date.now(), StorageScope.GLOBAL);
|
||||
(services.get(INotificationService) as INotificationService).notify({
|
||||
severity: Severity.Info,
|
||||
message: `[code-server v${json.latest}](https://github.com/cdr/code-server/releases/tag/v${json.latest}) has been released!`,
|
||||
});
|
||||
};
|
||||
|
||||
const updateLoop = (): void => {
|
||||
getUpdate().catch((error) => {
|
||||
logService.debug(`failed to check for update: ${error}`);
|
||||
}).finally(() => {
|
||||
// Check again every 6 hours.
|
||||
setTimeout(updateLoop, 1000*60*60*6);
|
||||
});
|
||||
};
|
||||
|
||||
if (!options.disableUpdateCheck) {
|
||||
updateLoop();
|
||||
}
|
||||
|
||||
// This will be used to set the background color while VS Code loads.
|
||||
const theme = storageService.get('colorThemeData', StorageScope.GLOBAL);
|
||||
if (theme) {
|
||||
localStorage.setItem('colorThemeData', theme);
|
||||
}
|
||||
};
|
||||
|
||||
export interface Query {
|
||||
[key: string]: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a string up to the delimiter. If the delimiter doesn't exist the first
|
||||
* item will have all the text and the second item will be an empty string.
|
||||
*/
|
||||
export const split = (str: string, delimiter: string): [string, string] => {
|
||||
const index = str.indexOf(delimiter);
|
||||
return index !== -1 ? [str.substring(0, index).trim(), str.substring(index + 1)] : [str, ''];
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the URL modified with the specified query variables. It's pretty
|
||||
* stupid so it probably doesn't cover any edge cases. Undefined values will
|
||||
* unset existing values. Doesn't allow duplicates.
|
||||
*/
|
||||
export const withQuery = (url: string, replace: Query): string => {
|
||||
const uri = URI.parse(url);
|
||||
const query = { ...replace };
|
||||
uri.query.split('&').forEach((kv) => {
|
||||
const [key, value] = split(kv, '=');
|
||||
if (!(key in query)) {
|
||||
query[key] = value;
|
||||
}
|
||||
});
|
||||
return uri.with({
|
||||
query: Object.keys(query)
|
||||
.filter((k) => typeof query[k] !== 'undefined')
|
||||
.map((k) => `${k}=${query[k]}`).join('&'),
|
||||
}).toString(true);
|
||||
};
|
||||
Reference in New Issue
Block a user