@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
JavaScript
/* @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