@balena/pinejs 20.0.0-build-esm-78a099606f287bc0df4001425788a7ad1c708ba1-1 → 20.0.0-build-esm-8834afea86dbfce451b8d32756c87887efe80ba9-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.
- package/.pinejs-cache.json +1 -1
- package/.versionbot/CHANGELOG.yml +48 -4
- package/CHANGELOG.md +9 -1
- package/out/sbvr-api/express-extension.d.ts +1 -2
- package/out/webresource-handler/handlers/index.d.ts +0 -1
- package/out/webresource-handler/handlers/index.js +0 -1
- package/out/webresource-handler/handlers/index.js.map +1 -1
- package/package.json +6 -5
- package/src/sbvr-api/express-extension.ts +1 -2
- package/src/webresource-handler/handlers/index.ts +0 -1
- package/out/webresource-handler/handlers/S3Handler.d.ts +0 -28
- package/out/webresource-handler/handlers/S3Handler.js +0 -97
- package/out/webresource-handler/handlers/S3Handler.js.map +0 -1
- package/src/webresource-handler/handlers/S3Handler.ts +0 -143
@@ -1,6 +1,14 @@
|
|
1
1
|
- commits:
|
2
|
+
- subject: Drop exposed S3 handler in favor of @balena/pinejs-webresource-s3 handler
|
3
|
+
hash: 8834afea86dbfce451b8d32756c87887efe80ba9
|
4
|
+
body: ""
|
5
|
+
footer:
|
6
|
+
Change-type: major
|
7
|
+
change-type: major
|
8
|
+
author: Otavio Jacobi
|
9
|
+
nested: []
|
2
10
|
- subject: Update commander to 13.x
|
3
|
-
hash:
|
11
|
+
hash: f816886183f4050542f86623445c9066ca5576ee
|
4
12
|
body: ""
|
5
13
|
footer:
|
6
14
|
Change-type: major
|
@@ -8,7 +16,7 @@
|
|
8
16
|
author: Pagan Gazzard
|
9
17
|
nested: []
|
10
18
|
- subject: Update chai to 5.x
|
11
|
-
hash:
|
19
|
+
hash: c5d4111f5d1c12e173240bb27f0f6db20869d965
|
12
20
|
body: ""
|
13
21
|
footer:
|
14
22
|
Change-type: patch
|
@@ -16,7 +24,7 @@
|
|
16
24
|
author: Pagan Gazzard
|
17
25
|
nested: []
|
18
26
|
- subject: Convert to ESM
|
19
|
-
hash:
|
27
|
+
hash: 0d24ee717f247343394f67988f7327ce6457b387
|
20
28
|
body: ""
|
21
29
|
footer:
|
22
30
|
Change-type: patch
|
@@ -25,7 +33,35 @@
|
|
25
33
|
nested: []
|
26
34
|
version: 20.0.0
|
27
35
|
title: ""
|
28
|
-
date: 2025-01-
|
36
|
+
date: 2025-01-06T21:58:49.000Z
|
37
|
+
- commits:
|
38
|
+
- subject: "Tests: use `@balena/pinejs` for resolving to the runtime files"
|
39
|
+
hash: fc28f2efec230ea40150c14cbf7f062649f2fc40
|
40
|
+
body: ""
|
41
|
+
footer:
|
42
|
+
Change-type: patch
|
43
|
+
change-type: patch
|
44
|
+
author: Pagan Gazzard
|
45
|
+
nested: []
|
46
|
+
- subject: "Tests: avoid deep imports in favor of the standard module entrypoint"
|
47
|
+
hash: 8e26f0f37189809bfc517a580a37ff659a207ba6
|
48
|
+
body: ""
|
49
|
+
footer:
|
50
|
+
Change-type: patch
|
51
|
+
change-type: patch
|
52
|
+
author: Pagan Gazzard
|
53
|
+
nested: []
|
54
|
+
- subject: "Tests: run against compiled source rather than transpiling on the fly"
|
55
|
+
hash: cf6351dac10a89633624a5ebf3b1aad29f0ab8ec
|
56
|
+
body: ""
|
57
|
+
footer:
|
58
|
+
Change-type: patch
|
59
|
+
change-type: patch
|
60
|
+
author: Pagan Gazzard
|
61
|
+
nested: []
|
62
|
+
version: 19.7.3
|
63
|
+
title: ""
|
64
|
+
date: 2025-01-04T23:36:23.908Z
|
29
65
|
- commits:
|
30
66
|
- subject: Update dependencies
|
31
67
|
hash: a909ee054f395de407ec4e1275fd0f5b7514fe29
|
@@ -2087,6 +2123,7 @@
|
|
2087
2123
|
|
2088
2124
|
|
2089
2125
|
|
2126
|
+
|
2090
2127
|
|
2091
2128
|
|
2092
2129
|
As balena-lint
|
@@ -2130,6 +2167,7 @@
|
|
2130
2167
|
|
2131
2168
|
|
2132
2169
|
|
2170
|
+
|
2133
2171
|
|
2134
2172
|
|
2135
2173
|
As engine and npm is
|
@@ -2188,6 +2226,7 @@
|
|
2188
2226
|
|
2189
2227
|
|
2190
2228
|
|
2229
|
+
|
2191
2230
|
|
2192
2231
|
|
2193
2232
|
Ensure that the
|
@@ -2309,6 +2348,7 @@
|
|
2309
2348
|
|
2310
2349
|
|
2311
2350
|
|
2351
|
+
|
2312
2352
|
|
2313
2353
|
|
2314
2354
|
This also deprecates
|
@@ -2358,6 +2398,7 @@
|
|
2358
2398
|
|
2359
2399
|
|
2360
2400
|
|
2401
|
+
|
2361
2402
|
|
2362
2403
|
|
2363
2404
|
It can in fact be a
|
@@ -2408,6 +2449,7 @@
|
|
2408
2449
|
|
2409
2450
|
|
2410
2451
|
|
2452
|
+
|
2411
2453
|
|
2412
2454
|
|
2413
2455
|
We know what type
|
@@ -2463,6 +2505,7 @@
|
|
2463
2505
|
|
2464
2506
|
|
2465
2507
|
|
2508
|
+
|
2466
2509
|
|
2467
2510
|
|
2468
2511
|
Update
|
@@ -2629,6 +2672,7 @@
|
|
2629
2672
|
|
2630
2673
|
|
2631
2674
|
|
2675
|
+
|
2632
2676
|
|
2633
2677
|
|
2634
2678
|
This also deprecates
|
package/CHANGELOG.md
CHANGED
@@ -5,12 +5,20 @@ automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
|
|
5
5
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
6
6
|
|
7
7
|
# v20.0.0
|
8
|
-
## (2025-01-
|
8
|
+
## (2025-01-06)
|
9
9
|
|
10
|
+
* Drop exposed S3 handler in favor of @balena/pinejs-webresource-s3 handler [Otavio Jacobi]
|
10
11
|
* Update commander to 13.x [Pagan Gazzard]
|
11
12
|
* Update chai to 5.x [Pagan Gazzard]
|
12
13
|
* Convert to ESM [Pagan Gazzard]
|
13
14
|
|
15
|
+
# v19.7.3
|
16
|
+
## (2025-01-04)
|
17
|
+
|
18
|
+
* Tests: use `@balena/pinejs` for resolving to the runtime files [Pagan Gazzard]
|
19
|
+
* Tests: avoid deep imports in favor of the standard module entrypoint [Pagan Gazzard]
|
20
|
+
* Tests: run against compiled source rather than transpiling on the fly [Pagan Gazzard]
|
21
|
+
|
14
22
|
# v19.7.2
|
15
23
|
## (2025-01-02)
|
16
24
|
|
@@ -1,6 +1,6 @@
|
|
1
|
+
import type { User as PineUser } from './sbvr-utils.js';
|
1
2
|
declare global {
|
2
3
|
namespace Express {
|
3
|
-
type PineUser = import('./sbvr-utils.js').User;
|
4
4
|
interface User extends PineUser {
|
5
5
|
}
|
6
6
|
interface Request {
|
@@ -9,4 +9,3 @@ declare global {
|
|
9
9
|
}
|
10
10
|
}
|
11
11
|
}
|
12
|
-
export {};
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/webresource-handler/handlers/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/webresource-handler/handlers/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC"}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@balena/pinejs",
|
3
|
-
"version": "20.0.0-build-esm-
|
3
|
+
"version": "20.0.0-build-esm-8834afea86dbfce451b8d32756c87887efe80ba9-1",
|
4
4
|
"main": "out/server-glue/module.js",
|
5
5
|
"type": "module",
|
6
6
|
"repository": "git@github.com:balena-io/pinejs.git",
|
@@ -19,10 +19,9 @@
|
|
19
19
|
"webpack-server": "grunt --preload ts-node/register/transpile-only --gruntfile Gruntfile.cts server",
|
20
20
|
"webpack-build": "npm run webpack-browser && npm run webpack-module && npm run webpack-server",
|
21
21
|
"lint": "balena-lint -t tsconfig.dev.json -e js -e ts src build typings Gruntfile.cts && npx tsc --project tsconfig.dev.json --noEmit",
|
22
|
-
"test": "npm run
|
23
|
-
"test:compose": "trap 'docker compose -f docker-compose.npm-test.yml down ; echo Stopped ; exit 0' INT; docker compose -f docker-compose.npm-test.yml up -d && sleep 2 && DATABASE_URL=postgres://docker:docker@localhost:5431/postgres PINEJS_WEBRESOURCE_MAXFILESIZE=1000000000 S3_ENDPOINT=http://localhost:43680 S3_ACCESS_KEY=USERNAME S3_SECRET_KEY=PASSWORD S3_STORAGE_ADAPTER_BUCKET=balena-pine-web-resources S3_REGION=us-east-1 PINEJS_QUEUE_CONCURRENCY=1 TZ=UTC
|
22
|
+
"test": "npm run build && npm run lint && npm run webpack-build && npm run test:compose && npm run test:generated-types",
|
23
|
+
"test:compose": "trap 'docker compose -f docker-compose.npm-test.yml down ; echo Stopped ; exit 0' INT; docker compose -f docker-compose.npm-test.yml up -d && sleep 2 && DATABASE_URL=postgres://docker:docker@localhost:5431/postgres PINEJS_WEBRESOURCE_MAXFILESIZE=1000000000 S3_ENDPOINT=http://localhost:43680 S3_ACCESS_KEY=USERNAME S3_SECRET_KEY=PASSWORD S3_STORAGE_ADAPTER_BUCKET=balena-pine-web-resources S3_REGION=us-east-1 PINEJS_QUEUE_CONCURRENCY=1 TZ=UTC npx mocha",
|
24
24
|
"test:generated-types": "npm run generate-types && git diff --exit-code ./src/sbvr-api/user.ts ./src/migrator/migrations.ts ./src/sbvr-api/dev.ts",
|
25
|
-
"mocha": "TS_NODE_FILES=true mocha",
|
26
25
|
"lint-fix": "balena-lint -t tsconfig.dev.json -e js -e ts --fix src test build typings Gruntfile.cts",
|
27
26
|
"generate-types": "node ./bin/sbvr-compiler.js generate-types ./src/sbvr-api/user.sbvr ./src/sbvr-api/user.ts && node ./bin/sbvr-compiler.js generate-types ./src/migrator/migrations.sbvr ./src/migrator/migrations.ts && node ./bin/sbvr-compiler.js generate-types ./src/sbvr-api/dev.sbvr ./src/sbvr-api/dev.ts && node ./bin/sbvr-compiler.js generate-types ./src/tasks/tasks.sbvr ./src/tasks/tasks.ts && balena-lint -t tsconfig.dev.json --fix ./src/sbvr-api/user.ts ./src/migrator/migrations.ts ./src/sbvr-api/dev.ts"
|
28
27
|
},
|
@@ -69,6 +68,8 @@
|
|
69
68
|
},
|
70
69
|
"devDependencies": {
|
71
70
|
"@balena/lint": "^8.2.8",
|
71
|
+
"@balena/pinejs": "file:./",
|
72
|
+
"@balena/pinejs-webresource-s3": "^1.0.2",
|
72
73
|
"@faker-js/faker": "^9.3.0",
|
73
74
|
"@types/busboy": "^1.5.4",
|
74
75
|
"@types/chai": "^5.0.1",
|
@@ -147,6 +148,6 @@
|
|
147
148
|
"recursive": true
|
148
149
|
},
|
149
150
|
"versionist": {
|
150
|
-
"publishedAt": "2025-01-
|
151
|
+
"publishedAt": "2025-01-06T21:58:50.126Z"
|
151
152
|
}
|
152
153
|
}
|
@@ -1,10 +1,9 @@
|
|
1
1
|
// Augment express.js with pinejs-specific attributes via declaration merging.
|
2
|
+
import type { User as PineUser } from './sbvr-utils.js';
|
2
3
|
|
3
4
|
declare global {
|
4
5
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
5
6
|
namespace Express {
|
6
|
-
type PineUser = import('./sbvr-utils.js').User;
|
7
|
-
|
8
7
|
// Augment Express.User to include the props of our PineUser.
|
9
8
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
10
9
|
interface User extends PineUser {}
|
@@ -1,28 +0,0 @@
|
|
1
|
-
import { type IncomingFile, type UploadResponse, type WebResourceHandler } from '../index.js';
|
2
|
-
import type { WebResourceType as WebResource } from '@balena/sbvr-types';
|
3
|
-
export interface S3HandlerProps {
|
4
|
-
region: string;
|
5
|
-
accessKey: string;
|
6
|
-
secretKey: string;
|
7
|
-
endpoint: string;
|
8
|
-
bucket: string;
|
9
|
-
maxSize?: number;
|
10
|
-
signedUrlExpireTimeSeconds?: number;
|
11
|
-
signedUrlCacheExpireTimeSeconds?: number;
|
12
|
-
}
|
13
|
-
export declare class S3Handler implements WebResourceHandler {
|
14
|
-
private readonly config;
|
15
|
-
private readonly bucket;
|
16
|
-
private readonly maxFileSize;
|
17
|
-
protected readonly signedUrlExpireTimeSeconds: number;
|
18
|
-
protected readonly signedUrlCacheExpireTimeSeconds: number;
|
19
|
-
protected cachedGetSignedUrl: (fileKey: string) => Promise<string>;
|
20
|
-
private client;
|
21
|
-
constructor(config: S3HandlerProps);
|
22
|
-
handleFile(resource: IncomingFile): Promise<UploadResponse>;
|
23
|
-
removeFile(href: string): Promise<void>;
|
24
|
-
onPreRespond(webResource: WebResource): Promise<WebResource>;
|
25
|
-
private s3SignUrl;
|
26
|
-
private getS3URL;
|
27
|
-
private getKeyFromHref;
|
28
|
-
}
|
@@ -1,97 +0,0 @@
|
|
1
|
-
import { FileSizeExceededError, normalizeHref, WebResourceError, } from '../index.js';
|
2
|
-
import { S3Client, DeleteObjectCommand, GetObjectCommand, } from '@aws-sdk/client-s3';
|
3
|
-
import { Upload } from '@aws-sdk/lib-storage';
|
4
|
-
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
5
|
-
import { randomUUID } from 'crypto';
|
6
|
-
import memoize from 'memoizee';
|
7
|
-
export class S3Handler {
|
8
|
-
config;
|
9
|
-
bucket;
|
10
|
-
maxFileSize;
|
11
|
-
signedUrlExpireTimeSeconds;
|
12
|
-
signedUrlCacheExpireTimeSeconds;
|
13
|
-
cachedGetSignedUrl;
|
14
|
-
client;
|
15
|
-
constructor(config) {
|
16
|
-
this.config = {
|
17
|
-
region: config.region,
|
18
|
-
credentials: {
|
19
|
-
accessKeyId: config.accessKey,
|
20
|
-
secretAccessKey: config.secretKey,
|
21
|
-
},
|
22
|
-
endpoint: config.endpoint,
|
23
|
-
forcePathStyle: true,
|
24
|
-
};
|
25
|
-
this.signedUrlExpireTimeSeconds =
|
26
|
-
config.signedUrlExpireTimeSeconds ?? 86400;
|
27
|
-
this.signedUrlCacheExpireTimeSeconds =
|
28
|
-
config.signedUrlCacheExpireTimeSeconds ?? 82800;
|
29
|
-
this.maxFileSize = config.maxSize ?? 52428800;
|
30
|
-
this.bucket = config.bucket;
|
31
|
-
this.client = new S3Client(this.config);
|
32
|
-
this.cachedGetSignedUrl = memoize(this.s3SignUrl, {
|
33
|
-
maxAge: this.signedUrlCacheExpireTimeSeconds * 1000,
|
34
|
-
});
|
35
|
-
}
|
36
|
-
async handleFile(resource) {
|
37
|
-
let size = 0;
|
38
|
-
const key = `${resource.fieldname}_${randomUUID()}_${resource.originalname}`;
|
39
|
-
const params = {
|
40
|
-
Bucket: this.bucket,
|
41
|
-
Key: key,
|
42
|
-
Body: resource.stream,
|
43
|
-
ContentType: resource.mimetype,
|
44
|
-
};
|
45
|
-
const upload = new Upload({ client: this.client, params });
|
46
|
-
upload.on('httpUploadProgress', async (ev) => {
|
47
|
-
size = ev.total ?? ev.loaded;
|
48
|
-
if (size > this.maxFileSize) {
|
49
|
-
await upload.abort();
|
50
|
-
}
|
51
|
-
});
|
52
|
-
try {
|
53
|
-
await upload.done();
|
54
|
-
}
|
55
|
-
catch (err) {
|
56
|
-
resource.stream.resume();
|
57
|
-
if (size > this.maxFileSize) {
|
58
|
-
throw new FileSizeExceededError(this.maxFileSize);
|
59
|
-
}
|
60
|
-
throw new WebResourceError(err);
|
61
|
-
}
|
62
|
-
const filename = this.getS3URL(key);
|
63
|
-
return { size, filename };
|
64
|
-
}
|
65
|
-
async removeFile(href) {
|
66
|
-
const fileKey = this.getKeyFromHref(href);
|
67
|
-
const command = new DeleteObjectCommand({
|
68
|
-
Bucket: this.bucket,
|
69
|
-
Key: fileKey,
|
70
|
-
});
|
71
|
-
await this.client.send(command);
|
72
|
-
}
|
73
|
-
async onPreRespond(webResource) {
|
74
|
-
if (webResource.href != null) {
|
75
|
-
const fileKey = this.getKeyFromHref(webResource.href);
|
76
|
-
webResource.href = await this.cachedGetSignedUrl(fileKey);
|
77
|
-
}
|
78
|
-
return webResource;
|
79
|
-
}
|
80
|
-
s3SignUrl(fileKey) {
|
81
|
-
const command = new GetObjectCommand({
|
82
|
-
Bucket: this.bucket,
|
83
|
-
Key: fileKey,
|
84
|
-
});
|
85
|
-
return getSignedUrl(this.client, command, {
|
86
|
-
expiresIn: this.signedUrlExpireTimeSeconds,
|
87
|
-
});
|
88
|
-
}
|
89
|
-
getS3URL(key) {
|
90
|
-
return `${this.config.endpoint}/${this.bucket}/${key}`;
|
91
|
-
}
|
92
|
-
getKeyFromHref(href) {
|
93
|
-
const hrefWithoutParams = normalizeHref(href);
|
94
|
-
return hrefWithoutParams.substring(hrefWithoutParams.lastIndexOf('/') + 1);
|
95
|
-
}
|
96
|
-
}
|
97
|
-
//# sourceMappingURL=S3Handler.js.map
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"file":"S3Handler.js","sourceRoot":"","sources":["../../../src/webresource-handler/handlers/S3Handler.ts"],"names":[],"mappings":"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"}
|
@@ -1,143 +0,0 @@
|
|
1
|
-
import {
|
2
|
-
FileSizeExceededError,
|
3
|
-
type IncomingFile,
|
4
|
-
normalizeHref,
|
5
|
-
type UploadResponse,
|
6
|
-
WebResourceError,
|
7
|
-
type WebResourceHandler,
|
8
|
-
} from '../index.js';
|
9
|
-
import {
|
10
|
-
S3Client,
|
11
|
-
type S3ClientConfig,
|
12
|
-
DeleteObjectCommand,
|
13
|
-
type PutObjectCommandInput,
|
14
|
-
GetObjectCommand,
|
15
|
-
} from '@aws-sdk/client-s3';
|
16
|
-
import { Upload } from '@aws-sdk/lib-storage';
|
17
|
-
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
18
|
-
|
19
|
-
import { randomUUID } from 'crypto';
|
20
|
-
import type { WebResourceType as WebResource } from '@balena/sbvr-types';
|
21
|
-
import memoize from 'memoizee';
|
22
|
-
|
23
|
-
export interface S3HandlerProps {
|
24
|
-
region: string;
|
25
|
-
accessKey: string;
|
26
|
-
secretKey: string;
|
27
|
-
endpoint: string;
|
28
|
-
bucket: string;
|
29
|
-
maxSize?: number;
|
30
|
-
signedUrlExpireTimeSeconds?: number;
|
31
|
-
signedUrlCacheExpireTimeSeconds?: number;
|
32
|
-
}
|
33
|
-
|
34
|
-
export class S3Handler implements WebResourceHandler {
|
35
|
-
private readonly config: S3ClientConfig;
|
36
|
-
private readonly bucket: string;
|
37
|
-
private readonly maxFileSize: number;
|
38
|
-
|
39
|
-
protected readonly signedUrlExpireTimeSeconds: number;
|
40
|
-
protected readonly signedUrlCacheExpireTimeSeconds: number;
|
41
|
-
protected cachedGetSignedUrl: (fileKey: string) => Promise<string>;
|
42
|
-
|
43
|
-
private client: S3Client;
|
44
|
-
|
45
|
-
constructor(config: S3HandlerProps) {
|
46
|
-
this.config = {
|
47
|
-
region: config.region,
|
48
|
-
credentials: {
|
49
|
-
accessKeyId: config.accessKey,
|
50
|
-
secretAccessKey: config.secretKey,
|
51
|
-
},
|
52
|
-
endpoint: config.endpoint,
|
53
|
-
forcePathStyle: true,
|
54
|
-
};
|
55
|
-
|
56
|
-
this.signedUrlExpireTimeSeconds =
|
57
|
-
config.signedUrlExpireTimeSeconds ?? 86400; // 24h
|
58
|
-
this.signedUrlCacheExpireTimeSeconds =
|
59
|
-
config.signedUrlCacheExpireTimeSeconds ?? 82800; // 22h
|
60
|
-
|
61
|
-
this.maxFileSize = config.maxSize ?? 52428800;
|
62
|
-
this.bucket = config.bucket;
|
63
|
-
this.client = new S3Client(this.config);
|
64
|
-
|
65
|
-
// Memoize expects maxAge in MS and s3 signing method in seconds.
|
66
|
-
// Normalization to use only seconds and therefore convert here from seconds to MS
|
67
|
-
this.cachedGetSignedUrl = memoize(this.s3SignUrl, {
|
68
|
-
maxAge: this.signedUrlCacheExpireTimeSeconds * 1000,
|
69
|
-
});
|
70
|
-
}
|
71
|
-
|
72
|
-
public async handleFile(resource: IncomingFile): Promise<UploadResponse> {
|
73
|
-
let size = 0;
|
74
|
-
const key = `${resource.fieldname}_${randomUUID()}_${
|
75
|
-
resource.originalname
|
76
|
-
}`;
|
77
|
-
const params: PutObjectCommandInput = {
|
78
|
-
Bucket: this.bucket,
|
79
|
-
Key: key,
|
80
|
-
Body: resource.stream,
|
81
|
-
ContentType: resource.mimetype,
|
82
|
-
};
|
83
|
-
const upload = new Upload({ client: this.client, params });
|
84
|
-
|
85
|
-
upload.on('httpUploadProgress', async (ev) => {
|
86
|
-
size = ev.total ?? ev.loaded!;
|
87
|
-
if (size > this.maxFileSize) {
|
88
|
-
await upload.abort();
|
89
|
-
}
|
90
|
-
});
|
91
|
-
|
92
|
-
try {
|
93
|
-
await upload.done();
|
94
|
-
} catch (err: any) {
|
95
|
-
resource.stream.resume();
|
96
|
-
if (size > this.maxFileSize) {
|
97
|
-
throw new FileSizeExceededError(this.maxFileSize);
|
98
|
-
}
|
99
|
-
throw new WebResourceError(err);
|
100
|
-
}
|
101
|
-
|
102
|
-
const filename = this.getS3URL(key);
|
103
|
-
return { size, filename };
|
104
|
-
}
|
105
|
-
|
106
|
-
public async removeFile(href: string): Promise<void> {
|
107
|
-
const fileKey = this.getKeyFromHref(href);
|
108
|
-
|
109
|
-
const command = new DeleteObjectCommand({
|
110
|
-
Bucket: this.bucket,
|
111
|
-
Key: fileKey,
|
112
|
-
});
|
113
|
-
|
114
|
-
await this.client.send(command);
|
115
|
-
}
|
116
|
-
|
117
|
-
public async onPreRespond(webResource: WebResource): Promise<WebResource> {
|
118
|
-
if (webResource.href != null) {
|
119
|
-
const fileKey = this.getKeyFromHref(webResource.href);
|
120
|
-
webResource.href = await this.cachedGetSignedUrl(fileKey);
|
121
|
-
}
|
122
|
-
return webResource;
|
123
|
-
}
|
124
|
-
|
125
|
-
private s3SignUrl(fileKey: string): Promise<string> {
|
126
|
-
const command = new GetObjectCommand({
|
127
|
-
Bucket: this.bucket,
|
128
|
-
Key: fileKey,
|
129
|
-
});
|
130
|
-
return getSignedUrl(this.client, command, {
|
131
|
-
expiresIn: this.signedUrlExpireTimeSeconds,
|
132
|
-
});
|
133
|
-
}
|
134
|
-
|
135
|
-
private getS3URL(key: string): string {
|
136
|
-
return `${this.config.endpoint}/${this.bucket}/${key}`;
|
137
|
-
}
|
138
|
-
|
139
|
-
private getKeyFromHref(href: string): string {
|
140
|
-
const hrefWithoutParams = normalizeHref(href);
|
141
|
-
return hrefWithoutParams.substring(hrefWithoutParams.lastIndexOf('/') + 1);
|
142
|
-
}
|
143
|
-
}
|