@uploadbox/core 0.1.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/dist/builder.d.ts +11 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/builder.js +27 -0
- package/dist/builder.js.map +1 -0
- package/dist/create-uploadbox.d.ts +13 -0
- package/dist/create-uploadbox.d.ts.map +1 -0
- package/dist/create-uploadbox.js +31 -0
- package/dist/create-uploadbox.js.map +1 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +53 -0
- package/dist/errors.js.map +1 -0
- package/dist/file-types.d.ts +6 -0
- package/dist/file-types.d.ts.map +1 -0
- package/dist/file-types.js +39 -0
- package/dist/file-types.js.map +1 -0
- package/dist/hooks/image-resize.d.ts +24 -0
- package/dist/hooks/image-resize.d.ts.map +1 -0
- package/dist/hooks/image-resize.js +78 -0
- package/dist/hooks/image-resize.js.map +1 -0
- package/dist/hooks/virus-scan.d.ts +24 -0
- package/dist/hooks/virus-scan.d.ts.map +1 -0
- package/dist/hooks/virus-scan.js +69 -0
- package/dist/hooks/virus-scan.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/processing.d.ts +25 -0
- package/dist/processing.d.ts.map +1 -0
- package/dist/processing.js +2 -0
- package/dist/processing.js.map +1 -0
- package/dist/s3-multipart.d.ts +12 -0
- package/dist/s3-multipart.d.ts.map +1 -0
- package/dist/s3-multipart.js +72 -0
- package/dist/s3-multipart.js.map +1 -0
- package/dist/s3.d.ts +20 -0
- package/dist/s3.d.ts.map +1 -0
- package/dist/s3.js +95 -0
- package/dist/s3.js.map +1 -0
- package/dist/server-api.d.ts +43 -0
- package/dist/server-api.d.ts.map +1 -0
- package/dist/server-api.js +48 -0
- package/dist/server-api.js.map +1 -0
- package/dist/server-upload.d.ts +19 -0
- package/dist/server-upload.d.ts.map +1 -0
- package/dist/server-upload.js +53 -0
- package/dist/server-upload.js.map +1 -0
- package/dist/types.d.ts +94 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +7 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +50 -0
- package/dist/utils.js.map +1 -0
- package/dist/validation.d.ts +3 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +44 -0
- package/dist/validation.js.map +1 -0
- package/package.json +58 -0
- package/src/builder.ts +39 -0
- package/src/create-uploadbox.ts +46 -0
- package/src/errors.ts +65 -0
- package/src/file-types.ts +37 -0
- package/src/hooks/image-resize.ts +102 -0
- package/src/hooks/virus-scan.ts +95 -0
- package/src/index.ts +38 -0
- package/src/processing.ts +28 -0
- package/src/s3-multipart.ts +107 -0
- package/src/s3.ts +142 -0
- package/src/server-api.ts +81 -0
- package/src/server-upload.ts +88 -0
- package/src/types.ts +105 -0
- package/src/utils.ts +54 -0
- package/src/validation.ts +52 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FileRouteConfig, FileRoute, MiddlewareFn, OnUploadCompleteFn } from "./types.js";
|
|
2
|
+
export declare class UploadBuilder<TMetadata = Record<string, unknown>, TReturn = unknown> {
|
|
3
|
+
/** @internal */ _config: FileRouteConfig;
|
|
4
|
+
/** @internal */ _middleware: MiddlewareFn<TMetadata>;
|
|
5
|
+
/** @internal */ _onUploadComplete: OnUploadCompleteFn<TMetadata, TReturn>;
|
|
6
|
+
constructor(config: FileRouteConfig);
|
|
7
|
+
middleware<TNewMetadata>(fn: MiddlewareFn<TNewMetadata>): UploadBuilder<TNewMetadata, TReturn>;
|
|
8
|
+
onUploadComplete<TNewReturn>(fn: OnUploadCompleteFn<TMetadata, TNewReturn>): FileRoute<TMetadata, TNewReturn>;
|
|
9
|
+
}
|
|
10
|
+
export declare function createUploadBuilder(config: FileRouteConfig): UploadBuilder;
|
|
11
|
+
//# sourceMappingURL=builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,SAAS,EACT,YAAY,EACZ,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAEpB,qBAAa,aAAa,CAAC,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO;IAC/E,gBAAgB,CAAC,OAAO,EAAE,eAAe,CAAC;IAC1C,gBAAgB,CAAC,WAAW,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;IACtD,gBAAgB,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;gBAE/D,MAAM,EAAE,eAAe;IAMnC,UAAU,CAAC,YAAY,EAAE,EAAE,EAAE,YAAY,CAAC,YAAY,CAAC,GAAG,aAAa,CAAC,YAAY,EAAE,OAAO,CAAC;IAO9F,gBAAgB,CAAC,UAAU,EACzB,EAAE,EAAE,kBAAkB,CAAC,SAAS,EAAE,UAAU,CAAC,GAC5C,SAAS,CAAC,SAAS,EAAE,UAAU,CAAC;CAOpC;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,eAAe,GAAG,aAAa,CAE1E"}
|
package/dist/builder.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export class UploadBuilder {
|
|
2
|
+
/** @internal */ _config;
|
|
3
|
+
/** @internal */ _middleware;
|
|
4
|
+
/** @internal */ _onUploadComplete;
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this._config = config;
|
|
7
|
+
this._middleware = (async () => ({}));
|
|
8
|
+
this._onUploadComplete = (async () => undefined);
|
|
9
|
+
}
|
|
10
|
+
middleware(fn) {
|
|
11
|
+
const builder = new UploadBuilder(this._config);
|
|
12
|
+
builder._middleware = fn;
|
|
13
|
+
builder._onUploadComplete = this._onUploadComplete;
|
|
14
|
+
return builder;
|
|
15
|
+
}
|
|
16
|
+
onUploadComplete(fn) {
|
|
17
|
+
return {
|
|
18
|
+
_config: this._config,
|
|
19
|
+
_middleware: this._middleware,
|
|
20
|
+
_onUploadComplete: fn,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function createUploadBuilder(config) {
|
|
25
|
+
return new UploadBuilder(config);
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builder.js","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAOA,MAAM,OAAO,aAAa;IACxB,gBAAgB,CAAC,OAAO,CAAkB;IAC1C,gBAAgB,CAAC,WAAW,CAA0B;IACtD,gBAAgB,CAAC,iBAAiB,CAAyC;IAE3E,YAAY,MAAuB;QACjC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,IAAI,CAAC,WAAW,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,CAAc,CAA4B,CAAC;QAC9E,IAAI,CAAC,iBAAiB,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,SAAS,CAAsD,CAAC;IACxG,CAAC;IAED,UAAU,CAAe,EAA8B;QACrD,MAAM,OAAO,GAAG,IAAI,aAAa,CAAwB,IAAI,CAAC,OAAO,CAAC,CAAC;QACvE,OAAO,CAAC,WAAW,GAAG,EAAE,CAAC;QACzB,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,iBAAyE,CAAC;QAC3G,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,gBAAgB,CACd,EAA6C;QAE7C,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,iBAAiB,EAAE,EAAE;SACtB,CAAC;IACJ,CAAC;CACF;AAED,MAAM,UAAU,mBAAmB,CAAC,MAAuB;IACzD,OAAO,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC;AACnC,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FileRouter, FileRouteConfig, UploadboxConfig } from "./types.js";
|
|
2
|
+
import type { S3Client } from "@aws-sdk/client-s3";
|
|
3
|
+
export interface UploadboxInstance<TRouter extends FileRouter> {
|
|
4
|
+
router: TRouter;
|
|
5
|
+
s3Client: S3Client;
|
|
6
|
+
config: UploadboxConfig;
|
|
7
|
+
}
|
|
8
|
+
export declare function createUploadbox<TRouter extends FileRouter>(opts: {
|
|
9
|
+
router: TRouter;
|
|
10
|
+
config?: Partial<UploadboxConfig>;
|
|
11
|
+
}): UploadboxInstance<TRouter>;
|
|
12
|
+
export declare function f(config: FileRouteConfig): import("./builder.js").UploadBuilder<Record<string, unknown>, unknown>;
|
|
13
|
+
//# sourceMappingURL=create-uploadbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-uploadbox.d.ts","sourceRoot":"","sources":["../src/create-uploadbox.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAG/E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD,MAAM,WAAW,iBAAiB,CAAC,OAAO,SAAS,UAAU;IAC3D,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,eAAe,CAAC;CACzB;AAiBD,wBAAgB,eAAe,CAAC,OAAO,SAAS,UAAU,EAAE,IAAI,EAAE;IAChE,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;CACnC,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAW7B;AAGD,wBAAgB,CAAC,CAAC,MAAM,EAAE,eAAe,0EAExC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createUploadBuilder } from "./builder.js";
|
|
2
|
+
import { createS3Client } from "./s3.js";
|
|
3
|
+
function getConfigFromEnv() {
|
|
4
|
+
return {
|
|
5
|
+
region: process.env.UPLOADBOX_AWS_REGION ?? "us-east-1",
|
|
6
|
+
bucket: process.env.UPLOADBOX_S3_BUCKET ?? "",
|
|
7
|
+
accessKeyId: process.env.UPLOADBOX_AWS_ACCESS_KEY_ID ?? "",
|
|
8
|
+
secretAccessKey: process.env.UPLOADBOX_AWS_SECRET_ACCESS_KEY ?? "",
|
|
9
|
+
cdnBaseUrl: process.env.UPLOADBOX_CDN_BASE_URL,
|
|
10
|
+
endpoint: process.env.UPLOADBOX_S3_ENDPOINT,
|
|
11
|
+
forcePathStyle: process.env.UPLOADBOX_S3_FORCE_PATH_STYLE === "true",
|
|
12
|
+
presignedUrlExpiry: process.env.UPLOADBOX_PRESIGNED_URL_EXPIRY
|
|
13
|
+
? parseInt(process.env.UPLOADBOX_PRESIGNED_URL_EXPIRY, 10)
|
|
14
|
+
: undefined,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function createUploadbox(opts) {
|
|
18
|
+
const envConfig = getConfigFromEnv();
|
|
19
|
+
const config = { ...envConfig, ...opts.config };
|
|
20
|
+
const s3Client = createS3Client(config);
|
|
21
|
+
return {
|
|
22
|
+
router: opts.router,
|
|
23
|
+
s3Client,
|
|
24
|
+
config,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
// The `f` function — shorthand for creating a file route builder
|
|
28
|
+
export function f(config) {
|
|
29
|
+
return createUploadBuilder(config);
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=create-uploadbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-uploadbox.js","sourceRoot":"","sources":["../src/create-uploadbox.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AASzC,SAAS,gBAAgB;IACvB,OAAO;QACL,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,WAAW;QACvD,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,EAAE;QAC7C,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,2BAA2B,IAAI,EAAE;QAC1D,eAAe,EAAE,OAAO,CAAC,GAAG,CAAC,+BAA+B,IAAI,EAAE;QAClE,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,sBAAsB;QAC9C,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,qBAAqB;QAC3C,cAAc,EAAE,OAAO,CAAC,GAAG,CAAC,6BAA6B,KAAK,MAAM;QACpE,kBAAkB,EAAE,OAAO,CAAC,GAAG,CAAC,8BAA8B;YAC5D,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,8BAA8B,EAAE,EAAE,CAAC;YAC1D,CAAC,CAAC,SAAS;KACd,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAA6B,IAG3D;IACC,MAAM,SAAS,GAAG,gBAAgB,EAAE,CAAC;IACrC,MAAM,MAAM,GAAoB,EAAE,GAAG,SAAS,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;IAEjE,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IAExC,OAAO;QACL,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,QAAQ;QACR,MAAM;KACP,CAAC;AACJ,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,CAAC,CAAC,MAAuB;IACvC,OAAO,mBAAmB,CAAC,MAAM,CAAC,CAAC;AACrC,CAAC"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare class UploadboxError extends Error {
|
|
2
|
+
readonly code: string;
|
|
3
|
+
readonly statusCode: number;
|
|
4
|
+
constructor(code: string, message: string, statusCode?: number);
|
|
5
|
+
static fileTooLarge(maxSize: string): UploadboxError;
|
|
6
|
+
static invalidFileType(type: string): UploadboxError;
|
|
7
|
+
static tooManyFiles(max: number): UploadboxError;
|
|
8
|
+
static tooFewFiles(min: number): UploadboxError;
|
|
9
|
+
static routeNotFound(route: string): UploadboxError;
|
|
10
|
+
static uploadFailed(reason: string): UploadboxError;
|
|
11
|
+
static unauthorized(message?: string): UploadboxError;
|
|
12
|
+
static rateLimited(retryAfter: number): UploadboxError;
|
|
13
|
+
static forbidden(message?: string): UploadboxError;
|
|
14
|
+
static quotaExceeded(): UploadboxError;
|
|
15
|
+
static s3Error(message: string): UploadboxError;
|
|
16
|
+
toJSON(): {
|
|
17
|
+
error: string;
|
|
18
|
+
message: string;
|
|
19
|
+
statusCode: number;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,cAAe,SAAQ,KAAK;IACvC,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,SAAgB,UAAU,EAAE,MAAM,CAAC;gBAEvB,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,GAAE,MAAY;IAOnE,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM;IAInC,MAAM,CAAC,eAAe,CAAC,IAAI,EAAE,MAAM;IAInC,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM;IAI/B,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,MAAM;IAI9B,MAAM,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM;IAIlC,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM;IAIlC,MAAM,CAAC,YAAY,CAAC,OAAO,SAAiB;IAI5C,MAAM,CAAC,WAAW,CAAC,UAAU,EAAE,MAAM;IAMrC,MAAM,CAAC,SAAS,CAAC,OAAO,SAAc;IAItC,MAAM,CAAC,aAAa;IAIpB,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM;IAI9B,MAAM;;;;;CAOP"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export class UploadboxError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
statusCode;
|
|
4
|
+
constructor(code, message, statusCode = 400) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "UploadboxError";
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.statusCode = statusCode;
|
|
9
|
+
}
|
|
10
|
+
static fileTooLarge(maxSize) {
|
|
11
|
+
return new UploadboxError("FILE_TOO_LARGE", `File exceeds maximum size of ${maxSize}`, 413);
|
|
12
|
+
}
|
|
13
|
+
static invalidFileType(type) {
|
|
14
|
+
return new UploadboxError("INVALID_FILE_TYPE", `File type "${type}" is not allowed`, 415);
|
|
15
|
+
}
|
|
16
|
+
static tooManyFiles(max) {
|
|
17
|
+
return new UploadboxError("TOO_MANY_FILES", `Maximum ${max} files allowed`, 400);
|
|
18
|
+
}
|
|
19
|
+
static tooFewFiles(min) {
|
|
20
|
+
return new UploadboxError("TOO_FEW_FILES", `Minimum ${min} files required`, 400);
|
|
21
|
+
}
|
|
22
|
+
static routeNotFound(route) {
|
|
23
|
+
return new UploadboxError("ROUTE_NOT_FOUND", `Upload route "${route}" not found`, 404);
|
|
24
|
+
}
|
|
25
|
+
static uploadFailed(reason) {
|
|
26
|
+
return new UploadboxError("UPLOAD_FAILED", `Upload failed: ${reason}`, 500);
|
|
27
|
+
}
|
|
28
|
+
static unauthorized(message = "Unauthorized") {
|
|
29
|
+
return new UploadboxError("UNAUTHORIZED", message, 401);
|
|
30
|
+
}
|
|
31
|
+
static rateLimited(retryAfter) {
|
|
32
|
+
const err = new UploadboxError("RATE_LIMITED", `Rate limit exceeded. Retry after ${retryAfter} seconds`, 429);
|
|
33
|
+
err.retryAfter = retryAfter;
|
|
34
|
+
return err;
|
|
35
|
+
}
|
|
36
|
+
static forbidden(message = "Forbidden") {
|
|
37
|
+
return new UploadboxError("FORBIDDEN", message, 403);
|
|
38
|
+
}
|
|
39
|
+
static quotaExceeded() {
|
|
40
|
+
return new UploadboxError("QUOTA_EXCEEDED", "Storage or upload quota exceeded", 413);
|
|
41
|
+
}
|
|
42
|
+
static s3Error(message) {
|
|
43
|
+
return new UploadboxError("S3_ERROR", `S3 error: ${message}`, 500);
|
|
44
|
+
}
|
|
45
|
+
toJSON() {
|
|
46
|
+
return {
|
|
47
|
+
error: this.code,
|
|
48
|
+
message: this.message,
|
|
49
|
+
statusCode: this.statusCode,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvB,IAAI,CAAS;IACb,UAAU,CAAS;IAEnC,YAAY,IAAY,EAAE,OAAe,EAAE,aAAqB,GAAG;QACjE,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;IAED,MAAM,CAAC,YAAY,CAAC,OAAe;QACjC,OAAO,IAAI,cAAc,CAAC,gBAAgB,EAAE,gCAAgC,OAAO,EAAE,EAAE,GAAG,CAAC,CAAC;IAC9F,CAAC;IAED,MAAM,CAAC,eAAe,CAAC,IAAY;QACjC,OAAO,IAAI,cAAc,CAAC,mBAAmB,EAAE,cAAc,IAAI,kBAAkB,EAAE,GAAG,CAAC,CAAC;IAC5F,CAAC;IAED,MAAM,CAAC,YAAY,CAAC,GAAW;QAC7B,OAAO,IAAI,cAAc,CAAC,gBAAgB,EAAE,WAAW,GAAG,gBAAgB,EAAE,GAAG,CAAC,CAAC;IACnF,CAAC;IAED,MAAM,CAAC,WAAW,CAAC,GAAW;QAC5B,OAAO,IAAI,cAAc,CAAC,eAAe,EAAE,WAAW,GAAG,iBAAiB,EAAE,GAAG,CAAC,CAAC;IACnF,CAAC;IAED,MAAM,CAAC,aAAa,CAAC,KAAa;QAChC,OAAO,IAAI,cAAc,CAAC,iBAAiB,EAAE,iBAAiB,KAAK,aAAa,EAAE,GAAG,CAAC,CAAC;IACzF,CAAC;IAED,MAAM,CAAC,YAAY,CAAC,MAAc;QAChC,OAAO,IAAI,cAAc,CAAC,eAAe,EAAE,kBAAkB,MAAM,EAAE,EAAE,GAAG,CAAC,CAAC;IAC9E,CAAC;IAED,MAAM,CAAC,YAAY,CAAC,OAAO,GAAG,cAAc;QAC1C,OAAO,IAAI,cAAc,CAAC,cAAc,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,CAAC,WAAW,CAAC,UAAkB;QACnC,MAAM,GAAG,GAAG,IAAI,cAAc,CAAC,cAAc,EAAE,oCAAoC,UAAU,UAAU,EAAE,GAAG,CAAC,CAAC;QAC7G,GAAW,CAAC,UAAU,GAAG,UAAU,CAAC;QACrC,OAAO,GAAG,CAAC;IACb,CAAC;IAED,MAAM,CAAC,SAAS,CAAC,OAAO,GAAG,WAAW;QACpC,OAAO,IAAI,cAAc,CAAC,WAAW,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,CAAC,aAAa;QAClB,OAAO,IAAI,cAAc,CAAC,gBAAgB,EAAE,kCAAkC,EAAE,GAAG,CAAC,CAAC;IACvF,CAAC;IAED,MAAM,CAAC,OAAO,CAAC,OAAe;QAC5B,OAAO,IAAI,cAAc,CAAC,UAAU,EAAE,aAAa,OAAO,EAAE,EAAE,GAAG,CAAC,CAAC;IACrE,CAAC;IAED,MAAM;QACJ,OAAO;YACL,KAAK,EAAE,IAAI,CAAC,IAAI;YAChB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,UAAU,EAAE,IAAI,CAAC,UAAU;SAC5B,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FileType } from "./types.js";
|
|
2
|
+
export declare const FILE_TYPE_MIME_MAP: Record<FileType, string[]>;
|
|
3
|
+
export declare const DEFAULT_MAX_FILE_SIZES: Record<FileType, string>;
|
|
4
|
+
export declare function getFileTypeFromMime(mimeType: string): FileType | null;
|
|
5
|
+
export declare function isFileTypeAllowed(mimeType: string, allowedTypes: FileType[]): boolean;
|
|
6
|
+
//# sourceMappingURL=file-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-types.d.ts","sourceRoot":"","sources":["../src/file-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,CAOzD,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAO3D,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAUrE;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,GAAG,OAAO,CAIrF"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const FILE_TYPE_MIME_MAP = {
|
|
2
|
+
image: ["image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml", "image/avif", "image/heic", "image/heif"],
|
|
3
|
+
video: ["video/mp4", "video/webm", "video/ogg", "video/quicktime", "video/x-msvideo"],
|
|
4
|
+
audio: ["audio/mpeg", "audio/ogg", "audio/wav", "audio/webm", "audio/aac", "audio/flac"],
|
|
5
|
+
pdf: ["application/pdf"],
|
|
6
|
+
text: ["text/plain", "text/csv", "text/html", "text/css", "text/javascript", "application/json", "application/xml"],
|
|
7
|
+
blob: ["application/octet-stream"],
|
|
8
|
+
};
|
|
9
|
+
export const DEFAULT_MAX_FILE_SIZES = {
|
|
10
|
+
image: "4MB",
|
|
11
|
+
video: "64MB",
|
|
12
|
+
audio: "16MB",
|
|
13
|
+
pdf: "16MB",
|
|
14
|
+
text: "1MB",
|
|
15
|
+
blob: "8MB",
|
|
16
|
+
};
|
|
17
|
+
export function getFileTypeFromMime(mimeType) {
|
|
18
|
+
for (const [fileType, mimes] of Object.entries(FILE_TYPE_MIME_MAP)) {
|
|
19
|
+
if (mimes.includes(mimeType))
|
|
20
|
+
return fileType;
|
|
21
|
+
}
|
|
22
|
+
// Fallback: check prefix
|
|
23
|
+
if (mimeType.startsWith("image/"))
|
|
24
|
+
return "image";
|
|
25
|
+
if (mimeType.startsWith("video/"))
|
|
26
|
+
return "video";
|
|
27
|
+
if (mimeType.startsWith("audio/"))
|
|
28
|
+
return "audio";
|
|
29
|
+
if (mimeType.startsWith("text/"))
|
|
30
|
+
return "text";
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
export function isFileTypeAllowed(mimeType, allowedTypes) {
|
|
34
|
+
if (allowedTypes.includes("blob"))
|
|
35
|
+
return true;
|
|
36
|
+
const fileType = getFileTypeFromMime(mimeType);
|
|
37
|
+
return fileType !== null && allowedTypes.includes(fileType);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=file-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-types.js","sourceRoot":"","sources":["../src/file-types.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,kBAAkB,GAA+B;IAC5D,KAAK,EAAE,CAAC,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,CAAC;IACxH,KAAK,EAAE,CAAC,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,iBAAiB,CAAC;IACrF,KAAK,EAAE,CAAC,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,CAAC;IACxF,GAAG,EAAE,CAAC,iBAAiB,CAAC;IACxB,IAAI,EAAE,CAAC,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,iBAAiB,CAAC;IACnH,IAAI,EAAE,CAAC,0BAA0B,CAAC;CACnC,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAA6B;IAC9D,KAAK,EAAE,KAAK;IACZ,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,MAAM;IACb,GAAG,EAAE,MAAM;IACX,IAAI,EAAE,KAAK;IACX,IAAI,EAAE,KAAK;CACZ,CAAC;AAEF,MAAM,UAAU,mBAAmB,CAAC,QAAgB;IAClD,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAAE,CAAC;QACnE,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,OAAO,QAAoB,CAAC;IAC5D,CAAC;IACD,yBAAyB;IACzB,IAAI,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,OAAO,CAAC;IAClD,IAAI,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,OAAO,CAAC;IAClD,IAAI,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,OAAO,CAAC;IAClD,IAAI,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,MAAM,CAAC;IAChD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,YAAwB;IAC1E,IAAI,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/C,MAAM,QAAQ,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IAC/C,OAAO,QAAQ,KAAK,IAAI,IAAI,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC9D,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ProcessingHook } from "../processing.js";
|
|
2
|
+
interface ImageResizeOptions {
|
|
3
|
+
maxWidth?: number;
|
|
4
|
+
maxHeight?: number;
|
|
5
|
+
quality?: number;
|
|
6
|
+
format?: "jpeg" | "png" | "webp";
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Example image resize hook using sharp.
|
|
10
|
+
* Install sharp as a dependency to use this hook:
|
|
11
|
+
* npm install sharp
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* import { createImageResizeHook } from "@uploadbox/core/hooks/image-resize";
|
|
15
|
+
*
|
|
16
|
+
* const resizeHook = createImageResizeHook({
|
|
17
|
+
* maxWidth: 1920,
|
|
18
|
+
* maxHeight: 1080,
|
|
19
|
+
* quality: 80,
|
|
20
|
+
* });
|
|
21
|
+
*/
|
|
22
|
+
export declare function createImageResizeHook(options?: ImageResizeOptions): ProcessingHook;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=image-resize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-resize.d.ts","sourceRoot":"","sources":["../../src/hooks/image-resize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAA2C,MAAM,kBAAkB,CAAC;AAEhG,UAAU,kBAAkB;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;CAClC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,kBAAuB,GAAG,cAAc,CA8EtF"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example image resize hook using sharp.
|
|
3
|
+
* Install sharp as a dependency to use this hook:
|
|
4
|
+
* npm install sharp
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* import { createImageResizeHook } from "@uploadbox/core/hooks/image-resize";
|
|
8
|
+
*
|
|
9
|
+
* const resizeHook = createImageResizeHook({
|
|
10
|
+
* maxWidth: 1920,
|
|
11
|
+
* maxHeight: 1080,
|
|
12
|
+
* quality: 80,
|
|
13
|
+
* });
|
|
14
|
+
*/
|
|
15
|
+
export function createImageResizeHook(options = {}) {
|
|
16
|
+
const { maxWidth = 1920, maxHeight = 1080, quality = 80, format = "webp", } = options;
|
|
17
|
+
return {
|
|
18
|
+
name: "image-resize",
|
|
19
|
+
timeoutMs: 60000,
|
|
20
|
+
shouldRun(ctx) {
|
|
21
|
+
return ctx.file.type.startsWith("image/");
|
|
22
|
+
},
|
|
23
|
+
async run(ctx) {
|
|
24
|
+
try {
|
|
25
|
+
// Dynamic import to make sharp optional
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
27
|
+
let sharp;
|
|
28
|
+
try {
|
|
29
|
+
sharp = await Function('return import("sharp")')();
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return {
|
|
33
|
+
success: false,
|
|
34
|
+
error: "sharp is not installed. Run: npm install sharp",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const { GetObjectCommand, PutObjectCommand } = await import("@aws-sdk/client-s3");
|
|
38
|
+
// Download file from S3
|
|
39
|
+
const response = await ctx.s3Client.send(new GetObjectCommand({
|
|
40
|
+
Bucket: ctx.config.bucket,
|
|
41
|
+
Key: ctx.file.key,
|
|
42
|
+
}));
|
|
43
|
+
const body = await response.Body?.transformToByteArray();
|
|
44
|
+
if (!body) {
|
|
45
|
+
return { success: false, error: "Failed to download file from S3" };
|
|
46
|
+
}
|
|
47
|
+
// Resize image
|
|
48
|
+
const sharpFn = sharp.default ?? sharp;
|
|
49
|
+
const resized = await sharpFn(Buffer.from(body))
|
|
50
|
+
.resize(maxWidth, maxHeight, { fit: "inside", withoutEnlargement: true })
|
|
51
|
+
.toFormat(format, { quality })
|
|
52
|
+
.toBuffer();
|
|
53
|
+
// Upload resized image back to S3
|
|
54
|
+
await ctx.s3Client.send(new PutObjectCommand({
|
|
55
|
+
Bucket: ctx.config.bucket,
|
|
56
|
+
Key: ctx.file.key,
|
|
57
|
+
Body: resized,
|
|
58
|
+
ContentType: `image/${format}`,
|
|
59
|
+
}));
|
|
60
|
+
return {
|
|
61
|
+
success: true,
|
|
62
|
+
data: {
|
|
63
|
+
originalSize: body.length,
|
|
64
|
+
resizedSize: resized.length,
|
|
65
|
+
format,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
return {
|
|
71
|
+
success: false,
|
|
72
|
+
error: err.message,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=image-resize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-resize.js","sourceRoot":"","sources":["../../src/hooks/image-resize.ts"],"names":[],"mappings":"AASA;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,qBAAqB,CAAC,UAA8B,EAAE;IACpE,MAAM,EACJ,QAAQ,GAAG,IAAI,EACf,SAAS,GAAG,IAAI,EAChB,OAAO,GAAG,EAAE,EACZ,MAAM,GAAG,MAAM,GAChB,GAAG,OAAO,CAAC;IAEZ,OAAO;QACL,IAAI,EAAE,cAAc;QACpB,SAAS,EAAE,KAAK;QAEhB,SAAS,CAAC,GAAsB;YAC9B,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QAC5C,CAAC;QAED,KAAK,CAAC,GAAG,CAAC,GAAsB;YAC9B,IAAI,CAAC;gBACH,wCAAwC;gBACxC,8DAA8D;gBAC9D,IAAI,KAAU,CAAC;gBACf,IAAI,CAAC;oBACH,KAAK,GAAG,MAAO,QAAQ,CAAC,wBAAwB,CAAC,EAAmB,CAAC;gBACvE,CAAC;gBAAC,MAAM,CAAC;oBACP,OAAO;wBACL,OAAO,EAAE,KAAK;wBACd,KAAK,EAAE,gDAAgD;qBACxD,CAAC;gBACJ,CAAC;gBAED,MAAM,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;gBAElF,wBAAwB;gBACxB,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,CACtC,IAAI,gBAAgB,CAAC;oBACnB,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM;oBACzB,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG;iBAClB,CAAC,CACH,CAAC;gBAEF,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,oBAAoB,EAAE,CAAC;gBACzD,IAAI,CAAC,IAAI,EAAE,CAAC;oBACV,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,iCAAiC,EAAE,CAAC;gBACtE,CAAC;gBAED,eAAe;gBACf,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC;gBACvC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;qBAC7C,MAAM,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC;qBACxE,QAAQ,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC;qBAC7B,QAAQ,EAAE,CAAC;gBAEd,kCAAkC;gBAClC,MAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,CACrB,IAAI,gBAAgB,CAAC;oBACnB,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM;oBACzB,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG;oBACjB,IAAI,EAAE,OAAO;oBACb,WAAW,EAAE,SAAS,MAAM,EAAE;iBAC/B,CAAC,CACH,CAAC;gBAEF,OAAO;oBACL,OAAO,EAAE,IAAI;oBACb,IAAI,EAAE;wBACJ,YAAY,EAAE,IAAI,CAAC,MAAM;wBACzB,WAAW,EAAE,OAAO,CAAC,MAAM;wBAC3B,MAAM;qBACP;iBACF,CAAC;YACJ,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO;oBACL,OAAO,EAAE,KAAK;oBACd,KAAK,EAAG,GAAa,CAAC,OAAO;iBAC9B,CAAC;YACJ,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ProcessingHook } from "../processing.js";
|
|
2
|
+
interface VirusScanOptions {
|
|
3
|
+
/** ClamAV REST API endpoint (e.g., http://localhost:3310/scan) */
|
|
4
|
+
clamavUrl: string;
|
|
5
|
+
/** Whether to delete the file if a virus is found */
|
|
6
|
+
deleteOnDetection?: boolean;
|
|
7
|
+
/** Timeout in ms for the scan request */
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Example virus scan hook using ClamAV REST API.
|
|
12
|
+
* Requires a running ClamAV REST service.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* import { createVirusScanHook } from "@uploadbox/core/hooks/virus-scan";
|
|
16
|
+
*
|
|
17
|
+
* const scanHook = createVirusScanHook({
|
|
18
|
+
* clamavUrl: "http://localhost:3310/scan",
|
|
19
|
+
* deleteOnDetection: true,
|
|
20
|
+
* });
|
|
21
|
+
*/
|
|
22
|
+
export declare function createVirusScanHook(options: VirusScanOptions): ProcessingHook;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=virus-scan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"virus-scan.d.ts","sourceRoot":"","sources":["../../src/hooks/virus-scan.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAA2C,MAAM,kBAAkB,CAAC;AAEhG,UAAU,gBAAgB;IACxB,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,GAAG,cAAc,CAuE7E"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example virus scan hook using ClamAV REST API.
|
|
3
|
+
* Requires a running ClamAV REST service.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* import { createVirusScanHook } from "@uploadbox/core/hooks/virus-scan";
|
|
7
|
+
*
|
|
8
|
+
* const scanHook = createVirusScanHook({
|
|
9
|
+
* clamavUrl: "http://localhost:3310/scan",
|
|
10
|
+
* deleteOnDetection: true,
|
|
11
|
+
* });
|
|
12
|
+
*/
|
|
13
|
+
export function createVirusScanHook(options) {
|
|
14
|
+
const { clamavUrl, deleteOnDetection = true, timeoutMs = 30000, } = options;
|
|
15
|
+
return {
|
|
16
|
+
name: "virus-scan",
|
|
17
|
+
timeoutMs,
|
|
18
|
+
shouldRun() {
|
|
19
|
+
// Scan all uploaded files
|
|
20
|
+
return true;
|
|
21
|
+
},
|
|
22
|
+
async run(ctx) {
|
|
23
|
+
try {
|
|
24
|
+
const { GetObjectCommand, DeleteObjectCommand } = await import("@aws-sdk/client-s3");
|
|
25
|
+
// Download file from S3
|
|
26
|
+
const response = await ctx.s3Client.send(new GetObjectCommand({
|
|
27
|
+
Bucket: ctx.config.bucket,
|
|
28
|
+
Key: ctx.file.key,
|
|
29
|
+
}));
|
|
30
|
+
const body = await response.Body?.transformToByteArray();
|
|
31
|
+
if (!body) {
|
|
32
|
+
return { success: false, error: "Failed to download file from S3" };
|
|
33
|
+
}
|
|
34
|
+
// Send to ClamAV for scanning
|
|
35
|
+
const scanResponse = await fetch(clamavUrl, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
body: Buffer.from(body),
|
|
38
|
+
headers: {
|
|
39
|
+
"Content-Type": "application/octet-stream",
|
|
40
|
+
},
|
|
41
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
42
|
+
});
|
|
43
|
+
const scanResult = await scanResponse.text();
|
|
44
|
+
const isClean = scanResponse.ok && !scanResult.toLowerCase().includes("found");
|
|
45
|
+
if (!isClean && deleteOnDetection) {
|
|
46
|
+
await ctx.s3Client.send(new DeleteObjectCommand({
|
|
47
|
+
Bucket: ctx.config.bucket,
|
|
48
|
+
Key: ctx.file.key,
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
success: isClean,
|
|
53
|
+
data: {
|
|
54
|
+
clean: isClean,
|
|
55
|
+
scanResult: scanResult.slice(0, 500),
|
|
56
|
+
},
|
|
57
|
+
error: isClean ? undefined : `Virus detected: ${scanResult.slice(0, 200)}`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
return {
|
|
62
|
+
success: false,
|
|
63
|
+
error: `Virus scan failed: ${err.message}`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=virus-scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"virus-scan.js","sourceRoot":"","sources":["../../src/hooks/virus-scan.ts"],"names":[],"mappings":"AAWA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAyB;IAC3D,MAAM,EACJ,SAAS,EACT,iBAAiB,GAAG,IAAI,EACxB,SAAS,GAAG,KAAK,GAClB,GAAG,OAAO,CAAC;IAEZ,OAAO;QACL,IAAI,EAAE,YAAY;QAClB,SAAS;QAET,SAAS;YACP,0BAA0B;YAC1B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,KAAK,CAAC,GAAG,CAAC,GAAsB;YAC9B,IAAI,CAAC;gBACH,MAAM,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;gBAErF,wBAAwB;gBACxB,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,CACtC,IAAI,gBAAgB,CAAC;oBACnB,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM;oBACzB,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG;iBAClB,CAAC,CACH,CAAC;gBAEF,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,oBAAoB,EAAE,CAAC;gBACzD,IAAI,CAAC,IAAI,EAAE,CAAC;oBACV,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,iCAAiC,EAAE,CAAC;gBACtE,CAAC;gBAED,8BAA8B;gBAC9B,MAAM,YAAY,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE;oBAC1C,MAAM,EAAE,MAAM;oBACd,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;oBACvB,OAAO,EAAE;wBACP,cAAc,EAAE,0BAA0B;qBAC3C;oBACD,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC;iBACvC,CAAC,CAAC;gBAEH,MAAM,UAAU,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,CAAC;gBAC7C,MAAM,OAAO,GAAG,YAAY,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;gBAE/E,IAAI,CAAC,OAAO,IAAI,iBAAiB,EAAE,CAAC;oBAClC,MAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,CACrB,IAAI,mBAAmB,CAAC;wBACtB,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM;wBACzB,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG;qBAClB,CAAC,CACH,CAAC;gBACJ,CAAC;gBAED,OAAO;oBACL,OAAO,EAAE,OAAO;oBAChB,IAAI,EAAE;wBACJ,KAAK,EAAE,OAAO;wBACd,UAAU,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;qBACrC;oBACD,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,mBAAmB,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;iBAC3E,CAAC;YACJ,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO;oBACL,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,sBAAuB,GAAa,CAAC,OAAO,EAAE;iBACtD,CAAC;YACJ,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { createUploadbox, f } from "./create-uploadbox.js";
|
|
2
|
+
export { UploadboxApi } from "./server-api.js";
|
|
3
|
+
export { UploadboxError } from "./errors.js";
|
|
4
|
+
export { createUploadBuilder } from "./builder.js";
|
|
5
|
+
export { validateFiles } from "./validation.js";
|
|
6
|
+
export { createS3Client, generatePresignedPutUrl, generatePresignedGetUrl, headObject, deleteObject, deleteObjects, listObjects } from "./s3.js";
|
|
7
|
+
export { parseFileSize, formatFileSize, generateFileKey, generateUploadId } from "./utils.js";
|
|
8
|
+
export { FILE_TYPE_MIME_MAP, DEFAULT_MAX_FILE_SIZES, getFileTypeFromMime, isFileTypeAllowed } from "./file-types.js";
|
|
9
|
+
export { serverUpload, serverUploadMany } from "./server-upload.js";
|
|
10
|
+
export type { ServerUploadInput, ServerUploadResult } from "./server-upload.js";
|
|
11
|
+
export { createMultipartUpload, generatePresignedPartUrls, completeMultipartUpload, abortMultipartUpload, } from "./s3-multipart.js";
|
|
12
|
+
export type { FileType, FileSize, ACL, ContentDisposition, FileRouteConfig, FileInfo, UploadedFileData, MiddlewareFn, OnUploadCompleteFn, FileRoute, FileRouter, PresignedUrlResponse, UploadCompleteResponse, UploadboxConfig, RouterConfig, AuthContext, MultipartPresignedResponse, CompletedPartInfo, } from "./types.js";
|
|
13
|
+
export { MULTIPART_THRESHOLD, DEFAULT_PART_SIZE } from "./types.js";
|
|
14
|
+
export type { ProcessingHook, ProcessingContext, ProcessingHookResult, ProcessingPipelineConfig } from "./processing.js";
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,CAAC,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACjJ,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9F,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACrH,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACpE,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAChF,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,QAAQ,EACR,QAAQ,EACR,GAAG,EACH,kBAAkB,EAClB,eAAe,EACf,QAAQ,EACR,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,SAAS,EACT,UAAU,EACV,oBAAoB,EACpB,sBAAsB,EACtB,eAAe,EACf,YAAY,EACZ,WAAW,EACX,0BAA0B,EAC1B,iBAAiB,GAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpE,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { createUploadbox, f } from "./create-uploadbox.js";
|
|
2
|
+
export { UploadboxApi } from "./server-api.js";
|
|
3
|
+
export { UploadboxError } from "./errors.js";
|
|
4
|
+
export { createUploadBuilder } from "./builder.js";
|
|
5
|
+
export { validateFiles } from "./validation.js";
|
|
6
|
+
export { createS3Client, generatePresignedPutUrl, generatePresignedGetUrl, headObject, deleteObject, deleteObjects, listObjects } from "./s3.js";
|
|
7
|
+
export { parseFileSize, formatFileSize, generateFileKey, generateUploadId } from "./utils.js";
|
|
8
|
+
export { FILE_TYPE_MIME_MAP, DEFAULT_MAX_FILE_SIZES, getFileTypeFromMime, isFileTypeAllowed } from "./file-types.js";
|
|
9
|
+
export { serverUpload, serverUploadMany } from "./server-upload.js";
|
|
10
|
+
export { createMultipartUpload, generatePresignedPartUrls, completeMultipartUpload, abortMultipartUpload, } from "./s3-multipart.js";
|
|
11
|
+
export { MULTIPART_THRESHOLD, DEFAULT_PART_SIZE } from "./types.js";
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,CAAC,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACjJ,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9F,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACrH,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEpE,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAqB3B,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { UploadedFileData, UploadboxConfig } from "./types.js";
|
|
2
|
+
import type { S3Client } from "@aws-sdk/client-s3";
|
|
3
|
+
export interface ProcessingContext {
|
|
4
|
+
file: UploadedFileData;
|
|
5
|
+
metadata: Record<string, unknown>;
|
|
6
|
+
s3Client: S3Client;
|
|
7
|
+
config: UploadboxConfig;
|
|
8
|
+
}
|
|
9
|
+
export interface ProcessingHookResult {
|
|
10
|
+
success: boolean;
|
|
11
|
+
data?: Record<string, unknown>;
|
|
12
|
+
error?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ProcessingHook {
|
|
15
|
+
name: string;
|
|
16
|
+
shouldRun: (ctx: ProcessingContext) => boolean;
|
|
17
|
+
run: (ctx: ProcessingContext) => Promise<ProcessingHookResult>;
|
|
18
|
+
timeoutMs?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface ProcessingPipelineConfig {
|
|
21
|
+
hooks: ProcessingHook[];
|
|
22
|
+
mode?: "sequential" | "parallel";
|
|
23
|
+
continueOnError?: boolean;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=processing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"processing.d.ts","sourceRoot":"","sources":["../src/processing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AACpE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,eAAe,CAAC;CACzB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,OAAO,CAAC;IAC/C,GAAG,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,IAAI,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;IACjC,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"processing.js","sourceRoot":"","sources":["../src/processing.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { S3Client } from "@aws-sdk/client-s3";
|
|
2
|
+
export declare function createMultipartUpload(client: S3Client, bucket: string, key: string, contentType: string): Promise<string>;
|
|
3
|
+
export declare function generatePresignedPartUrls(client: S3Client, bucket: string, key: string, uploadId: string, partNumbers: number[], expiresIn?: number): Promise<{
|
|
4
|
+
partNumber: number;
|
|
5
|
+
url: string;
|
|
6
|
+
}[]>;
|
|
7
|
+
export declare function completeMultipartUpload(client: S3Client, bucket: string, key: string, uploadId: string, parts: {
|
|
8
|
+
partNumber: number;
|
|
9
|
+
etag: string;
|
|
10
|
+
}[]): Promise<void>;
|
|
11
|
+
export declare function abortMultipartUpload(client: S3Client, bucket: string, key: string, uploadId: string): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=s3-multipart.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-multipart.d.ts","sourceRoot":"","sources":["../src/s3-multipart.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EAKT,MAAM,oBAAoB,CAAC;AAI5B,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC,CAiBjB;AAED,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EAAE,EACrB,SAAS,GAAE,MAAa,GACvB,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAiBhD;AAED,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,GAC5C,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAaf"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { CreateMultipartUploadCommand, UploadPartCommand, CompleteMultipartUploadCommand, AbortMultipartUploadCommand, } from "@aws-sdk/client-s3";
|
|
2
|
+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
3
|
+
import { UploadboxError } from "./errors.js";
|
|
4
|
+
export async function createMultipartUpload(client, bucket, key, contentType) {
|
|
5
|
+
try {
|
|
6
|
+
const response = await client.send(new CreateMultipartUploadCommand({
|
|
7
|
+
Bucket: bucket,
|
|
8
|
+
Key: key,
|
|
9
|
+
ContentType: contentType,
|
|
10
|
+
}));
|
|
11
|
+
if (!response.UploadId) {
|
|
12
|
+
throw new Error("No UploadId returned from S3");
|
|
13
|
+
}
|
|
14
|
+
return response.UploadId;
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
if (err instanceof UploadboxError)
|
|
18
|
+
throw err;
|
|
19
|
+
throw UploadboxError.s3Error(err.message);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export async function generatePresignedPartUrls(client, bucket, key, uploadId, partNumbers, expiresIn = 3600) {
|
|
23
|
+
try {
|
|
24
|
+
return await Promise.all(partNumbers.map(async (partNumber) => {
|
|
25
|
+
const command = new UploadPartCommand({
|
|
26
|
+
Bucket: bucket,
|
|
27
|
+
Key: key,
|
|
28
|
+
UploadId: uploadId,
|
|
29
|
+
PartNumber: partNumber,
|
|
30
|
+
});
|
|
31
|
+
const url = await getSignedUrl(client, command, { expiresIn });
|
|
32
|
+
return { partNumber, url };
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
throw UploadboxError.s3Error(err.message);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function completeMultipartUpload(client, bucket, key, uploadId, parts) {
|
|
40
|
+
try {
|
|
41
|
+
await client.send(new CompleteMultipartUploadCommand({
|
|
42
|
+
Bucket: bucket,
|
|
43
|
+
Key: key,
|
|
44
|
+
UploadId: uploadId,
|
|
45
|
+
MultipartUpload: {
|
|
46
|
+
Parts: parts
|
|
47
|
+
.sort((a, b) => a.partNumber - b.partNumber)
|
|
48
|
+
.map((p) => ({
|
|
49
|
+
PartNumber: p.partNumber,
|
|
50
|
+
ETag: p.etag,
|
|
51
|
+
})),
|
|
52
|
+
},
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
throw UploadboxError.s3Error(err.message);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export async function abortMultipartUpload(client, bucket, key, uploadId) {
|
|
60
|
+
try {
|
|
61
|
+
await client.send(new AbortMultipartUploadCommand({
|
|
62
|
+
Bucket: bucket,
|
|
63
|
+
Key: key,
|
|
64
|
+
UploadId: uploadId,
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
// Ignore errors when aborting — the upload may already be aborted
|
|
69
|
+
console.warn("[uploadbox] Failed to abort multipart upload:", err.message);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=s3-multipart.js.map
|