uploadthing
Version:
Learn more: [docs.uploadthing.com](https://docs.uploadthing.com)
331 lines (328 loc) • 12.7 kB
JavaScript
const require_chunk = require('../dist/chunk-CUT6urMc.cjs');
const require_package = require('../dist/package-CKF1Vr61.cjs');
const require_ut_reporter = require('../dist/ut-reporter-Cpt8AGso.cjs');
const require_deprecations = require('../dist/deprecations-DPGpmqha.cjs');
const effect_Array = require_chunk.__toESM(require("effect/Array"));
const effect_Micro = require_chunk.__toESM(require("effect/Micro"));
const __uploadthing_shared = require_chunk.__toESM(require("@uploadthing/shared"));
const effect_Function = require_chunk.__toESM(require("effect/Function"));
const effect_Predicate = require_chunk.__toESM(require("effect/Predicate"));
//#region src/_internal/upload-browser.ts
const uploadWithProgress = (file, rangeStart, presigned, opts) => effect_Micro.async((resume) => {
const xhr = new XMLHttpRequest();
xhr.open("PUT", presigned.url, true);
xhr.setRequestHeader("Range", `bytes=${rangeStart}-`);
xhr.setRequestHeader("x-uploadthing-version", require_package.version);
xhr.setRequestHeader("b3", opts.traceHeaders.b3);
xhr.setRequestHeader("traceparent", opts.traceHeaders.traceparent);
xhr.responseType = "json";
let previousLoaded = 0;
xhr.upload.addEventListener("progress", ({ loaded }) => {
const delta = loaded - previousLoaded;
opts.onUploadProgress?.({
loaded,
delta
});
previousLoaded = loaded;
});
xhr.addEventListener("load", () => {
if (xhr.status >= 200 && xhr.status < 300 && (0, effect_Predicate.isRecord)(xhr.response)) if ((0, effect_Predicate.hasProperty)(xhr.response, "error")) resume(new __uploadthing_shared.UploadThingError({
code: "UPLOAD_FAILED",
message: String(xhr.response.error),
data: xhr.response
}));
else resume(effect_Micro.succeed(xhr.response));
else resume(new __uploadthing_shared.UploadThingError({
code: "UPLOAD_FAILED",
message: `XHR failed ${xhr.status} ${xhr.statusText}`,
data: xhr.response
}));
});
xhr.addEventListener("error", () => {
resume(new __uploadthing_shared.UploadThingError({ code: "UPLOAD_FAILED" }));
});
const formData = new FormData();
/**
* iOS/React Native FormData handling requires special attention:
*
* Issue: In React Native, iOS crashes with "attempt to insert nil object" when appending File directly
* to FormData. This happens because iOS tries to create NSDictionary from the file object and expects
* specific structure {uri, type, name}.
*
*
* Note: Don't try to use Blob or modify File object - iOS specifically needs plain object
* with these properties to create valid NSDictionary.
*/
if ("uri" in file) formData.append("file", {
uri: file.uri,
type: file.type,
name: file.name,
...rangeStart > 0 && { range: rangeStart }
});
else formData.append("file", rangeStart > 0 ? file.slice(rangeStart) : file);
xhr.send(formData);
return effect_Micro.sync(() => xhr.abort());
});
const uploadFile = (file, presigned, opts) => (0, __uploadthing_shared.fetchEff)(presigned.url, {
method: "HEAD",
headers: opts.traceHeaders
}).pipe(effect_Micro.map(({ headers }) => parseInt(headers.get("x-ut-range-start") ?? "0", 10)), effect_Micro.tap((start) => opts.onUploadProgress?.({
delta: start,
loaded: start
})), effect_Micro.flatMap((start) => uploadWithProgress(file, start, presigned, {
traceHeaders: opts.traceHeaders,
onUploadProgress: (progressEvent) => opts.onUploadProgress?.({
delta: progressEvent.delta,
loaded: progressEvent.loaded + start
})
})), effect_Micro.map(effect_Function.unsafeCoerce), effect_Micro.map((uploadResponse) => ({
name: file.name,
size: file.size,
key: presigned.key,
lastModified: file.lastModified,
serverData: uploadResponse.serverData,
get url() {
require_deprecations.logDeprecationWarning("`file.url` is deprecated and will be removed in uploadthing v9. Use `file.ufsUrl` instead.");
return uploadResponse.url;
},
get appUrl() {
require_deprecations.logDeprecationWarning("`file.appUrl` is deprecated and will be removed in uploadthing v9. Use `file.ufsUrl` instead.");
return uploadResponse.appUrl;
},
ufsUrl: uploadResponse.ufsUrl,
customId: presigned.customId,
type: file.type,
fileHash: uploadResponse.fileHash
})));
const uploadFilesInternal = (endpoint, opts) => {
const traceHeaders = require_ut_reporter.generateTraceHeaders();
const reportEventToUT = require_ut_reporter.createUTReporter({
endpoint: String(endpoint),
package: opts.package,
url: opts.url,
headers: opts.headers,
traceHeaders
});
const totalSize = opts.files.reduce((acc, f) => acc + f.size, 0);
let totalLoaded = 0;
return effect_Micro.flatMap(reportEventToUT("upload", {
input: "input" in opts ? opts.input : null,
files: opts.files.map((f) => ({
name: f.name,
size: f.size,
type: f.type,
lastModified: f.lastModified
}))
}), (presigneds) => effect_Micro.forEach(presigneds, (presigned, i) => effect_Micro.flatMap(effect_Micro.sync(() => opts.onUploadBegin?.({ file: opts.files[i].name })), () => uploadFile(opts.files[i], presigned, {
traceHeaders,
onUploadProgress: (ev) => {
totalLoaded += ev.delta;
opts.onUploadProgress?.({
file: opts.files[i],
progress: ev.loaded / opts.files[i].size * 100,
loaded: ev.loaded,
delta: ev.delta,
totalLoaded,
totalProgress: totalLoaded / totalSize
});
}
})), { concurrency: 6 }));
};
//#endregion
//#region src/client.ts
const version$1 = require_package.version;
/**
* Validate that a file is of a valid type given a route config
* @public
*/
const isValidFileType = (file, routeConfig) => effect_Micro.runSync((0, __uploadthing_shared.matchFileType)(file, (0, __uploadthing_shared.objectKeys)(routeConfig)).pipe(effect_Micro.map((type) => file.type.includes(type)), effect_Micro.orElseSucceed(() => false)));
/**
* Validate that a file is of a valid size given a route config
* @public
*/
const isValidFileSize = (file, routeConfig) => effect_Micro.runSync((0, __uploadthing_shared.matchFileType)(file, (0, __uploadthing_shared.objectKeys)(routeConfig)).pipe(effect_Micro.flatMap((type) => (0, __uploadthing_shared.fileSizeToBytes)(routeConfig[type].maxFileSize)), effect_Micro.map((maxFileSize) => file.size <= maxFileSize), effect_Micro.orElseSucceed(() => false)));
/**
* Generate a typed uploader for a given FileRouter
* @public
*/
const genUploader = (initOpts) => {
const routeRegistry = (0, __uploadthing_shared.createIdentityProxy)();
const controllableUpload = async (slug, opts) => {
const uploads = new Map();
const endpoint = typeof slug === "function" ? slug(routeRegistry) : slug;
const traceHeaders = require_ut_reporter.generateTraceHeaders();
const utReporter = require_ut_reporter.createUTReporter({
endpoint: String(endpoint),
package: initOpts?.package ?? "uploadthing/client",
url: (0, __uploadthing_shared.resolveMaybeUrlArg)(initOpts?.url),
headers: opts.headers,
traceHeaders
});
const fetchFn = initOpts?.fetch ?? window.fetch;
const presigneds = await effect_Micro.runPromise(utReporter("upload", {
input: "input" in opts ? opts.input : null,
files: opts.files.map((f) => ({
name: f.name,
size: f.size,
type: f.type,
lastModified: f.lastModified
}))
}).pipe(effect_Micro.provideService(__uploadthing_shared.FetchContext, fetchFn)));
const totalSize = opts.files.reduce((acc, f) => acc + f.size, 0);
let totalLoaded = 0;
const uploadEffect = (file, presigned) => uploadFile(file, presigned, {
traceHeaders,
onUploadProgress: (progressEvent) => {
totalLoaded += progressEvent.delta;
opts.onUploadProgress?.({
...progressEvent,
file,
progress: Math.round(progressEvent.loaded / file.size * 100),
totalLoaded,
totalProgress: Math.round(totalLoaded / totalSize * 100)
});
}
}).pipe(effect_Micro.provideService(__uploadthing_shared.FetchContext, fetchFn));
for (const [i, p] of presigneds.entries()) {
const file = opts.files[i];
if (!file) continue;
const deferred = require_ut_reporter.createDeferred();
uploads.set(file, {
deferred,
presigned: p
});
effect_Micro.runPromiseExit(uploadEffect(file, p), { signal: deferred.ac.signal }).then((result) => {
if (result._tag === "Success") return deferred.resolve(result.value);
else if (result.cause._tag === "Interrupt") throw new __uploadthing_shared.UploadPausedError();
throw effect_Micro.causeSquash(result.cause);
}).catch((err) => {
if (err instanceof __uploadthing_shared.UploadPausedError) return;
deferred.reject(err);
});
}
/**
* Pause an ongoing upload
* @param file The file upload you want to pause. Can be omitted to pause all files
*/
const pauseUpload = (file) => {
const files = effect_Array.ensure(file ?? opts.files);
for (const file$1 of files) {
const upload = uploads.get(file$1);
if (!upload) return;
if (upload.deferred.ac.signal.aborted) throw new __uploadthing_shared.UploadAbortedError();
upload.deferred.ac.abort();
}
};
/**
* Resume a paused upload
* @param file The file upload you want to resume. Can be omitted to resume all files
*/
const resumeUpload = (file) => {
const files = effect_Array.ensure(file ?? opts.files);
for (const file$1 of files) {
const upload = uploads.get(file$1);
if (!upload) throw "No upload found";
upload.deferred.ac = new AbortController();
effect_Micro.runPromiseExit(uploadEffect(file$1, upload.presigned), { signal: upload.deferred.ac.signal }).then((result) => {
if (result._tag === "Success") return upload.deferred.resolve(result.value);
else if (result.cause._tag === "Interrupt") throw new __uploadthing_shared.UploadPausedError();
throw effect_Micro.causeSquash(result.cause);
}).catch((err) => {
if (err instanceof __uploadthing_shared.UploadPausedError) return;
upload.deferred.reject(err);
});
}
};
/**
* Wait for an upload to complete
* @param file The file upload you want to wait for. Can be omitted to wait for all files
*/
const done = async (file) => {
const promises = [];
const files = effect_Array.ensure(file ?? opts.files);
for (const file$1 of files) {
const upload = uploads.get(file$1);
if (!upload) throw "No upload found";
promises.push(upload.deferred.promise);
}
const results = await Promise.all(promises);
return file ? results[0] : results;
};
return {
pauseUpload,
resumeUpload,
done
};
};
/**
* One step upload function that both requests presigned URLs
* and then uploads the files to UploadThing
*/
const typedUploadFiles = (slug, opts) => {
const endpoint = typeof slug === "function" ? slug(routeRegistry) : slug;
const fetchFn = initOpts?.fetch ?? window.fetch;
return uploadFilesInternal(endpoint, {
...opts,
skipPolling: {},
url: (0, __uploadthing_shared.resolveMaybeUrlArg)(initOpts?.url),
package: initOpts?.package ?? "uploadthing/client",
input: opts.input
}).pipe(effect_Micro.provideService(__uploadthing_shared.FetchContext, fetchFn), (effect) => effect_Micro.runPromiseExit(effect, opts.signal && { signal: opts.signal })).then((exit) => {
if (exit._tag === "Success") return exit.value;
else if (exit.cause._tag === "Interrupt") throw new __uploadthing_shared.UploadAbortedError();
throw effect_Micro.causeSquash(exit.cause);
});
};
return {
uploadFiles: typedUploadFiles,
createUpload: controllableUpload,
routeRegistry
};
};
//#endregion
Object.defineProperty(exports, 'UploadAbortedError', {
enumerable: true,
get: function () {
return __uploadthing_shared.UploadAbortedError;
}
});
Object.defineProperty(exports, 'UploadPausedError', {
enumerable: true,
get: function () {
return __uploadthing_shared.UploadPausedError;
}
});
Object.defineProperty(exports, 'allowedContentTextLabelGenerator', {
enumerable: true,
get: function () {
return __uploadthing_shared.allowedContentTextLabelGenerator;
}
});
Object.defineProperty(exports, 'bytesToFileSize', {
enumerable: true,
get: function () {
return __uploadthing_shared.bytesToFileSize;
}
});
exports.genUploader = genUploader;
Object.defineProperty(exports, 'generateClientDropzoneAccept', {
enumerable: true,
get: function () {
return __uploadthing_shared.generateClientDropzoneAccept;
}
});
Object.defineProperty(exports, 'generateMimeTypes', {
enumerable: true,
get: function () {
return __uploadthing_shared.generateMimeTypes;
}
});
Object.defineProperty(exports, 'generatePermittedFileTypes', {
enumerable: true,
get: function () {
return __uploadthing_shared.generatePermittedFileTypes;
}
});
exports.isValidFileSize = isValidFileSize;
exports.isValidFileType = isValidFileType;
exports.version = version$1;