UNPKG

modern-file-saver

Version:

Modern file saving library for browsers with File System Access API support and fallback

1 lines 12.2 kB
{"version":3,"sources":["../src/utils/blob.ts","../src/utils/logger.ts","../src/utils/file-picker.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import { InputType, SaveOptions } from '../types';\n\nfunction isBase64DataUrl(str: string): boolean {\n return str.startsWith('data:') && str.includes(';base64,');\n}\n\nasync function base64ToBlob(base64: string, mimeType?: string): Promise<Blob> {\n const dataUrl = isBase64DataUrl(base64)\n ? base64\n : `data:${mimeType || 'application/octet-stream'};base64,${base64}`;\n\n const response = await fetch(dataUrl);\n const blob = await response.blob();\n\n // If mimeType is explicitly provided, create a new Blob with that type\n if (mimeType && blob.type !== mimeType) {\n return new Blob([await blob.arrayBuffer()], { type: mimeType });\n }\n\n return blob;\n}\n\nexport async function convertToBlob(input: InputType, options: SaveOptions = {}): Promise<Blob> {\n // Object handling (automatically convert to JSON)\n if (isObjectType(input)) {\n return new Blob([JSON.stringify(input, null, 2)], {\n type: options.mimeType || 'application/json'\n });\n }\n\n // String handling\n if (typeof input === 'string') {\n // Case 1: Explicit base64 flag\n if (options.isBase64) {\n return base64ToBlob(input, options.mimeType);\n }\n\n // Case 2: Data URL with base64 encoding\n if (isBase64DataUrl(input)) {\n return base64ToBlob(input, options.mimeType);\n }\n\n // Case 3: Plain string\n return new Blob([input], {\n type: options.mimeType || 'text/plain'\n });\n }\n\n // Blob\n if (input instanceof Blob) {\n if (options.mimeType && options.mimeType !== input.type) {\n // if a different MIME type is required, a new blob is created\n return new Blob([await input.arrayBuffer()], { type: options.mimeType });\n }\n return input;\n }\n\n // ArrayBuffer or TypedArray\n if (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) {\n return new Blob([input], {\n type: options.mimeType || 'application/octet-stream'\n });\n }\n\n // URLSearchParams\n if (input instanceof URLSearchParams) {\n return new Blob([input.toString()], {\n type: 'application/x-www-form-urlencoded'\n });\n }\n\n // FormData\n if (input instanceof FormData) {\n const pairs: string[] = [];\n input.forEach((value, key) => {\n pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(value.toString())}`);\n });\n return new Blob([pairs.join('&')], {\n type: 'multipart/form-data'\n });\n }\n\n throw new Error('Unsupported input type');\n}\n\nfunction isObjectType(input: InputType): boolean {\n return (\n typeof input === 'object' &&\n !(input instanceof Blob) &&\n !(input instanceof ArrayBuffer) &&\n !ArrayBuffer.isView(input) &&\n !(input instanceof URLSearchParams) &&\n !(input instanceof FormData)\n );\n}\n","export type LogLevel = 'debug' | 'none';\n\nexport interface Logger {\n debug: (message: string, ...args: unknown[]) => void;\n}\n\nexport class ConsoleLogger implements Logger {\n constructor(private level: LogLevel = 'none') {}\n\n debug(message: string, ...args: unknown[]): void {\n if (this.level === 'debug') {\n console.log(`[modern-file-saver] ${message}`, ...args);\n }\n }\n}\n\nexport function createLogger(level: LogLevel = 'none'): Logger {\n return new ConsoleLogger(level);\n}\n","export function getFilePickerOptions(blob: Blob, fileName: string): SaveFilePickerOptions {\n // make sure that the MIME type has the format type/subtype\n let mimeType = blob.type || 'application/octet-stream';\n if (!mimeType.includes('/')) {\n mimeType = 'application/octet-stream';\n }\n\n let extension = fileName.split('.').pop() || 'bin';\n extension = extension.startsWith('.') ? extension : `.${extension}`;\n\n const accept: Record<MIMEType, FileExtension[]> = {\n [mimeType as MIMEType]: [extension as FileExtension]\n };\n\n return {\n suggestedName: fileName,\n types: [\n {\n description: 'File',\n accept\n }\n ]\n };\n}\n","import { InputType, SaveOptions } from './types';\nimport { convertToBlob } from './utils/blob';\nimport { createLogger } from './utils/logger';\nimport { getFilePickerOptions } from './utils/file-picker';\n\nexport async function saveFile(input: InputType, options: SaveOptions = {}): Promise<void> {\n const {\n fileName = input instanceof File ? input.name : 'download',\n promptSaveAs = true,\n logLevel = 'none'\n } = options;\n\n const logger = createLogger(logLevel);\n\n try {\n logger.debug('Converting input to blob', { type: input.constructor.name });\n const blob = await convertToBlob(input, options);\n logger.debug('Blob created', { type: blob.type, size: blob.size });\n\n // Modern File System Access API\n if (promptSaveAs && 'showSaveFilePicker' in window) {\n try {\n logger.debug('Attempting to use File System Access API');\n const handle = await window.showSaveFilePicker(\n getFilePickerOptions(blob, fileName)\n );\n\n logger.debug('File handle obtained, creating writable');\n const writable = await handle.createWritable();\n await writable.write(blob);\n await writable.close();\n logger.debug('File saved successfully using File System Access API');\n return;\n } catch (err) {\n if ((err as Error).name === 'AbortError') {\n logger.debug('User aborted File System Access API save dialog');\n throw err;\n }\n logger.debug('File System Access API failed, falling back to legacy method', err);\n }\n } else {\n logger.debug('Using legacy download method', {\n reason: promptSaveAs ? 'API not available' : 'promptSaveAs is false'\n });\n }\n\n // fallback for older browsers, Firefox or if promptSaveAs is false\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.style.display = 'none';\n link.href = url;\n link.download = fileName;\n\n document.body.appendChild(link);\n link.click();\n\n setTimeout(() => {\n URL.revokeObjectURL(url);\n document.body.removeChild(link);\n logger.debug('File saved successfully using legacy method');\n }, 100);\n } catch (error) {\n logger.debug('Error saving file:', error);\n throw error;\n }\n}\n"]}