src/vs/platform/userDataSync/common/userDataSyncStoreService.ts

761 LOC · 556 covered · 205 uncovered · 141 ranges · 585 concepts · 32 introducers · 348 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- abstractSynchronizer.ts ×49
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { CancelablePromise, createCancelablePromise, timeout } from '../../../base/common/async.js';
7 > import { CancellationToken } from '../../../base/common/cancellation.js';
8 > import { getErrorMessage, isCancellationError } from '../../../base/common/errors.js';
9 > import { Emitter, Event } from '../../../base/common/event.js';
10 > import { Disposable, DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';
11 > import { Mimes } from '../../../base/common/mime.js';
12 > import { isWeb } from '../../../base/common/platform.js';
13 > import { ConfigurationSyncStore } from '../../../base/common/product.js';
14 > import { joinPath, relativePath } from '../../../base/common/resources.js';
15 > import { isObject, isString } from '../../../base/common/types.js';
16 > import { URI } from '../../../base/common/uri.js';
17 > import { generateUuid } from '../../../base/common/uuid.js';
18 > import { IHeaders, IRequestContext, IRequestOptions } from '../../../base/parts/request/common/request.js';
19 > import { IConfigurationService } from '../../configuration/common/configuration.js';
20 > import { IEnvironmentService } from '../../environment/common/environment.js';
21 > import { IFileService } from '../../files/common/files.js';
22 > import { IProductService } from '../../product/common/productService.js';
23 > import { asJson, asText, asTextOrError, hasNoContent, IRequestService, isSuccess, isSuccess as isSuccessContext } from '../../request/common/request.js';
24 > import { getServiceMachineId } from '../../externalServices/common/serviceMachineId.js';
25 > import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';
26 > import { HEADER_EXECUTION_ID, HEADER_OPERATION_ID, IAuthenticationProvider, IResourceRefHandle, IUserData, IUserDataManifest, IUserDataSyncLatestData, IUserDataSyncLogService, IUserDataSyncStore, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, ServerResource, SYNC_SERVICE_URL_TYPE, UserDataSyncErrorCode, UserDataSyncStoreError, UserDataSyncStoreType } from './userDataSync.js';
27 > import { VSBufferReadableStream } from '../../../base/common/buffer.js';
28 > import { IStringDictionary } from '../../../base/common/collections.js';
29 >
30 > type IDownloadLatestDataType = {
31 > resources?: {
32 > [resourceId: string]: [IUserData];
33 > };
34 > collections?: {
35 > [collectionId: string]: {
36 > resources?: {
37 > [resourceId: string]: [IUserData];
38 > } | undefined;
39 > };
40 > };
41 > };
42 >
43 > const CONFIGURATION_SYNC_STORE_KEY = 'configurationSync.store';
44 > const SYNC_PREVIOUS_STORE = 'sync.previous.store';
45 > const DONOT_MAKE_REQUESTS_UNTIL_KEY = 'sync.donot-make-requests-until';
46 > const USER_SESSION_ID_KEY = 'sync.user-session-id';
47 > const MACHINE_SESSION_ID_KEY = 'sync.machine-session-id';
48 > const REQUEST_SESSION_LIMIT = 100;
49 > const REQUEST_SESSION_INTERVAL = 1000 * 60 * 5; /* 5 minutes */
50 >
51 > type UserDataSyncStore = IUserDataSyncStore & { defaultType: UserDataSyncStoreType };
52 >
53 > export abstract class AbstractUserDataSyncStoreManagementService extends Disposable implements IUserDataSyncStoreManagementService {
54 >
55 > _serviceBrand: undefined;
56 >
57 > private readonly _onDidChangeUserDataSyncStore = this._register(new Emitter<void>());
58 > readonly onDidChangeUserDataSyncStore = this._onDidChangeUserDataSyncStore.event;
59 > private _userDataSyncStore: UserDataSyncStore | undefined;
60 > get userDataSyncStore(): UserDataSyncStore | undefined { return this._userDataSyncStore; }
61 >
62 > protected get userDataSyncStoreType(): UserDataSyncStoreType | undefined {
63 return this.storageService.get(SYNC_SERVICE_URL_TYPE, StorageScope.APPLICATION) as UserDataSyncStoreType;
64 }
65 > protected set userDataSyncStoreType(type: UserDataSyncStoreType | undefined) { abstractSynchronizer.ts ×49
66 this.storageService.store(SYNC_SERVICE_URL_TYPE, type, StorageScope.APPLICATION, isWeb ? StorageTarget.USER /* sync in web */ : StorageTarget.MACHINE);
67 }
69 > constructor(
70 > @IProductService protected readonly productService: IProductService, userDataSyncStoreService.ts ×36
71 > @IConfigurationService protected readonly configurationService: IConfigurationService,
72 > @IStorageService protected readonly storageService: IStorageService,
73 > ) {
74 > super();
75 > this.updateUserDataSyncStore();
76 > const disposable = this._register(new DisposableStore());
77 > this._register(Event.filter(storageService.onDidChangeValue(StorageScope.APPLICATION, SYNC_SERVICE_URL_TYPE, disposable), () => this.userDataSyncStoreType !== this.userDataSyncStore?.type, disposable)(() => this.updateUserDataSyncStore()));
78 > }
80 > protected updateUserDataSyncStore(): void {
81 > this._userDataSyncStore = this.toUserDataSyncStore(this.productService[CONFIGURATION_SYNC_STORE_KEY]); userDataSyncStoreService.ts ×36
82 > this._onDidChangeUserDataSyncStore.fire();
83 > }
85 > protected toUserDataSyncStore(configurationSyncStore: ConfigurationSyncStore & { web?: ConfigurationSyncStore } | undefined): UserDataSyncStore | undefined {
86 > if (!configurationSyncStore) { userDataSyncStoreService.ts ×36
87 return undefined;
88 }
89 > // Check for web overrides for backward compatibility while reading previous store userDataSyncStoreService.ts ×36
90 > configurationSyncStore = isWeb && configurationSyncStore.web ? { ...configurationSyncStore, ...configurationSyncStore.web } : configurationSyncStore;
91 > if (isString(configurationSyncStore.url)
92 > && isObject(configurationSyncStore.authenticationProviders)
93 > && Object.keys(configurationSyncStore.authenticationProviders).every(authenticationProviderId => Array.isArray(configurationSyncStore.authenticationProviders[authenticationProviderId].scopes))
94 > ) {
95 > const syncStore = configurationSyncStore as ConfigurationSyncStore;
96 > const canSwitch = !!syncStore.canSwitch;
97 > const defaultType: UserDataSyncStoreType = syncStore.url === syncStore.insidersUrl ? 'insiders' : 'stable';
98 > const type: UserDataSyncStoreType = (canSwitch ? this.userDataSyncStoreType : undefined) || defaultType;
99 > const url = type === 'insiders' ? syncStore.insidersUrl
100 : type === 'stable' ? syncStore.stableUrl
101 : syncStore.url;
103 > url: URI.parse(url),
104 > type,
105 > defaultType,
106 > defaultUrl: URI.parse(syncStore.url),
107 > stableUrl: URI.parse(syncStore.stableUrl),
108 > insidersUrl: URI.parse(syncStore.insidersUrl),
109 > canSwitch,
110 > authenticationProviders: Object.keys(syncStore.authenticationProviders).reduce<IAuthenticationProvider[]>((result, id) => {
111 > result.push({ id, scopes: syncStore.authenticationProviders[id].scopes });
112 > return result;
113 > }, [])
114 > };
115 > }
116 return undefined;
119 > abstract switch(type: UserDataSyncStoreType): Promise<void>;
120 > abstract getPreviousUserDataSyncStore(): Promise<IUserDataSyncStore | undefined>;
121 >
122 > }
123 >
124 > export class UserDataSyncStoreManagementService extends AbstractUserDataSyncStoreManagementService implements IUserDataSyncStoreManagementService {
125 >
126 > private readonly previousConfigurationSyncStore: ConfigurationSyncStore | undefined;
127 >
128 > constructor(
129 > @IProductService productService: IProductService, userDataSyncStoreService.ts ×36
130 > @IConfigurationService configurationService: IConfigurationService,
131 > @IStorageService storageService: IStorageService,
132 > ) {
133 > super(productService, configurationService, storageService);
134 >
135 > const previousConfigurationSyncStore = this.storageService.get(SYNC_PREVIOUS_STORE, StorageScope.APPLICATION);
136 > if (previousConfigurationSyncStore) {
137 this.previousConfigurationSyncStore = JSON.parse(previousConfigurationSyncStore);
138 }
140 > const syncStore = this.productService[CONFIGURATION_SYNC_STORE_KEY];
141 > if (syncStore) {
142 > this.storageService.store(SYNC_PREVIOUS_STORE, JSON.stringify(syncStore), StorageScope.APPLICATION, StorageTarget.MACHINE);
143 > } else {
144 this.storageService.remove(SYNC_PREVIOUS_STORE, StorageScope.APPLICATION);
145 }
148 > async switch(type: UserDataSyncStoreType): Promise<void> {
149 if (type !== this.userDataSyncStoreType) {
150 this.userDataSyncStoreType = type;
151 this.updateUserDataSyncStore();
152 }
153 }
155 > async getPreviousUserDataSyncStore(): Promise<IUserDataSyncStore | undefined> {
156 return this.toUserDataSyncStore(this.previousConfigurationSyncStore);
157 }
159 >
160 > export class UserDataSyncStoreClient extends Disposable {
161 >
162 > private userDataSyncStoreUrl: URI | undefined;
163 >
164 > private authToken: { token: string; type: string } | undefined;
165 > private readonly commonHeadersPromise: Promise<IHeaders>;
166 > private readonly session: RequestsSession;
167 >
168 > private _onTokenFailed = this._register(new Emitter<UserDataSyncErrorCode>());
169 > readonly onTokenFailed = this._onTokenFailed.event;
170 >
171 > private _onTokenSucceed: Emitter<void> = this._register(new Emitter<void>());
172 > readonly onTokenSucceed: Event<void> = this._onTokenSucceed.event;
173 >
174 > private _donotMakeRequestsUntil: Date | undefined = undefined;
175 > get donotMakeRequestsUntil() { return this._donotMakeRequestsUntil; }
176 > private _onDidChangeDonotMakeRequestsUntil = this._register(new Emitter<void>());
177 > readonly onDidChangeDonotMakeRequestsUntil = this._onDidChangeDonotMakeRequestsUntil.event;
178 >
179 > constructor(
180 > userDataSyncStoreUrl: URI | undefined, userDataSyncStoreService.ts ×36
181 > @IProductService productService: IProductService,
182 > @IRequestService private readonly requestService: IRequestService,
183 > @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
184 > @IEnvironmentService environmentService: IEnvironmentService,
185 > @IFileService fileService: IFileService,
186 > @IStorageService private readonly storageService: IStorageService,
187 > ) {
188 > super();
189 > this.updateUserDataSyncStoreUrl(userDataSyncStoreUrl);
190 > this.commonHeadersPromise = getServiceMachineId(environmentService, fileService, storageService)
191 > .then(uuid => {
192 > const headers: IHeaders = {
193 > 'X-Client-Name': `${productService.applicationName}${isWeb ? '-web' : ''}`,
194 > 'X-Client-Version': productService.version,
195 > };
196 > if (productService.commit) {
197 headers['X-Client-Commit'] = productService.commit;
198 }
199 > return headers; userDataSyncStoreService.ts ×36
200 > });
201 >
202 > /* A requests session that limits requests per sessions */
203 > this.session = new RequestsSession(REQUEST_SESSION_LIMIT, REQUEST_SESSION_INTERVAL, this.requestService, this.logService);
204 > this.initDonotMakeRequestsUntil();
205 > this._register(toDisposable(() => {
206 > if (this.resetDonotMakeRequestsUntilPromise) {
207 > this.resetDonotMakeRequestsUntilPromise.cancel(); userDataSyncStoreService.ts ×1
208 > this.resetDonotMakeRequestsUntilPromise = undefined;
209 > }
211 > }
213 > setAuthToken(token: string, type: string): void {
214 > this.authToken = { token, type }; userDataSyncStoreService.ts ×36
215 > }
217 > protected updateUserDataSyncStoreUrl(userDataSyncStoreUrl: URI | undefined): void {
218 > this.userDataSyncStoreUrl = userDataSyncStoreUrl ? joinPath(userDataSyncStoreUrl, 'v1') : undefined; userDataSyncStoreService.ts ×36
219 > }
221 > private initDonotMakeRequestsUntil(): void {
222 > const donotMakeRequestsUntil = this.storageService.getNumber(DONOT_MAKE_REQUESTS_UNTIL_KEY, StorageScope.APPLICATION); userDataSyncStoreService.ts ×36
223 > if (donotMakeRequestsUntil && Date.now() < donotMakeRequestsUntil) {
224 > this.setDonotMakeRequestsUntil(new Date(donotMakeRequestsUntil)); userDataSyncStoreService.ts ×1
225 > }
228 > private resetDonotMakeRequestsUntilPromise: CancelablePromise<void> | undefined = undefined;
229 > private setDonotMakeRequestsUntil(donotMakeRequestsUntil: Date | undefined): void {
230 > if (this._donotMakeRequestsUntil?.getTime() !== donotMakeRequestsUntil?.getTime()) { userDataSyncStoreService.ts ×36
231 > this._donotMakeRequestsUntil = donotMakeRequestsUntil; userDataSyncStoreService.ts ×4
232 >
233 > if (this.resetDonotMakeRequestsUntilPromise) {
234 > this.resetDonotMakeRequestsUntilPromise.cancel(); userDataSyncStoreService.ts ×2
235 > this.resetDonotMakeRequestsUntilPromise = undefined;
236 > }
238 > if (this._donotMakeRequestsUntil) {
239 > this.storageService.store(DONOT_MAKE_REQUESTS_UNTIL_KEY, this._donotMakeRequestsUntil.getTime(), StorageScope.APPLICATION, StorageTarget.MACHINE);
240 > this.resetDonotMakeRequestsUntilPromise = createCancelablePromise(token => timeout(this._donotMakeRequestsUntil!.getTime() - Date.now(), token).then(() => this.setDonotMakeRequestsUntil(undefined)));
241 > this.resetDonotMakeRequestsUntilPromise.then(null, e => null /* ignore error */);
242 > } else {
243 > this.storageService.remove(DONOT_MAKE_REQUESTS_UNTIL_KEY, StorageScope.APPLICATION); userDataSyncStoreService.ts ×2
244 > }
246 > this._onDidChangeDonotMakeRequestsUntil.fire();
247 > }
250 > // #region Collection
251 >
252 > async getAllCollections(headers: IHeaders = {}): Promise<string[]> {
253 if (!this.userDataSyncStoreUrl) {
254 throw new Error('No settings sync store url configured.');
255 }
256
257 const url = joinPath(this.userDataSyncStoreUrl, 'collection').toString();
258 headers = { ...headers };
259 headers['Content-Type'] = 'application/json';
260
261 const context = await this.request(url, { type: 'GET', headers, callSite: 'userDataSync.getAllCollections' }, [], CancellationToken.None);
262
263 return (await asJson<{ id: string }[]>(context))?.map(({ id }) => id) || [];
264 }
266 > async createCollection(headers: IHeaders = {}): Promise<string> {
267 > if (!this.userDataSyncStoreUrl) { userDataProfilesManifestSync.ts ×14
268 throw new Error('No settings sync store url configured.');
269 }
271 > const url = joinPath(this.userDataSyncStoreUrl, 'collection').toString();
272 > headers = { ...headers };
273 > headers['Content-Type'] = Mimes.text;
274 >
275 > const context = await this.request(url, { type: 'POST', headers, callSite: 'userDataSync.createCollection' }, [], CancellationToken.None);
276 > const collectionId = await asTextOrError(context);
277 > if (!collectionId) {
278 throw new UserDataSyncStoreError('Server did not return the collection id', url, UserDataSyncErrorCode.NoCollection, context.res.statusCode, context.res.headers[HEADER_OPERATION_ID]);
279 }
280 > return collectionId; userDataProfilesManifestSync.ts ×14
281 > }
283 > async deleteCollection(collection?: string, headers: IHeaders = {}): Promise<void> {
284 > if (!this.userDataSyncStoreUrl) { userDataSyncStoreService.ts ×6
285 throw new Error('No settings sync store url configured.');
286 }
288 > const url = collection ? joinPath(this.userDataSyncStoreUrl, 'collection', collection).toString() : joinPath(this.userDataSyncStoreUrl, 'collection').toString();
289 > headers = { ...headers };
290 >
291 > await this.request(url, { type: 'DELETE', headers, callSite: 'userDataSync.deleteCollection' }, [], CancellationToken.None);
292 > }
294 > // #endregion
295 >
296 > // #region Resource
297 >
298 > async getAllResourceRefs(resource: ServerResource, collection?: string): Promise<IResourceRefHandle[]> {
299 if (!this.userDataSyncStoreUrl) {
300 throw new Error('No settings sync store url configured.');
301 }
302
303 const uri = this.getResourceUrl(this.userDataSyncStoreUrl, collection, resource);
304 const headers: IHeaders = {};
305
306 const context = await this.request(uri.toString(), { type: 'GET', headers, callSite: 'userDataSync.getAllResourceRefs' }, [], CancellationToken.None);
307
308 const result = await asJson<{ url: string; created: number }[]>(context) || [];
309 return result.map(({ url, created }) => ({ ref: relativePath(uri, uri.with({ path: url }))!, created: created * 1000 /* Server returns in seconds */ }));
310 }
312 > async resolveResourceContent(resource: ServerResource, ref: string, collection?: string, headers: IHeaders = {}): Promise<string | null> {
313 > if (!this.userDataSyncStoreUrl) { userDataSyncStoreService.ts ×3
314 throw new Error('No settings sync store url configured.');
315 }
317 > const url = joinPath(this.getResourceUrl(this.userDataSyncStoreUrl, collection, resource), ref).toString();
318 > headers = { ...headers };
319 > headers['Cache-Control'] = 'no-cache';
320 >
321 > const context = await this.request(url, { type: 'GET', headers, callSite: 'userDataSync.resolveResourceContent' }, [], CancellationToken.None);
322 > const content = await asTextOrError(context); userDataSyncStoreService.ts ×1
323 > return content;
326 > async deleteResource(resource: ServerResource, ref: string | null, collection?: string): Promise<void> {
327 > if (!this.userDataSyncStoreUrl) { userDataSyncClient.ts ×4
328 throw new Error('No settings sync store url configured.');
329 }
331 > const url = ref !== null ? joinPath(this.getResourceUrl(this.userDataSyncStoreUrl, collection, resource), ref).toString() : this.getResourceUrl(this.userDataSyncStoreUrl, collection, resource).toString();
332 > const headers: IHeaders = {};
333 >
334 > await this.request(url, { type: 'DELETE', headers, callSite: 'userDataSync.deleteResource' }, [], CancellationToken.None);
335 > }
337 > async deleteResources(): Promise<void> {
338 > if (!this.userDataSyncStoreUrl) { userDataSyncStoreService.ts ×6
339 throw new Error('No settings sync store url configured.');
340 }
342 > const url = joinPath(this.userDataSyncStoreUrl, 'resource').toString();
343 > const headers: IHeaders = { 'Content-Type': Mimes.text };
344 >
345 > await this.request(url, { type: 'DELETE', headers, callSite: 'userDataSync.deleteResources' }, [], CancellationToken.None);
346 > }
348 > async readResource(resource: ServerResource, oldValue: IUserData | null, collection?: string, headers: IHeaders = {}): Promise<IUserData> {
349 > if (!this.userDataSyncStoreUrl) { userDataSyncStoreService.ts ×6
350 throw new Error('No settings sync store url configured.');
351 }
353 > const url = joinPath(this.getResourceUrl(this.userDataSyncStoreUrl, collection, resource), 'latest').toString();
354 > headers = { ...headers };
355 > // Disable caching as they are cached by synchronisers
356 > headers['Cache-Control'] = 'no-cache';
357 > if (oldValue) {
358 > headers['If-None-Match'] = oldValue.ref; userDataSyncStoreService.ts ×1
359 > }
361 > const context = await this.request(url, { type: 'GET', headers, callSite: 'userDataSync.readResource' }, [304], CancellationToken.None);
362 >
363 > let userData: IUserData | null = null;
364 > if (context.res.statusCode === 304) {
365 > userData = oldValue; userDataSyncStoreService.ts ×1
366 > }
368 > if (userData === null) {
369 > const ref = context.res.headers['etag'];
370 > if (!ref) {
371 throw new UserDataSyncStoreError('Server did not return the ref', url, UserDataSyncErrorCode.NoRef, context.res.statusCode, context.res.headers[HEADER_OPERATION_ID]);
372 }
374 > const content = await asTextOrError(context);
375 > if (!content && context.res.statusCode === 304) {
376 throw new UserDataSyncStoreError('Empty response', url, UserDataSyncErrorCode.EmptyResponse, context.res.statusCode, context.res.headers[HEADER_OPERATION_ID]);
377 }
379 > userData = { ref, content };
380 > }
381 >
382 > return userData;
383 > }
385 > async writeResource(resource: ServerResource, data: string, ref: string | null, collection?: string, headers: IHeaders = {}): Promise<string> {
386 > if (!this.userDataSyncStoreUrl) { userDataSyncClient.ts ×5
387 throw new Error('No settings sync store url configured.');
388 }
390 > const url = this.getResourceUrl(this.userDataSyncStoreUrl, collection, resource).toString();
391 > headers = { ...headers };
392 > headers['Content-Type'] = Mimes.text;
393 > if (ref) {
394 > headers['If-Match'] = ref; abstractSynchronizer.ts ×2
395 > }
397 > const context = await this.request(url, { type: 'POST', data, headers, callSite: 'userDataSync.writeResource' }, [], CancellationToken.None);
398 >
399 > const newRef = context.res.headers['etag'];
400 > if (!newRef) {
401 throw new UserDataSyncStoreError('Server did not return the ref', url, UserDataSyncErrorCode.NoRef, context.res.statusCode, context.res.headers[HEADER_OPERATION_ID]);
402 }
403 > return newRef; userDataSyncClient.ts ×5
404 > }
406 > // #endregion
407 >
408 > async manifest(oldValue: IUserDataManifest | null, headers: IHeaders = {}): Promise<IUserDataManifest | null> {
409 > if (!this.userDataSyncStoreUrl) { userDataSyncStoreService.ts ×10
410 throw new Error('No settings sync store url configured.');
411 }
413 > const url = joinPath(this.userDataSyncStoreUrl, 'manifest').toString();
414 > headers = { ...headers };
415 > headers['Content-Type'] = 'application/json';
416 > if (oldValue) {
417 > headers['If-None-Match'] = oldValue.ref; userDataSyncStoreService.ts ×1
418 > }
420 > const context = await this.request(url, { type: 'GET', headers, callSite: 'userDataSync.manifest' }, [304], CancellationToken.None);
421 >
422 > let manifest: IUserDataManifest | null = null;
423 > if (context.res.statusCode === 304) {
424 manifest = oldValue;
425 }
427 > if (!manifest) {
428 > const ref = context.res.headers['etag'];
429 > if (!ref) {
430 throw new UserDataSyncStoreError('Server did not return the ref', url, UserDataSyncErrorCode.NoRef, context.res.statusCode, context.res.headers[HEADER_OPERATION_ID]);
431 }
433 > const content = await asTextOrError(context);
434 > if (!content && context.res.statusCode === 304) {
435 throw new UserDataSyncStoreError('Empty response', url, UserDataSyncErrorCode.EmptyResponse, context.res.statusCode, context.res.headers[HEADER_OPERATION_ID]);
436 }
438 > if (content) {
439 > manifest = { ...JSON.parse(content), ref }; userDataSyncStoreService.ts ×2
440 > }
442 >
443 > const currentSessionId = this.storageService.get(USER_SESSION_ID_KEY, StorageScope.APPLICATION);
444 >
445 > if (currentSessionId && manifest && currentSessionId !== manifest.session) {
446 > // Server session is different from client session so clear cached session. userDataSyncStoreService.ts ×1
447 > this.clearSession();
448 > }
450 > if (manifest === null && currentSessionId) {
451 > // server session is cleared so clear cached session. userDataSyncStoreService.ts ×1
452 > this.clearSession();
453 > }
455 > if (manifest) {
456 > // update session userDataSyncStoreService.ts ×2
457 > this.storageService.store(USER_SESSION_ID_KEY, manifest.session, StorageScope.APPLICATION, StorageTarget.MACHINE);
458 > }
460 > return manifest;
461 > }
463 > async clear(): Promise<void> {
464 > if (!this.userDataSyncStoreUrl) { userDataSyncStoreService.ts ×6
465 throw new Error('No settings sync store url configured.');
466 }
468 > await this.deleteCollection();
469 > await this.deleteResources();
470 >
471 > // clear cached session.
472 > this.clearSession();
473 > }
475 > async getLatestData(headers: IHeaders = {}): Promise<IUserDataSyncLatestData | null> {
476 if (!this.userDataSyncStoreUrl) {
477 throw new Error('No settings sync store url configured.');
478 }
479
480 const url = joinPath(this.userDataSyncStoreUrl, 'download', 'latest').toString();
481
482 headers = { ...headers };
483 headers['Content-Type'] = 'application/json';
484 const context = await this.request(url, { type: 'GET', headers, callSite: 'userDataSync.getLatestData' }, [], CancellationToken.None);
485
486 if (!isSuccess(context)) {
487 throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, url, UserDataSyncErrorCode.EmptyResponse, context.res.statusCode, context.res.headers[HEADER_OPERATION_ID]);
488 }
489
490 const serverData = await asJson<IDownloadLatestDataType>(context);
491 if (!serverData) {
492 return null;
493 }
494
495 const result: IUserDataSyncLatestData = {};
496 if (serverData.resources) {
497 result.resources = {};
498 for (const resource in serverData.resources) {
499 const [resourceData] = serverData.resources[resource];
500 result.resources[resource] = {
501 content: resourceData.content,
502 ref: resourceData.ref
503 };
504 }
505 }
506
507 if (serverData.collections) {
508 result.collections = {};
509 for (const collection in serverData.collections) {
510 const resources: IStringDictionary<IUserData> = {};
511 result.collections[collection] = { resources };
512 for (const resource in serverData.collections[collection].resources) {
513 const [resourceData] = serverData.collections[collection].resources[resource];
514 resources[resource] = {
515 content: resourceData.content,
516 ref: resourceData.ref
517 };
518 }
519 }
520 }
521
522 return result;
523 }
525 > async getActivityData(): Promise<VSBufferReadableStream> {
526 if (!this.userDataSyncStoreUrl) {
527 throw new Error('No settings sync store url configured.');
528 }
529
530 const url = joinPath(this.userDataSyncStoreUrl, 'download').toString();
531 const headers: IHeaders = {};
532
533 const context = await this.request(url, { type: 'GET', headers, callSite: 'userDataSync.getActivityData' }, [], CancellationToken.None);
534
535 if (!isSuccess(context)) {
536 throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, url, UserDataSyncErrorCode.EmptyResponse, context.res.statusCode, context.res.headers[HEADER_OPERATION_ID]);
537 }
538
539 if (hasNoContent(context)) {
540 throw new UserDataSyncStoreError('Empty response', url, UserDataSyncErrorCode.EmptyResponse, context.res.statusCode, context.res.headers[HEADER_OPERATION_ID]);
541 }
542
543 return context.stream;
544 }
546 > private getResourceUrl(userDataSyncStoreUrl: URI, collection: string | undefined, resource: ServerResource): URI {
547 > return collection ? joinPath(userDataSyncStoreUrl, 'collection', collection, 'resource', resource) : joinPath(userDataSyncStoreUrl, 'resource', resource); userDataSyncStoreService.ts ×1
548 > }
550 > private clearSession(): void {
551 > this.storageService.remove(USER_SESSION_ID_KEY, StorageScope.APPLICATION); userDataSyncStoreService.ts ×1
552 > this.storageService.remove(MACHINE_SESSION_ID_KEY, StorageScope.APPLICATION);
553 > }
555 > private async request(url: string, options: IRequestOptions, successCodes: number[], token: CancellationToken): Promise<IRequestContext> {
556 > if (!this.authToken) { userDataSyncStoreService.ts ×36
557 throw new UserDataSyncStoreError('No Auth Token Available', url, UserDataSyncErrorCode.Unauthorized, undefined, undefined);
558 }
560 > if (this._donotMakeRequestsUntil && Date.now() < this._donotMakeRequestsUntil.getTime()) {
561 throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because of too many requests (429).`, url, UserDataSyncErrorCode.TooManyRequestsAndRetryAfter, undefined, undefined);
562 }
563 > this.setDonotMakeRequestsUntil(undefined); userDataSyncStoreService.ts ×36
564 >
565 > const commonHeaders = await this.commonHeadersPromise;
566 > options.headers = {
567 > ...(options.headers || {}),
568 > ...commonHeaders,
569 > 'X-Account-Type': this.authToken.type,
570 > 'authorization': `Bearer ${this.authToken.token}`,
571 > };
572 >
573 > // Add session headers
574 > this.addSessionHeaders(options.headers);
575 >
576 > this.logService.trace('Sending request to server', { url, type: options.type, headers: { ...options.headers, ...{ authorization: undefined } } });
577 >
578 > let context;
579 > try {
580 > context = await this.session.request(url, options, token);
581 > } catch (e) {
582 if (!(e instanceof UserDataSyncStoreError)) {
583 let code = UserDataSyncErrorCode.RequestFailed;
584 const errorMessage = getErrorMessage(e).toLowerCase();
585
586 // Request timed out
587 if (errorMessage.includes('xhr timeout')) {
588 code = UserDataSyncErrorCode.RequestTimeout;
589 }
590
591 // Request protocol not supported
592 else if (errorMessage.includes('protocol') && errorMessage.includes('not supported')) {
593 code = UserDataSyncErrorCode.RequestProtocolNotSupported;
594 }
595
596 // Request path not escaped
597 else if (errorMessage.includes('request path contains unescaped characters')) {
598 code = UserDataSyncErrorCode.RequestPathNotEscaped;
599 }
600
601 // Request header not an object
602 else if (errorMessage.includes('headers must be an object')) {
603 code = UserDataSyncErrorCode.RequestHeadersNotObject;
604 }
605
606 // Request canceled
607 else if (isCancellationError(e)) {
608 code = UserDataSyncErrorCode.RequestCanceled;
609 }
610
611 e = new UserDataSyncStoreError(`Connection refused for the request '${url}'.`, url, code, undefined, undefined);
612 }
613 this.logService.info('Request failed', url);
614 throw e;
615 }
617 > const operationId = context.res.headers[HEADER_OPERATION_ID];
618 > const requestInfo = { url, status: context.res.statusCode, 'execution-id': options.headers[HEADER_EXECUTION_ID], 'operation-id': operationId };
619 > const isSuccess = isSuccessContext(context) || (context.res.statusCode && successCodes.includes(context.res.statusCode));
620 > let failureMessage = '';
621 > if (isSuccess) {
622 > this.logService.trace('Request succeeded', requestInfo);
623 > } else {
624 > failureMessage = await asText(context) || ''; userDataSyncStoreService.ts ×1
625 > this.logService.info('Request failed', requestInfo, failureMessage);
626 > }
628 > if (context.res.statusCode === 401 || context.res.statusCode === 403) {
629 this.authToken = undefined;
630 if (context.res.statusCode === 401) {
631 this._onTokenFailed.fire(UserDataSyncErrorCode.Unauthorized);
632 throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because of Unauthorized (401).`, url, UserDataSyncErrorCode.Unauthorized, context.res.statusCode, operationId);
633 }
634 if (context.res.statusCode === 403) {
635 this._onTokenFailed.fire(UserDataSyncErrorCode.Forbidden);
636 throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because the access is forbidden (403).`, url, UserDataSyncErrorCode.Forbidden, context.res.statusCode, operationId);
637 }
638 }
640 > this._onTokenSucceed.fire();
641 >
642 > if (context.res.statusCode === 404) {
643 > throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because the requested resource is not found (404).`, url, UserDataSyncErrorCode.NotFound, context.res.statusCode, operationId); abstractSynchronizer.ts ×3
644 > }
646 > if (context.res.statusCode === 405) {
647 throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because the requested endpoint is not found (405). ${failureMessage}`, url, UserDataSyncErrorCode.MethodNotFound, context.res.statusCode, operationId);
648 }
650 > if (context.res.statusCode === 409) {
651 throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because of Conflict (409). There is new data for this resource. Make the request again with latest data.`, url, UserDataSyncErrorCode.Conflict, context.res.statusCode, operationId);
652 }
654 > if (context.res.statusCode === 410) {
655 throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because the requested resource is not longer available (410).`, url, UserDataSyncErrorCode.Gone, context.res.statusCode, operationId);
656 }
658 > if (context.res.statusCode === 412) {
659 > throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because of Precondition Failed (412). There is new data for this resource. Make the request again with latest data.`, url, UserDataSyncErrorCode.PreconditionFailed, context.res.statusCode, operationId); abstractSynchronizer.ts ×1
660 > }
662 > if (context.res.statusCode === 413) {
663 throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because of too large payload (413).`, url, UserDataSyncErrorCode.TooLarge, context.res.statusCode, operationId);
664 }
666 > if (context.res.statusCode === 426) {
667 throw new UserDataSyncStoreError(`${options.type} request '${url}' failed with status Upgrade Required (426). Please upgrade the client and try again.`, url, UserDataSyncErrorCode.UpgradeRequired, context.res.statusCode, operationId);
668 }
670 > if (context.res.statusCode === 429) {
671 > const retryAfter = context.res.headers['retry-after']; userDataSyncStoreService.ts ×3
672 > if (retryAfter) {
673 > this.setDonotMakeRequestsUntil(new Date(Date.now() + (parseInt(retryAfter) * 1000))); userDataSyncStoreService.ts ×4
674 > throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because of too many requests (429).`, url, UserDataSyncErrorCode.TooManyRequestsAndRetryAfter, context.res.statusCode, operationId);
676 > throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because of too many requests (429).`, url, UserDataSyncErrorCode.TooManyRequests, context.res.statusCode, operationId); userDataAutoSyncService.ts ×6
677 > }
680 > if (!isSuccess) {
681 throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, url, UserDataSyncErrorCode.Unknown, context.res.statusCode, operationId);
682 }
684 > return context;
685 > }
687 > private addSessionHeaders(headers: IHeaders): void {
688 > let machineSessionId = this.storageService.get(MACHINE_SESSION_ID_KEY, StorageScope.APPLICATION); userDataSyncStoreService.ts ×36
689 > if (machineSessionId === undefined) {
690 > machineSessionId = generateUuid();
691 > this.storageService.store(MACHINE_SESSION_ID_KEY, machineSessionId, StorageScope.APPLICATION, StorageTarget.MACHINE);
692 > }
693 > headers['X-Machine-Session-Id'] = machineSessionId;
694 >
695 > const userSessionId = this.storageService.get(USER_SESSION_ID_KEY, StorageScope.APPLICATION);
696 > if (userSessionId !== undefined) {
697 > headers['X-User-Session-Id'] = userSessionId; userDataSyncStoreService.ts ×1
698 > }
701 > }
702 >
703 > export class UserDataSyncStoreService extends UserDataSyncStoreClient implements IUserDataSyncStoreService {
704 >
705 > _serviceBrand: undefined;
706 >
707 > constructor(
708 > @IUserDataSyncStoreManagementService userDataSyncStoreManagementService: IUserDataSyncStoreManagementService, userDataSyncStoreService.ts ×36
709 > @IProductService productService: IProductService,
710 > @IRequestService requestService: IRequestService,
711 > @IUserDataSyncLogService logService: IUserDataSyncLogService,
712 > @IEnvironmentService environmentService: IEnvironmentService,
713 > @IFileService fileService: IFileService,
714 > @IStorageService storageService: IStorageService,
715 > ) {
716 > super(userDataSyncStoreManagementService.userDataSyncStore?.url, productService, requestService, logService, environmentService, fileService, storageService);
717 > this._register(userDataSyncStoreManagementService.onDidChangeUserDataSyncStore(() => this.updateUserDataSyncStoreUrl(userDataSyncStoreManagementService.userDataSyncStore?.url)));
718 > }
720 > }
721 >
722 > export class RequestsSession {
723 >
724 > private requests: string[] = [];
725 > private startTime: Date | undefined = undefined;
726 >
727 > constructor(
728 > private readonly limit: number, userDataSyncStoreService.ts ×5
729 > private readonly interval: number, /* in ms */
730 > private readonly requestService: IRequestService,
731 > private readonly logService: IUserDataSyncLogService,
732 > ) { }
734 > request(url: string, options: IRequestOptions, token: CancellationToken): Promise<IRequestContext> {
735 > if (this.isExpired()) { userDataSyncStoreService.ts ×5
736 > this.reset(); userDataSyncStoreService.ts ×2
737 > }
739 > options.url = url;
740 >
741 > if (this.requests.length >= this.limit) {
742 > this.logService.info('Too many requests', ...this.requests); userDataSyncStoreService.ts ×1
743 > throw new UserDataSyncStoreError(`Too many requests. Only ${this.limit} requests allowed in ${this.interval / (1000 * 60)} minutes.`, url, UserDataSyncErrorCode.LocalTooManyRequests, undefined, undefined);
744 > }
746 > this.startTime = this.startTime || new Date();
747 > this.requests.push(url);
748 >
749 > return this.requestService.request(options, token);
750 > }
752 > private isExpired(): boolean {
753 > return this.startTime !== undefined && new Date().getTime() - this.startTime.getTime() > this.interval; userDataSyncStoreService.ts ×5
754 > }
756 > private reset(): void {
757 > this.requests = []; userDataSyncStoreService.ts ×2
758 > this.startTime = undefined;
759 > }
761 > }