@nexim/upload-sdk
Version:
TypeScript SDK for seamless integration with Nexim Media Upload Service. It provides state machine-based upload handling, progress tracking, and type-safe API for image optimization and file uploads.
8 lines (7 loc) • 12 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../src/main.ts", "../src/lib/logger.ts", "../src/lib/upload-file.ts", "../src/lib/upload-image.ts", "../src/lib/virtual-file-input.ts"],
"sourcesContent": ["import { packageTracer } from '@alwatr/package-tracer';\n\n__dev_mode__: packageTracer.add(__package_name__, __package_version__);\n\nexport * from './lib/upload-file.js';\nexport * from './lib/upload-image.js';\nexport * from './lib/virtual-file-input.js';\n", "import { createLogger } from '@alwatr/logger';\n\nexport const logger = createLogger('@nexim/upload-sdk');\n", "import { logger } from './logger.js';\n\nimport type { ServiceResponse, UserAuth } from '@nexim/upload-types';\n\nexport type UploadFileOptions = {\n path: string;\n auth: UserAuth;\n description: string;\n apiEndpoint: string;\n /**\n * Extra headers to include in the request\n */\n extraHeaders?: Record<string, string>;\n\n /**\n * Maximum number of retries for the upload request.\n * @defaultValue 3\n */\n maxRetries?: number;\n\n /**\n * Delay in milliseconds between retries.\n * @defaultValue 2000\n */\n retryDelay?: number;\n};\n\n/**\n * Uploads a file to the server.\n *\n * @param file - The file to upload.\n * @param options - The upload options.\n * @returns A promise that resolves to the server response.\n *\n * @example\n * ```typescript\n * import { uploadFile } from '@nexim/upload-sdk';\n *\n * // Usually used inside a file picker callback\n * const file = new File(['content'], 'test.txt', { type: 'text/plain' });\n *\n * const options = {\n * path: 'files/test.txt',\n * auth: { id: 'user', token: 'token' },\n * description: 'Test file',\n * apiEndpoint: 'https://api.example.com/upload',\n * };\n *\n * try {\n * const response = await uploadFile(file, options);\n * console.log('Upload success:', response);\n * }\n * catch (err) {\n * console.error('Upload failed:', err);\n * }\n * ```\n */\nexport async function uploadFile(file: Blob, options: UploadFileOptions): Promise<ServiceResponse<{ files: string[] }>> {\n logger.logMethodArgs?.('uploadFile', { options });\n\n const buffer = await file.arrayBuffer();\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/octet-stream',\n authorization: `Alwatr ${options.auth.id}:${options.auth.token}`,\n 'x-upload-path': encodeURIComponent(options.path),\n 'x-upload-description': encodeURIComponent(options.description),\n ...options.extraHeaders,\n };\n\n let attempt = 0;\n const maxRetries = options.maxRetries ?? 3;\n const retryDelay = options.retryDelay ?? 2_000;\n\n while (attempt < maxRetries) {\n try {\n const response = await fetch(options.apiEndpoint, {\n method: 'POST',\n headers,\n body: buffer,\n });\n\n if (!response.ok) {\n throw new Error(`Upload failed with status: ${response.status}`);\n }\n\n const data = (await response.json()) as ServiceResponse<{ files: string[] }>;\n return data;\n }\n catch (err) {\n attempt++;\n logger.error('uploadFile', 'upload_failed', { attempt, err });\n\n if (attempt >= maxRetries) {\n throw err;\n }\n\n await new Promise((resolve) => setTimeout(resolve, retryDelay));\n }\n }\n\n throw new Error('Upload failed after maximum retries'); // This line should never be reached\n}\n", "import { logger } from './logger.js';\nimport { uploadFile, type UploadFileOptions } from './upload-file.js';\n\nimport type { ServiceResponse, UploadImagePreset } from '@nexim/upload-types';\n\nexport type UploadImageOptions = Omit<UploadFileOptions, 'path'> & {\n path: string;\n preset: UploadImagePreset;\n throwOnClientOptimizationError?: boolean;\n};\n\n/**\n * Uploads an image to the server, optimizing it first.\n *\n * @param file - The image file to upload.\n * @param options - The upload options.\n * @returns A promise that resolves to the server response.\n *\n * @example\n * ```typescript\n * import { uploadImage } from '@nexim/upload-sdk';\n *\n * // Usually used inside a file picker callback\n * const file = new File(['image data'], 'photo.jpg', { type: 'image/jpeg' });\n *\n * const options = {\n * path: 'images/photo',\n * auth: { id: 'user', token: 'token' },\n * description: 'User photo',\n * apiEndpoint: 'https://api.example.com/upload',\n * preset: {\n * client: {\n * width: 800,\n * height: -1,\n * quality: 80\n * },\n * // ... other preset options\n * },\n * };\n *\n * await uploadImage(file, options);\n * ```\n */\nexport async function uploadImage(file: Blob, options: UploadImageOptions): Promise<ServiceResponse<{ files: string[] }>> {\n logger.logMethodArgs?.('uploadImage', { options });\n\n let fileToUpload = file;\n try {\n fileToUpload = await optimizeImage(file, options.preset.client);\n }\n catch (err) {\n logger.error('uploadImage', 'optimization_failed', { err });\n if (options.throwOnClientOptimizationError) {\n throw err;\n }\n // Fallback to original file\n }\n\n return uploadFile(fileToUpload, {\n ...options,\n extraHeaders: {\n 'x-upload-preset': btoa(JSON.stringify(options.preset)),\n },\n });\n}\n\n/**\n * Optimizes an image on the client side.\n *\n * @param rawImage - The raw image blob.\n * @param clientConfig - The client-side optimization configuration.\n * @returns A promise that resolves to the optimized image blob.\n *\n * @example\n * ```typescript\n * const rawImage = new Blob(['...'], { type: 'image/jpeg' });\n * const config = {\n * width: 100,\n * height: 100,\n * quality: 90\n * };\n *\n * const optimizedBlob = await optimizeImage(rawImage, config);\n * ```\n */\nexport function optimizeImage(rawImage: Blob, clientConfig: UploadImagePreset['client']): Promise<Blob> {\n logger.logMethod?.('optimizeImage');\n\n return new Promise((resolve, reject) => {\n const canvas = document.createElement('canvas');\n const context = canvas.getContext('2d');\n if (!context) {\n resolve(rawImage);\n return;\n }\n\n let width = clientConfig.width;\n let height = clientConfig.height;\n\n const image = new Image();\n const objectUrl = URL.createObjectURL(rawImage);\n\n image.onload = () => {\n const aspect = image.width / image.height;\n\n if (width && height === -1) {\n height = width / aspect;\n }\n else if (height && width === -1) {\n width = height * aspect;\n }\n\n canvas.width = width;\n canvas.height = height;\n\n context.drawImage(image, 0, 0, width, height);\n URL.revokeObjectURL(objectUrl);\n\n canvas.toBlob(\n (blob) => {\n if (blob) {\n resolve(blob);\n }\n else {\n reject(new Error('Failed to create blob from canvas.'));\n }\n },\n rawImage.type,\n clientConfig.quality / 100,\n );\n };\n\n image.addEventListener('error', (error) => {\n URL.revokeObjectURL(objectUrl);\n reject(new Error(error.message || 'Image load error'));\n });\n\n image.src = objectUrl;\n });\n}\n", "import { logger } from './logger.js';\n\nimport type { MaybePromise } from '@alwatr/type-helper';\n\n/**\n * Creates and triggers a virtual file input element to handle file selection.\n * This utility creates a temporary file input element, attaches event handlers,\n * and removes itself after use.\n *\n * Features:\n * - Creates a hidden file input element\n * - Handles file selection through browser's native interface\n * - Automatically cleans up after selection\n * - Supports async callbacks for processing selected files\n * - Filters files based on accept parameter (MIME types)\n *\n * @param accept - Comma-separated list of allowed file types (e.g., 'image/*', '.pdf', 'image/jpeg,image/png')\n * @param callback - Function to handle the selected file. Can be async for long-running operations.\n *\n * @example\n * ```typescript\n * // Example 1: Pick an image and upload it using uploadImage\n * import { pickAndProcessFile, uploadImage } from '@nexim/upload-sdk';\n *\n * pickAndProcessFile('image/*', async (file) => {\n * await uploadImage(file, {\n * path: 'images/user-avatar',\n * auth: { id: '...', token: '...' },\n * description: 'User Avatar',\n * apiEndpoint: 'https://api.example.com/upload',\n * preset: {\n * client: { width: 512, height: 512, quality: 90 },\n * // ...\n * },\n * });\n * });\n * ```\n *\n * @example\n * ```typescript\n * // Example 2: Pick a PDF and upload it using uploadFile\n * import { pickAndProcessFile, uploadFile } from '@nexim/upload-sdk';\n *\n * pickAndProcessFile('.pdf', async (file) => {\n * await uploadFile(file, {\n * path: 'documents/report.pdf',\n * auth: { id: '...', token: '...' },\n * description: 'Monthly Report',\n * apiEndpoint: 'https://api.example.com/upload',\n * });\n * });\n * ```\n */\nexport function pickAndProcessFile(accept: string, callback: (file: File) => MaybePromise<void>): void {\n logger.logMethod?.('pickAndProcessFile');\n\n const input = document.createElement('input');\n input.type = 'file';\n input.accept = accept;\n\n input.addEventListener('change', async () => {\n logger.logOther?.('changed');\n if (input.files === null || input.files.length === 0) return;\n const file = input.files[0];\n\n logger.logOther?.('pickAndProcessFile.change', { file });\n\n input.remove(); // Remove the input element from the DOM to free-up\n\n await callback(file);\n });\n\n input.click();\n}\n"],
"mappings": "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",
"names": []
}