@useupup/core 3.3.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/LICENSE +21 -0
- package/NOTICE +8 -0
- package/README.md +75 -0
- package/dist/browser-SXR426OQ.cjs +10 -0
- package/dist/browser-WD6T5ABJ.js +1 -0
- package/dist/chunk-2H5ICKFT.cjs +122 -0
- package/dist/chunk-6HF3I2JS.cjs +39 -0
- package/dist/chunk-6S5DQDQH.js +212 -0
- package/dist/chunk-BMQC27OE.cjs +92 -0
- package/dist/chunk-BSGQGINR.cjs +219 -0
- package/dist/chunk-CPKZ2CXD.cjs +179 -0
- package/dist/chunk-DTKDIMKT.cjs +573 -0
- package/dist/chunk-DYE5XJ6Z.cjs +2134 -0
- package/dist/chunk-E3ZCNOXG.cjs +74 -0
- package/dist/chunk-FEQY2WYY.cjs +2377 -0
- package/dist/chunk-FMURBCZ7.js +120 -0
- package/dist/chunk-FYQQRULL.js +37 -0
- package/dist/chunk-GE4LIVXP.js +133 -0
- package/dist/chunk-HFR5HZJZ.js +56 -0
- package/dist/chunk-I7ULPCA2.js +72 -0
- package/dist/chunk-MHYXDLZR.js +569 -0
- package/dist/chunk-N2SLHVAW.cjs +61 -0
- package/dist/chunk-OOPDEOTM.js +169 -0
- package/dist/chunk-PMBR3OUW.js +2359 -0
- package/dist/chunk-QK7HZIVY.cjs +144 -0
- package/dist/chunk-T2AQHY7V.cjs +59 -0
- package/dist/chunk-TRCP5FUR.js +90 -0
- package/dist/chunk-U7KL5M2H.cjs +374 -0
- package/dist/chunk-UXNPM54W.js +2108 -0
- package/dist/chunk-VV6QCQLL.js +365 -0
- package/dist/chunk-ZMS2HXLB.js +56 -0
- package/dist/context-shapes-123dd3b6.d.cts +207 -0
- package/dist/context-shapes-27b24c8f.d.ts +207 -0
- package/dist/contracts-pipeline-cf177270.d.cts +78 -0
- package/dist/contracts-pipeline-cf177270.d.ts +78 -0
- package/dist/contracts-strategies-378a16b7.d.cts +168 -0
- package/dist/contracts-strategies-378a16b7.d.ts +168 -0
- package/dist/contracts.cjs +181 -0
- package/dist/contracts.d.cts +11 -0
- package/dist/contracts.d.ts +11 -0
- package/dist/contracts.js +4 -0
- package/dist/core-141335ac.d.ts +651 -0
- package/dist/core-bea2ceca.d.cts +651 -0
- package/dist/create-worker-provider-AA5SRII6.js +58 -0
- package/dist/create-worker-provider-KJNUSSTW.cjs +60 -0
- package/dist/eligibility-ONLNJ6FB.cjs +10 -0
- package/dist/eligibility-ULFC3E4G.js +8 -0
- package/dist/error-code-map-9c44a425.d.ts +62 -0
- package/dist/error-code-map-dd5becfa.d.cts +62 -0
- package/dist/i18n/index.cjs +90 -0
- package/dist/i18n/index.d.cts +3 -0
- package/dist/i18n/index.d.ts +3 -0
- package/dist/i18n/index.js +1 -0
- package/dist/index.cjs +1770 -0
- package/dist/index.d.cts +280 -0
- package/dist/index.d.ts +280 -0
- package/dist/index.js +1589 -0
- package/dist/internal.cjs +2407 -0
- package/dist/internal.d.cts +595 -0
- package/dist/internal.d.ts +595 -0
- package/dist/internal.js +2275 -0
- package/dist/pipeline/index.cjs +10 -0
- package/dist/pipeline/index.d.cts +11 -0
- package/dist/pipeline/index.d.ts +11 -0
- package/dist/pipeline/index.js +1 -0
- package/dist/pipeline-worker.cjs +199 -0
- package/dist/pipeline-worker.d.cts +2 -0
- package/dist/pipeline-worker.d.ts +2 -0
- package/dist/pipeline-worker.js +197 -0
- package/dist/resolve-locale-81feef21.d.ts +20 -0
- package/dist/resolve-locale-9cb63600.d.cts +20 -0
- package/dist/resolve-theme-01758105.d.ts +53 -0
- package/dist/resolve-theme-66cf8d98.d.cts +53 -0
- package/dist/server-mode-drive-controller-b5f8ee12.d.cts +180 -0
- package/dist/server-mode-drive-controller-b5f8ee12.d.ts +180 -0
- package/dist/steps/compress.cjs +66 -0
- package/dist/steps/compress.d.cts +10 -0
- package/dist/steps/compress.d.ts +10 -0
- package/dist/steps/compress.js +64 -0
- package/dist/steps/exif.cjs +44 -0
- package/dist/steps/exif.d.cts +5 -0
- package/dist/steps/exif.d.ts +5 -0
- package/dist/steps/exif.js +42 -0
- package/dist/steps/hash.cjs +44 -0
- package/dist/steps/hash.d.cts +5 -0
- package/dist/steps/hash.d.ts +5 -0
- package/dist/steps/hash.js +42 -0
- package/dist/steps/heic.cjs +59 -0
- package/dist/steps/heic.d.cts +5 -0
- package/dist/steps/heic.d.ts +5 -0
- package/dist/steps/heic.js +57 -0
- package/dist/steps/thumbnail.cjs +54 -0
- package/dist/steps/thumbnail.d.cts +10 -0
- package/dist/steps/thumbnail.d.ts +10 -0
- package/dist/steps/thumbnail.js +52 -0
- package/dist/strategies/index.cjs +82 -0
- package/dist/strategies/index.d.cts +187 -0
- package/dist/strategies/index.d.ts +187 -0
- package/dist/strategies/index.js +60 -0
- package/dist/strategies/tus-upload.cjs +14 -0
- package/dist/strategies/tus-upload.d.cts +15 -0
- package/dist/strategies/tus-upload.d.ts +15 -0
- package/dist/strategies/tus-upload.js +5 -0
- package/dist/theme/index.cjs +38 -0
- package/dist/theme/index.d.cts +3 -0
- package/dist/theme/index.d.ts +3 -0
- package/dist/theme/index.js +1 -0
- package/dist/theme-store-2e56a674.d.cts +39 -0
- package/dist/theme-store-f13e0315.d.ts +39 -0
- package/dist/token-endpoint-5fdb4a35.d.ts +20 -0
- package/dist/token-endpoint-a7d15ab6.d.cts +20 -0
- package/dist/ui-translations-c5d00392.d.cts +455 -0
- package/dist/ui-translations-c5d00392.d.ts +455 -0
- package/dist/uploader-props-72242002.d.ts +340 -0
- package/dist/uploader-props-7f956e60.d.cts +340 -0
- package/dist/vars-d3421f6c.d.cts +240 -0
- package/dist/vars-d3421f6c.d.ts +240 -0
- package/package.json +182 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Devino
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
@useupup/core bundles libheif (via libheif-js) for HEIC/HEIF image decoding.
|
|
2
|
+
|
|
3
|
+
libheif is licensed under the GNU Lesser General Public License v3.0 (LGPL-3.0).
|
|
4
|
+
Source: https://github.com/strukturag/libheif
|
|
5
|
+
libheif-js: https://github.com/catdad-experiments/libheif-js
|
|
6
|
+
|
|
7
|
+
The libheif WebAssembly artifact is a separable component and may be replaced
|
|
8
|
+
with a compatible build under the terms of the LGPL-3.0.
|
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# @useupup/core
|
|
2
|
+
|
|
3
|
+
The headless engine behind [upup](https://github.com/DevinoSolutions/upup): file
|
|
4
|
+
state and upload orchestration, the upload pipeline (image compression, HEIC
|
|
5
|
+
decode, web-worker offload), cloud-drive plugins (Google Drive, OneDrive,
|
|
6
|
+
Dropbox, Box), theming, and ICU i18n. It has **zero framework dependencies** and
|
|
7
|
+
powers every `@useupup/*` UI package.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
Most consumers should install a framework package instead —
|
|
12
|
+
[`@useupup/react`](https://www.npmjs.com/package/@useupup/react), `@useupup/vue`,
|
|
13
|
+
`@useupup/svelte`, `@useupup/angular`, `@useupup/vanilla`, or `@useupup/preact` — which bundle
|
|
14
|
+
this engine and render a ready-made UI. Install `@useupup/core` directly only to
|
|
15
|
+
drive uploads headlessly or to build your own UI binding.
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm i @useupup/core
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Headless usage
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { UpupCore } from '@useupup/core'
|
|
25
|
+
|
|
26
|
+
const core = new UpupCore({
|
|
27
|
+
provider: 'aws',
|
|
28
|
+
uploadEndpoint: '/api/upload-token',
|
|
29
|
+
})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`UpupCore` owns the file/progress state, the upload lifecycle, and the
|
|
33
|
+
cloud-drive plugins. See the docs for the full event and method surface.
|
|
34
|
+
|
|
35
|
+
## Errors
|
|
36
|
+
|
|
37
|
+
Upload failures are typed. Catch `UpupError` and branch on its stable
|
|
38
|
+
`UpupErrorCode`; the concrete subclasses (`UpupValidationError`,
|
|
39
|
+
`UpupNetworkError`, `UpupStorageError`, `UpupAuthError`, `UpupQuotaError`,
|
|
40
|
+
`UpupConfigError`) let you narrow further.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { UpupError, UpupErrorCode } from '@useupup/core'
|
|
44
|
+
|
|
45
|
+
function isRetryable(err: unknown): boolean {
|
|
46
|
+
return err instanceof UpupError && err.code === UpupErrorCode.NETWORK_ERROR
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`StorageProvider` enumerates the supported storage providers
|
|
51
|
+
(`StorageProvider.AWS`, `StorageProvider.CloudflareR2`, `StorageProvider.MinIO`,
|
|
52
|
+
and more).
|
|
53
|
+
|
|
54
|
+
## Entry points
|
|
55
|
+
|
|
56
|
+
The main `@useupup/core` entry is a curated allow-list of stable exports (the
|
|
57
|
+
`UpupCore` engine, the drive plugins, the `UpupError` taxonomy — `UpupError`
|
|
58
|
+
plus its typed subclasses and the `UpupErrorCode` enum — the locale bundles, and
|
|
59
|
+
the theme presets). Advanced, deep-import-only subpaths are also published:
|
|
60
|
+
|
|
61
|
+
- `@useupup/core/internal` — implementation internals (managers, orchestrator, controllers)
|
|
62
|
+
- `@useupup/core/contracts`, `@useupup/core/i18n`, `@useupup/core/theme`, `@useupup/core/strategies`
|
|
63
|
+
- `@useupup/core/pipeline` and `@useupup/core/steps/*` (`hash`, `heic`, `exif`, `compress`, `thumbnail`)
|
|
64
|
+
|
|
65
|
+
Heavy capabilities (HEIC decode, resumable/tus uploads) load lazily via these
|
|
66
|
+
subpaths, so they never weigh down the main entry.
|
|
67
|
+
|
|
68
|
+
## Links
|
|
69
|
+
|
|
70
|
+
- [Documentation](https://useupup.com/docs/)
|
|
71
|
+
- [Source & monorepo](https://github.com/DevinoSolutions/upup)
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { BrowserRuntime } from './chunk-HFR5HZJZ.js';
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/steps/animated-image.ts
|
|
4
|
+
function ascii(bytes, start, length) {
|
|
5
|
+
let out = "";
|
|
6
|
+
for (let i = start; i < start + length; i += 1) {
|
|
7
|
+
const byte = bytes[i];
|
|
8
|
+
if (byte === void 0) return out;
|
|
9
|
+
out += String.fromCharCode(byte);
|
|
10
|
+
}
|
|
11
|
+
return out;
|
|
12
|
+
}
|
|
13
|
+
function byteAt(bytes, index) {
|
|
14
|
+
return bytes[index] ?? 0;
|
|
15
|
+
}
|
|
16
|
+
function skipSubBlocks(bytes, start) {
|
|
17
|
+
let offset = start;
|
|
18
|
+
while (offset < bytes.length) {
|
|
19
|
+
const size = byteAt(bytes, offset);
|
|
20
|
+
offset += 1;
|
|
21
|
+
if (size === 0) return offset;
|
|
22
|
+
offset += size;
|
|
23
|
+
}
|
|
24
|
+
return bytes.length;
|
|
25
|
+
}
|
|
26
|
+
function isAnimatedGif(bytes) {
|
|
27
|
+
if (bytes.length < 13) return false;
|
|
28
|
+
if (ascii(bytes, 0, 3) !== "GIF") return false;
|
|
29
|
+
const screenPacked = byteAt(bytes, 10);
|
|
30
|
+
let offset = 13;
|
|
31
|
+
if ((screenPacked & 128) !== 0) {
|
|
32
|
+
offset += 3 * (1 << (screenPacked & 7) + 1);
|
|
33
|
+
}
|
|
34
|
+
let frames = 0;
|
|
35
|
+
while (offset < bytes.length) {
|
|
36
|
+
const block = byteAt(bytes, offset);
|
|
37
|
+
if (block === 59) return false;
|
|
38
|
+
if (block === 33) {
|
|
39
|
+
const label = byteAt(bytes, offset + 1);
|
|
40
|
+
offset += 2;
|
|
41
|
+
if (label === 255) {
|
|
42
|
+
const identifier = ascii(
|
|
43
|
+
bytes,
|
|
44
|
+
offset + 1,
|
|
45
|
+
byteAt(bytes, offset)
|
|
46
|
+
);
|
|
47
|
+
if (identifier === "NETSCAPE2.0" || identifier === "ANIMEXTS1.0") {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
offset = skipSubBlocks(bytes, offset);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (block === 44) {
|
|
55
|
+
frames += 1;
|
|
56
|
+
if (frames > 1) return true;
|
|
57
|
+
const imagePacked = byteAt(bytes, offset + 9);
|
|
58
|
+
offset += 10;
|
|
59
|
+
if ((imagePacked & 128) !== 0) {
|
|
60
|
+
offset += 3 * (1 << (imagePacked & 7) + 1);
|
|
61
|
+
}
|
|
62
|
+
offset += 1;
|
|
63
|
+
offset = skipSubBlocks(bytes, offset);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
var PNG_SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
71
|
+
function isAnimatedPng(bytes) {
|
|
72
|
+
if (bytes.length < PNG_SIGNATURE.length) return false;
|
|
73
|
+
for (const [index, expected] of PNG_SIGNATURE.entries()) {
|
|
74
|
+
if (byteAt(bytes, index) !== expected) return false;
|
|
75
|
+
}
|
|
76
|
+
let offset = PNG_SIGNATURE.length;
|
|
77
|
+
while (offset + 8 <= bytes.length) {
|
|
78
|
+
const length = byteAt(bytes, offset) * 16777216 + byteAt(bytes, offset + 1) * 65536 + byteAt(bytes, offset + 2) * 256 + byteAt(bytes, offset + 3);
|
|
79
|
+
const type = ascii(bytes, offset + 4, 4);
|
|
80
|
+
if (type === "acTL") return true;
|
|
81
|
+
if (type === "IDAT") return false;
|
|
82
|
+
offset += 12 + length;
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
function isAnimatedWebp(bytes) {
|
|
87
|
+
if (bytes.length < 16) return false;
|
|
88
|
+
if (ascii(bytes, 0, 4) !== "RIFF" || ascii(bytes, 8, 4) !== "WEBP") {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
let offset = 12;
|
|
92
|
+
while (offset + 8 <= bytes.length) {
|
|
93
|
+
const fourCC = ascii(bytes, offset, 4);
|
|
94
|
+
const size = byteAt(bytes, offset + 4) + byteAt(bytes, offset + 5) * 256 + byteAt(bytes, offset + 6) * 65536 + byteAt(bytes, offset + 7) * 16777216;
|
|
95
|
+
if (fourCC === "ANIM" || fourCC === "ANMF") return true;
|
|
96
|
+
if (fourCC === "VP8X" && (byteAt(bytes, offset + 8) & 2) !== 0) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
offset += 8 + size + size % 2;
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
var SNIFFERS = {
|
|
104
|
+
"image/gif": isAnimatedGif,
|
|
105
|
+
"image/png": isAnimatedPng,
|
|
106
|
+
"image/apng": isAnimatedPng,
|
|
107
|
+
"image/webp": isAnimatedWebp
|
|
108
|
+
};
|
|
109
|
+
function baseMimeType(type) {
|
|
110
|
+
return type.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
111
|
+
}
|
|
112
|
+
async function isAnimatedImage(file) {
|
|
113
|
+
const sniff = SNIFFERS[baseMimeType(file.type)];
|
|
114
|
+
if (!sniff) return false;
|
|
115
|
+
try {
|
|
116
|
+
return sniff(new Uint8Array(await file.arrayBuffer()));
|
|
117
|
+
} catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
exports.isAnimatedImage = isAnimatedImage;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/pipeline/engine.ts
|
|
4
|
+
var PipelineEngine = class {
|
|
5
|
+
constructor(steps) {
|
|
6
|
+
this.steps = steps;
|
|
7
|
+
}
|
|
8
|
+
get stepCount() {
|
|
9
|
+
return this.steps.length;
|
|
10
|
+
}
|
|
11
|
+
async process(file, context) {
|
|
12
|
+
context.emit("pipeline-start", {
|
|
13
|
+
fileId: file.id,
|
|
14
|
+
steps: this.steps.map((s) => s.name)
|
|
15
|
+
});
|
|
16
|
+
let current = file;
|
|
17
|
+
for (const step of this.steps) {
|
|
18
|
+
if (step.shouldProcess && !step.shouldProcess(current)) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
current = await step.process(current, context);
|
|
22
|
+
context.emit("pipeline-step", {
|
|
23
|
+
fileId: file.id,
|
|
24
|
+
step: step.name
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
context.emit("pipeline-complete", { fileId: file.id });
|
|
28
|
+
return current;
|
|
29
|
+
}
|
|
30
|
+
async processAll(files, context) {
|
|
31
|
+
const results = [];
|
|
32
|
+
for (const file of files) {
|
|
33
|
+
results.push(await this.process(file, context));
|
|
34
|
+
}
|
|
35
|
+
return results;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
exports.PipelineEngine = PipelineEngine;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { UpupError } from './chunk-GE4LIVXP.js';
|
|
2
|
+
|
|
3
|
+
// src/steps/image-utils.ts
|
|
4
|
+
function hasDocument() {
|
|
5
|
+
return typeof document !== "undefined" && "createElement" in document;
|
|
6
|
+
}
|
|
7
|
+
function clampQuality(value, fallback) {
|
|
8
|
+
if (typeof value !== "number" || Number.isNaN(value)) return fallback;
|
|
9
|
+
return Math.min(1, Math.max(0.05, value));
|
|
10
|
+
}
|
|
11
|
+
function scaleDimensions(width, height, maxWidthOrHeight) {
|
|
12
|
+
if (!maxWidthOrHeight || maxWidthOrHeight <= 0) return { width, height };
|
|
13
|
+
const largest = Math.max(width, height);
|
|
14
|
+
if (largest <= maxWidthOrHeight) return { width, height };
|
|
15
|
+
const scale = maxWidthOrHeight / largest;
|
|
16
|
+
return {
|
|
17
|
+
width: Math.max(1, Math.round(width * scale)),
|
|
18
|
+
height: Math.max(1, Math.round(height * scale))
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function outputTypeFor(file, requestedType) {
|
|
22
|
+
if (requestedType) return requestedType;
|
|
23
|
+
if (file.type === "image/png" || file.type === "image/webp")
|
|
24
|
+
return file.type;
|
|
25
|
+
return "image/jpeg";
|
|
26
|
+
}
|
|
27
|
+
function createCanvas(width, height) {
|
|
28
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
29
|
+
return new OffscreenCanvas(width, height);
|
|
30
|
+
}
|
|
31
|
+
if (!hasDocument()) return null;
|
|
32
|
+
const canvas = document.createElement("canvas");
|
|
33
|
+
canvas.width = width;
|
|
34
|
+
canvas.height = height;
|
|
35
|
+
return canvas;
|
|
36
|
+
}
|
|
37
|
+
async function canvasToBlob(canvas, type, quality) {
|
|
38
|
+
if ("convertToBlob" in canvas && typeof canvas.convertToBlob === "function") {
|
|
39
|
+
return canvas.convertToBlob({ type, quality });
|
|
40
|
+
}
|
|
41
|
+
return new Promise((resolve) => {
|
|
42
|
+
canvas.toBlob(
|
|
43
|
+
(blob) => {
|
|
44
|
+
resolve(blob);
|
|
45
|
+
},
|
|
46
|
+
type,
|
|
47
|
+
quality
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
async function decodeImage(file) {
|
|
52
|
+
if (typeof createImageBitmap === "function") {
|
|
53
|
+
try {
|
|
54
|
+
const bitmap = await createImageBitmap(file);
|
|
55
|
+
return {
|
|
56
|
+
source: bitmap,
|
|
57
|
+
width: bitmap.width,
|
|
58
|
+
height: bitmap.height,
|
|
59
|
+
close: () => {
|
|
60
|
+
bitmap.close();
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
} catch {
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (!hasDocument()) return null;
|
|
67
|
+
return new Promise((resolve) => {
|
|
68
|
+
const img = new Image();
|
|
69
|
+
const url = URL.createObjectURL(file);
|
|
70
|
+
const cleanup = () => {
|
|
71
|
+
URL.revokeObjectURL(url);
|
|
72
|
+
};
|
|
73
|
+
img.onload = () => {
|
|
74
|
+
cleanup();
|
|
75
|
+
resolve({
|
|
76
|
+
source: img,
|
|
77
|
+
width: img.naturalWidth || img.width,
|
|
78
|
+
height: img.naturalHeight || img.height
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
img.onerror = () => {
|
|
82
|
+
cleanup();
|
|
83
|
+
resolve(null);
|
|
84
|
+
};
|
|
85
|
+
img.src = url;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
async function encodeImageFile(file, options = {}) {
|
|
89
|
+
const decoded = await decodeImage(file).catch(() => null);
|
|
90
|
+
if (!decoded) return null;
|
|
91
|
+
try {
|
|
92
|
+
const dimensions = scaleDimensions(
|
|
93
|
+
decoded.width,
|
|
94
|
+
decoded.height,
|
|
95
|
+
options.maxWidthOrHeight
|
|
96
|
+
);
|
|
97
|
+
const canvas = createCanvas(dimensions.width, dimensions.height);
|
|
98
|
+
const ctx = canvas?.getContext("2d");
|
|
99
|
+
if (!canvas || !ctx) return null;
|
|
100
|
+
ctx.drawImage(decoded.source, 0, 0, dimensions.width, dimensions.height);
|
|
101
|
+
const type = outputTypeFor(file, options.type);
|
|
102
|
+
const quality = clampQuality(options.quality, 0.82);
|
|
103
|
+
const blob = await canvasToBlob(canvas, type, quality);
|
|
104
|
+
if (!blob) return null;
|
|
105
|
+
const nextFile = new File([blob], file.name, {
|
|
106
|
+
type: blob.type || type || file.type,
|
|
107
|
+
lastModified: file.lastModified
|
|
108
|
+
});
|
|
109
|
+
return cloneUploadFile(file, nextFile, {
|
|
110
|
+
width: dimensions.width,
|
|
111
|
+
height: dimensions.height,
|
|
112
|
+
...options.metadata
|
|
113
|
+
});
|
|
114
|
+
} finally {
|
|
115
|
+
decoded.close?.();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function blobToDataUrl(blob) {
|
|
119
|
+
if (typeof FileReader !== "undefined") {
|
|
120
|
+
return new Promise((resolve, reject) => {
|
|
121
|
+
const reader = new FileReader();
|
|
122
|
+
reader.onload = () => {
|
|
123
|
+
const result = reader.result;
|
|
124
|
+
resolve(typeof result === "string" ? result : "");
|
|
125
|
+
};
|
|
126
|
+
reader.onerror = () => {
|
|
127
|
+
reject(reader.error ?? new Error("Failed to read blob"));
|
|
128
|
+
};
|
|
129
|
+
reader.readAsDataURL(blob);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const bytes = new Uint8Array(await blob.arrayBuffer());
|
|
133
|
+
let binary = "";
|
|
134
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
135
|
+
const encode = typeof btoa === "function" ? btoa : globalThis.Buffer ? (input) => globalThis.Buffer.from(input, "binary").toString("base64") : null;
|
|
136
|
+
if (!encode)
|
|
137
|
+
throw new UpupError(
|
|
138
|
+
"No base64 encoder available",
|
|
139
|
+
"BAD_REQUEST" /* BAD_REQUEST */
|
|
140
|
+
);
|
|
141
|
+
return `data:${blob.type || "application/octet-stream"};base64,${encode(binary)}`;
|
|
142
|
+
}
|
|
143
|
+
async function createThumbnail(file, options = {}) {
|
|
144
|
+
const decoded = await decodeImage(file).catch(() => null);
|
|
145
|
+
if (!decoded) return null;
|
|
146
|
+
try {
|
|
147
|
+
const maxWidth = options.width ?? 320;
|
|
148
|
+
const maxHeight = options.height ?? maxWidth;
|
|
149
|
+
const scale = Math.min(
|
|
150
|
+
1,
|
|
151
|
+
maxWidth / decoded.width,
|
|
152
|
+
maxHeight / decoded.height
|
|
153
|
+
);
|
|
154
|
+
const width = Math.max(1, Math.round(decoded.width * scale));
|
|
155
|
+
const height = Math.max(1, Math.round(decoded.height * scale));
|
|
156
|
+
const canvas = createCanvas(width, height);
|
|
157
|
+
const ctx = canvas?.getContext("2d");
|
|
158
|
+
if (!canvas || !ctx) return null;
|
|
159
|
+
ctx.drawImage(decoded.source, 0, 0, width, height);
|
|
160
|
+
const type = options.type ?? "image/jpeg";
|
|
161
|
+
const quality = clampQuality(options.quality, 0.75);
|
|
162
|
+
const blob = await canvasToBlob(canvas, type, quality);
|
|
163
|
+
if (!blob) return null;
|
|
164
|
+
const ext = type === "image/png" ? "png" : type === "image/webp" ? "webp" : "jpg";
|
|
165
|
+
const thumbnailFile = new File(
|
|
166
|
+
[blob],
|
|
167
|
+
`${file.name}.thumbnail.${ext}`,
|
|
168
|
+
{
|
|
169
|
+
type: blob.type || type,
|
|
170
|
+
lastModified: file.lastModified
|
|
171
|
+
}
|
|
172
|
+
);
|
|
173
|
+
return {
|
|
174
|
+
file: thumbnailFile,
|
|
175
|
+
dataUrl: await blobToDataUrl(blob),
|
|
176
|
+
width,
|
|
177
|
+
height
|
|
178
|
+
};
|
|
179
|
+
} finally {
|
|
180
|
+
decoded.close?.();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function cloneUploadFile(original, replacement, metadata = {}) {
|
|
184
|
+
return Object.assign(replacement, {
|
|
185
|
+
id: original.id,
|
|
186
|
+
source: original.source,
|
|
187
|
+
status: original.status,
|
|
188
|
+
metadata: {
|
|
189
|
+
...original.metadata,
|
|
190
|
+
...metadata
|
|
191
|
+
},
|
|
192
|
+
url: original.url,
|
|
193
|
+
relativePath: original.relativePath,
|
|
194
|
+
key: original.key,
|
|
195
|
+
etag: original.etag,
|
|
196
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- v3 removal tracked
|
|
197
|
+
fileHash: original.fileHash,
|
|
198
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- v3 removal tracked
|
|
199
|
+
checksumSHA256: original.checksumSHA256,
|
|
200
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- v3 removal tracked
|
|
201
|
+
thumbnail: original.thumbnail
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
function uploadFileFromImageResult(original, result) {
|
|
205
|
+
const file = new File([result.bytes], result.name ?? original.name, {
|
|
206
|
+
type: result.type || original.type,
|
|
207
|
+
lastModified: original.lastModified
|
|
208
|
+
});
|
|
209
|
+
return cloneUploadFile(original, file, result.metadata ?? {});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export { canvasToBlob, cloneUploadFile, createCanvas, createThumbnail, encodeImageFile, uploadFileFromImageResult };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkQK7HZIVY_cjs = require('./chunk-QK7HZIVY.cjs');
|
|
4
|
+
|
|
5
|
+
// src/strategies/tus-upload.ts
|
|
6
|
+
var TusUpload = class {
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
}
|
|
10
|
+
async upload(file, _credentials, options) {
|
|
11
|
+
let tus;
|
|
12
|
+
try {
|
|
13
|
+
tus = await import('tus-js-client');
|
|
14
|
+
} catch {
|
|
15
|
+
throw new chunkQK7HZIVY_cjs.UpupError(
|
|
16
|
+
'Resumable (tus) uploads require the optional dependency "tus-js-client". Install it: npm i tus-js-client',
|
|
17
|
+
"UPLOAD_FAILED" /* UPLOAD_FAILED */
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
const fileName = file instanceof File ? file.name : "blob";
|
|
21
|
+
const fileType = file.type || "application/octet-stream";
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
let settled = false;
|
|
24
|
+
let upload = null;
|
|
25
|
+
const finishReject = (error) => {
|
|
26
|
+
if (settled) return;
|
|
27
|
+
settled = true;
|
|
28
|
+
reject(error);
|
|
29
|
+
};
|
|
30
|
+
const abortUpload = () => {
|
|
31
|
+
upload?.abort().catch((error) => {
|
|
32
|
+
finishReject(
|
|
33
|
+
error instanceof Error ? error : new chunkQK7HZIVY_cjs.UpupNetworkError("Tus upload abort failed")
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
finishReject(new chunkQK7HZIVY_cjs.UpupNetworkError("Upload aborted"));
|
|
37
|
+
};
|
|
38
|
+
if (options.signal.aborted) {
|
|
39
|
+
finishReject(new chunkQK7HZIVY_cjs.UpupNetworkError("Upload aborted"));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
options.signal.addEventListener("abort", abortUpload, {
|
|
43
|
+
once: true
|
|
44
|
+
});
|
|
45
|
+
const uploadOptions = {
|
|
46
|
+
endpoint: this.options.endpoint,
|
|
47
|
+
metadata: {
|
|
48
|
+
filename: fileName,
|
|
49
|
+
filetype: fileType,
|
|
50
|
+
...this.options.metadata
|
|
51
|
+
},
|
|
52
|
+
onProgress: (bytesUploaded, bytesTotal) => {
|
|
53
|
+
options.onProgress(bytesUploaded, bytesTotal);
|
|
54
|
+
},
|
|
55
|
+
onError: (error) => {
|
|
56
|
+
options.signal.removeEventListener("abort", abortUpload);
|
|
57
|
+
finishReject(
|
|
58
|
+
error instanceof Error ? error : new chunkQK7HZIVY_cjs.UpupNetworkError("Tus upload failed")
|
|
59
|
+
);
|
|
60
|
+
},
|
|
61
|
+
onSuccess: () => {
|
|
62
|
+
if (settled) return;
|
|
63
|
+
settled = true;
|
|
64
|
+
options.signal.removeEventListener("abort", abortUpload);
|
|
65
|
+
resolve({
|
|
66
|
+
key: upload?.url ?? fileName,
|
|
67
|
+
publicUrl: upload?.url ?? void 0
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const legacyChunkSize = this.options.chunkSize;
|
|
72
|
+
const chunkSize = this.options.chunkSizeBytes ?? legacyChunkSize;
|
|
73
|
+
if (this.options.headers !== void 0)
|
|
74
|
+
uploadOptions.headers = this.options.headers;
|
|
75
|
+
if (chunkSize !== void 0) uploadOptions.chunkSize = chunkSize;
|
|
76
|
+
if (this.options.retryDelays !== void 0)
|
|
77
|
+
uploadOptions.retryDelays = this.options.retryDelays;
|
|
78
|
+
if (this.options.storeFingerprintForResuming !== void 0) {
|
|
79
|
+
uploadOptions.storeFingerprintForResuming = this.options.storeFingerprintForResuming;
|
|
80
|
+
}
|
|
81
|
+
if (this.options.removeFingerprintOnSuccess !== void 0) {
|
|
82
|
+
uploadOptions.removeFingerprintOnSuccess = this.options.removeFingerprintOnSuccess;
|
|
83
|
+
}
|
|
84
|
+
if (this.options.parallelUploads !== void 0)
|
|
85
|
+
uploadOptions.parallelUploads = this.options.parallelUploads;
|
|
86
|
+
upload = new tus.Upload(file, uploadOptions);
|
|
87
|
+
upload.start();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
exports.TusUpload = TusUpload;
|