@pi-r/aws-v3 0.6.0 → 0.6.2
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/README.md +2 -0
- package/client/index.js +148 -35
- package/package.json +9 -8
- package/upload/index.js +33 -37
package/README.md
CHANGED
package/client/index.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.executeBatchQuery = exports.executeQuery = exports.deleteObjectsV2 = exports.deleteObjects = exports.setBucketWebsite = exports.setBucketPolicy = exports.createBucketV2 = exports.createBucket = exports.validateDatabase = exports.validateStorage = exports.createDatabaseClient = exports.createStorageClient = void 0;
|
|
4
4
|
const Lib = require("@aws-sdk/lib-dynamodb");
|
|
5
5
|
const Client = require("@aws-sdk/client-dynamodb");
|
|
6
|
+
const Providers = require("@aws-sdk/credential-providers");
|
|
6
7
|
const aws_1 = require("@pi-r/aws");
|
|
7
8
|
const util_1 = require("@e-mc/cloud/util");
|
|
8
9
|
const types_1 = require("@e-mc/types");
|
|
@@ -14,11 +15,14 @@ async function setCannedAcl(S3, client, Bucket, ACL, service = 'aws-v3', recursi
|
|
|
14
15
|
case 1:
|
|
15
16
|
Policy = (0, aws_1.getBucketPublicReadPolicy)(Bucket);
|
|
16
17
|
break;
|
|
17
|
-
case '
|
|
18
|
-
Policy = (0, aws_1.
|
|
18
|
+
case 'public-read-write':
|
|
19
|
+
Policy = (0, aws_1.getPublicReadPolicy)(Bucket, false, true);
|
|
20
|
+
break;
|
|
21
|
+
case 'authenticated-read':
|
|
22
|
+
Policy = (0, aws_1.getPublicReadPolicy)(Bucket, true);
|
|
19
23
|
break;
|
|
20
24
|
default:
|
|
21
|
-
Policy = (0, aws_1.
|
|
25
|
+
Policy = (0, aws_1.getPrivatePolicy)(Bucket);
|
|
22
26
|
break;
|
|
23
27
|
}
|
|
24
28
|
return client.send(new S3.PutBucketPolicyCommand({ Bucket, Policy }))
|
|
@@ -54,16 +58,77 @@ function sanitizeCredentials(credential) {
|
|
|
54
58
|
}
|
|
55
59
|
return result;
|
|
56
60
|
}
|
|
61
|
+
function fromProvider(credential) {
|
|
62
|
+
const from = credential.provider;
|
|
63
|
+
let credentials = credential.credentials;
|
|
64
|
+
if ((0, types_1.isPlainObject)(from)) {
|
|
65
|
+
if (from.http) {
|
|
66
|
+
credentials = Providers.fromHttp((0, types_1.isPlainObject)(from.http) ? from.http : {});
|
|
67
|
+
}
|
|
68
|
+
else if ((0, types_1.isPlainObject)(from.ini)) {
|
|
69
|
+
credentials = Providers.fromIni(from.ini);
|
|
70
|
+
}
|
|
71
|
+
else if ((0, types_1.isPlainObject)(from.cognitoIdentity)) {
|
|
72
|
+
credentials = Providers.fromCognitoIdentity(from.cognitoIdentity);
|
|
73
|
+
}
|
|
74
|
+
else if ((0, types_1.isPlainObject)(from.cognitoIdentityPool)) {
|
|
75
|
+
credentials = Providers.fromCognitoIdentityPool(from.cognitoIdentityPool);
|
|
76
|
+
}
|
|
77
|
+
else if ((0, types_1.isPlainObject)(from.temporaryCredentials)) {
|
|
78
|
+
credentials = Providers.fromTemporaryCredentials(from.temporaryCredentials);
|
|
79
|
+
}
|
|
80
|
+
else if ((0, types_1.isPlainObject)(from.webToken)) {
|
|
81
|
+
credentials = Providers.fromWebToken(from.webToken);
|
|
82
|
+
}
|
|
83
|
+
else if ((0, types_1.isPlainObject)(from.containerMetadata)) {
|
|
84
|
+
credentials = Providers.fromContainerMetadata(from.containerMetadata);
|
|
85
|
+
}
|
|
86
|
+
else if ((0, types_1.isPlainObject)(from.instanceMetadata)) {
|
|
87
|
+
credentials = Providers.fromInstanceMetadata(from.instanceMetadata);
|
|
88
|
+
}
|
|
89
|
+
else if ((0, types_1.isPlainObject)(from.process)) {
|
|
90
|
+
credentials = Providers.fromProcess(from.process);
|
|
91
|
+
}
|
|
92
|
+
else if ((0, types_1.isPlainObject)(from.tokenFile)) {
|
|
93
|
+
credentials = Providers.fromTokenFile(from.tokenFile);
|
|
94
|
+
}
|
|
95
|
+
else if ((0, types_1.isPlainObject)(from.sso)) {
|
|
96
|
+
credentials = Providers.fromSSO(from.sso);
|
|
97
|
+
}
|
|
98
|
+
else if (from.env && (0, aws_1.isEnvDefined)()) {
|
|
99
|
+
credentials = Providers.fromEnv();
|
|
100
|
+
}
|
|
101
|
+
else if (from.ini && (0, aws_1.isSharedCredentialsDefined)()) {
|
|
102
|
+
credentials = Providers.fromIni();
|
|
103
|
+
}
|
|
104
|
+
if ((0, types_1.isPlainObject)(credentials)) {
|
|
105
|
+
if (from.nodeProviderChain) {
|
|
106
|
+
credentials = Providers.fromNodeProviderChain((0, types_1.isPlainObject)(from.nodeProviderChain) ? { ...credentials, ...from.nodeProviderChain } : credentials);
|
|
107
|
+
}
|
|
108
|
+
credential.credentials = credentials;
|
|
109
|
+
}
|
|
110
|
+
delete credential.provider;
|
|
111
|
+
}
|
|
112
|
+
if ((0, types_1.isPlainObject)(credentials)) {
|
|
113
|
+
let expiration = credentials.expiration;
|
|
114
|
+
if (expiration) {
|
|
115
|
+
if ((0, types_1.isString)(expiration)) {
|
|
116
|
+
expiration = new Date(expiration);
|
|
117
|
+
credentials.expiration = expiration;
|
|
118
|
+
}
|
|
119
|
+
if (!(expiration instanceof Date && !isNaN(expiration.getTime()))) {
|
|
120
|
+
delete credentials.expiration;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return credential;
|
|
125
|
+
}
|
|
57
126
|
const isNoSuchBucket = (err) => err instanceof Error && err.name === 'NoSuchBucket';
|
|
58
127
|
function createStorageClient(credential, service = 'aws-v3', sdk = '@aws-sdk/client-s3') {
|
|
59
128
|
try {
|
|
60
129
|
sanitizeCredentials(credential);
|
|
61
|
-
const expiration = credential.credentials?.expiration;
|
|
62
|
-
if (expiration && !(expiration instanceof Date)) {
|
|
63
|
-
delete credential.expiration;
|
|
64
|
-
}
|
|
65
130
|
const AWS = require(sdk);
|
|
66
|
-
return [new AWS.S3Client(credential), AWS];
|
|
131
|
+
return [new AWS.S3Client(fromProvider(credential)), AWS];
|
|
67
132
|
}
|
|
68
133
|
catch (err) {
|
|
69
134
|
this.checkPackage(err, sdk.split('/')[0], { passThrough: true });
|
|
@@ -72,18 +137,17 @@ function createStorageClient(credential, service = 'aws-v3', sdk = '@aws-sdk/cli
|
|
|
72
137
|
}
|
|
73
138
|
exports.createStorageClient = createStorageClient;
|
|
74
139
|
function createDatabaseClient(credential) {
|
|
75
|
-
return [Lib.DynamoDBDocumentClient.from(new Client.DynamoDBClient(credential), credential.translateConfig), Lib];
|
|
140
|
+
return [Lib.DynamoDBDocumentClient.from(new Client.DynamoDBClient(fromProvider(credential)), credential.translateConfig), Lib];
|
|
76
141
|
}
|
|
77
142
|
exports.createDatabaseClient = createDatabaseClient;
|
|
78
143
|
function validateStorage(credential) {
|
|
79
144
|
const credentials = sanitizeCredentials(credential);
|
|
80
145
|
const profile = credential.profile;
|
|
81
|
-
if (profile || !credentials &&
|
|
82
|
-
|
|
83
|
-
credential.credentials = fromIni({ profile });
|
|
146
|
+
if (profile || !credentials && !credential.provider && (0, aws_1.isSharedCredentialsDefined)()) {
|
|
147
|
+
credential.credentials = Providers.fromIni({ profile });
|
|
84
148
|
return true;
|
|
85
149
|
}
|
|
86
|
-
return !!credentials && (typeof credentials === 'function' || (0, aws_1.isAccessDefined)(credentials) || (0, aws_1.isEnvDefined)()
|
|
150
|
+
return !!credentials && (typeof credentials === 'function' || (0, aws_1.isAccessDefined)(credentials)) || (0, types_1.isPlainObject)(credential.provider) || (0, aws_1.isEnvDefined)();
|
|
87
151
|
}
|
|
88
152
|
exports.validateStorage = validateStorage;
|
|
89
153
|
function validateDatabase(credential, data) {
|
|
@@ -107,7 +171,7 @@ async function createBucketV2(credential, Bucket, ACL, options, service = 'aws-v
|
|
|
107
171
|
.catch(async () => {
|
|
108
172
|
const input = { ...options, Bucket };
|
|
109
173
|
const region = credential.region;
|
|
110
|
-
if (
|
|
174
|
+
if ((0, types_1.isString)(region) && region !== 'us-east-1') {
|
|
111
175
|
input.CreateBucketConfiguration || (input.CreateBucketConfiguration = { LocationConstraint: region });
|
|
112
176
|
}
|
|
113
177
|
return client.send(new AWS.CreateBucketCommand(input))
|
|
@@ -213,40 +277,42 @@ async function executeQuery(credential, data, sessionKey) {
|
|
|
213
277
|
}
|
|
214
278
|
exports.executeQuery = executeQuery;
|
|
215
279
|
async function executeBatchQuery(credential, batch, sessionKey) {
|
|
280
|
+
var _a;
|
|
216
281
|
const length = batch.length;
|
|
217
282
|
const result = new Array(length);
|
|
218
283
|
const caching = length > 0 && this.hasCache(batch[0].service, sessionKey);
|
|
219
284
|
const cacheValue = { value: this.valueOfKey(credential, 'cache'), sessionKey };
|
|
220
285
|
let client;
|
|
221
|
-
const createClient = () => client || (client = createDatabaseClient.call(this,
|
|
286
|
+
const createClient = () => client || (client = createDatabaseClient.call(this, credential));
|
|
222
287
|
const closeClient = () => client?.[0].destroy();
|
|
288
|
+
(0, aws_1.setDatabaseEndpoint)(credential);
|
|
223
289
|
for (let i = 0; i < length; ++i) {
|
|
224
290
|
const item = batch[i];
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
closeClient();
|
|
228
|
-
throw (0, util_1.formatError)(item, "Missing database table" /* ERR_DB.TABLE */);
|
|
229
|
-
}
|
|
230
|
-
(0, aws_1.setDatabaseEndpoint)(credential);
|
|
231
|
-
const renewCache = ignoreCache === 0;
|
|
291
|
+
let { service, table: TableName, id, query, partitionKey, key = partitionKey, limit = 0, update, ignoreCache } = item;
|
|
292
|
+
const useCache = caching && ignoreCache !== true;
|
|
232
293
|
const getCache = (value) => {
|
|
233
294
|
if (ignoreCache !== 1) {
|
|
234
|
-
cacheValue.renewCache =
|
|
295
|
+
cacheValue.renewCache = ignoreCache === 0;
|
|
235
296
|
return this.getQueryResult(service, credential, value, cacheValue);
|
|
236
297
|
}
|
|
237
298
|
};
|
|
238
|
-
|
|
299
|
+
cacheValue.exclusiveOf = Array.isArray(ignoreCache) ? ignoreCache : undefined;
|
|
300
|
+
let rows, queryString = '';
|
|
239
301
|
if (key && (id || (0, types_1.isPlainObject)(key))) {
|
|
240
|
-
if (
|
|
241
|
-
|
|
302
|
+
if (!TableName) {
|
|
303
|
+
closeClient();
|
|
304
|
+
throw (0, util_1.formatError)(item, "Missing database table" /* ERR_DB.TABLE */);
|
|
305
|
+
}
|
|
306
|
+
if (useCache) {
|
|
307
|
+
queryString = TableName + '_' + Module.asString(key, true) + (id !== undefined ? '_' + Module.asString(id, true) : '');
|
|
242
308
|
if (!update && (rows = getCache(queryString))) {
|
|
243
309
|
result[i] = rows;
|
|
244
310
|
continue;
|
|
245
311
|
}
|
|
246
312
|
}
|
|
247
313
|
const [db, AWS] = createClient();
|
|
248
|
-
const Key = (0, types_1.isPlainObject)(key) ? key : { [key]: id };
|
|
249
|
-
const command = { TableName
|
|
314
|
+
const Key = (0, types_1.isPlainObject)(key) ? key : { [key]: (0, aws_1.parseAttributeValue)(id) };
|
|
315
|
+
const command = { TableName, Key };
|
|
250
316
|
if (update) {
|
|
251
317
|
await db.send(new AWS.UpdateCommand({ ...command, ...update }));
|
|
252
318
|
}
|
|
@@ -256,18 +322,65 @@ async function executeBatchQuery(credential, batch, sessionKey) {
|
|
|
256
322
|
}
|
|
257
323
|
}
|
|
258
324
|
else if ((0, types_1.isPlainObject)(query)) {
|
|
259
|
-
if (
|
|
260
|
-
|
|
261
|
-
|
|
325
|
+
if (TableName) {
|
|
326
|
+
query.TableName = TableName;
|
|
327
|
+
}
|
|
328
|
+
if (!query.TableName) {
|
|
329
|
+
closeClient();
|
|
330
|
+
throw (0, util_1.formatError)(item, "Missing database table" /* ERR_DB.TABLE */);
|
|
262
331
|
}
|
|
263
|
-
query.TableName = table;
|
|
264
332
|
if (limit > 0) {
|
|
265
333
|
query.Limit = limit;
|
|
266
334
|
}
|
|
335
|
+
if (useCache && (rows = getCache(queryString = Module.asString(query, true)))) {
|
|
336
|
+
result[i] = rows;
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
const [db, AWS] = createClient();
|
|
340
|
+
const { Count, Items } = await db.send(new AWS.QueryCommand(query));
|
|
341
|
+
if (Count && Items) {
|
|
342
|
+
rows = Items;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
else if ((0, types_1.isArray)(query)) {
|
|
346
|
+
let params = (item.params || {});
|
|
347
|
+
if (!(0, types_1.isPlainObject)(params.RequestItems)) {
|
|
348
|
+
params.RequestItems = {};
|
|
349
|
+
}
|
|
350
|
+
TableName || (TableName = Object.keys(params.RequestItems)[0]);
|
|
351
|
+
if (!TableName) {
|
|
352
|
+
throw (0, util_1.formatError)(item, "Missing database table" /* ERR_DB.TABLE */);
|
|
353
|
+
}
|
|
354
|
+
// @ts-ignore
|
|
355
|
+
const Item = (_a = params.RequestItems)[TableName] || (_a[TableName] = {});
|
|
356
|
+
Item.Keys = query;
|
|
357
|
+
params = { RequestItems: { [TableName]: Item } };
|
|
358
|
+
if (useCache && (rows = getCache(queryString = Module.asString(params, true)))) {
|
|
359
|
+
result[i] = rows;
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
const [db, AWS] = createClient();
|
|
363
|
+
const { Responses } = await db.send(new AWS.BatchGetCommand(params));
|
|
364
|
+
if (Responses) {
|
|
365
|
+
rows = Responses[TableName];
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
else if (TableName) {
|
|
369
|
+
let params = item.params;
|
|
370
|
+
if ((0, types_1.isPlainObject)(params)) {
|
|
371
|
+
params.TableName = TableName;
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
params = { TableName };
|
|
375
|
+
}
|
|
376
|
+
if (useCache && (rows = getCache(queryString = Module.asString(params, true)))) {
|
|
377
|
+
result[i] = rows;
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
267
380
|
const [db, AWS] = createClient();
|
|
268
|
-
const
|
|
269
|
-
if (
|
|
270
|
-
rows =
|
|
381
|
+
const { Count, Items } = await db.send(new AWS.ScanCommand(params));
|
|
382
|
+
if (Count && Items) {
|
|
383
|
+
rows = Items;
|
|
271
384
|
}
|
|
272
385
|
}
|
|
273
386
|
else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-r/aws-v3",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "AWS V3 cloud functions for E-mc.",
|
|
5
5
|
"main": "client/index.js",
|
|
6
6
|
"publishConfig": {
|
|
@@ -20,12 +20,13 @@
|
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"homepage": "https://github.com/anpham6/pi-r#readme",
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@e-mc/cloud": "^0.8.
|
|
24
|
-
"@e-mc/module": "^0.8.
|
|
25
|
-
"@e-mc/types": "^0.8.
|
|
26
|
-
"@pi-r/aws": "^0.6.
|
|
27
|
-
"@aws-sdk/client-dynamodb": "^3.
|
|
28
|
-
"@aws-sdk/client-s3": "^3.
|
|
29
|
-
"@aws-sdk/
|
|
23
|
+
"@e-mc/cloud": "^0.8.2",
|
|
24
|
+
"@e-mc/module": "^0.8.2",
|
|
25
|
+
"@e-mc/types": "^0.8.2",
|
|
26
|
+
"@pi-r/aws": "^0.6.2",
|
|
27
|
+
"@aws-sdk/client-dynamodb": "^3.501.0",
|
|
28
|
+
"@aws-sdk/client-s3": "^3.501.0",
|
|
29
|
+
"@aws-sdk/credential-providers": "^3.501.0",
|
|
30
|
+
"@aws-sdk/lib-dynamodb": "^3.501.0"
|
|
30
31
|
}
|
|
31
32
|
}
|
package/upload/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const path = require("path");
|
|
4
|
-
const fs = require("fs");
|
|
5
4
|
const stream = require("stream");
|
|
6
5
|
const util_1 = require("@e-mc/cloud/util");
|
|
7
6
|
const aws_1 = require("@pi-r/aws");
|
|
@@ -80,15 +79,10 @@ function upload(credential, service = 'aws-v3', sdk = '@aws-sdk/client-s3') {
|
|
|
80
79
|
const Body = [data.buffer];
|
|
81
80
|
const ContentType = [contentType];
|
|
82
81
|
if (fileGroup) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
catch (err) {
|
|
89
|
-
addLog(err);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
82
|
+
const [key, body, type] = (0, util_1.createKeyAndBody)(filename, fileGroup, addLog);
|
|
83
|
+
Key.push(...key);
|
|
84
|
+
Body.push(...body);
|
|
85
|
+
ContentType.push(...type);
|
|
92
86
|
}
|
|
93
87
|
for (let i = 0; i < Key.length; ++i) {
|
|
94
88
|
const first = i === 0;
|
|
@@ -140,35 +134,37 @@ function upload(credential, service = 'aws-v3', sdk = '@aws-sdk/client-s3') {
|
|
|
140
134
|
client.send(new AWS.PutObjectCommand(params), { abortSignal: this.signal })
|
|
141
135
|
.then(() => {
|
|
142
136
|
const url = endpoint ? Module.joinPath(endpoint, objectKey) : Module.joinPath(`https://${Bucket}.s3.${!credential.region || credential.region === 'us-east-1' ? 'us-east-1.' : ''}amazonaws.com`, objectKey);
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
addLog(error);
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
else if (tags === false) {
|
|
159
|
-
client.send(new AWS.DeleteObjectTaggingCommand({ Bucket, Key: params.Key, ExpectedBucketOwner: params.ExpectedBucketOwner }), error => {
|
|
160
|
-
if (!error) {
|
|
161
|
-
this.formatMessage(64 /* LOG_TYPE.CLOUD */, service, ["Tags deleted" /* VAL_CLOUD.DELETE_TAG */, Bucket], params.Key, { ...Cloud.LOG_CLOUD_COMMAND });
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
164
|
-
addLog(error);
|
|
165
|
-
}
|
|
166
|
-
});
|
|
137
|
+
this.formatMessage(64 /* LOG_TYPE.CLOUD */, service, "Upload success" /* VAL_CLOUD.UPLOAD_FILE */, url, { ...Cloud.LOG_CLOUD_UPLOAD });
|
|
138
|
+
if (!first) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
let length = -1;
|
|
142
|
+
if ((0, types_1.isPlainObject)(tags) && (length = Object.keys(tags).length) > 0) {
|
|
143
|
+
const TagSet = [];
|
|
144
|
+
for (const name in tags) {
|
|
145
|
+
TagSet.push({ Key: name, Value: tags[name] });
|
|
167
146
|
}
|
|
168
|
-
|
|
169
|
-
|
|
147
|
+
client.send(new AWS.PutObjectTaggingCommand({ Bucket, Key: params.Key, ExpectedBucketOwner: params.ExpectedBucketOwner, Tagging: { TagSet }, RequestPayer: params.RequestPayer }), error => {
|
|
148
|
+
if (!error) {
|
|
149
|
+
this.formatMessage(64 /* LOG_TYPE.CLOUD */, service, ["Tags created" /* VAL_CLOUD.CREATE_TAG */, Bucket], params.Key, { ...Cloud.LOG_CLOUD_COMMAND });
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
addLog(error);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
170
155
|
}
|
|
171
|
-
|
|
156
|
+
else if (tags === false || length === 0) {
|
|
157
|
+
client.send(new AWS.DeleteObjectTaggingCommand({ Bucket, Key: params.Key, ExpectedBucketOwner: params.ExpectedBucketOwner }), error => {
|
|
158
|
+
if (!error) {
|
|
159
|
+
this.formatMessage(64 /* LOG_TYPE.CLOUD */, service, ["Tags deleted" /* VAL_CLOUD.DELETE_TAG */, Bucket], params.Key, { ...Cloud.LOG_CLOUD_COMMAND });
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
addLog(error);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
cleanup();
|
|
167
|
+
callback(null, url);
|
|
172
168
|
})
|
|
173
169
|
.catch(err => {
|
|
174
170
|
if (first) {
|