1
+ import * as fs from 'fs' ;
2
+ import * as os from 'os' ;
3
+ import * as path from 'path' ;
4
+ import * as semver from 'semver' ;
5
+ import { requestBody , JsonObject , requestBinary } from './utils/http_utils' ;
6
+ import { getBinaryPathFromConfig , removeFiles , unzipFile } from './utils/file_utils' ;
7
+ import { isExpired } from './utils/file_utils' ;
8
+ import { OUT_DIR , ProviderClass , ProviderConfig , ProviderInterface } from './provider' ;
9
+
10
+ export class Chromium extends ProviderClass implements ProviderInterface {
11
+ cacheFileName = 'chromium-all.json' ;
12
+ cacheVersionFileName = 'chromium-version.json' ;
13
+ cacheStorageFileName = 'chromium-storage.json' ;
14
+ compressedBinaryFileName = 'chromium.zip' ;
15
+ configFileName = 'chromium.config.json' ;
16
+ ignoreSSL = false ;
17
+ osType = os . type ( ) ;
18
+ osArch = os . arch ( ) ;
19
+ outDir = OUT_DIR ;
20
+ proxy : string = null ;
21
+
22
+ constructor ( config ?: ProviderConfig ) {
23
+ super ( ) ;
24
+ super ( ) ;
25
+ this . cacheFileName = this . setVar ( 'cacheFileName' , this . cacheFileName , config ) ;
26
+ this . configFileName = this . setVar ( 'configFileName' , this . configFileName , config ) ;
27
+ this . ignoreSSL = this . setVar ( 'ignoreSSL' , this . ignoreSSL , config ) ;
28
+ this . osArch = this . setVar ( 'osArch' , this . osArch , config ) ;
29
+ this . osType = this . setVar ( 'osType' , this . osType , config ) ;
30
+ this . outDir = this . setVar ( 'outDir' , this . outDir , config ) ;
31
+ this . proxy = this . setVar ( 'proxy' , this . proxy , config ) ;
32
+ }
33
+
34
+ private makeDirectory ( fileName : string ) {
35
+ const dir = path . dirname ( fileName ) ;
36
+ try {
37
+ fs . mkdirSync ( dir ) ;
38
+ } catch ( err ) {
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Step 1: Download the json file that contains all the releases by OS. Each
44
+ * OS will have a list of release versions. The requested body will also be
45
+ * written to the out directory.
46
+ *
47
+ * The requested url is https://omahaproxy.appspot.com/all.json. Some other
48
+ * urls include a timestamped csv https://omahaproxy.appspot.com/history.
49
+ * @return Promise of the all-json file.
50
+ */
51
+ async downloadAllJson ( ) : Promise < JsonObject > {
52
+ const fileName = path . resolve ( this . outDir , this . cacheFileName ) ;
53
+ if ( ! isExpired ( fileName ) ) {
54
+ return JSON . parse ( fs . readFileSync ( fileName ) . toString ( ) ) ;
55
+ } else {
56
+ this . makeDirectory ( fileName ) ;
57
+ const httpOptions = { fileName, ignoreSSL : this . ignoreSSL ,
58
+ proxy : this . proxy } ;
59
+
60
+ if ( isExpired ( fileName ) ) {
61
+ const allJsonUrl = 'https://omahaproxy.appspot.com/all.json' ;
62
+ let contents = await requestBody ( allJsonUrl , httpOptions ) ;
63
+ contents = `{ "all": ${ contents } }` ;
64
+ const jsonObj = JSON . parse ( contents ) ;
65
+ fs . writeFileSync ( fileName , JSON . stringify ( jsonObj , null , 2 ) ) ;
66
+ return jsonObj ;
67
+ } else {
68
+ const contents = fs . readFileSync ( fileName ) . toString ( ) ;
69
+ return JSON . parse ( contents ) ;
70
+ }
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Step 2: From the all-json object, make a request that matches the major
76
+ * version requested. The requested body will also be written to file in the
77
+ * out directory.
78
+ *
79
+ * An example of a requsted url is
80
+ * https://omahaproxy.appspot.com/deps.json?version=72.0.3626.81
81
+ * @param allJson The all-json object.
82
+ * @param majorVersion The major version, this must be a whole number.
83
+ */
84
+ async downloadVersionJson ( allJson : JsonObject , majorVersion : string
85
+ ) : Promise < JsonObject > {
86
+ const fileName = path . resolve ( this . outDir ,
87
+ this . cacheVersionFileName . replace ( '.json' , `-${ majorVersion } .json` ) ) ;
88
+ if ( ! isExpired ( fileName ) ) {
89
+ return JSON . parse ( fs . readFileSync ( fileName ) . toString ( ) ) ;
90
+ }
91
+ this . makeDirectory ( fileName ) ;
92
+
93
+ // Look up a version that makes sense.
94
+ const all = allJson [ 'all' ] ;
95
+ let os = '' ;
96
+ if ( this . osType === 'Windows_NT' ) {
97
+ os = 'win' ;
98
+ if ( this . osArch === 'x64' ) {
99
+ os = 'win64' ;
100
+ }
101
+ } else if ( this . osType === 'Linux' ) {
102
+ os = 'linux' ;
103
+ } else {
104
+ os = 'mac' ;
105
+ }
106
+
107
+ let workingFullVersion = '' ;
108
+ let workingSemanticVersion = '0.0.0' ;
109
+ for ( let item of all ) {
110
+ if ( item [ 'os' ] === os ) {
111
+ const versions = item [ 'versions' ] ;
112
+ for ( let version of versions ) {
113
+ const fullVersion = version [ 'current_version' ] ;
114
+ const major = fullVersion . split ( '.' ) [ 0 ] ;
115
+ const minor = fullVersion . split ( '.' ) [ 1 ] ;
116
+ const patch = fullVersion . split ( '.' ) [ 2 ] ;
117
+ const semanticVersion = `${ major } .${ minor } .${ patch } ` ;
118
+ if ( majorVersion === major ) {
119
+ if ( semver . gt ( semanticVersion , workingSemanticVersion ) ) {
120
+ workingFullVersion = fullVersion ;
121
+ }
122
+ }
123
+ }
124
+ }
125
+ }
126
+
127
+ // Make a request and write it out to file.
128
+ const httpOptions = { fileName, ignoreSSL : this . ignoreSSL ,
129
+ proxy : this . proxy } ;
130
+
131
+ const depsUrl = 'https://omahaproxy.appspot.com/deps.json?version=' +
132
+ workingFullVersion ;
133
+ const contents = await requestBody ( depsUrl , httpOptions ) ;
134
+ const jsonObj = JSON . parse ( contents ) ;
135
+ fs . writeFileSync ( fileName , JSON . stringify ( jsonObj , null , 2 ) ) ;
136
+ return jsonObj ;
137
+ }
138
+
139
+ /**
140
+ * Step 3: From the downloaded-version-json object, get the revision number.
141
+ * This is the "chromium_base_position" and make a request to the storage
142
+ * bucket. If the returned value is {"kind": "storage#objects"}, then
143
+ * decrement the revision number.
144
+ *
145
+ * An example is the chromium_base_position revision number (612437).
146
+ * https://www.googleapis.com/storage/v1/b/chromium-browser-snapshots/o?delimiter=/&prefix=Linux_x64/612437/
147
+ * returns {"kind": "storage#objects"}.
148
+ *
149
+ * We keep decrementing the number until we reach 612434 where there is a list
150
+ * of items.
151
+ * @param downloadJson The download-version-json object.
152
+ * @param majorVersion The major version, this must be a whole number.
153
+ */
154
+ async downloadStorageObject ( downloadJson : JsonObject , majorVersion : string
155
+ ) : Promise < JsonObject > {
156
+ const fileName = path . resolve ( this . outDir ,
157
+ this . cacheStorageFileName . replace ( '.json' , `-${ majorVersion } .json` ) ) ;
158
+ if ( ! isExpired ( fileName ) ) {
159
+ return JSON . parse ( fs . readFileSync ( fileName ) . toString ( ) ) ;
160
+ }
161
+ this . makeDirectory ( fileName ) ;
162
+ let revisionUrl = 'https://www.googleapis.com/storage/v1/b/' +
163
+ 'chromium-browser-snapshots/o?delimiter=/&prefix=' ;
164
+ let os = '' ;
165
+ if ( this . osType === 'Windows_NT' ) {
166
+ os = 'Win' ;
167
+ if ( this . osArch === 'x64' ) {
168
+ os = 'Win_x64' ;
169
+ }
170
+ } else if ( this . osType === 'Linux' ) {
171
+ os = 'Linux' ;
172
+ if ( this . osArch === 'x64' ) {
173
+ os = 'Linux_x64' ;
174
+ }
175
+ } else {
176
+ os = 'Mac' ;
177
+ }
178
+ revisionUrl += os + '/' ;
179
+ let chromiumBasePosition : number = downloadJson [ 'chromium_base_position' ] ;
180
+
181
+ const httpOptions = { fileName, ignoreSSL : this . ignoreSSL ,
182
+ proxy : this . proxy } ;
183
+ while ( chromiumBasePosition > 0 ) {
184
+ const revisionBasePositionUrl =
185
+ `${ revisionUrl } ${ chromiumBasePosition } /` ;
186
+ const body = await requestBody ( revisionBasePositionUrl , httpOptions ) ;
187
+ const jsonBody = JSON . parse ( body ) ;
188
+ if ( jsonBody [ 'items' ] ) {
189
+ fs . writeFileSync ( fileName , JSON . stringify ( jsonBody , null , 2 ) ) ;
190
+ return jsonBody ;
191
+ } else {
192
+ chromiumBasePosition -- ;
193
+ }
194
+ }
195
+ return null ;
196
+ }
197
+
198
+ /**
199
+ * Step 4: Get the download url for the chromium zip. Unzipping the zip file
200
+ * directory. The folders and binaries uncompressed are different for each OS.
201
+ * The following is examples of each OS:
202
+ *
203
+ * downloads/
204
+ * |- chrome-linux/chrome
205
+ * |- chrome-mac/Chromium.app
206
+ * |- chrome-win/chrome.exe
207
+ *
208
+ * @param storageObject The download-storage-json object
209
+ * @param majorVersion The major version, this must be a whole number.
210
+ */
211
+ async downloadUrl ( storageObject : JsonObject , majorVersion : string
212
+ ) : Promise < void > {
213
+ const fileName = path . resolve ( this . outDir ,
214
+ this . compressedBinaryFileName . replace ( '.zip' , `-${ majorVersion } .zip` ) ) ;
215
+ if ( isExpired ( fileName ) ) {
216
+ const httpOptions = { fileName, ignoreSSL : this . ignoreSSL ,
217
+ proxy : this . proxy } ;
218
+ for ( let item of storageObject [ 'items' ] as JsonObject [ ] ) {
219
+ const name : string = item [ 'name' ] ;
220
+ if ( name . indexOf ( 'chrome' ) >= 0 ) {
221
+ const downloadUrl = item [ 'mediaLink' ] ;
222
+ await requestBinary ( downloadUrl , httpOptions ) ;
223
+ break ;
224
+ }
225
+ }
226
+ }
227
+ unzipFile ( fileName , this . outDir ) ;
228
+ }
229
+
230
+ async updateBinary ( majorVersion ?: string ) : Promise < void > {
231
+ const allJson = await this . downloadAllJson ( ) ;
232
+ const downloadVersionJson = await this . downloadVersionJson (
233
+ allJson , majorVersion ) ;
234
+ const storageObject = await this . downloadStorageObject (
235
+ downloadVersionJson , majorVersion ) ;
236
+ await this . downloadUrl ( storageObject , majorVersion ) ;
237
+ }
238
+
239
+ getBinaryPath ( version ?: string ) : string | null {
240
+ try {
241
+ const configFilePath = path . resolve ( this . outDir , this . configFileName ) ;
242
+ return getBinaryPathFromConfig ( configFilePath , version ) ;
243
+ } catch ( _ ) {
244
+ return null ;
245
+ }
246
+ }
247
+
248
+ getStatus ( ) : string | null {
249
+ return '' ;
250
+ }
251
+
252
+ cleanFiles ( ) : string {
253
+ return removeFiles ( this . outDir , [ / c h r o m i u m .* / g] ) ;
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Helps translate the os type and arch to the download name associated
259
+ * with composing the download link.
260
+ * @param ostype The operating stystem type.
261
+ * @param osarch The chip architecture.
262
+ * @returns The download name associated with composing the download link.
263
+ */
264
+ export function osHelper ( ostype : string , osarch : string ) : string {
265
+ if ( ostype === 'Darwin' ) {
266
+ return 'Mac' ;
267
+ } else if ( ostype === 'Windows_NT' ) {
268
+ if ( osarch === 'x64' ) {
269
+ return 'Win_x64' ;
270
+ } else if ( osarch === 'x32' ) {
271
+ return 'Win' ;
272
+ }
273
+ } else if ( ostype === 'Linux' ) {
274
+ if ( osarch === 'x64' ) {
275
+ return 'Linux_64' ;
276
+ }
277
+ }
278
+ return null ;
279
+ }
0 commit comments