@appcircle/codepush-cli 0.0.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/.eslintrc.json +17 -0
  2. package/.github/pre-push +30 -0
  3. package/.github/prepare-commit--msg +2 -0
  4. package/CONTRIBUTING.md +71 -0
  5. package/Dockerfile +9 -0
  6. package/Jenkinsfile +45 -0
  7. package/README.md +837 -0
  8. package/bin/script/acquisition-sdk.js +178 -0
  9. package/bin/script/cli.js +23 -0
  10. package/bin/script/command-executor.js +1292 -0
  11. package/bin/script/command-parser.js +1123 -0
  12. package/bin/script/commands/debug.js +125 -0
  13. package/bin/script/hash-utils.js +203 -0
  14. package/bin/script/index.js +5 -0
  15. package/bin/script/management-sdk.js +454 -0
  16. package/bin/script/react-native-utils.js +249 -0
  17. package/bin/script/sign.js +69 -0
  18. package/bin/script/types/cli.js +40 -0
  19. package/bin/script/types/rest-definitions.js +19 -0
  20. package/bin/script/types.js +4 -0
  21. package/bin/script/utils/file-utils.js +50 -0
  22. package/bin/test/acquisition-rest-mock.js +108 -0
  23. package/bin/test/acquisition-sdk.js +188 -0
  24. package/bin/test/cli.js +1342 -0
  25. package/bin/test/hash-utils.js +149 -0
  26. package/bin/test/management-sdk.js +338 -0
  27. package/package.json +74 -0
  28. package/prettier.config.js +7 -0
  29. package/script/acquisition-sdk.ts +273 -0
  30. package/script/cli.ts +27 -0
  31. package/script/command-executor.ts +1614 -0
  32. package/script/command-parser.ts +1340 -0
  33. package/script/commands/debug.ts +148 -0
  34. package/script/hash-utils.ts +241 -0
  35. package/script/index.ts +5 -0
  36. package/script/management-sdk.ts +627 -0
  37. package/script/react-native-utils.ts +283 -0
  38. package/script/sign.ts +80 -0
  39. package/script/types/cli.ts +234 -0
  40. package/script/types/rest-definitions.ts +152 -0
  41. package/script/types.ts +35 -0
  42. package/script/utils/check-package.mjs +11 -0
  43. package/script/utils/file-utils.ts +46 -0
  44. package/test/acquisition-rest-mock.ts +125 -0
  45. package/test/acquisition-sdk.ts +272 -0
  46. package/test/cli.ts +1692 -0
  47. package/test/hash-utils.ts +170 -0
  48. package/test/management-sdk.ts +438 -0
  49. package/test/resources/TestApp/android/app/build.gradle +56 -0
  50. package/test/resources/TestApp/iOS/TestApp/Info.plist +49 -0
  51. package/test/resources/TestApp/index.android.js +2 -0
  52. package/test/resources/TestApp/index.ios.js +2 -0
  53. package/test/resources/TestApp/index.windows.js +2 -0
  54. package/test/resources/TestApp/package.json +6 -0
  55. package/test/resources/TestApp/windows/TestApp/Package.appxmanifest +46 -0
  56. package/test/resources/ignoredMetadata.zip +0 -0
  57. package/test/resources/test.zip +0 -0
  58. package/test/superagent-mock-config.js +58 -0
  59. package/tsconfig.json +13 -0
