33 * Licensed under the MIT License. See License.txt in the project root for license information.
44 *--------------------------------------------------------------------------------------------*/
55
6+ import { localize } from 'vs/nls' ;
7+ import { tmpdir } from 'os' ;
8+ import * as path from 'path' ;
69import { TPromise } from 'vs/base/common/winjs.base' ;
7- import { IGalleryExtension , IExtensionGalleryService , IGalleryVersion , IQueryOptions , SortBy , SortOrder } from 'vs/platform/extensionManagement/common/extensionManagement' ;
10+ import { IGalleryExtension , IExtensionGalleryService , IQueryOptions , SortBy , SortOrder , IExtensionManifest } from 'vs/platform/extensionManagement/common/extensionManagement' ;
811import { isUndefined } from 'vs/base/common/types' ;
912import { assign , getOrDefault } from 'vs/base/common/objects' ;
1013import { IRequestService } from 'vs/platform/request/common/request' ;
1114import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry' ;
1215import { IPager } from 'vs/base/common/paging' ;
16+ import { download , json , IRequestOptions } from 'vs/base/node/request' ;
17+ import { getProxyAgent } from 'vs/base/node/proxy' ;
18+ import { IConfigurationService } from 'vs/platform/configuration/common/configuration' ;
1319import pkg from 'vs/platform/package' ;
1420import product from 'vs/platform/product' ;
21+ import { isValidExtensionVersion } from 'vs/platform/extensions/node/extensionValidator' ;
1522
1623interface IRawGalleryExtensionFile {
1724 assetType : string ;
@@ -158,21 +165,21 @@ function getAssetSource(files: IRawGalleryExtensionFile[], type: string): string
158165 return result && result . source ;
159166}
160167
161- function toExtension ( galleryExtension : IRawGalleryExtension , extensionsGalleryUrl : string , downloadHeaders : any ) : IGalleryExtension {
162- const versions = galleryExtension . versions . map < IGalleryVersion > ( v => ( {
163- version : v . version ,
164- date : v . lastUpdated ,
165- downloadHeaders,
166- downloadUrl : `${ v . assetUri } /${ AssetType . VSIX } ?install=true` ,
167- manifestUrl : `${ v . assetUri } /${ AssetType . Manifest } ` ,
168- readmeUrl : `${ v . assetUri } /${ AssetType . Details } ` ,
169- iconUrl : getAssetSource ( v . files , AssetType . Icon ) || require . toUrl ( './media/defaultIcon.png' ) ,
170- licenseUrl : getAssetSource ( v . files , AssetType . License )
171- } ) ) ;
168+ function toExtension ( galleryExtension : IRawGalleryExtension , extensionsGalleryUrl : string , downloadHeaders : { [ key : string ] : string ; } ) : IGalleryExtension {
169+ const [ version ] = galleryExtension . versions ;
170+ const assets = {
171+ manifest : getAssetSource ( version . files , AssetType . Manifest ) ,
172+ readme : getAssetSource ( version . files , AssetType . Details ) ,
173+ download : `${ getAssetSource ( version . files , AssetType . VSIX ) } ?install=true` ,
174+ icon : getAssetSource ( version . files , AssetType . Icon ) || require . toUrl ( './media/defaultIcon.png' ) ,
175+ license : getAssetSource ( version . files , AssetType . License )
176+ } ;
172177
173178 return {
174179 id : galleryExtension . extensionId ,
175180 name : galleryExtension . extensionName ,
181+ version : version . version ,
182+ date : version . lastUpdated ,
176183 displayName : galleryExtension . displayName ,
177184 publisherId : galleryExtension . publisher . publisherId ,
178185 publisher : galleryExtension . publisher . publisherName ,
@@ -181,7 +188,8 @@ function toExtension(galleryExtension: IRawGalleryExtension, extensionsGalleryUr
181188 installCount : getStatistic ( galleryExtension . statistics , 'install' ) ,
182189 rating : getStatistic ( galleryExtension . statistics , 'averagerating' ) ,
183190 ratingCount : getStatistic ( galleryExtension . statistics , 'ratingcount' ) ,
184- versions
191+ assets,
192+ downloadHeaders
185193 } ;
186194}
187195
@@ -190,15 +198,29 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
190198 _serviceBrand : any ;
191199
192200 private extensionsGalleryUrl : string ;
193- private machineId : TPromise < string > ;
201+
202+ private getCommonHeaders ( ) : TPromise < { [ key : string ] : string ; } > {
203+ return this . telemetryService . getTelemetryInfo ( ) . then ( ( { machineId } ) => {
204+ const result : { [ key : string ] : string ; } = {
205+ 'X-Market-Client-Id' : `VSCode ${ pkg . version } ` ,
206+ 'User-Agent' : `VSCode ${ pkg . version } `
207+ } ;
208+
209+ if ( machineId ) {
210+ result [ 'X-Market-User-Id' ] = machineId ;
211+ }
212+
213+ return result ;
214+ } ) ;
215+ }
194216
195217 constructor (
196218 @IRequestService private requestService : IRequestService ,
197- @ITelemetryService private telemetryService : ITelemetryService
219+ @ITelemetryService private telemetryService : ITelemetryService ,
220+ @IConfigurationService private configurationService : IConfigurationService
198221 ) {
199222 const config = product . extensionsGallery ;
200223 this . extensionsGalleryUrl = config && config . serviceUrl ;
201- this . machineId = telemetryService . getTelemetryInfo ( ) . then ( ( { machineId } ) => machineId ) ;
202224 }
203225
204226 private api ( path = '' ) : string {
@@ -221,10 +243,10 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
221243 this . telemetryService . publicLog ( 'galleryService:query' , { type, text } ) ;
222244
223245 let query = new Query ( )
224- . withFlags ( Flags . IncludeVersions , Flags . IncludeCategoryAndTags , Flags . IncludeAssetUri , Flags . IncludeStatistics , Flags . IncludeFiles )
246+ . withFlags ( Flags . IncludeLatestVersionOnly , Flags . IncludeAssetUri , Flags . IncludeStatistics , Flags . IncludeFiles )
225247 . withPage ( 1 , pageSize )
226248 . withFilter ( FilterType . Target , 'Microsoft.VisualStudio.Code' )
227- . withAssetTypes ( AssetType . Icon , AssetType . License ) ;
249+ . withAssetTypes ( AssetType . Icon , AssetType . License , AssetType . Details , AssetType . Manifest , AssetType . VSIX ) ;
228250
229251 if ( text ) {
230252 query = query . withFilter ( FilterType . SearchText , text ) . withSortBy ( SortBy . NoneOrRelevance ) ;
@@ -245,7 +267,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
245267 }
246268
247269 return this . queryGallery ( query ) . then ( ( { galleryExtensions, total } ) => {
248- return this . getRequestHeaders ( ) . then ( downloadHeaders => {
270+ return this . getCommonHeaders ( ) . then ( downloadHeaders => {
249271 const extensions = galleryExtensions . map ( e => toExtension ( e , this . extensionsGalleryUrl , downloadHeaders ) ) ;
250272 const pageSize = query . pageSize ;
251273 const getPage = pageIndex => this . queryGallery ( query . withPage ( pageIndex + 1 ) )
@@ -258,27 +280,19 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
258280
259281 private queryGallery ( query : Query ) : TPromise < { galleryExtensions : IRawGalleryExtension [ ] , total : number ; } > {
260282 const data = JSON . stringify ( query . raw ) ;
261-
262- return this . getRequestHeaders ( )
263- . then ( headers => {
264- headers = assign ( headers , {
265- 'Content-Type' : 'application/json' ,
266- 'Accept' : 'application/json;api-version=3.0-preview.1' ,
267- 'Accept-Encoding' : 'gzip' ,
268- 'Content-Length' : data . length
269- } ) ;
270-
271- const request = {
272- type : 'POST' ,
273- url : this . api ( '/extensionquery' ) ,
274- data,
275- headers
276- } ;
277-
278- return this . requestService . makeRequest ( request ) ;
279- } )
280- . then ( r => JSON . parse ( r . responseText ) . results [ 0 ] )
281- . then ( r => {
283+ const request = this . request ( this . api ( '/extensionquery' ) ) ;
284+
285+ return this . getCommonHeaders ( )
286+ . then ( headers => assign ( headers , {
287+ 'Content-Type' : 'application/json' ,
288+ 'Accept' : 'application/json;api-version=3.0-preview.1' ,
289+ 'Accept-Encoding' : 'gzip' ,
290+ 'Content-Length' : data . length
291+ } ) )
292+ . then ( headers => assign ( request , { type : 'POST' , data, headers } ) )
293+ . then ( ( ) => json < any > ( request ) )
294+ . then ( result => {
295+ const r = result . results [ 0 ] ;
282296 const galleryExtensions = r . extensions ;
283297 const resultCount = r . resultMetadata && r . resultMetadata . filter ( m => m . metadataType === 'ResultCount' ) [ 0 ] ;
284298 const total = resultCount && resultCount . metadataItems . filter ( i => i . name === 'TotalCount' ) [ 0 ] . count || 0 ;
@@ -287,18 +301,67 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
287301 } ) ;
288302 }
289303
290- private getRequestHeaders ( ) : TPromise < any > {
291- return this . machineId . then ( machineId => {
292- const result = {
293- 'X-Market-Client-Id' : `VSCode ${ pkg . version } ` ,
294- 'User-Agent' : `VSCode ${ pkg . version } `
304+ download ( extension : IGalleryExtension ) : TPromise < string > {
305+ const query = new Query ( )
306+ . withFlags ( Flags . IncludeVersions , Flags . IncludeFiles )
307+ . withPage ( 1 , 1 )
308+ . withFilter ( FilterType . Target , 'Microsoft.VisualStudio.Code' )
309+ . withAssetTypes ( AssetType . Manifest , AssetType . VSIX )
310+ . withFilter ( FilterType . ExtensionId , extension . id ) ;
311+
312+ return this . queryGallery ( query ) . then ( ( { galleryExtensions } ) => {
313+ const [ rawExtension ] = galleryExtensions ;
314+
315+ if ( ! rawExtension ) {
316+ return TPromise . wrapError ( new Error ( localize ( 'notFound' , "Extension not found" ) ) ) ;
317+ }
318+
319+ return this . getLastValidExtensionVersion ( rawExtension , rawExtension . versions ) . then ( rawVersion => {
320+ const url = `${ getAssetSource ( rawVersion . files , AssetType . VSIX ) } ?install=true` ;
321+ const zipPath = path . join ( tmpdir ( ) , extension . id ) ;
322+ const request = this . request ( url ) ;
323+
324+ return this . getCommonHeaders ( )
325+ . then ( headers => assign ( request , { headers } ) )
326+ . then ( ( ) => download ( zipPath , request ) )
327+ . then ( ( ) => zipPath ) ;
328+ } ) ;
329+ } ) ;
330+ }
331+
332+ private getLastValidExtensionVersion ( extension : IRawGalleryExtension , versions : IRawGalleryExtensionVersion [ ] ) : TPromise < IRawGalleryExtensionVersion > {
333+ if ( ! versions . length ) {
334+ return TPromise . wrapError ( new Error ( localize ( 'noCompatible' , "Couldn't find a compatible version of {0} with this version of Code." , extension . displayName || extension . extensionName ) ) ) ;
335+ }
336+
337+ const version = versions [ 0 ] ;
338+ const url = getAssetSource ( version . files , AssetType . Manifest ) ;
339+ let request = this . request ( url ) ;
340+ request = assign ( request , { headers : { 'accept-encoding' : 'gzip' } } ) ;
341+
342+ return json < IExtensionManifest > ( request ) . then ( manifest => {
343+ const desc = {
344+ isBuiltin : false ,
345+ engines : { vscode : manifest . engines . vscode } ,
346+ main : manifest . main
295347 } ;
296348
297- if ( machineId ) {
298- result [ 'X-Market-User-Id' ] = machineId ;
349+ if ( ! isValidExtensionVersion ( pkg . version , desc , [ ] ) ) {
350+ return this . getLastValidExtensionVersion ( extension , versions . slice ( 1 ) ) ;
299351 }
300352
301- return result ;
353+ return version ;
302354 } ) ;
303355 }
356+
357+ // Helper for proxy business... shameful.
358+ // This should be pushed down and not rely on the context service
359+ private request ( url : string ) : IRequestOptions {
360+ const httpConfig = this . configurationService . getConfiguration < any > ( 'http' ) || { } ;
361+ const proxyUrl = httpConfig . proxy as string ;
362+ const strictSSL = httpConfig . proxyStrictSSL as boolean ;
363+ const agent = getProxyAgent ( url , { proxyUrl, strictSSL } ) ;
364+
365+ return { url, agent, strictSSL } ;
366+ }
304367}
0 commit comments