cloudron 5.13.0 → 5.14.0
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.
- package/bin/cloudron +2 -2
- package/package.json +1 -2
- package/src/actions.js +45 -45
- package/src/appstore-actions.js +22 -22
- package/src/build-actions.js +10 -10
- package/src/superagent.js +64 -40
package/bin/cloudron
CHANGED
|
@@ -10,7 +10,7 @@ const actions = require('../src/actions.js'),
|
|
|
10
10
|
Command = require('commander').Command,
|
|
11
11
|
safe = require('safetydance'),
|
|
12
12
|
semver = require('semver'),
|
|
13
|
-
superagent = require('superagent'),
|
|
13
|
+
superagent = require('../src/superagent.js'),
|
|
14
14
|
util = require('util');
|
|
15
15
|
|
|
16
16
|
const version = require('../package.json').version;
|
|
@@ -293,7 +293,7 @@ program.command('update')
|
|
|
293
293
|
if (Date.now() - (config.get('lastCliUpdateCheck') || 0) > 24*60*60*1000) {
|
|
294
294
|
// check if cli tool is up-to-date
|
|
295
295
|
const [error, response] = await safe(superagent.get('https://registry.npmjs.org/cloudron').retry(0).ok(() => true));
|
|
296
|
-
if (!error && response.
|
|
296
|
+
if (!error && response.status === 200 && response.body['dist-tags'].latest !== version) {
|
|
297
297
|
const updateCommand = 'npm install -g cloudron@' + response.body['dist-tags'].latest;
|
|
298
298
|
process.stderr.write(util.format('A new version of Cloudron CLI is available. Please update with: %s\n', updateCommand));
|
|
299
299
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloudron",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.14.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Cloudron Commandline Tool",
|
|
6
6
|
"main": "main.js",
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
"micromatch": "^4.0.8",
|
|
27
27
|
"open": "^10.1.0",
|
|
28
28
|
"safetydance": "^2.5.0",
|
|
29
|
-
"superagent": "^10.1.1",
|
|
30
29
|
"tar-fs": "^3.0.8"
|
|
31
30
|
},
|
|
32
31
|
"engines": {
|
package/src/actions.js
CHANGED
|
@@ -15,7 +15,7 @@ const assert = require('assert'),
|
|
|
15
15
|
safe = require('safetydance'),
|
|
16
16
|
spawn = require('child_process').spawn,
|
|
17
17
|
semver = require('semver'),
|
|
18
|
-
superagent = require('superagent'),
|
|
18
|
+
superagent = require('./superagent.js'),
|
|
19
19
|
Table = require('easy-table'),
|
|
20
20
|
tar = require('tar-fs'),
|
|
21
21
|
timers = require('timers/promises'),
|
|
@@ -79,7 +79,7 @@ function createRequest(method, apiPath, options) {
|
|
|
79
79
|
let url = `https://${adminFqdn}${apiPath}`;
|
|
80
80
|
if (url.includes('?')) url += '&'; else url += '?';
|
|
81
81
|
url += `access_token=${token}`;
|
|
82
|
-
const request = superagent(method, url);
|
|
82
|
+
const request = superagent.request(method, url);
|
|
83
83
|
if (!rejectUnauthorized) request.disableTLSCerts();
|
|
84
84
|
request.retry(3);
|
|
85
85
|
request.ok(() => true);
|
|
@@ -88,9 +88,9 @@ function createRequest(method, apiPath, options) {
|
|
|
88
88
|
|
|
89
89
|
// error for the request module
|
|
90
90
|
function requestError(response) {
|
|
91
|
-
if (response.
|
|
91
|
+
if (response.status === 401) return 'Invalid token. Use cloudron login again.';
|
|
92
92
|
|
|
93
|
-
return `${response.
|
|
93
|
+
return `${response.status} message: ${response.body.message || JSON.stringify(response.body)}`; // body is sometimes just a string like in 401
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
async function selectDomain(location, options) {
|
|
@@ -100,7 +100,7 @@ async function selectDomain(location, options) {
|
|
|
100
100
|
const { adminFqdn } = requestOptions(options);
|
|
101
101
|
|
|
102
102
|
const response = await createRequest('GET', '/api/v1/domains', options);
|
|
103
|
-
if (response.
|
|
103
|
+
if (response.status !== 200) throw new Error(`Failed to list domains: ${requestError(response)}`);
|
|
104
104
|
|
|
105
105
|
const domains = response.body.domains;
|
|
106
106
|
|
|
@@ -134,7 +134,7 @@ async function stopActiveTask(app, options) {
|
|
|
134
134
|
console.log(`Stopping app's current active task ${app.taskId}`);
|
|
135
135
|
|
|
136
136
|
const response = await createRequest('POST', `/api/v1/tasks/${app.taskId}/stop`, options);
|
|
137
|
-
if (response.
|
|
137
|
+
if (response.status !== 204) throw `Failed to stop active task: ${requestError(response)}`;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
async function selectAppWithRepository(repository, options) {
|
|
@@ -142,7 +142,7 @@ async function selectAppWithRepository(repository, options) {
|
|
|
142
142
|
assert.strictEqual(typeof options, 'object');
|
|
143
143
|
|
|
144
144
|
const response = await createRequest('GET', '/api/v1/apps', options);
|
|
145
|
-
if (response.
|
|
145
|
+
if (response.status !== 200) throw new Error(`Failed to install app: ${requestError(response)}`);
|
|
146
146
|
|
|
147
147
|
const matchingApps = response.body.apps.filter(function (app) {
|
|
148
148
|
return !app.appStoreId && app.manifest.dockerImage.startsWith(repository); // never select apps from the store
|
|
@@ -190,12 +190,12 @@ async function getApp(options) {
|
|
|
190
190
|
return result;
|
|
191
191
|
} else if (app.match(/.{8}-.{4}-.{4}-.{4}-.{8}/)) { // it is an id
|
|
192
192
|
const response = await createRequest('GET', `/api/v1/apps/${app}`, options);
|
|
193
|
-
if (response.
|
|
193
|
+
if (response.status !== 200) throw new Error(`Failed to get app: ${requestError(response)}`);
|
|
194
194
|
|
|
195
195
|
return response.body;
|
|
196
196
|
} else { // it is a location
|
|
197
197
|
const response = await createRequest('GET', '/api/v1/apps', options);
|
|
198
|
-
if (response.
|
|
198
|
+
if (response.status !== 200) throw new Error(`Failed to get apps: ${requestError(response)}`);
|
|
199
199
|
|
|
200
200
|
const match = response.body.apps.filter(function (m) { return m.subdomain === app || m.location === app || m.fqdn === app; });
|
|
201
201
|
if (match.length == 0) throw new Error(`App at location ${app} not found`);
|
|
@@ -215,7 +215,7 @@ async function waitForHealthy(appId, options) {
|
|
|
215
215
|
while (true) {
|
|
216
216
|
await timers.setTimeout(1000);
|
|
217
217
|
const response = await createRequest('GET', `/api/v1/apps/${appId}`, options);
|
|
218
|
-
if (response.
|
|
218
|
+
if (response.status !== 200) throw new Error(`Failed to get app: ${requestError(response)}`);
|
|
219
219
|
|
|
220
220
|
// do not check installation state here. it can be pending_backup etc (this is a bug in box code)
|
|
221
221
|
if (response.body.health === 'healthy') return;
|
|
@@ -238,7 +238,7 @@ async function waitForTask(taskId, options) {
|
|
|
238
238
|
|
|
239
239
|
while (true) {
|
|
240
240
|
const response = await createRequest('GET', `/api/v1/tasks/${taskId}`, options);
|
|
241
|
-
if (response.
|
|
241
|
+
if (response.status !== 200) throw new Error(`Failed to get task: ${requestError(response)}`);
|
|
242
242
|
|
|
243
243
|
if (!response.body.active) return response.body;
|
|
244
244
|
|
|
@@ -272,7 +272,7 @@ async function waitForFinishInstallation(appId, taskId, options) {
|
|
|
272
272
|
await waitForTask(taskId, options);
|
|
273
273
|
|
|
274
274
|
const response = await createRequest('GET', `/api/v1/apps/${appId}`, options);
|
|
275
|
-
if (response.
|
|
275
|
+
if (response.status !== 200) throw new Error(`Failed to get app: ${requestError(response)}`);
|
|
276
276
|
|
|
277
277
|
if (response.body.installationState !== 'installed') throw new Error(`Installation failed: ${response.body.error ? response.body.error.message : ''}`);
|
|
278
278
|
|
|
@@ -289,7 +289,7 @@ async function waitForFinishBackup(appId, taskId, options) {
|
|
|
289
289
|
if (result.error) throw new Error(`Backup failed: ${result.error.message}`);
|
|
290
290
|
|
|
291
291
|
const response = await createRequest('GET', `/api/v1/apps/${appId}`, options);
|
|
292
|
-
if (response.
|
|
292
|
+
if (response.status !== 200) throw new Error(`Failed to get app: ${requestError(response)}`);
|
|
293
293
|
}
|
|
294
294
|
|
|
295
295
|
async function stopApp(app, options) {
|
|
@@ -297,7 +297,7 @@ async function stopApp(app, options) {
|
|
|
297
297
|
assert.strictEqual(typeof options, 'object');
|
|
298
298
|
|
|
299
299
|
const response = await createRequest('POST', `/api/v1/apps/${app.id}/stop`, options);
|
|
300
|
-
if (response.
|
|
300
|
+
if (response.status !== 202) throw `Failed to stop app: ${requestError(response)}`;
|
|
301
301
|
|
|
302
302
|
await waitForTask(response.body.taskId, options);
|
|
303
303
|
}
|
|
@@ -307,7 +307,7 @@ async function startApp(app, options) {
|
|
|
307
307
|
assert.strictEqual(typeof options, 'object');
|
|
308
308
|
|
|
309
309
|
const response = await createRequest('POST', `/api/v1/apps/${app.id}/start`, options);
|
|
310
|
-
if (response.
|
|
310
|
+
if (response.status !== 202) throw `Failed to start app: ${requestError(response)}`;
|
|
311
311
|
|
|
312
312
|
await waitForTask(response.body.taskId, options);
|
|
313
313
|
}
|
|
@@ -317,7 +317,7 @@ async function restartApp(app, options) {
|
|
|
317
317
|
assert.strictEqual(typeof options, 'object');
|
|
318
318
|
|
|
319
319
|
const response = await createRequest('POST', `/api/v1/apps/${app.id}/restart`, options);
|
|
320
|
-
if (response.
|
|
320
|
+
if (response.status !== 202) throw `Failed to restart app: ${requestError(response)}`;
|
|
321
321
|
|
|
322
322
|
await waitForTask(response.body.taskId, options);
|
|
323
323
|
}
|
|
@@ -351,7 +351,7 @@ async function authenticate(adminFqdn, username, password, options) {
|
|
|
351
351
|
}
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
-
if (response.
|
|
354
|
+
if (response.status !== 200) throw new Error(`Login failed: Status code: ${requestError(response)}`);
|
|
355
355
|
|
|
356
356
|
return response.body.accessToken;
|
|
357
357
|
}
|
|
@@ -425,7 +425,7 @@ async function list(localOptions, cmd) {
|
|
|
425
425
|
const options = cmd.optsWithGlobals();
|
|
426
426
|
const [error, response] = await safe(createRequest('GET', '/api/v1/apps', options));
|
|
427
427
|
if (error) return exit(error);
|
|
428
|
-
if (response.
|
|
428
|
+
if (response.status !== 200) return exit(`Failed to list apps: ${requestError(response)}`);
|
|
429
429
|
|
|
430
430
|
let apps = response.body.apps;
|
|
431
431
|
|
|
@@ -447,7 +447,7 @@ async function list(localOptions, cmd) {
|
|
|
447
447
|
|
|
448
448
|
const response = result.value;
|
|
449
449
|
|
|
450
|
-
if (response.
|
|
450
|
+
if (response.status !== 200) return exit(`Failed to list app: ${requestError(response)}`);
|
|
451
451
|
response.body.location = response.body.location || response.body.subdomain; // LEGACY support
|
|
452
452
|
|
|
453
453
|
const detailedApp = response.body;
|
|
@@ -513,7 +513,7 @@ async function downloadManifest(appstoreId) {
|
|
|
513
513
|
|
|
514
514
|
const [error, response] = await safe(superagent.get(url).ok(() => true));
|
|
515
515
|
if (error) throw new Error(`Failed to list apps from appstore: ${error.message}`);
|
|
516
|
-
if (response.
|
|
516
|
+
if (response.status !== 200) throw new Error(`Failed to get app info from store: ${requestError(response)}`);
|
|
517
517
|
|
|
518
518
|
return { manifest: response.body.manifest, manifestFilePath: null /* manifest file path */ };
|
|
519
519
|
}
|
|
@@ -647,7 +647,7 @@ async function install(localOptions, cmd) {
|
|
|
647
647
|
|
|
648
648
|
const request = createRequest('POST', '/api/v1/apps/install', options);
|
|
649
649
|
const response = await request.send(data);
|
|
650
|
-
if (response.
|
|
650
|
+
if (response.status !== 202) return exit(`Failed to install app: ${requestError(response)}`);
|
|
651
651
|
|
|
652
652
|
const appId = response.body.id;
|
|
653
653
|
|
|
@@ -744,7 +744,7 @@ async function setLocation(localOptions, cmd) {
|
|
|
744
744
|
|
|
745
745
|
const request = createRequest('POST', `/api/v1/apps/${app.id}/configure/location`, options);
|
|
746
746
|
const response = await request.send(data);
|
|
747
|
-
if (response.
|
|
747
|
+
if (response.status !== 202) return exit(`Failed to configure app: ${requestError(response)}`);
|
|
748
748
|
|
|
749
749
|
await waitForTask(response.body.taskId, options);
|
|
750
750
|
await waitForHealthy(app.id, options);
|
|
@@ -800,7 +800,7 @@ async function update(localOptions, cmd) {
|
|
|
800
800
|
|
|
801
801
|
const request = createRequest('POST', apiPath, options);
|
|
802
802
|
const response = await request.send(data);
|
|
803
|
-
if (response.
|
|
803
|
+
if (response.status !== 202) return exit(`Failed to update app: ${requestError(response)}`);
|
|
804
804
|
|
|
805
805
|
process.stdout.write('\n => ' + 'Waiting for app to be updated ');
|
|
806
806
|
|
|
@@ -829,7 +829,7 @@ async function debug(args, localOptions, cmd) {
|
|
|
829
829
|
|
|
830
830
|
const request = createRequest('POST', `/api/v1/apps/${app.id}/configure/debug_mode`, options);
|
|
831
831
|
const response = await request.send(data);
|
|
832
|
-
if (response.
|
|
832
|
+
if (response.status !== 202) return exit(`Failed to set debug mode: ${requestError(response)}`);
|
|
833
833
|
|
|
834
834
|
await waitForTask(response.body.taskId, options);
|
|
835
835
|
|
|
@@ -843,7 +843,7 @@ async function debug(args, localOptions, cmd) {
|
|
|
843
843
|
|
|
844
844
|
const request2 = createRequest('POST', `/api/v1/apps/${app.id}/configure/memory_limit`, options);
|
|
845
845
|
const response2 = await request2.send({ memoryLimit });
|
|
846
|
-
if (response2.
|
|
846
|
+
if (response2.status !== 202) return exit(`Failed to set memory limit: ${requestError(response2)}`);
|
|
847
847
|
|
|
848
848
|
await waitForTask(response2.body.taskId, options);
|
|
849
849
|
console.log('\n\nDone');
|
|
@@ -863,7 +863,7 @@ async function repair(localOptions, cmd) {
|
|
|
863
863
|
|
|
864
864
|
const request = createRequest('POST', `/api/v1/apps/${app.id}/repair`, options);
|
|
865
865
|
const response = await request.send(data);
|
|
866
|
-
if (response.
|
|
866
|
+
if (response.status !== 202) return exit(`Failed to set repair mode: ${requestError(response)}`);
|
|
867
867
|
|
|
868
868
|
process.stdout.write('\n => ' + 'Waiting for app to be repaired ');
|
|
869
869
|
|
|
@@ -896,13 +896,13 @@ async function uninstall(localOptions, cmd) {
|
|
|
896
896
|
await stopActiveTask(app, options);
|
|
897
897
|
|
|
898
898
|
const response = await createRequest('POST', `/api/v1/apps/${app.id}/uninstall`, options);
|
|
899
|
-
if (response.
|
|
899
|
+
if (response.status !== 202) return exit(`Failed to uninstall app: ${requestError(response)}`);
|
|
900
900
|
|
|
901
901
|
process.stdout.write('\n => ' + 'Waiting for app to be uninstalled ');
|
|
902
902
|
|
|
903
903
|
await waitForTask(response.body.taskId, options);
|
|
904
904
|
const response2 = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
|
|
905
|
-
if (response2.
|
|
905
|
+
if (response2.status === 404) {
|
|
906
906
|
console.log('\n\nApp %s successfully uninstalled.', app.fqdn);
|
|
907
907
|
} else if (response2.body.installationState === 'error') {
|
|
908
908
|
console.log('\n\nApp uninstallation failed.\n');
|
|
@@ -968,7 +968,7 @@ async function logs(localOptions, cmd) {
|
|
|
968
968
|
|
|
969
969
|
const req = superagent.get(url, { rejectUnauthorized });
|
|
970
970
|
req.on('response', function (response) {
|
|
971
|
-
if (response.
|
|
971
|
+
if (response.status !== 200) return exit(`Failed to get logs: ${requestError(response)}`);
|
|
972
972
|
});
|
|
973
973
|
req.on('error', (error) => exit(`Pipe error: ${error.message}`));
|
|
974
974
|
|
|
@@ -1003,13 +1003,13 @@ async function inspect(localOptions, cmd) {
|
|
|
1003
1003
|
try {
|
|
1004
1004
|
const options = cmd.optsWithGlobals();
|
|
1005
1005
|
const response = await createRequest('GET', '/api/v1/apps', options);
|
|
1006
|
-
if (response.
|
|
1006
|
+
if (response.status !== 200) return exit(`Failed to list apps: ${requestError(response)}`);
|
|
1007
1007
|
|
|
1008
1008
|
const apps = [];
|
|
1009
1009
|
|
|
1010
1010
|
for (const app of response.body.apps) {
|
|
1011
1011
|
const response2 = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
|
|
1012
|
-
if (response2.
|
|
1012
|
+
if (response2.status !== 200) return exit(`Failed to list app: ${requestError(response2)}`);
|
|
1013
1013
|
response2.body.location = response2.body.location || response2.body.subdomain; // LEGACY support
|
|
1014
1014
|
apps.push(response2.body);
|
|
1015
1015
|
}
|
|
@@ -1070,7 +1070,7 @@ async function backupCreate(localOptions, cmd) {
|
|
|
1070
1070
|
if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
|
|
1071
1071
|
|
|
1072
1072
|
const response = await createRequest('POST', `/api/v1/apps/${app.id}/backup`, options);
|
|
1073
|
-
if (response.
|
|
1073
|
+
if (response.status !== 202) return exit(`Failed to start backup: ${requestError(response)}`);
|
|
1074
1074
|
|
|
1075
1075
|
// FIXME: this should be waitForHealthCheck but the box code incorrectly modifies the installationState
|
|
1076
1076
|
await waitForFinishBackup(app.id, response.body.taskId, options);
|
|
@@ -1087,7 +1087,7 @@ async function backupList(localOptions, cmd) {
|
|
|
1087
1087
|
if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
|
|
1088
1088
|
|
|
1089
1089
|
const response = await createRequest('GET', `/api/v1/apps/${app.id}/backups`, options);
|
|
1090
|
-
if (response.
|
|
1090
|
+
if (response.status !== 200) return exit(`Failed to list backups: ${requestError(response)}`);
|
|
1091
1091
|
|
|
1092
1092
|
if (options.raw) return console.log(JSON.stringify(response.body.backups, null, 4));
|
|
1093
1093
|
|
|
@@ -1117,7 +1117,7 @@ async function backupList(localOptions, cmd) {
|
|
|
1117
1117
|
|
|
1118
1118
|
async function getLastBackup(app, options) {
|
|
1119
1119
|
const response = await createRequest('GET', `/api/v1/apps/${app.id}/backups`, options);
|
|
1120
|
-
if (response.
|
|
1120
|
+
if (response.status !== 200) throw new Error(`Failed to list backups: ${requestError(response)}`);
|
|
1121
1121
|
if (response.body.backups.length === 0) throw new Error('No backups');
|
|
1122
1122
|
|
|
1123
1123
|
response.body.backups = response.body.backups.map(function (backup) {
|
|
@@ -1143,7 +1143,7 @@ async function restore(localOptions, cmd) {
|
|
|
1143
1143
|
|
|
1144
1144
|
const request = createRequest('POST', `/api/v1/apps/${app.id}/restore`, options);
|
|
1145
1145
|
const response = await request.send({ backupId });
|
|
1146
|
-
if (response.
|
|
1146
|
+
if (response.status !== 202) return exit(`Failed to restore app: ${requestError(response)}`);
|
|
1147
1147
|
|
|
1148
1148
|
// FIXME: this should be waitForHealthCheck but the box code incorrectly modifies the installationState
|
|
1149
1149
|
await waitForFinishInstallation(app.id, response.body.taskId, options);
|
|
@@ -1202,7 +1202,7 @@ async function importApp(localOptions, cmd) {
|
|
|
1202
1202
|
}
|
|
1203
1203
|
|
|
1204
1204
|
const response = await createRequest('POST', `/api/v1/apps/${app.id}/import`, options);
|
|
1205
|
-
if (response.
|
|
1205
|
+
if (response.status !== 202) return exit(`Failed to import app: ${requestError(response)}`);
|
|
1206
1206
|
|
|
1207
1207
|
await waitForFinishInstallation(app.id, response.body.taskId, options);
|
|
1208
1208
|
console.log('\n\nApp is restored');
|
|
@@ -1223,7 +1223,7 @@ async function exportApp(localOptions, cmd) {
|
|
|
1223
1223
|
|
|
1224
1224
|
const request = createRequest('POST', `/api/v1/apps/${app.id}/export`, options);
|
|
1225
1225
|
const response = await request.send(data);
|
|
1226
|
-
if (response.
|
|
1226
|
+
if (response.status !== 202) return exit(`Failed to export app: ${requestError(response)}`);
|
|
1227
1227
|
|
|
1228
1228
|
await waitForFinishInstallation(app.id, response.body.taskId, options);
|
|
1229
1229
|
console.log('\n\nApp is exported');
|
|
@@ -1256,7 +1256,7 @@ async function clone(localOptions, cmd) {
|
|
|
1256
1256
|
};
|
|
1257
1257
|
const request = createRequest('POST', `/api/v1/apps/${app.id}/clone`, options);
|
|
1258
1258
|
const response = await request.send(data);
|
|
1259
|
-
if (response.
|
|
1259
|
+
if (response.status !== 201) return exit(`Failed to list apps: ${requestError(response)}`);
|
|
1260
1260
|
|
|
1261
1261
|
// FIXME: this should be waitForHealthCheck but the box code incorrectly modifies the installationState
|
|
1262
1262
|
console.log('App cloned as id ' + response.body.id);
|
|
@@ -1317,12 +1317,12 @@ async function exec(args, localOptions, cmd) {
|
|
|
1317
1317
|
|
|
1318
1318
|
const request = createRequest('POST', `/api/v1/apps/${app.id}/exec`, options);
|
|
1319
1319
|
const response = await request.send({ cmd: args, tty, lang });
|
|
1320
|
-
if (response.
|
|
1320
|
+
if (response.status !== 200) return exit(`Failed to create exec: ${requestError(response)}`);
|
|
1321
1321
|
const execId = response.body.id;
|
|
1322
1322
|
|
|
1323
1323
|
async function exitWithCode() {
|
|
1324
1324
|
const response2 = await createRequest('GET', `/api/v1/apps/${app.id}/exec/${execId}`, options);
|
|
1325
|
-
if (response2.
|
|
1325
|
+
if (response2.status !== 200) return exit(`Failed to get exec code: ${requestError(response2)}`);
|
|
1326
1326
|
|
|
1327
1327
|
process.exit(response2.body.exitCode);
|
|
1328
1328
|
}
|
|
@@ -1519,7 +1519,7 @@ async function setEnv(app, env, options) {
|
|
|
1519
1519
|
|
|
1520
1520
|
const request = createRequest('POST', `/api/v1/apps/${app.id}/configure/env`, options);
|
|
1521
1521
|
const response = await request.send({ env });
|
|
1522
|
-
if (response.
|
|
1522
|
+
if (response.status !== 202) return exit(`Failed to set env: ${requestError(response)}`);
|
|
1523
1523
|
|
|
1524
1524
|
await waitForTask(response.body.taskId, options);
|
|
1525
1525
|
console.log('\n');
|
|
@@ -1532,7 +1532,7 @@ async function envSet(envVars, localOptions, cmd) {
|
|
|
1532
1532
|
if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
|
|
1533
1533
|
|
|
1534
1534
|
const response = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
|
|
1535
|
-
if (response.
|
|
1535
|
+
if (response.status !== 200) return exit(`Failed to get app: ${requestError(response)}`);
|
|
1536
1536
|
|
|
1537
1537
|
const env = response.body.env;
|
|
1538
1538
|
envVars.forEach(envVar => {
|
|
@@ -1554,7 +1554,7 @@ async function envUnset(envNames, localOptions, cmd) {
|
|
|
1554
1554
|
if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
|
|
1555
1555
|
|
|
1556
1556
|
const response = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
|
|
1557
|
-
if (response.
|
|
1557
|
+
if (response.status !== 200) return exit(`Failed to get app: ${requestError(response)}`);
|
|
1558
1558
|
|
|
1559
1559
|
const env = response.body.env;
|
|
1560
1560
|
envNames.forEach(name => delete env[name]);
|
|
@@ -1572,7 +1572,7 @@ async function envList(localOptions, cmd) {
|
|
|
1572
1572
|
if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
|
|
1573
1573
|
|
|
1574
1574
|
const response = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
|
|
1575
|
-
if (response.
|
|
1575
|
+
if (response.status !== 200) return exit(`Failed to get app: ${requestError(response)}`);
|
|
1576
1576
|
|
|
1577
1577
|
const t = new Table();
|
|
1578
1578
|
|
|
@@ -1600,7 +1600,7 @@ async function envGet(envName, localOptions, cmd) {
|
|
|
1600
1600
|
if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
|
|
1601
1601
|
|
|
1602
1602
|
const response = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
|
|
1603
|
-
if (response.
|
|
1603
|
+
if (response.status !== 200) return exit(`Failed to get app: ${requestError(response)}`);
|
|
1604
1604
|
|
|
1605
1605
|
console.log(response.body.env[envName]);
|
|
1606
1606
|
} catch (error) {
|
package/src/appstore-actions.js
CHANGED
|
@@ -11,7 +11,7 @@ const assert = require('assert'),
|
|
|
11
11
|
path = require('path'),
|
|
12
12
|
readline = require('./readline.js'),
|
|
13
13
|
safe = require('safetydance'),
|
|
14
|
-
superagent = require('superagent'),
|
|
14
|
+
superagent = require('./superagent.js'),
|
|
15
15
|
Table = require('easy-table');
|
|
16
16
|
|
|
17
17
|
exports = module.exports = {
|
|
@@ -31,9 +31,9 @@ exports = module.exports = {
|
|
|
31
31
|
const NO_MANIFEST_FOUND_ERROR_STRING = 'No CloudronManifest.json found';
|
|
32
32
|
|
|
33
33
|
function requestError(response) {
|
|
34
|
-
if (response.
|
|
34
|
+
if (response.status === 401) return 'Invalid token. Use cloudron appstore login again.';
|
|
35
35
|
|
|
36
|
-
return `${response.
|
|
36
|
+
return `${response.status} message: ${response.body?.message || response.text || JSON.stringify(response.body)}`; // body is sometimes just a string like in 401
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
function createRequest(method, apiPath, options) {
|
|
@@ -42,7 +42,7 @@ function createRequest(method, apiPath, options) {
|
|
|
42
42
|
let url = `${config.appStoreOrigin()}${apiPath}`;
|
|
43
43
|
if (url.includes('?')) url += '&'; else url += '?';
|
|
44
44
|
url += `accessToken=${token}`;
|
|
45
|
-
const request = superagent(method, url);
|
|
45
|
+
const request = superagent.request(method, url);
|
|
46
46
|
request.retry(3);
|
|
47
47
|
request.ok(() => true);
|
|
48
48
|
return request;
|
|
@@ -77,7 +77,7 @@ async function authenticate(options) { // maybe we can use options.token to vali
|
|
|
77
77
|
config.setAppStoreToken(null);
|
|
78
78
|
|
|
79
79
|
const response = await superagent.post(createUrl('/api/v1/login')).auth(email, password).send({ totpToken: options.totpToken }).ok(() => true);
|
|
80
|
-
if (response.
|
|
80
|
+
if (response.status === 401 && response.body.message.indexOf('TOTP') !== -1) {
|
|
81
81
|
if (response.body.message === 'TOTP token missing') console.log('A 2FA TOTP Token is required for this account.');
|
|
82
82
|
|
|
83
83
|
options.totpToken = await readline.question('2FA token: ', {});
|
|
@@ -88,7 +88,7 @@ async function authenticate(options) { // maybe we can use options.token to vali
|
|
|
88
88
|
return await authenticate(options); // try again with top set
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
if (response.
|
|
91
|
+
if (response.status !== 200) {
|
|
92
92
|
console.log('Login failed.');
|
|
93
93
|
|
|
94
94
|
options.hideBanner = true;
|
|
@@ -118,7 +118,7 @@ async function info(localOptions, cmd) {
|
|
|
118
118
|
const [id, version] = await getAppstoreId(options.appstoreId);
|
|
119
119
|
|
|
120
120
|
const response = await createRequest('GET', `/api/v1/developers/apps/${id}/versions/${version}`, options);
|
|
121
|
-
if (response.
|
|
121
|
+
if (response.status !== 200) return exit(new Error(`Failed to list versions: ${requestError(response)}`));
|
|
122
122
|
|
|
123
123
|
const manifest = response.body.manifest;
|
|
124
124
|
console.log('id: %s', manifest.id);
|
|
@@ -134,7 +134,7 @@ async function listVersions(localOptions, cmd) {
|
|
|
134
134
|
const [id] = await getAppstoreId(options.appstoreId);
|
|
135
135
|
|
|
136
136
|
const response = await createRequest('GET', `/api/v1/developers/apps/${id}/versions`, options);
|
|
137
|
-
if (response.
|
|
137
|
+
if (response.status !== 200) return exit(new Error(`Failed to list versions: ${requestError(response)}`));
|
|
138
138
|
|
|
139
139
|
if (response.body.versions.length === 0) return console.log('No versions found.');
|
|
140
140
|
|
|
@@ -219,8 +219,8 @@ async function addVersion(manifest, baseDir, options) {
|
|
|
219
219
|
if (iconFilePath) request.attach('icon', iconFilePath);
|
|
220
220
|
request.attach('manifest', Buffer.from(JSON.stringify(manifest)), 'manifest');
|
|
221
221
|
const response = await request;
|
|
222
|
-
if (response.
|
|
223
|
-
if (response.
|
|
222
|
+
if (response.status === 409) throw new Error('This version already exists. Use --force to overwrite.');
|
|
223
|
+
if (response.status !== 204) throw new Error(`Failed to publish version: ${requestError(response)}`);
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
async function updateVersion(manifest, baseDir, options) {
|
|
@@ -262,7 +262,7 @@ async function updateVersion(manifest, baseDir, options) {
|
|
|
262
262
|
if (iconFilePath) request.attach('icon', iconFilePath);
|
|
263
263
|
request.attach('manifest', Buffer.from(JSON.stringify(manifest)), 'manifest');
|
|
264
264
|
const response = await request;
|
|
265
|
-
if (response.
|
|
265
|
+
if (response.status !== 204) throw new Error(`Failed to publish version: ${requestError(response)}`);
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
async function verifyManifest(localOptions, cmd) {
|
|
@@ -334,7 +334,7 @@ async function upload(localOptions, cmd) {
|
|
|
334
334
|
|
|
335
335
|
const [repo, tag] = manifest.dockerImage.split(':');
|
|
336
336
|
const [tagError, tagResponse] = await safe(superagent.get(`https://hub.docker.com/v2/repositories/${repo}/tags/${tag}`).ok(() => true));
|
|
337
|
-
if (tagError || tagResponse.
|
|
337
|
+
if (tagError || tagResponse.status !== 200) return exit(`Failed to find docker image in dockerhub. check https://hub.docker.com/r/${repo}/tags : ${tagError || requestError(tagResponse)}`);
|
|
338
338
|
|
|
339
339
|
// ensure the app is known on the appstore side
|
|
340
340
|
const baseDir = path.dirname(manifestFilePath);
|
|
@@ -342,7 +342,7 @@ async function upload(localOptions, cmd) {
|
|
|
342
342
|
const request = createRequest('POST', '/api/v1/developers/apps', options);
|
|
343
343
|
request.send({ id: manifest.id });
|
|
344
344
|
const response = await request;
|
|
345
|
-
if (response.
|
|
345
|
+
if (response.status !== 409 && response.status !== 201) return exit(`Failed to add app: ${requestError(response)}`); // 409 means already exists
|
|
346
346
|
console.log(`Uploading ${manifest.id}@${manifest.version} (dockerImage: ${manifest.dockerImage}) for testing`);
|
|
347
347
|
|
|
348
348
|
const [error2] = await safe(options.force ? updateVersion(manifest, baseDir, options) : addVersion(manifest, baseDir, options));
|
|
@@ -362,12 +362,12 @@ async function submit(localOptions, cmd) {
|
|
|
362
362
|
const manifest = result.manifest;
|
|
363
363
|
|
|
364
364
|
const response = await createRequest('POST', `/api/v1/developers/apps/${manifest.id}/versions/${manifest.version}/submit`, options);
|
|
365
|
-
if (response.
|
|
365
|
+
if (response.status === 404) {
|
|
366
366
|
console.log(`No version ${manifest.version} found. Please use 'cloudron apsptore upload' first`);
|
|
367
367
|
return exit('Failed to submit app for review.');
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
-
if (response.
|
|
370
|
+
if (response.status !== 200) return exit(`Failed to submit app: ${requestError(response)}`);
|
|
371
371
|
|
|
372
372
|
console.log('App submitted for review.');
|
|
373
373
|
console.log('You will receive an email when approved.');
|
|
@@ -381,7 +381,7 @@ async function revoke(localOptions, cmd) {
|
|
|
381
381
|
console.log(`Revoking ${id}@${version}`);
|
|
382
382
|
|
|
383
383
|
const response = await createRequest('POST', `/api/v1/developers/apps/${id}/versions/${version}/revoke`, options);
|
|
384
|
-
if (response.
|
|
384
|
+
if (response.status !== 200) return exit(`Failed to revoke version: ${requestError(response)}`);
|
|
385
385
|
|
|
386
386
|
console.log('version revoked.');
|
|
387
387
|
}
|
|
@@ -418,7 +418,7 @@ async function approve(localOptions, cmd) {
|
|
|
418
418
|
}
|
|
419
419
|
|
|
420
420
|
const response = await createRequest('POST', `/api/v1/developers/apps/${appstoreId}/versions/${version}/approve`, options);
|
|
421
|
-
if (response.
|
|
421
|
+
if (response.status !== 200) return exit(`Failed to approve version: ${requestError(response)}`);
|
|
422
422
|
|
|
423
423
|
if (options.gitPush) {
|
|
424
424
|
safe.child_process.execSync(`git push --atomic origin ${defaultBranch} ${latestTag}`, { encoding: 'utf8' });
|
|
@@ -429,7 +429,7 @@ async function approve(localOptions, cmd) {
|
|
|
429
429
|
console.log('');
|
|
430
430
|
|
|
431
431
|
const response2 = await createRequest('GET', `/api/v1/developers/apps/${appstoreId}/versions/${version}`, options);
|
|
432
|
-
if (response2.
|
|
432
|
+
if (response2.status !== 200) return exit(`Failed to list apps: ${requestError(response)}`);
|
|
433
433
|
|
|
434
434
|
console.log('Changelog for forum update: ' + response2.body.manifest.forumUrl);
|
|
435
435
|
console.log('');
|
|
@@ -469,18 +469,18 @@ async function notify() {
|
|
|
469
469
|
const categoryId = categoryMatch[1];
|
|
470
470
|
|
|
471
471
|
const categoryResponse = await superagent.get(`https://forum.cloudron.io/api/v3/categories/${categoryId}/topics`).set('Authorization', `Bearer ${apiToken}`).ok(() => true);
|
|
472
|
-
if (categoryResponse.
|
|
472
|
+
if (categoryResponse.status !== 200) return exit(`Unable to get topics of category: ${requestError(categoryResponse)}`);
|
|
473
473
|
const topic = categoryResponse.body.response.topics.find(t => t.title.includes('Package Updates'));
|
|
474
474
|
if (!topic) return exit('Could not find the Package Update topic');
|
|
475
475
|
const topicId = topic.tid;
|
|
476
476
|
|
|
477
477
|
const pageCountResponse = await superagent.get(`https://forum.cloudron.io/api/topic/pagination/${topicId}`).set('Authorization', `Bearer ${apiToken}`).ok(() => true);
|
|
478
|
-
if (pageCountResponse.
|
|
478
|
+
if (pageCountResponse.status !== 200) return exit(`Unable to get page count of topic: ${requestError(pageCountResponse)}`);
|
|
479
479
|
const pageCount = pageCountResponse.body.pagination.pageCount;
|
|
480
480
|
|
|
481
481
|
for (let page = 1; page <= pageCount; page++) {
|
|
482
482
|
const pageResponse = await superagent.get(`https://forum.cloudron.io/api/topic/${topicId}?page=${page}`).set('Authorization', `Bearer ${apiToken}`).ok(() => true);
|
|
483
|
-
if (pageResponse.
|
|
483
|
+
if (pageResponse.status !== 200) return exit(`Unable to get topics of category: ${requestError(pageResponse)}`);
|
|
484
484
|
for (const post of pageResponse.body.posts) { // post.content is html!
|
|
485
485
|
if (post.content.includes(`[${manifest.version}]`)) return exit(`Version ${manifest.version} is already on the forum.\n${post.content}`);
|
|
486
486
|
}
|
|
@@ -492,6 +492,6 @@ async function notify() {
|
|
|
492
492
|
toPid: 0 // which post is this post a reply to
|
|
493
493
|
};
|
|
494
494
|
const postResponse = await superagent.post(`https://forum.cloudron.io/api/v3/topics/${topicId}`).set('Authorization', `Bearer ${apiToken}`).send(postData).ok(() => true);
|
|
495
|
-
if (postResponse.
|
|
495
|
+
if (postResponse.status !== 200) return exit(`Unable to create changelog post: ${requestError(postResponse)}`);
|
|
496
496
|
console.log('Posted to forum');
|
|
497
497
|
}
|
package/src/build-actions.js
CHANGED
|
@@ -18,19 +18,19 @@ const assert = require('assert'),
|
|
|
18
18
|
helper = require('./helper.js'),
|
|
19
19
|
manifestFormat = require('cloudron-manifestformat'),
|
|
20
20
|
micromatch = require('micromatch'),
|
|
21
|
-
readline = require('./readline.js'),
|
|
22
|
-
superagent = require('superagent'),
|
|
23
21
|
os = require('os'),
|
|
24
22
|
path = require('path'),
|
|
23
|
+
readline = require('./readline.js'),
|
|
25
24
|
safe = require('safetydance'),
|
|
26
25
|
stream = require('stream/promises'),
|
|
26
|
+
superagent = require('./superagent.js'),
|
|
27
27
|
tar = require('tar-fs'),
|
|
28
28
|
url = require('url');
|
|
29
29
|
|
|
30
30
|
function requestError(response) {
|
|
31
|
-
if (response.
|
|
31
|
+
if (response.status === 401 || response.status === 403) return 'Invalid token. Use cloudron build login again.';
|
|
32
32
|
|
|
33
|
-
return `${response.
|
|
33
|
+
return `${response.status} message: ${response.body?.message || response.text || null}`;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
// analyzes options and merges with any existing build service config
|
|
@@ -73,8 +73,8 @@ async function login(localOptions, cmd) {
|
|
|
73
73
|
const token = options.buildServiceToken || await readline.question('Token: ', {});
|
|
74
74
|
|
|
75
75
|
const response = await superagent.get(`${buildServiceConfig.url}/api/v1/profile`).query({ accessToken: token }).ok(() => true);
|
|
76
|
-
if (response.
|
|
77
|
-
if (response.
|
|
76
|
+
if (response.status === 401 || response.status === 403) return exit(`Authentication error: ${requestError(response)}`);
|
|
77
|
+
if (response.status !== 200) return exit(`Unexpected response: ${requestError(response)}`);
|
|
78
78
|
|
|
79
79
|
buildServiceConfig.token = token;
|
|
80
80
|
config.setBuildServiceConfig(buildServiceConfig);
|
|
@@ -156,7 +156,7 @@ async function getStatus(buildId) {
|
|
|
156
156
|
const response2 = await superagent.get(`${buildServiceConfig.url}/api/v1/builds/${buildId}`)
|
|
157
157
|
.query({ accessToken: buildServiceConfig.token })
|
|
158
158
|
.ok(() => true);
|
|
159
|
-
if (response2.
|
|
159
|
+
if (response2.status !== 200) throw new Error(`Failed to get status: ${requestError(response2)}`);
|
|
160
160
|
return response2.body.status;
|
|
161
161
|
}
|
|
162
162
|
|
|
@@ -278,8 +278,8 @@ async function buildRemote(manifest, sourceDir, appConfig, options) {
|
|
|
278
278
|
.field('buildArgs', JSON.stringify(buildArgsObject))
|
|
279
279
|
.attach('sourceArchive', sourceArchiveFilePath)
|
|
280
280
|
.ok(() => true);
|
|
281
|
-
if (response.
|
|
282
|
-
if (response.
|
|
281
|
+
if (response.status === 413) return exit('Failed to build app. The app source is too large.\nPlease adjust your .dockerignore file to only include neccessary files.');
|
|
282
|
+
if (response.status !== 201) return exit(`Failed to upload app for building: ${requestError(response)}`);
|
|
283
283
|
|
|
284
284
|
const buildId = response.body.id;
|
|
285
285
|
console.log(`BuildId: ${buildId}`);
|
|
@@ -380,7 +380,7 @@ async function push(localOptions, cmd) {
|
|
|
380
380
|
.query({ accessToken: buildServiceConfig.token })
|
|
381
381
|
.send({ dockerImageRepo: repository, dockerImageTag: tag })
|
|
382
382
|
.ok(() => true);
|
|
383
|
-
if (response.
|
|
383
|
+
if (response.status !== 201) return exit(`Failed to push: ${requestError(response)}`);
|
|
384
384
|
|
|
385
385
|
const [logsError] = await safe(followBuildLog(options.id, !!options.raw));
|
|
386
386
|
if (logsError) console.log(`Failed to get logs: ${logsError.message}`);
|
package/src/superagent.js
CHANGED
|
@@ -21,20 +21,30 @@ const assert = require('assert'),
|
|
|
21
21
|
safe = require('safetydance');
|
|
22
22
|
|
|
23
23
|
class Request {
|
|
24
|
+
#boundary;
|
|
25
|
+
#redirectCount;
|
|
26
|
+
#retryCount;
|
|
27
|
+
#timer;
|
|
28
|
+
#body;
|
|
29
|
+
#okFunc;
|
|
30
|
+
#options;
|
|
31
|
+
#url;
|
|
32
|
+
|
|
24
33
|
constructor(method, url) {
|
|
25
34
|
assert.strictEqual(typeof url, 'string');
|
|
26
35
|
|
|
27
|
-
this
|
|
28
|
-
this
|
|
36
|
+
this.#url = new URL(url);
|
|
37
|
+
this.#options = {
|
|
29
38
|
method,
|
|
30
39
|
headers: {},
|
|
31
40
|
signal: null // set for timeouts
|
|
32
41
|
};
|
|
33
|
-
this
|
|
34
|
-
this
|
|
35
|
-
this
|
|
36
|
-
this
|
|
37
|
-
this
|
|
42
|
+
this.#okFunc = ({ status }) => status >=200 && status <= 299;
|
|
43
|
+
this.#timer = { timeout: 0, id: null, controller: null };
|
|
44
|
+
this.#retryCount = 0;
|
|
45
|
+
this.#body = Buffer.alloc(0);
|
|
46
|
+
this.#redirectCount = 5;
|
|
47
|
+
this.#boundary = null; // multipart only
|
|
38
48
|
}
|
|
39
49
|
|
|
40
50
|
async _handleResponse(url, response) {
|
|
@@ -74,7 +84,7 @@ class Request {
|
|
|
74
84
|
async _makeRequest(url) {
|
|
75
85
|
return new Promise((resolve, reject) => {
|
|
76
86
|
const proto = url.protocol === 'https:' ? https : http;
|
|
77
|
-
const request = proto.request(url, this
|
|
87
|
+
const request = proto.request(url, this.#options); // ClientRequest
|
|
78
88
|
|
|
79
89
|
request.on('error', reject); // network error, dns error
|
|
80
90
|
request.on('response', async (response) => {
|
|
@@ -82,7 +92,7 @@ class Request {
|
|
|
82
92
|
if (error) reject(error); else resolve(result);
|
|
83
93
|
});
|
|
84
94
|
|
|
85
|
-
|
|
95
|
+
request.write(this.#body);
|
|
86
96
|
|
|
87
97
|
request.end();
|
|
88
98
|
});
|
|
@@ -91,24 +101,24 @@ class Request {
|
|
|
91
101
|
async _start() {
|
|
92
102
|
let error;
|
|
93
103
|
|
|
94
|
-
for (let i = 0; i < this
|
|
95
|
-
if (this
|
|
96
|
-
debug(`${this
|
|
104
|
+
for (let i = 0; i < this.#retryCount+1; i++) {
|
|
105
|
+
if (this.#timer.timeout) this.#timer.id = setTimeout(() => this.#timer.controller.abort(), this.#timer.timeout);
|
|
106
|
+
debug(`${this.#options.method} ${this.#url.toString()}` + (i ? ` try ${i+1}` : ''));
|
|
97
107
|
|
|
98
|
-
let response, url = this
|
|
99
|
-
for (let redirects = 0; redirects < this
|
|
108
|
+
let response, url = this.#url;
|
|
109
|
+
for (let redirects = 0; redirects < this.#redirectCount+1; redirects++) {
|
|
100
110
|
[error, response] = await safe(this._makeRequest(url));
|
|
101
|
-
if (error || (response.status < 300 || response.status > 399) || (this
|
|
111
|
+
if (error || (response.status < 300 || response.status > 399) || (this.#options.method !== 'GET')) break;
|
|
102
112
|
url = response.url; // follow
|
|
103
113
|
}
|
|
104
114
|
|
|
105
|
-
if (!error && !this
|
|
115
|
+
if (!error && !this.#okFunc({ status: response.status })) {
|
|
106
116
|
error = new Error(`${response.status} ${http.STATUS_CODES[response.status]}`);
|
|
107
117
|
Object.assign(error, response);
|
|
108
118
|
}
|
|
109
119
|
|
|
110
|
-
if (error) debug(`${this
|
|
111
|
-
if (this
|
|
120
|
+
if (error) debug(`${this.#options.method} ${this.#url.toString()} ${error.message}`);
|
|
121
|
+
if (this.#timer.timeout) clearTimeout(this.#timer.id);
|
|
112
122
|
if (!error) return response;
|
|
113
123
|
}
|
|
114
124
|
|
|
@@ -116,52 +126,52 @@ class Request {
|
|
|
116
126
|
}
|
|
117
127
|
|
|
118
128
|
set(name, value) {
|
|
119
|
-
this
|
|
129
|
+
this.#options.headers[name.toLowerCase()] = value;
|
|
120
130
|
return this;
|
|
121
131
|
}
|
|
122
132
|
|
|
123
133
|
query(data) {
|
|
124
|
-
Object.entries(data).forEach(([key, value]) => this
|
|
134
|
+
Object.entries(data).forEach(([key, value]) => this.#url.searchParams.append(key, value));
|
|
125
135
|
return this;
|
|
126
136
|
}
|
|
127
137
|
|
|
128
138
|
redirects(count) {
|
|
129
|
-
this
|
|
139
|
+
this.#redirectCount = count;
|
|
130
140
|
return this;
|
|
131
141
|
}
|
|
132
142
|
|
|
133
143
|
send(data) {
|
|
134
|
-
const contentType = this
|
|
144
|
+
const contentType = this.#options.headers['content-type'];
|
|
135
145
|
if (!contentType || contentType === 'application/json') {
|
|
136
|
-
this
|
|
137
|
-
this
|
|
138
|
-
this
|
|
146
|
+
this.#options.headers['content-type'] = 'application/json';
|
|
147
|
+
this.#body = Buffer.from(JSON.stringify(data), 'utf8');
|
|
148
|
+
this.#options.headers['content-length'] = this.#body.byteLength;
|
|
139
149
|
} else if (contentType === 'application/x-www-form-urlencoded') {
|
|
140
|
-
this
|
|
141
|
-
this
|
|
150
|
+
this.#body = Buffer.from((new URLSearchParams(data)).toString(), 'utf8');
|
|
151
|
+
this.#options.headers['content-length'] = this.#body.byteLength;
|
|
142
152
|
}
|
|
143
153
|
return this;
|
|
144
154
|
}
|
|
145
155
|
|
|
146
156
|
timeout(msecs) {
|
|
147
|
-
this
|
|
148
|
-
this
|
|
149
|
-
this
|
|
157
|
+
this.#timer.controller = new AbortController();
|
|
158
|
+
this.#timer.timeout = msecs;
|
|
159
|
+
this.#options.signal = this.#timer.controller.signal;
|
|
150
160
|
return this;
|
|
151
161
|
}
|
|
152
162
|
|
|
153
163
|
retry(count) {
|
|
154
|
-
this
|
|
164
|
+
this.#retryCount = Math.max(0, count);
|
|
155
165
|
return this;
|
|
156
166
|
}
|
|
157
167
|
|
|
158
168
|
ok(func) {
|
|
159
|
-
this
|
|
169
|
+
this.#okFunc = func;
|
|
160
170
|
return this;
|
|
161
171
|
}
|
|
162
172
|
|
|
163
173
|
disableTLSCerts() {
|
|
164
|
-
this
|
|
174
|
+
this.#options.rejectUnauthorized = true;
|
|
165
175
|
return this;
|
|
166
176
|
}
|
|
167
177
|
|
|
@@ -170,21 +180,35 @@ class Request {
|
|
|
170
180
|
return this;
|
|
171
181
|
}
|
|
172
182
|
|
|
183
|
+
field(name, value) {
|
|
184
|
+
if (!this.#boundary) this.#boundary = '----WebKitFormBoundary' + Math.random().toString(36).substring(2);
|
|
185
|
+
|
|
186
|
+
const partHeader = Buffer.from(`--${this.#boundary}\r\nContent-Disposition: form-data; name="${name}"\r\n\r\n`, 'utf8');
|
|
187
|
+
const partData = Buffer.from(value, 'utf8');
|
|
188
|
+
this.#body = Buffer.concat([this.#body, partHeader, partData, Buffer.from('\r\n', 'utf8')]);
|
|
189
|
+
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
|
|
173
193
|
attach(name, filepath) { // this is only used in tests and thus simplistic
|
|
174
|
-
|
|
194
|
+
if (!this.#boundary) this.#boundary = '----WebKitFormBoundary' + Math.random().toString(36).substring(2);
|
|
175
195
|
|
|
176
|
-
const partHeader = Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="${name}" filename="${path.basename(filepath)}"\r\n\r\n`, 'utf8');
|
|
196
|
+
const partHeader = Buffer.from(`--${this.#boundary}\r\nContent-Disposition: form-data; name="${name}" filename="${path.basename(filepath)}"\r\n\r\n`, 'utf8');
|
|
177
197
|
const partData = fs.readFileSync(filepath);
|
|
178
|
-
|
|
179
|
-
this.body = Buffer.concat([partHeader, partData, partTrailer]);
|
|
180
|
-
|
|
181
|
-
this.options.headers['content-type'] = `multipart/form-data; boundary=${boundary}`;
|
|
182
|
-
this.options.headers['content-length'] = this.body.byteLength;
|
|
198
|
+
this.#body = Buffer.concat([this.#body, partHeader, partData, Buffer.from('\r\n', 'utf8')]);
|
|
183
199
|
|
|
184
200
|
return this;
|
|
185
201
|
}
|
|
186
202
|
|
|
187
203
|
then(onFulfilled, onRejected) {
|
|
204
|
+
if (this.#boundary) {
|
|
205
|
+
const partTrailer = Buffer.from(`--${this.#boundary}--\r\n`, 'utf8');
|
|
206
|
+
this.#body = Buffer.concat([this.#body, partTrailer]);
|
|
207
|
+
|
|
208
|
+
this.#options.headers['content-type'] = `multipart/form-data; boundary=${this.#boundary}`;
|
|
209
|
+
this.#options.headers['content-length'] = this.#body.byteLength;
|
|
210
|
+
}
|
|
211
|
+
|
|
188
212
|
this._start().then(onFulfilled, onRejected);
|
|
189
213
|
}
|
|
190
214
|
}
|