@@ -0,0 +1,627 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+
4
+ import * as fs from "fs";
5
+ import * as os from "os";
6
+ import * as path from "path";
7
+ import Q = require("q");
8
+ import superagent = require("superagent");
9
+ import * as recursiveFs from "recursive-fs";
10
+ import * as yazl from "yazl";
11
+ import slash = require("slash");
12
+ import qs = require('qs');
13
+
14
+ import Promise = Q.Promise;
15
+
16
+ import {
17
+ AccessKey,
18
+ AccessKeyRequest,
19
+ Account,
20
+ App,
21
+ CodePushError,
22
+ CollaboratorMap,
23
+ Deployment,
24
+ DeploymentMetrics,
25
+ Headers,
26
+ Package,
27
+ PackageInfo,
28
+ ServerAccessKey,
29
+ Session,
30
+ } from "./types";
31
+
32
+ const packageJson = require("../../package.json");
33
+
34
+ interface JsonResponse {
35
+ headers: Headers;
36
+ body?: any;
37
+ }
38
+
39
+ interface PackageFile {
40
+ isTemporary: boolean;
41
+ path: string;
42
+ }
43
+
44
+ interface AppcircleTokenResponse {
45
+ access_token: string;
46
+ expires_in: number;
47
+ refresh_expires_in: number;
48
+ refresh_token: string;
49
+ token_type: string;
50
+ id_token: string;
51
+ "not-before-policy": string;
52
+ session_state: string;
53
+ scope: string
54
+ }
55
+
56
+ // A template string tag function that URL encodes the substituted values
57
+ function urlEncode(strings: string[], ...values: string[]): string {
58
+ let result = "";
59
+ for (let i = 0; i < strings.length; i++) {
60
+ result += strings[i];
61
+ if (i < values.length) {
62
+ result += encodeURIComponent(values[i]);
63
+ }
64
+ }
65
+
66
+ return result;
67
+ }
68
+
69
+ class AccountManager {
70
+ public static AppPermission = {
71
+ OWNER: "Owner",
72
+ COLLABORATOR: "Collaborator",
73
+ };
74
+ public static SERVER_URL ="https://api.appcircle.io/codepush"
75
+ public static AUTH_URL = "https://auth.appcircle.io";
76
+
77
+ private static API_VERSION: number = 2;
78
+
79
+ public static ERROR_GATEWAY_TIMEOUT = 504; // Used if there is a network error
80
+ public static ERROR_INTERNAL_SERVER = 500;
81
+ public static ERROR_NOT_FOUND = 404;
82
+ public static ERROR_CONFLICT = 409; // Used if the resource already exists
83
+ public static ERROR_UNAUTHORIZED = 401;
84
+
85
+ private _accessKey: string;
86
+ private _pat: string;
87
+ private _serverUrl: string;
88
+ private _authUrl: string;
89
+ private _customHeaders: Headers;
90
+
91
+ constructor(accessKey:string, pat:string, customHeaders?: Headers, serverUrl?: string, authUrl?: string) {
92
+ if (!accessKey && !pat ) throw new Error("An access key or PAT must be specified.");
93
+
94
+ this._accessKey = accessKey ||"";
95
+ this._pat = pat || "";
96
+ this._customHeaders = customHeaders;
97
+ this._serverUrl = serverUrl || AccountManager.SERVER_URL;
98
+ this._authUrl = authUrl || AccountManager.AUTH_URL;
99
+ }
100
+
101
+
102
+ public get accessKey(): string {
103
+ return this._accessKey;
104
+ }
105
+
106
+ private loginToAppcircleWithPAT(): Promise<string>{
107
+ return Promise<string>((resolve,reject) => {
108
+ if(!this._accessKey){
109
+ const request : superagent.Request<any> = superagent.post(`${this._authUrl}${urlEncode([`/auth/v1/token`])}`);
110
+ const data = qs.stringify({
111
+ pat: this._pat
112
+ });
113
+ request.set("Accept", 'application/json');
114
+ request.set("Content-Type",'application/x-www-form-urlencoded')
115
+ request
116
+ .send(data)
117
+
118
+ request.end((err: any, res: superagent.Response) => {
119
+ if (err) {
120
+ reject(this.getCodePushError(err, res));
121
+ return;
122
+ }
123
+ resolve(res.body.access_token)
124
+ return;
125
+ });
126
+ }
127
+ else {
128
+ resolve(this._accessKey);
129
+ }
130
+ })
131
+ }
132
+
133
+ public isAuthenticated(throwIfUnauthorized?: boolean): Promise<boolean> {
134
+ const accessTokenPromise = this.loginToAppcircleWithPAT();
135
+ return accessTokenPromise.then(appcircleAccessToken => {
136
+ this._accessKey = appcircleAccessToken;
137
+ return Promise<any>(async (resolve, reject, notify) => {
138
+ const request: superagent.Request<any> = superagent.get(`${this._serverUrl}${urlEncode(["/authenticated"])}`);
139
+ this.attachCredentials(request);
140
+
141
+ request.end((err: any, res: superagent.Response) => {
142
+ const status: number = this.getErrorStatus(err, res);
143
+ if (err && status !== AccountManager.ERROR_UNAUTHORIZED) {
144
+ reject(this.getCodePushError(err, res));
145
+ return;
146
+ }
147
+
148
+ const authenticated: boolean = status === 200;
149
+
150
+ if (!authenticated && throwIfUnauthorized) {
151
+ reject(this.getCodePushError(err, res));
152
+ return;
153
+ }
154
+
155
+ resolve(authenticated);
156
+ });
157
+ });
158
+ })
159
+
160
+
161
+ }
162
+
163
+ public addAccessKey(friendlyName: string, ttl?: number): Promise<AccessKey> {
164
+ if (!friendlyName) {
165
+ throw new Error("A name must be specified when adding an access key.");
166
+ }
167
+
168
+ const accessKeyRequest: AccessKeyRequest = {
169
+ createdBy: os.hostname(),
170
+ friendlyName,
171
+ ttl,
172
+ };
173
+
174
+ return this.post(urlEncode(["/accessKeys"]), JSON.stringify(accessKeyRequest), /*expectResponseBody=*/ true).then(
175
+ (response: JsonResponse) => {
176
+ return {
177
+ createdTime: response.body.accessKey.createdTime,
178
+ expires: response.body.accessKey.expires,
179
+ key: response.body.accessKey.name,
180
+ name: response.body.accessKey.friendlyName,
181
+ };
182
+ }
183
+ );
184
+ }
185
+
186
+ public getAccessKey(accessKeyName: string): Promise<AccessKey> {
187
+ return this.get(urlEncode([`/accessKeys/${accessKeyName}`])).then((res: JsonResponse) => {
188
+ return {
189
+ createdTime: res.body.accessKey.createdTime,
190
+ expires: res.body.accessKey.expires,
191
+ name: res.body.accessKey.friendlyName,
192
+ };
193
+ });
194
+ }
195
+
196
+ public getAccessKeys(): Promise<AccessKey[]> {
197
+ return this.get(urlEncode(["/accessKeys"])).then((res: JsonResponse) => {
198
+ const accessKeys: AccessKey[] = [];
199
+
200
+ res.body.accessKeys.forEach((serverAccessKey: ServerAccessKey) => {
201
+ !serverAccessKey.isSession &&
202
+ accessKeys.push({
203
+ createdTime: serverAccessKey.createdTime,
204
+ expires: serverAccessKey.expires,
205
+ name: serverAccessKey.friendlyName,
206
+ });
207
+ });
208
+
209
+ return accessKeys;
210
+ });
211
+ }
212
+
213
+ public getSessions(): Promise<Session[]> {
214
+ return this.get(urlEncode(["/accessKeys"])).then((res: JsonResponse) => {
215
+ // A machine name might be associated with multiple session keys,
216
+ // but we should only return one per machine name.
217
+ const sessionMap: { [machineName: string]: Session } = {};
218
+ const now: number = new Date().getTime();
219
+ res.body.accessKeys.forEach((serverAccessKey: ServerAccessKey) => {
220
+ if (serverAccessKey.isSession && serverAccessKey.expires > now) {
221
+ sessionMap[serverAccessKey.createdBy] = {
222
+ loggedInTime: serverAccessKey.createdTime,
223
+ machineName: serverAccessKey.createdBy,
224
+ };
225
+ }
226
+ });
227
+
228
+ const sessions: Session[] = Object.keys(sessionMap).map((machineName: string) => sessionMap[machineName]);
229
+
230
+ return sessions;
231
+ });
232
+ }
233
+
234
+ public patchAccessKey(oldName: string, newName?: string, ttl?: number): Promise<AccessKey> {
235
+ const accessKeyRequest: AccessKeyRequest = {
236
+ friendlyName: newName,
237
+ ttl,
238
+ };
239
+
240
+ return this.patch(urlEncode([`/accessKeys/${oldName}`]), JSON.stringify(accessKeyRequest)).then((res: JsonResponse) => {
241
+ return {
242
+ createdTime: res.body.accessKey.createdTime,
243
+ expires: res.body.accessKey.expires,
244
+ name: res.body.accessKey.friendlyName,
245
+ };
246
+ });
247
+ }
248
+
249
+ public removeAccessKey(name: string): Promise<void> {
250
+ return this.del(urlEncode([`/accessKeys/${name}`])).then(() => null);
251
+ }
252
+
253
+ public removeSession(machineName: string): Promise<void> {
254
+ return this.del(urlEncode([`/sessions/${machineName}`])).then(() => null);
255
+ }
256
+
257
+ // Account
258
+ public getAccountInfo(): Promise<Account> {
259
+ return this.get(urlEncode(["/account"])).then((res: JsonResponse) => res.body.account);
260
+ }
261
+
262
+ // Apps
263
+ public getApps(): Promise<App[]> {
264
+ return this.get(urlEncode(["/apps"])).then((res: JsonResponse) => res.body.apps);
265
+ }
266
+
267
+ public getApp(appName: string): Promise<App> {
268
+ return this.get(urlEncode([`/apps/${appName}`])).then((res: JsonResponse) => res.body.app);
269
+ }
270
+
271
+ public addApp(appName: string): Promise<App> {
272
+ const app: App = { name: appName };
273
+ return this.post(urlEncode(["/apps"]), JSON.stringify(app), /*expectResponseBody=*/ false).then(() => app);
274
+ }
275
+
276
+ public removeApp(appName: string): Promise<void> {
277
+ return this.del(urlEncode([`/apps/${appName}`])).then(() => null);
278
+ }
279
+
280
+ public renameApp(oldAppName: string, newAppName: string): Promise<void> {
281
+ return this.patch(urlEncode([`/apps/${oldAppName}`]), JSON.stringify({ name: newAppName })).then(() => null);
282
+ }
283
+
284
+ public transferApp(appName: string, email: string): Promise<void> {
285
+ return this.post(urlEncode([`/apps/${appName}/transfer/${email}`]), /*requestBody=*/ null, /*expectResponseBody=*/ false).then(
286
+ () => null
287
+ );
288
+ }
289
+
290
+ // Collaborators
291
+ public getCollaborators(appName: string): Promise<CollaboratorMap> {
292
+ return this.get(urlEncode([`/apps/${appName}/collaborators`])).then((res: JsonResponse) => res.body.collaborators);
293
+ }
294
+
295
+ public addCollaborator(appName: string, email: string): Promise<void> {
296
+ return this.post(
297
+ urlEncode([`/apps/${appName}/collaborators/${email}`]),
298
+ /*requestBody=*/ null,
299
+ /*expectResponseBody=*/ false
300
+ ).then(() => null);
301
+ }
302
+
303
+ public removeCollaborator(appName: string, email: string): Promise<void> {
304
+ return this.del(urlEncode([`/apps/${appName}/collaborators/${email}`])).then(() => null);
305
+ }
306
+
307
+ // Deployments
308
+ public addDeployment(appName: string, deploymentName: string, deploymentKey?: string): Promise<Deployment> {
309
+ const deployment = <Deployment>{ name: deploymentName, key: deploymentKey };
310
+ return this.post(urlEncode([`/apps/${appName}/deployments`]), JSON.stringify(deployment), /*expectResponseBody=*/ true).then(
311
+ (res: JsonResponse) => res.body.deployment
312
+ );
313
+ }
314
+
315
+ public clearDeploymentHistory(appName: string, deploymentName: string): Promise<void> {
316
+ return this.del(urlEncode([`/apps/${appName}/deployments/${deploymentName}/history`])).then(() => null);
317
+ }
318
+
319
+ public getDeployments(appName: string): Promise<Deployment[]> {
320
+ return this.get(urlEncode([`/apps/${appName}/deployments`])).then((res: JsonResponse) => res.body.deployments);
321
+ }
322
+
323
+ public getDeployment(appName: string, deploymentName: string): Promise<Deployment> {
324
+ return this.get(urlEncode([`/apps/${appName}/deployments/${deploymentName}`])).then((res: JsonResponse) => res.body.deployment);
325
+ }
326
+
327
+ public renameDeployment(appName: string, oldDeploymentName: string, newDeploymentName: string): Promise<void> {
328
+ return this.patch(
329
+ urlEncode([`/apps/${appName}/deployments/${oldDeploymentName}`]),
330
+ JSON.stringify({ name: newDeploymentName })
331
+ ).then(() => null);
332
+ }
333
+
334
+ public removeDeployment(appName: string, deploymentName: string): Promise<void> {
335
+ return this.del(urlEncode([`/apps/${appName}/deployments/${deploymentName}`])).then(() => null);
336
+ }
337
+
338
+ public getDeploymentMetrics(appName: string, deploymentName: string): Promise<DeploymentMetrics> {
339
+ return this.get(urlEncode([`/apps/${appName}/deployments/${deploymentName}/metrics`])).then(
340
+ (res: JsonResponse) => res.body.metrics
341
+ );
342
+ }
343
+
344
+ public getDeploymentHistory(appName: string, deploymentName: string): Promise<Package[]> {
345
+ return this.get(urlEncode([`/apps/${appName}/deployments/${deploymentName}/history`])).then(
346
+ (res: JsonResponse) => res.body.history
347
+ );
348
+ }
349
+
350
+ public release(
351
+ appName: string,
352
+ deploymentName: string,
353
+ filePath: string,
354
+ targetBinaryVersion: string,
355
+ updateMetadata: PackageInfo,
356
+ uploadProgressCallback?: (progress: number) => void
357
+ ): Promise<void> {
358
+ return Promise<void>((resolve, reject, notify) => {
359
+ updateMetadata.appVersion = targetBinaryVersion;
360
+ const request: superagent.Request<any> = superagent.post(
361
+ this._serverUrl + urlEncode([`/apps/${appName}/deployments/${deploymentName}/release`])
362
+ );
363
+
364
+ this.attachCredentials(request);
365
+
366
+ const getPackageFilePromise = Q.Promise((resolve, reject) => {
367
+ this.packageFileFromPath(filePath)
368
+ .then((result) => {
369
+ resolve(result);
370
+ })
371
+ .catch((error) => {
372
+ reject(error);
373
+ });
374
+ });
375
+
376
+ getPackageFilePromise.then((packageFile: PackageFile) => {
377
+ const file: any = fs.createReadStream(packageFile.path);
378
+ request
379
+ .attach("package", file)
380
+ .field("packageInfo", JSON.stringify(updateMetadata))
381
+ .on("progress", (event: any) => {
382
+ if (uploadProgressCallback && event && event.total > 0) {
383
+ const currentProgress: number = (event.loaded / event.total) * 100;
384
+ uploadProgressCallback(currentProgress);
385
+ }
386
+ })
387
+ .end((err: any, res: superagent.Response) => {
388
+ if (packageFile.isTemporary) {
389
+ fs.unlinkSync(packageFile.path);
390
+ }
391
+
392
+ if (err) {
393
+ reject(this.getCodePushError(err, res));
394
+ return;
395
+ }
396
+
397
+ if (res.ok) {
398
+ resolve(<void>null);
399
+ } else {
400
+ let body;
401
+ try {
402
+ body = JSON.parse(res.text);
403
+ } catch (err) {}
404
+
405
+ if (body) {
406
+ reject(<CodePushError>{
407
+ message: body.message,
408
+ statusCode: res && res.status,
409
+ });
410
+ } else {
411
+ reject(<CodePushError>{
412
+ message: res.text,
413
+ statusCode: res && res.status,
414
+ });
415
+ }
416
+ }
417
+ });
418
+ });
419
+ });
420
+ }
421
+
422
+ public patchRelease(appName: string, deploymentName: string, label: string, updateMetadata: PackageInfo): Promise<void> {
423
+ updateMetadata.label = label;
424
+ const requestBody: string = JSON.stringify({ packageInfo: updateMetadata });
425
+ return this.patch(
426
+ urlEncode([`/apps/${appName}/deployments/${deploymentName}/release`]),
427
+ requestBody,
428
+ /*expectResponseBody=*/ false
429
+ ).then(() => null);
430
+ }
431
+
432
+ public promote(
433
+ appName: string,
434
+ sourceDeploymentName: string,
435
+ destinationDeploymentName: string,
436
+ updateMetadata: PackageInfo
437
+ ): Promise<void> {
438
+ const requestBody: string = JSON.stringify({ packageInfo: updateMetadata });
439
+ return this.post(
440
+ urlEncode([`/apps/${appName}/deployments/${sourceDeploymentName}/promote/${destinationDeploymentName}`]),
441
+ requestBody,
442
+ /*expectResponseBody=*/ false
443
+ ).then(() => null);
444
+ }
445
+
446
+ public rollback(appName: string, deploymentName: string, targetRelease?: string): Promise<void> {
447
+ return this.post(
448
+ urlEncode([`/apps/${appName}/deployments/${deploymentName}/rollback/${targetRelease || ``}`]),
449
+ /*requestBody=*/ null,
450
+ /*expectResponseBody=*/ false
451
+ ).then(() => null);
452
+ }
453
+
454
+ private packageFileFromPath(filePath: string) {
455
+ let getPackageFilePromise: Promise<PackageFile>;
456
+ if (fs.lstatSync(filePath).isDirectory()) {
457
+ getPackageFilePromise = Promise<PackageFile>((resolve: (file: PackageFile) => void, reject: (reason: Error) => void): void => {
458
+ const directoryPath: string = filePath;
459
+
460
+ recursiveFs.readdirr(directoryPath, (error?: any, directories?: string[], files?: string[]) => {
461
+ if (error) {
462
+ reject(error);
463
+ return;
464
+ }
465
+
466
+ const baseDirectoryPath = path.dirname(directoryPath);
467
+ const fileName: string = this.generateRandomFilename(15) + ".zip";
468
+ const zipFile = new yazl.ZipFile();
469
+ const writeStream: fs.WriteStream = fs.createWriteStream(fileName);
470
+
471
+ zipFile.outputStream
472
+ .pipe(writeStream)
473
+ .on("error", (error: Error): void => {
474
+ reject(error);
475
+ })
476
+ .on("close", (): void => {
477
+ filePath = path.join(process.cwd(), fileName);
478
+
479
+ resolve({ isTemporary: true, path: filePath });
480
+ });
481
+
482
+ for (let i = 0; i < files.length; ++i) {
483
+ const file: string = files[i];
484
+ // yazl does not like backslash (\) in the metadata path.
485
+ const relativePath: string = slash(path.relative(baseDirectoryPath, file));
486
+
487
+ zipFile.addFile(file, relativePath);
488
+ }
489
+
490
+ zipFile.end();
491
+ });
492
+ });
493
+ } else {
494
+ getPackageFilePromise = Q({ isTemporary: false, path: filePath });
495
+ }
496
+ return getPackageFilePromise;
497
+ }
498
+
499
+ private generateRandomFilename(length: number): string {
500
+ let filename: string = "";
501
+ const validChar: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
502
+
503
+ for (let i = 0; i < length; i++) {
504
+ filename += validChar.charAt(Math.floor(Math.random() * validChar.length));
505
+ }
506
+
507
+ return filename;
508
+ }
509
+
510
+ private get(endpoint: string, expectResponseBody: boolean = true): Promise<JsonResponse> {
511
+ return this.makeApiRequest("get", endpoint, /*requestBody=*/ null, expectResponseBody, /*contentType=*/ null);
512
+ }
513
+
514
+ private post(
515
+ endpoint: string,
516
+ requestBody: string,
517
+ expectResponseBody: boolean,
518
+ contentType: string = "application/json;charset=UTF-8"
519
+ ): Promise<JsonResponse> {
520
+ return this.makeApiRequest("post", endpoint, requestBody, expectResponseBody, contentType);
521
+ }
522
+
523
+ private patch(
524
+ endpoint: string,
525
+ requestBody: string,
526
+ expectResponseBody: boolean = false,
527
+ contentType: string = "application/json;charset=UTF-8"
528
+ ): Promise<JsonResponse> {
529
+ return this.makeApiRequest("patch", endpoint, requestBody, expectResponseBody, contentType);
530
+ }
531
+
532
+ private del(endpoint: string, expectResponseBody: boolean = false): Promise<JsonResponse> {
533
+ return this.makeApiRequest("del", endpoint, /*requestBody=*/ null, expectResponseBody, /*contentType=*/ null);
534
+ }
535
+
536
+ private makeApiRequest(
537
+ method: string,
538
+ endpoint: string,
539
+ requestBody: string,
540
+ expectResponseBody: boolean,
541
+ contentType: string
542
+ ): Promise<JsonResponse> {
543
+ return Promise<JsonResponse>((resolve, reject, notify) => {
544
+ let request: superagent.Request<any> = (<any>superagent)[method](this._serverUrl + endpoint);
545
+
546
+ this.attachCredentials(request);
547
+
548
+ if (requestBody) {
549
+ if (contentType) {
550
+ request = request.set("Content-Type", contentType);
551
+ }
552
+
553
+ request = request.send(requestBody);
554
+ }
555
+
556
+ request.end((err: any, res: superagent.Response) => {
557
+ if (err) {
558
+ reject(this.getCodePushError(err, res));
559
+ return;
560
+ }
561
+ let body;
562
+ try {
563
+ body = JSON.parse(res.text);
564
+ } catch (err) {}
565
+
566
+ if (res.ok) {
567
+ if (expectResponseBody && !body) {
568
+ reject(<CodePushError>{
569
+ message: `Could not parse response: ${res.text}`,
570
+ statusCode: AccountManager.ERROR_INTERNAL_SERVER,
571
+ });
572
+ } else {
573
+ resolve(<JsonResponse>{
574
+ headers: res.header,
575
+ body: body,
576
+ });
577
+ }
578
+ } else {
579
+ if (body) {
580
+ reject(<CodePushError>{
581
+ message: body.message,
582
+ statusCode: this.getErrorStatus(err, res),
583
+ });
584
+ } else {
585
+ reject(<CodePushError>{
586
+ message: res.text,
587
+ statusCode: this.getErrorStatus(err, res),
588
+ });
589
+ }
590
+ }
591
+ });
592
+ });
593
+ }
594
+
595
+ private getCodePushError(error: any, response: superagent.Response): CodePushError {
596
+ if (error.syscall === "getaddrinfo") {
597
+ error.message = `Unable to connect to the CodePush server. Are you offline, or behind a firewall or proxy?\n(${error.message})`;
598
+ }
599
+
600
+ return {
601
+ message: this.getErrorMessage(error, response),
602
+ statusCode: this.getErrorStatus(error, response),
603
+ };
604
+ }
605
+
606
+ private getErrorStatus(error: any, response: superagent.Response): number {
607
+ return (error && error.status) || (response && response.status) || AccountManager.ERROR_GATEWAY_TIMEOUT;
608
+ }
609
+
610
+ private getErrorMessage(error: Error, response: superagent.Response): string {
611
+ return response && response.text ? response.text : error.message;
612
+ }
613
+
614
+ private attachCredentials(request: superagent.Request<any>): void {
615
+ if (this._customHeaders) {
616
+ for (const headerName in this._customHeaders) {
617
+ request.set(headerName, this._customHeaders[headerName]);
618
+ }
619
+ }
620
+
621
+ request.set("Accept", `application/vnd.code-push.v${AccountManager.API_VERSION}+json`);
622
+ request.set("Authorization", `Bearer ${this._accessKey}`);
623
+ request.set("X-CodePush-SDK-Version", packageJson.version);
624
+ }
625
+ }
626
+
627
+ export = AccountManager;