@cedarjs/uploads 8.0.0-canary.3274 → 8.0.0-canary.3285
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/web/createUppy.d.ts +19 -2
- package/dist/web/createUppy.d.ts.map +1 -1
- package/dist/web/createUppy.js +37 -15
- package/dist/web/hooks/useFsUpload.d.ts.map +1 -1
- package/dist/web/hooks/useFsUpload.js +21 -9
- package/dist/web/hooks/useS3Upload.d.ts.map +1 -1
- package/dist/web/hooks/useS3Upload.js +12 -5
- package/dist/web/hooks/useUploadToken.d.ts +27 -1
- package/dist/web/hooks/useUploadToken.d.ts.map +1 -1
- package/dist/web/hooks/useUploadToken.js +72 -3
- package/dist/web/hooks/useUppyUpload.d.ts.map +1 -1
- package/dist/web/hooks/useUppyUpload.js +2 -8
- package/package.json +7 -7
package/dist/web/createUppy.d.ts
CHANGED
|
@@ -37,8 +37,11 @@ export interface FsUppyOptions extends BaseUppyOptions {
|
|
|
37
37
|
provider: 'fs';
|
|
38
38
|
/** Full URL of the api's `POST {prefix}/fs` route, or a function returning it. */
|
|
39
39
|
endpoint: string | (() => string);
|
|
40
|
-
/**
|
|
41
|
-
|
|
40
|
+
/**
|
|
41
|
+
* The upload token to send with `file`. Called per request, so each file
|
|
42
|
+
* can carry a different token.
|
|
43
|
+
*/
|
|
44
|
+
getUploadToken: (file: Pick<CedarUppyFile, 'id'>) => string | null;
|
|
42
45
|
}
|
|
43
46
|
export type CreateUppyOptions = S3UppyOptions | FsUppyOptions;
|
|
44
47
|
/** Response body of the api's `POST {prefix}/fs` route. */
|
|
@@ -49,6 +52,20 @@ export interface FsUploadResponseBody {
|
|
|
49
52
|
filename: string;
|
|
50
53
|
}[];
|
|
51
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Applies a profile's constraints to `uppy` as restrictions. The file-count
|
|
57
|
+
* limit is checked against the files still waiting to upload, not every file
|
|
58
|
+
* in the queue: each upload batch gets a token of its own, so files that
|
|
59
|
+
* already finished uploading do not count against the next batch. Uppy's own
|
|
60
|
+
* `maxNumberOfFiles` counts finished files too, so it is not used.
|
|
61
|
+
*
|
|
62
|
+
* The limit is added on top of the instance's current `onBeforeFileAdded`
|
|
63
|
+
* callback, which still runs first: returning `false` from it rejects the
|
|
64
|
+
* file, and returning a file object adds that file instead. Calling this
|
|
65
|
+
* again with new constraints replaces the limit without wrapping the same
|
|
66
|
+
* callback twice.
|
|
67
|
+
*/
|
|
68
|
+
export declare function applyUploadConstraints(uppy: CedarUppy, constraints: UploadConstraints): void;
|
|
52
69
|
/**
|
|
53
70
|
* Creates an Uppy instance pre-configured for one of Cedar's upload flows.
|
|
54
71
|
* Direct-to-S3 uploads use `@uppy/aws-s3` with the presigned URLs the api
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createUppy.d.ts","sourceRoot":"","sources":["../../src/web/createUppy.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,YAAY,CAAA;AAC7B,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAIhD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAErD,wEAAwE;AACxE,MAAM,MAAM,SAAS,GAAG,IAAI,CAAA;AAE5B,4CAA4C;AAC5C,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAA;AAEjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAErD,kEAAkE;AAClE,eAAO,MAAM,sBAAsB,eAAe,CAAA;AAElD,MAAM,WAAW,eAAe;IAC9B,uEAAuE;IACvE,WAAW,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAA;IACtC,sEAAsE;IACtE,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AAED,MAAM,WAAW,aAAc,SAAQ,eAAe;IACpD,QAAQ,EAAE,IAAI,CAAA;IACd;;;OAGG;IACH,mBAAmB,EAAE,CACnB,IAAI,EAAE,aAAa,KAChB,OAAO,CAAC,yBAAyB,CAAC,CAAA;CACxC;AAED,MAAM,WAAW,aAAc,SAAQ,eAAe;IACpD,QAAQ,EAAE,IAAI,CAAA;IACd,kFAAkF;IAClF,QAAQ,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,CAAA;IACjC
|
|
1
|
+
{"version":3,"file":"createUppy.d.ts","sourceRoot":"","sources":["../../src/web/createUppy.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,YAAY,CAAA;AAC7B,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAIhD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAErD,wEAAwE;AACxE,MAAM,MAAM,SAAS,GAAG,IAAI,CAAA;AAE5B,4CAA4C;AAC5C,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAA;AAEjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAErD,kEAAkE;AAClE,eAAO,MAAM,sBAAsB,eAAe,CAAA;AAElD,MAAM,WAAW,eAAe;IAC9B,uEAAuE;IACvE,WAAW,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAA;IACtC,sEAAsE;IACtE,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AAED,MAAM,WAAW,aAAc,SAAQ,eAAe;IACpD,QAAQ,EAAE,IAAI,CAAA;IACd;;;OAGG;IACH,mBAAmB,EAAE,CACnB,IAAI,EAAE,aAAa,KAChB,OAAO,CAAC,yBAAyB,CAAC,CAAA;CACxC;AAED,MAAM,WAAW,aAAc,SAAQ,eAAe;IACpD,QAAQ,EAAE,IAAI,CAAA;IACd,kFAAkF;IAClF,QAAQ,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,CAAA;IACjC;;;OAGG;IACH,cAAc,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,KAAK,MAAM,GAAG,IAAI,CAAA;CACnE;AAED,MAAM,MAAM,iBAAiB,GAAG,aAAa,GAAG,aAAa,CAAA;AAE7D,2DAA2D;AAC3D,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAC5D;AAUD;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,SAAS,EACf,WAAW,EAAE,iBAAiB,QAwC/B;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,SAAS,CAAC,CA2EpB;AAED,wEAAwE;AACxE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,CAInE"}
|
package/dist/web/createUppy.js
CHANGED
|
@@ -2,23 +2,44 @@ import Uppy from "@uppy/core";
|
|
|
2
2
|
import { UPLOAD_TOKEN_HEADER } from "../constants.js";
|
|
3
3
|
import { UPLOAD_TOKEN_HEADER as UPLOAD_TOKEN_HEADER2 } from "../constants.js";
|
|
4
4
|
const DEFAULT_FS_UPLOAD_PATH = "/upload/fs";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
const wrappedCallbacks = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
function applyUploadConstraints(uppy, constraints) {
|
|
7
|
+
const current = uppy.opts.onBeforeFileAdded;
|
|
8
|
+
const inner = current ? wrappedCallbacks.get(current) ?? current : void 0;
|
|
9
|
+
const onBeforeFileAdded = (file, files) => {
|
|
10
|
+
const result = inner ? inner(file, files) : !Object.hasOwn(files, file.id);
|
|
11
|
+
if (result === false) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
const pending = Object.values(files).filter(
|
|
15
|
+
(queued) => !queued.isGhost && !queued.progress.uploadComplete
|
|
16
|
+
);
|
|
17
|
+
if (pending.length >= constraints.maxFiles) {
|
|
18
|
+
uppy.info(
|
|
19
|
+
uppy.i18n("youCanOnlyUploadX", { smart_count: constraints.maxFiles }),
|
|
20
|
+
"error"
|
|
21
|
+
);
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
13
25
|
};
|
|
26
|
+
if (inner) {
|
|
27
|
+
wrappedCallbacks.set(onBeforeFileAdded, inner);
|
|
28
|
+
}
|
|
29
|
+
uppy.setOptions({
|
|
30
|
+
restrictions: {
|
|
31
|
+
allowedFileTypes: constraints.allowedMimeTypes,
|
|
32
|
+
maxFileSize: constraints.maxFileSize
|
|
33
|
+
},
|
|
34
|
+
onBeforeFileAdded
|
|
35
|
+
});
|
|
14
36
|
}
|
|
15
37
|
async function createUppy(options) {
|
|
16
38
|
const { constraints, autoProceed = true, debug = false } = options;
|
|
17
|
-
const uppy = new Uppy({
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
});
|
|
39
|
+
const uppy = new Uppy({ autoProceed, debug });
|
|
40
|
+
if (constraints) {
|
|
41
|
+
applyUploadConstraints(uppy, constraints);
|
|
42
|
+
}
|
|
22
43
|
if (options.provider === "s3") {
|
|
23
44
|
const { default: AwsS3 } = await import("@uppy/aws-s3");
|
|
24
45
|
uppy.use(AwsS3, {
|
|
@@ -52,9 +73,9 @@ async function createUppy(options) {
|
|
|
52
73
|
formData: true,
|
|
53
74
|
fieldName: "file",
|
|
54
75
|
bundle: false,
|
|
55
|
-
headers: () => {
|
|
76
|
+
headers: (file) => {
|
|
56
77
|
const headers = {};
|
|
57
|
-
const token = options.getUploadToken();
|
|
78
|
+
const token = options.getUploadToken(file);
|
|
58
79
|
if (token) {
|
|
59
80
|
headers[UPLOAD_TOKEN_HEADER] = token;
|
|
60
81
|
}
|
|
@@ -77,6 +98,7 @@ function getCedarUploadId(file) {
|
|
|
77
98
|
export {
|
|
78
99
|
DEFAULT_FS_UPLOAD_PATH,
|
|
79
100
|
UPLOAD_TOKEN_HEADER2 as UPLOAD_TOKEN_HEADER,
|
|
101
|
+
applyUploadConstraints,
|
|
80
102
|
createUppy,
|
|
81
103
|
getCedarUploadId
|
|
82
104
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFsUpload.d.ts","sourceRoot":"","sources":["../../../src/web/hooks/useFsUpload.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAE/E,MAAM,WAAW,kBAAmB,SAAQ,mBAAmB;IAC7D,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,MAAM,iBAAiB,GAAG,gBAAgB,GAAG;IACjD,YAAY,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;CACpC,CAAA;AAWD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,QAAQ,EACR,gBAAgB,EAChB,aAAa,GACd,EAAE,kBAAkB,GAAG,iBAAiB,
|
|
1
|
+
{"version":3,"file":"useFsUpload.d.ts","sourceRoot":"","sources":["../../../src/web/hooks/useFsUpload.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAE/E,MAAM,WAAW,kBAAmB,SAAQ,mBAAmB;IAC7D,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,MAAM,iBAAiB,GAAG,gBAAgB,GAAG;IACjD,YAAY,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;CACpC,CAAA;AAWD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,QAAQ,EACR,gBAAgB,EAChB,aAAa,GACd,EAAE,kBAAkB,GAAG,iBAAiB,CA6ExC"}
|
|
@@ -12,7 +12,8 @@ function useFsUpload({
|
|
|
12
12
|
onUploadComplete,
|
|
13
13
|
onUploadError
|
|
14
14
|
}) {
|
|
15
|
-
const { requestToken,
|
|
15
|
+
const { requestToken, acquireToken, hasUsableToken, constraints } = useUploadToken({ profile });
|
|
16
|
+
const fileTokens = useRef(/* @__PURE__ */ new Map());
|
|
16
17
|
const endpointRef = useRef(endpoint ?? defaultEndpoint());
|
|
17
18
|
useEffect(() => {
|
|
18
19
|
endpointRef.current = endpoint ?? defaultEndpoint();
|
|
@@ -22,7 +23,7 @@ function useFsUpload({
|
|
|
22
23
|
provider: "fs",
|
|
23
24
|
constraints,
|
|
24
25
|
endpoint: () => endpointRef.current,
|
|
25
|
-
getUploadToken:
|
|
26
|
+
getUploadToken: (file) => fileTokens.current.get(file.id) ?? null
|
|
26
27
|
}),
|
|
27
28
|
constraints,
|
|
28
29
|
{ onUploadComplete, onUploadError }
|
|
@@ -33,24 +34,35 @@ function useFsUpload({
|
|
|
33
34
|
return;
|
|
34
35
|
}
|
|
35
36
|
const onFileAdded = () => {
|
|
36
|
-
if (!
|
|
37
|
+
if (!hasUsableToken()) {
|
|
37
38
|
requestToken().catch((e) => {
|
|
38
39
|
onUploadError?.(e instanceof Error ? e : new Error(String(e)));
|
|
39
40
|
});
|
|
40
41
|
}
|
|
41
42
|
};
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
|
|
43
|
+
const assignTokens = async (fileIDs) => {
|
|
44
|
+
if (fileIDs.length === 0) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const token = await acquireToken(fileIDs.length);
|
|
48
|
+
for (const fileID of fileIDs) {
|
|
49
|
+
fileTokens.current.set(fileID, token);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const onFileRemoved = (file) => {
|
|
53
|
+
if (file) {
|
|
54
|
+
fileTokens.current.delete(file.id);
|
|
45
55
|
}
|
|
46
56
|
};
|
|
47
57
|
uppy.on("file-added", onFileAdded);
|
|
48
|
-
uppy.
|
|
58
|
+
uppy.on("file-removed", onFileRemoved);
|
|
59
|
+
uppy.addPreProcessor(assignTokens);
|
|
49
60
|
return () => {
|
|
50
61
|
uppy.off("file-added", onFileAdded);
|
|
51
|
-
uppy.
|
|
62
|
+
uppy.off("file-removed", onFileRemoved);
|
|
63
|
+
uppy.removePreProcessor(assignTokens);
|
|
52
64
|
};
|
|
53
|
-
}, [uppy,
|
|
65
|
+
}, [uppy, acquireToken, hasUsableToken, requestToken, onUploadError]);
|
|
54
66
|
return { ...upload, requestToken };
|
|
55
67
|
}
|
|
56
68
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useS3Upload.d.ts","sourceRoot":"","sources":["../../../src/web/hooks/useS3Upload.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAE/E,MAAM,WAAW,kBAAmB,SAAQ,mBAAmB;IAC7D,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,MAAM,iBAAiB,GAAG,gBAAgB,GAAG;IACjD,yEAAyE;IACzE,YAAY,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;CACpC,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,OAAc,EACd,gBAAgB,EAChB,aAAa,GACd,EAAE,kBAAkB,GAAG,iBAAiB,
|
|
1
|
+
{"version":3,"file":"useS3Upload.d.ts","sourceRoot":"","sources":["../../../src/web/hooks/useS3Upload.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAE/E,MAAM,WAAW,kBAAmB,SAAQ,mBAAmB;IAC7D,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,MAAM,iBAAiB,GAAG,gBAAgB,GAAG;IACjD,yEAAyE;IACzE,YAAY,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;CACpC,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,OAAc,EACd,gBAAgB,EAChB,aAAa,GACd,EAAE,kBAAkB,GAAG,iBAAiB,CAiIxC"}
|
|
@@ -10,12 +10,12 @@ function useS3Upload({
|
|
|
10
10
|
onUploadComplete,
|
|
11
11
|
onUploadError
|
|
12
12
|
}) {
|
|
13
|
-
const { requestToken,
|
|
13
|
+
const { requestToken, acquireToken, hasUsableToken, constraints } = useUploadToken({ profile });
|
|
14
14
|
const [createPresignedUploadUrl] = useMutation(CREATE_PRESIGNED_UPLOAD_URL);
|
|
15
15
|
const [confirmUpload] = useMutation(CONFIRM_UPLOAD);
|
|
16
16
|
const getUploadParameters = useCallback(
|
|
17
17
|
async (file) => {
|
|
18
|
-
const uploadToken =
|
|
18
|
+
const uploadToken = await acquireToken();
|
|
19
19
|
const result = await createPresignedUploadUrl({
|
|
20
20
|
variables: {
|
|
21
21
|
input: {
|
|
@@ -32,7 +32,7 @@ function useS3Upload({
|
|
|
32
32
|
}
|
|
33
33
|
return data;
|
|
34
34
|
},
|
|
35
|
-
[createPresignedUploadUrl,
|
|
35
|
+
[createPresignedUploadUrl, acquireToken]
|
|
36
36
|
);
|
|
37
37
|
const getUploadParametersRef = useRef(getUploadParameters);
|
|
38
38
|
useEffect(() => {
|
|
@@ -53,7 +53,7 @@ function useS3Upload({
|
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
55
|
const onFileAdded = () => {
|
|
56
|
-
if (!
|
|
56
|
+
if (!hasUsableToken()) {
|
|
57
57
|
requestToken().catch((e) => {
|
|
58
58
|
onUploadError?.(e instanceof Error ? e : new Error(String(e)));
|
|
59
59
|
});
|
|
@@ -91,7 +91,14 @@ function useS3Upload({
|
|
|
91
91
|
uppy.off("file-added", onFileAdded);
|
|
92
92
|
uppy.removePostProcessor(confirmAll);
|
|
93
93
|
};
|
|
94
|
-
}, [
|
|
94
|
+
}, [
|
|
95
|
+
uppy,
|
|
96
|
+
confirm,
|
|
97
|
+
confirmUpload,
|
|
98
|
+
hasUsableToken,
|
|
99
|
+
requestToken,
|
|
100
|
+
onUploadError
|
|
101
|
+
]);
|
|
95
102
|
return { ...upload, requestToken };
|
|
96
103
|
}
|
|
97
104
|
export {
|
|
@@ -10,6 +10,19 @@ export interface UseUploadTokenResult {
|
|
|
10
10
|
* in-flight request.
|
|
11
11
|
*/
|
|
12
12
|
requestToken: () => Promise<string>;
|
|
13
|
+
/**
|
|
14
|
+
* Resolves with a token that can still create `fileCount` files (default
|
|
15
|
+
* 1) and counts them against it. Reuses the current token while it has
|
|
16
|
+
* files left and is not about to expire, and fetches a fresh one
|
|
17
|
+
* otherwise. Call it once per file (or once per batch, with the batch
|
|
18
|
+
* size) right before the token is sent.
|
|
19
|
+
*/
|
|
20
|
+
acquireToken: (fileCount?: number) => Promise<string>;
|
|
21
|
+
/**
|
|
22
|
+
* Whether the current token is unexpired and has at least one file left,
|
|
23
|
+
* so the next `acquireToken()` call can resolve without a request.
|
|
24
|
+
*/
|
|
25
|
+
hasUsableToken: () => boolean;
|
|
13
26
|
/**
|
|
14
27
|
* Returns the most recently fetched token, or `null`, without triggering
|
|
15
28
|
* a render. Meant for event handlers and Uppy callbacks.
|
|
@@ -25,10 +38,23 @@ export interface UseUploadTokenResult {
|
|
|
25
38
|
loading: boolean;
|
|
26
39
|
error: ErrorLike | undefined;
|
|
27
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Reads the lifetime (`exp - iat`, in milliseconds) from a signed token's
|
|
43
|
+
* claims without verifying it. The lifetime is anchored to the client's
|
|
44
|
+
* clock when the token is requested, so clock skew between browser and server
|
|
45
|
+
* does not matter. Returns `null` for a token whose claims cannot be read.
|
|
46
|
+
*/
|
|
47
|
+
export declare function readTokenLifetimeMs(token: string): number | null;
|
|
28
48
|
/**
|
|
29
49
|
* Fetches upload tokens for a profile from the `requestUploadToken` query.
|
|
30
50
|
* Tokens expire, so the query runs with `fetchPolicy: 'no-cache'` and only
|
|
31
|
-
* when
|
|
51
|
+
* when a token is needed, never on mount.
|
|
52
|
+
*
|
|
53
|
+
* A token expires and can only create `maxFiles` files, so the hook tracks
|
|
54
|
+
* both for the current token. `acquireToken()` hands the token out only
|
|
55
|
+
* while it has files left and is not about to expire, and fetches a fresh
|
|
56
|
+
* one otherwise, which lets one mounted component upload any number of
|
|
57
|
+
* times.
|
|
32
58
|
*/
|
|
33
59
|
export declare function useUploadToken({ profile, }: UseUploadTokenOptions): UseUploadTokenResult;
|
|
34
60
|
//# sourceMappingURL=useUploadToken.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useUploadToken.d.ts","sourceRoot":"","sources":["../../../src/web/hooks/useUploadToken.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAI/C,OAAO,KAAK,EAGV,iBAAiB,EAClB,MAAM,eAAe,CAAA;AAEtB,MAAM,WAAW,qBAAqB;IACpC,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,YAAY,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;IACnC;;;OAGG;IACH,QAAQ,EAAE,MAAM,MAAM,GAAG,IAAI,CAAA;IAC7B,kDAAkD;IAClD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB;;;OAGG;IACH,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAAA;IACrC,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,SAAS,GAAG,SAAS,CAAA;CAC7B;
|
|
1
|
+
{"version":3,"file":"useUploadToken.d.ts","sourceRoot":"","sources":["../../../src/web/hooks/useUploadToken.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAI/C,OAAO,KAAK,EAGV,iBAAiB,EAClB,MAAM,eAAe,CAAA;AAEtB,MAAM,WAAW,qBAAqB;IACpC,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,YAAY,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;IACnC;;;;;;OAMG;IACH,YAAY,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IACrD;;;OAGG;IACH,cAAc,EAAE,MAAM,OAAO,CAAA;IAC7B;;;OAGG;IACH,QAAQ,EAAE,MAAM,MAAM,GAAG,IAAI,CAAA;IAC7B,kDAAkD;IAClD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB;;;OAGG;IACH,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAAA;IACrC,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,SAAS,GAAG,SAAS,CAAA;CAC7B;AA+BD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA0BhE;AA2BD;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,EAC7B,OAAO,GACR,EAAE,qBAAqB,GAAG,oBAAoB,CAoH9C"}
|
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
2
|
import { useLazyQuery } from "@apollo/client/react";
|
|
3
3
|
import { REQUEST_UPLOAD_TOKEN } from "../graphql.js";
|
|
4
|
+
const EXPIRY_MARGIN_MS = 3e4;
|
|
5
|
+
function decodeBase64Url(value) {
|
|
6
|
+
const base64 = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
7
|
+
const padded = base64.padEnd(
|
|
8
|
+
base64.length + (4 - base64.length % 4) % 4,
|
|
9
|
+
"="
|
|
10
|
+
);
|
|
11
|
+
return atob(padded);
|
|
12
|
+
}
|
|
13
|
+
function readTokenLifetimeMs(token) {
|
|
14
|
+
const [, claims] = token.split(".");
|
|
15
|
+
if (!claims) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const parsed = JSON.parse(decodeBase64Url(claims));
|
|
20
|
+
if (typeof parsed === "object" && parsed !== null && "exp" in parsed && "iat" in parsed && typeof parsed.exp === "number" && typeof parsed.iat === "number" && parsed.exp > parsed.iat) {
|
|
21
|
+
return (parsed.exp - parsed.iat) * 1e3;
|
|
22
|
+
}
|
|
23
|
+
} catch {
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
function expiresAtFor(token, requestedAt) {
|
|
28
|
+
const lifetime = readTokenLifetimeMs(token);
|
|
29
|
+
if (lifetime === null) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return requestedAt + lifetime - Math.min(EXPIRY_MARGIN_MS, lifetime / 2);
|
|
33
|
+
}
|
|
34
|
+
function canCreate(state, fileCount) {
|
|
35
|
+
if (!state) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (state.expiresAt !== null && Date.now() >= state.expiresAt) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return state.used === 0 || state.used + fileCount <= state.maxFiles;
|
|
42
|
+
}
|
|
4
43
|
function useUploadToken({
|
|
5
44
|
profile
|
|
6
45
|
}) {
|
|
@@ -23,6 +62,7 @@ function useUploadToken({
|
|
|
23
62
|
}
|
|
24
63
|
const requestedProfile = profile;
|
|
25
64
|
const request = (async () => {
|
|
65
|
+
const requestedAt = Date.now();
|
|
26
66
|
const result = await execute({ variables: { profile: requestedProfile } });
|
|
27
67
|
if (requestedProfile !== profileRef.current) {
|
|
28
68
|
throw new Error(
|
|
@@ -36,7 +76,12 @@ function useUploadToken({
|
|
|
36
76
|
if (!data) {
|
|
37
77
|
throw new Error("The upload token query returned no data.");
|
|
38
78
|
}
|
|
39
|
-
tokenRef.current =
|
|
79
|
+
tokenRef.current = {
|
|
80
|
+
token: data.token,
|
|
81
|
+
maxFiles: data.maxFiles,
|
|
82
|
+
used: 0,
|
|
83
|
+
expiresAt: expiresAtFor(data.token, requestedAt)
|
|
84
|
+
};
|
|
40
85
|
setToken(data.token);
|
|
41
86
|
setConstraints({
|
|
42
87
|
allowedMimeTypes: data.allowedMimeTypes,
|
|
@@ -52,9 +97,33 @@ function useUploadToken({
|
|
|
52
97
|
inFlight.current = request;
|
|
53
98
|
return request;
|
|
54
99
|
}, [execute, profile]);
|
|
55
|
-
const
|
|
56
|
-
|
|
100
|
+
const acquireToken = useCallback(
|
|
101
|
+
async (fileCount = 1) => {
|
|
102
|
+
for (; ; ) {
|
|
103
|
+
const current = tokenRef.current;
|
|
104
|
+
if (current && canCreate(current, fileCount)) {
|
|
105
|
+
current.used += fileCount;
|
|
106
|
+
return current.token;
|
|
107
|
+
}
|
|
108
|
+
await requestToken();
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
[requestToken]
|
|
112
|
+
);
|
|
113
|
+
const hasUsableToken = useCallback(() => canCreate(tokenRef.current, 1), []);
|
|
114
|
+
const getToken = useCallback(() => tokenRef.current?.token ?? null, []);
|
|
115
|
+
return {
|
|
116
|
+
requestToken,
|
|
117
|
+
acquireToken,
|
|
118
|
+
hasUsableToken,
|
|
119
|
+
getToken,
|
|
120
|
+
token,
|
|
121
|
+
constraints,
|
|
122
|
+
loading,
|
|
123
|
+
error
|
|
124
|
+
};
|
|
57
125
|
}
|
|
58
126
|
export {
|
|
127
|
+
readTokenLifetimeMs,
|
|
59
128
|
useUploadToken
|
|
60
129
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useUppyUpload.d.ts","sourceRoot":"","sources":["../../../src/web/hooks/useUppyUpload.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAiB,MAAM,kBAAkB,CAAA;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAEtD,MAAM,WAAW,mBAAmB;IAClC,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;IAChD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;CACvC;AAED,MAAM,WAAW,gBAAgB;IAC/B,8DAA8D;IAC9D,IAAI,EAAE,SAAS,GAAG,IAAI,CAAA;IACtB,gEAAgE;IAChE,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,WAAW,EAAE,OAAO,CAAA;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC,EAChC,WAAW,EAAE,iBAAiB,GAAG,IAAI,EACrC,EAAE,gBAAgB,EAAE,aAAa,EAAE,EAAE,mBAAmB,GACvD,gBAAgB,
|
|
1
|
+
{"version":3,"file":"useUppyUpload.d.ts","sourceRoot":"","sources":["../../../src/web/hooks/useUppyUpload.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAiB,MAAM,kBAAkB,CAAA;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAEtD,MAAM,WAAW,mBAAmB;IAClC,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;IAChD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;CACvC;AAED,MAAM,WAAW,gBAAgB;IAC/B,8DAA8D;IAC9D,IAAI,EAAE,SAAS,GAAG,IAAI,CAAA;IACtB,gEAAgE;IAChE,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,WAAW,EAAE,OAAO,CAAA;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC,EAChC,WAAW,EAAE,iBAAiB,GAAG,IAAI,EACrC,EAAE,gBAAgB,EAAE,aAAa,EAAE,EAAE,mBAAmB,GACvD,gBAAgB,CA8FlB"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from "react";
|
|
2
|
-
import { getCedarUploadId } from "../createUppy.js";
|
|
2
|
+
import { applyUploadConstraints, getCedarUploadId } from "../createUppy.js";
|
|
3
3
|
function useUppyUpload(create, constraints, { onUploadComplete, onUploadError }) {
|
|
4
4
|
const [uppy, setUppy] = useState(null);
|
|
5
5
|
const [completedUploads, setCompletedUploads] = useState([]);
|
|
@@ -33,13 +33,7 @@ function useUppyUpload(create, constraints, { onUploadComplete, onUploadError })
|
|
|
33
33
|
if (!uppy || !constraints) {
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
|
-
uppy
|
|
37
|
-
restrictions: {
|
|
38
|
-
allowedFileTypes: constraints.allowedMimeTypes,
|
|
39
|
-
maxFileSize: constraints.maxFileSize,
|
|
40
|
-
maxNumberOfFiles: constraints.maxFiles
|
|
41
|
-
}
|
|
42
|
-
});
|
|
36
|
+
applyUploadConstraints(uppy, constraints);
|
|
43
37
|
}, [uppy, constraints]);
|
|
44
38
|
useEffect(() => {
|
|
45
39
|
if (!uppy) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cedarjs/uploads",
|
|
3
|
-
"version": "8.0.0-canary.
|
|
3
|
+
"version": "8.0.0-canary.3285",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/cedarjs/cedar.git",
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"test:watch": "vitest watch"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@cedarjs/api": "8.0.0-canary.
|
|
47
|
-
"@cedarjs/context": "8.0.0-canary.
|
|
48
|
-
"@cedarjs/graphql-server": "8.0.0-canary.
|
|
46
|
+
"@cedarjs/api": "8.0.0-canary.3285",
|
|
47
|
+
"@cedarjs/context": "8.0.0-canary.3285",
|
|
48
|
+
"@cedarjs/graphql-server": "8.0.0-canary.3285",
|
|
49
49
|
"@fastify/multipart": "9.4.0",
|
|
50
50
|
"graphql": "16.14.2",
|
|
51
51
|
"mime-types": "2.1.35"
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"@arethetypeswrong/cli": "0.18.5",
|
|
56
56
|
"@aws-sdk/client-s3": "3.1139.0",
|
|
57
57
|
"@aws-sdk/s3-request-presigner": "3.1139.0",
|
|
58
|
-
"@cedarjs/framework-tools": "8.0.0-canary.
|
|
59
|
-
"@cedarjs/web": "8.0.0-canary.
|
|
58
|
+
"@cedarjs/framework-tools": "8.0.0-canary.3285",
|
|
59
|
+
"@cedarjs/web": "8.0.0-canary.3285",
|
|
60
60
|
"@prisma/adapter-better-sqlite3": "7.10.0",
|
|
61
61
|
"@prisma/client": "7.10.0",
|
|
62
62
|
"@testing-library/dom": "10.4.2",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"@apollo/client": "4.3.1",
|
|
91
91
|
"@aws-sdk/client-s3": "^3.0.0",
|
|
92
92
|
"@aws-sdk/s3-request-presigner": "^3.0.0",
|
|
93
|
-
"@cedarjs/web": "8.0.0-canary.
|
|
93
|
+
"@cedarjs/web": "8.0.0-canary.3285",
|
|
94
94
|
"@uppy/aws-s3": "^6.0.0",
|
|
95
95
|
"@uppy/core": "^6.0.0",
|
|
96
96
|
"@uppy/dashboard": "^6.0.0",
|