UNPKG

@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.

340 lines (334 loc) 10.8 kB
/* @nexim/upload-sdk v1.0.0 */ "use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/main.ts var main_exports = {}; __export(main_exports, { UploadFileMachine: () => UploadFileMachine, UploadImageMachine: () => UploadImageMachine, pickAndProcessFile: () => pickAndProcessFile }); module.exports = __toCommonJS(main_exports); var import_package_tracer = require("@alwatr/package-tracer"); // src/lib/upload-file-machine.ts var import_flatomise = require("@alwatr/flatomise"); var import_flux = require("@alwatr/flux"); var import_action_state = require("@nexim/action-state"); var UploadFileMachine = class extends import_flux.AlwatrFluxStateMachine { constructor(options) { super({ name: options.pathWithoutExtension, initialState: "initial" }); this.fileBlob_ = null; this.stateRecord_ = { initial: { request: "loading" }, loading: { loading_failed: "failed", loading_success: "complete" }, failed: { request: "loading" }, complete: {} }; this.actionRecord_ = { on_state_loading_enter: this.uploadFile_.bind(this), on_state_complete_enter: this.clear.bind(this) }; this.apiRequestFetchMachine__ = new import_flux.AlwatrJsonFetchStateMachine({ name: `upload:${this.name_}`, fetch: this.generateFetchOption_(options) }); this.apiRequestFetchMachine__.subscribe(({ state }) => { if (state === "complete") { this.transition("loading_success"); } else if (state === "failed") { this.transition("loading_failed"); } }); } /** * Uploads an file synchronously and returns the default path upon completion. * Creates a promise wrapper around the asynchronous upload process. * Subscribes to state changes and resolves/rejects based on upload completion state. * * @param file - The blob object containing the file data to upload * @returns Promise that resolves with the default path string if successful, null if failed * * @example * ```ts * const uploadFileMachine = new UploadFileMachine({ * pathWithoutExtension: 'path/to/file', * authHeader: 'Bearer token', * description: 'File description', * apiEndpoint: 'https://api.example.com/upload', * }); * uploadFileMachine.syncUpload(file) * .then((path) => { * console.log('File uploaded successfully to:', path); * }) * .catch(() => { * console.error('File upload failed'); * }); * ``` */ syncUpload(file) { this.logger_.logMethodArgs?.("syncUpload", { rawImage: file }); const flatomise = (0, import_flatomise.newFlatomise)(); const { unsubscribe } = this.subscribe(({ state }) => { (0, import_action_state.actionState)(state, { complete: () => { flatomise.resolve(true); unsubscribe(); }, failed: () => { flatomise.reject(false); unsubscribe(); } }); }); this.upload(file); return flatomise.promise; } /** * Upload file. * * @param file - File to upload * * @example * ```ts * const uploadFileMachine = new UploadFileMachine({ * pathWithoutExtension: 'path/to/file', * authHeader: 'Bearer token', * description: 'File description', * apiEndpoint: 'https://api.example.com/upload', * }); * uploadFileMachine.subscribe(({state}) => { * if (state === 'complete') { * console.log('File uploaded successfully'); * } * else if (state === 'failed') { * console.error('File upload failed'); * } * }); * * uploadFileMachine.upload(file); * ``` */ upload(file) { this.logger_.logMethodArgs?.("upload", { file }); this.fileBlob_ = file; this.transition("request"); } /** * Clear the state. */ clear() { this.logger_.logMethod?.("clear"); this.fileBlob_ = null; this.apiRequestFetchMachine__.clean(); } generateFetchOption_(options) { this.logger_.logMethod?.("generateFetchOption_"); return { removeDuplicate: "auto", retry: 2, retryDelay: 2e3, timeout: 6e4, method: "POST", headers: { "Content-Type": "application/octet-stream", authorization: options.authHeader }, url: options.apiEndpoint, queryParams: { path: options.pathWithoutExtension, description: options.description } }; } uploadFile_() { if (this.fileBlob_ === null) return; this.logger_.logMethod?.("uploadFile_"); const reader = new FileReader(); reader.addEventListener("loadend", (event) => { const buffer = event.target?.result; if (!buffer) { this.transition("loading_failed"); return; } this.apiRequestFetchMachine__.request({ body: buffer }); }); reader.addEventListener("error", () => { this.logger_.error("uploadFile_", "File reading error"); this.transition("loading_failed"); }); reader.readAsArrayBuffer(this.fileBlob_); } }; // src/lib/upload-image-machine.ts var import_upload_types = require("@nexim/upload-types"); var UploadImageMachine = class extends UploadFileMachine { constructor(options) { super(options); /** Temporarily stores the original file before optimization */ this.nonOptimizedFileBlob__ = null; this.actionRecord_ = { ...this.actionRecord_, on_state_loading_enter: this.uploadAfterResize__.bind(this) }; this.uploadImagePresetRecord__ = import_upload_types.uploadImagePresetRecord[options.presetName]; this.defaultPath = options.pathWithoutExtension + this.uploadImagePresetRecord__.client.defaultAppendName; } /** * Initiates an image upload process. * The image will be optimized according to the preset configuration before uploading. * * @param file - The image file to upload * * @example * ```ts * const uploadImageMachine = new UploadImageMachine({ * pathWithoutExtension: 'path/to/image', * authHeader: 'Bearer token', * description: 'Image description', * apiEndpoint: 'https://api.example.com/upload', * presetName: 'thumbnail', * }); * uploadImageMachine.subscribe(({state}) => { * if (state === 'complete') { * console.log('Image uploaded successfully'); * } * else if (state === 'failed') { * console.error('Image upload failed'); * } * }); * * uploadImageMachine.upload(file); * ``` */ upload(file) { this.logger_.logMethodArgs?.("upload", { file }); this.nonOptimizedFileBlob__ = file; this.transition("request"); } /** * Handles the image resizing process before uploading. * Called automatically when entering the loading state. */ uploadAfterResize__() { if (this.nonOptimizedFileBlob__ === null) return; this.resizeImage__(this.nonOptimizedFileBlob__).then((blob) => { this.fileBlob_ = blob; this.uploadFile_(); }).catch((error) => { this.logger_.error("resizeImage", "catch", { error }); this.transition("loading_failed"); }); } /** * Generates fetch options for the image upload request. * Extends the base fetch options with image-specific parameters. */ generateFetchOption_(options) { this.logger_.logMethod?.("generateFetchOption_"); return { removeDuplicate: "auto", retry: 2, retryDelay: 2e3, timeout: 3e4, method: "POST", headers: { "Content-Type": "application/octet-stream", authorization: options.authHeader }, url: options.apiEndpoint, queryParams: { path: options.pathWithoutExtension, presetName: options.presetName, description: options.description } }; } /** * Resizes an image according to preset configuration. * Maintains aspect ratio when only width or height is specified. * * @param file - The original image file * @returns A promise resolving to the resized image blob */ resizeImage__(file) { this.logger_.logMethod?.("resizeImage__"); return new Promise((resolve, reject) => { const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); if (!context) { resolve(file); return; } let width = this.uploadImagePresetRecord__.client.width; let height = this.uploadImagePresetRecord__.client.height; const image = new Image(); image.onload = () => { const aspect = image.width / image.height; if (width && height === -1) { height = width / aspect; } else if (height && width === -1) { width = height * aspect; } canvas.width = width; canvas.height = height; context.drawImage(image, 0, 0, width, height); canvas.toBlob( (blob) => { resolve(blob); }, file.type, this.uploadImagePresetRecord__.client.quality / 100 ); }; image.addEventListener("error", (error) => { this.logger_.error("resizeImage", "image_onerror", { error }); reject(error); }); image.src = URL.createObjectURL(file); }); } }; // src/lib/virtual-file-input.ts var import_logger = require("@alwatr/logger"); var logger = (0, import_logger.createLogger)("@nexim/upload-sdk"); function pickAndProcessFile(accept, callback) { logger.logMethod?.("pickAndProcessFile"); const input = document.createElement("input"); input.type = "file"; input.accept = accept; input.addEventListener("change", async () => { logger.logOther?.("changed"); if (input.files === null || input.files.length === 0) return; const file = input.files[0]; logger.logOther?.("pickAndProcessFile.change", { file }); input.remove(); await callback(file); }); input.click(); } // src/main.ts __dev_mode__: import_package_tracer.packageTracer.add("@nexim/upload-sdk", "1.0.0"); //# sourceMappingURL=main.cjs.map