modern-file-saver
Version:
Modern file saving library for browsers with File System Access API support and fallback
157 lines (152 loc) • 4.93 kB
JavaScript
// src/utils/blob.ts
function isBase64DataUrl(str) {
return str.startsWith("data:") && str.includes(";base64,");
}
async function base64ToBlob(base64, mimeType) {
const dataUrl = isBase64DataUrl(base64) ? base64 : `data:${mimeType || "application/octet-stream"};base64,${base64}`;
const response = await fetch(dataUrl);
const blob = await response.blob();
if (mimeType && blob.type !== mimeType) {
return new Blob([await blob.arrayBuffer()], { type: mimeType });
}
return blob;
}
async function convertToBlob(input, options = {}) {
if (isObjectType(input)) {
return new Blob([JSON.stringify(input, null, 2)], {
type: options.mimeType || "application/json"
});
}
if (typeof input === "string") {
if (options.isBase64) {
return base64ToBlob(input, options.mimeType);
}
if (isBase64DataUrl(input)) {
return base64ToBlob(input, options.mimeType);
}
return new Blob([input], {
type: options.mimeType || "text/plain"
});
}
if (input instanceof Blob) {
if (options.mimeType && options.mimeType !== input.type) {
return new Blob([await input.arrayBuffer()], { type: options.mimeType });
}
return input;
}
if (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) {
return new Blob([input], {
type: options.mimeType || "application/octet-stream"
});
}
if (input instanceof URLSearchParams) {
return new Blob([input.toString()], {
type: "application/x-www-form-urlencoded"
});
}
if (input instanceof FormData) {
const pairs = [];
input.forEach((value, key) => {
pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(value.toString())}`);
});
return new Blob([pairs.join("&")], {
type: "multipart/form-data"
});
}
throw new Error("Unsupported input type");
}
function isObjectType(input) {
return typeof input === "object" && !(input instanceof Blob) && !(input instanceof ArrayBuffer) && !ArrayBuffer.isView(input) && !(input instanceof URLSearchParams) && !(input instanceof FormData);
}
// src/utils/logger.ts
var ConsoleLogger = class {
constructor(level = "none") {
this.level = level;
}
debug(message, ...args) {
if (this.level === "debug") {
console.log(`[modern-file-saver] ${message}`, ...args);
}
}
};
function createLogger(level = "none") {
return new ConsoleLogger(level);
}
// src/utils/file-picker.ts
function getFilePickerOptions(blob, fileName) {
let mimeType = blob.type || "application/octet-stream";
if (!mimeType.includes("/")) {
mimeType = "application/octet-stream";
}
let extension = fileName.split(".").pop() || "bin";
extension = extension.startsWith(".") ? extension : `.${extension}`;
const accept = {
[mimeType]: [extension]
};
return {
suggestedName: fileName,
types: [
{
description: "File",
accept
}
]
};
}
// src/index.ts
async function saveFile(input, options = {}) {
const {
fileName = input instanceof File ? input.name : "download",
promptSaveAs = true,
logLevel = "none"
} = options;
const logger = createLogger(logLevel);
try {
logger.debug("Converting input to blob", { type: input.constructor.name });
const blob = await convertToBlob(input, options);
logger.debug("Blob created", { type: blob.type, size: blob.size });
if (promptSaveAs && "showSaveFilePicker" in window) {
try {
logger.debug("Attempting to use File System Access API");
const handle = await window.showSaveFilePicker(
getFilePickerOptions(blob, fileName)
);
logger.debug("File handle obtained, creating writable");
const writable = await handle.createWritable();
await writable.write(blob);
await writable.close();
logger.debug("File saved successfully using File System Access API");
return;
} catch (err) {
if (err.name === "AbortError") {
logger.debug("User aborted File System Access API save dialog");
throw err;
}
logger.debug("File System Access API failed, falling back to legacy method", err);
}
} else {
logger.debug("Using legacy download method", {
reason: promptSaveAs ? "API not available" : "promptSaveAs is false"
});
}
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.style.display = "none";
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
setTimeout(() => {
URL.revokeObjectURL(url);
document.body.removeChild(link);
logger.debug("File saved successfully using legacy method");
}, 100);
} catch (error) {
logger.debug("Error saving file:", error);
throw error;
}
}
exports.saveFile = saveFile;
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map
;