JavaScript SDK for Qiniu Cloud Storage that enables browser-based file uploads with resumable transfer, image processing, and comprehensive error handling.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Helper functions for image compression, base64 encoding, and upload configuration management.
Client-side image compression to reduce file sizes before upload.
/**
* Compress an image file on the client side
* @param file - Image file to compress
* @param options - Compression configuration
* @returns Promise with compressed image and metadata
*/
function compressImage(file: File, options: CompressOptions): Promise<CompressResult>;
interface CompressOptions {
/** Output quality (0-1), defaults to 0.92 */
quality?: number;
/** Don't compress if result is larger than original */
noCompressIfLarger?: boolean;
/** Maximum width constraint in pixels */
maxWidth?: number;
/** Maximum height constraint in pixels */
maxHeight?: number;
}
interface CompressResult {
/** Compressed image as Blob or original File */
dist: Blob | File;
/** Final width in pixels */
width: number;
/** Final height in pixels */
height: number;
}Usage Examples:
import { compressImage } from "qiniu-js";
// Basic compression
const fileInput = document.getElementById('imageInput') as HTMLInputElement;
const originalFile = fileInput.files[0];
try {
const compressed = await compressImage(originalFile, {
quality: 0.8,
maxWidth: 1920,
maxHeight: 1080,
noCompressIfLarger: true
});
console.log(`Original: ${originalFile.size} bytes`);
console.log(`Compressed: ${compressed.dist.size} bytes`);
console.log(`Dimensions: ${compressed.width}x${compressed.height}`);
// Use compressed image for upload
const subscription = upload(compressed.dist, 'compressed-image.jpg', token);
} catch (error) {
console.error('Compression failed:', error);
}
// Aggressive compression for thumbnails
async function createThumbnail(file: File): Promise<Blob> {
const result = await compressImage(file, {
quality: 0.7,
maxWidth: 300,
maxHeight: 300
});
return result.dist as Blob;
}
// Quality comparison
async function compressByQuality(file: File) {
const qualities = [0.5, 0.7, 0.9];
for (const quality of qualities) {
const result = await compressImage(file, { quality });
console.log(`Quality ${quality}: ${result.dist.size} bytes`);
}
}URL-safe base64 encoding and decoding functions for Qiniu API compatibility.
/**
* Encode data to URL-safe base64 format used by Qiniu APIs
* @param v - Data to encode (string or other)
* @returns URL-safe base64 encoded string
*/
function urlSafeBase64Encode(v: any): string;
/**
* Decode URL-safe base64 data
* @param v - URL-safe base64 encoded string
* @returns Decoded string
*/
function urlSafeBase64Decode(v: any): string;Usage Examples:
import { urlSafeBase64Encode, urlSafeBase64Decode } from "qiniu-js";
// Encode text for watermarks
const watermarkText = "© 2024 My Company";
const encodedText = urlSafeBase64Encode(watermarkText);
console.log('Encoded:', encodedText); // Safe for URLs
// Encode image URLs for watermarks
const logoUrl = "https://example.com/logo.png";
const encodedLogoUrl = urlSafeBase64Encode(logoUrl);
// Use in watermark operations
const watermarkUrl = `watermark/1/image/${encodedLogoUrl}/dissolve/80`;
// Decode data
const decodedText = urlSafeBase64Decode(encodedText);
console.log('Decoded:', decodedText); // "© 2024 My Company"
// Handle Chinese and Unicode text
const chineseText = "七牛云存储";
const encodedChinese = urlSafeBase64Encode(chineseText);
const decodedChinese = urlSafeBase64Decode(encodedChinese);
console.log('Chinese text preserved:', decodedChinese === chineseText);Generate properly formatted headers for different types of upload requests.
/**
* Generate headers for final file creation requests
* @param token - Upload token
* @returns Headers object with authorization and content-type
*/
function getHeadersForMkFile(token: string): { [key: string]: string };
/**
* Generate headers for chunk upload requests
* @param token - Upload token
* @returns Headers object with authorization and content-type
*/
function getHeadersForChunkUpload(token: string): { [key: string]: string };Usage Examples:
import { getHeadersForMkFile, getHeadersForChunkUpload } from "qiniu-js";
// Get headers for file completion
const token = "your-upload-token";
const mkFileHeaders = getHeadersForMkFile(token);
console.log(mkFileHeaders);
// Output: {
// "Authorization": "UpToken your-upload-token",
// "content-type": "application/json"
// }
// Get headers for chunk uploads
const chunkHeaders = getHeadersForChunkUpload(token);
console.log(chunkHeaders);
// Output: {
// "Authorization": "UpToken your-upload-token",
// "content-type": "application/octet-stream"
// }
// Use in custom HTTP requests
async function customChunkUpload(chunkData: Blob, uploadUrl: string, token: string) {
const headers = getHeadersForChunkUpload(token);
const response = await fetch(uploadUrl, {
method: 'PUT',
headers: {
...headers,
'Content-MD5': await calculateMD5(chunkData)
},
body: chunkData
});
return response.json();
}Handle different scenarios and edge cases with image compression.
import { compressImage, QiniuError, QiniuErrorName } from "qiniu-js";
async function smartCompress(file: File): Promise<File | Blob> {
// Skip compression for very small images
if (file.size < 100 * 1024) { // Less than 100KB
console.log('File too small, skipping compression');
return file;
}
// Skip compression for non-image files
if (!file.type.startsWith('image/')) {
console.log('Not an image file, skipping compression');
return file;
}
try {
// Try aggressive compression first
let result = await compressImage(file, {
quality: 0.7,
maxWidth: 1920,
maxHeight: 1080,
noCompressIfLarger: true
});
// If still too large, try more aggressive compression
if (result.dist.size > 2 * 1024 * 1024) { // > 2MB
result = await compressImage(file, {
quality: 0.5,
maxWidth: 1280,
maxHeight: 720,
noCompressIfLarger: true
});
}
console.log(`Compression ratio: ${(result.dist.size / file.size * 100).toFixed(1)}%`);
return result.dist;
} catch (error) {
if (error instanceof QiniuError) {
if (error.name === QiniuErrorName.UnsupportedFileType) {
console.log('Unsupported image format, using original');
return file;
} else if (error.name === QiniuErrorName.GetCanvasContextFailed) {
console.log('Canvas not available, using original');
return file;
}
}
console.error('Compression failed, using original:', error);
return file;
}
}
// Batch compression with progress
async function compressBatch(files: File[], onProgress?: (index: number, total: number) => void): Promise<(File | Blob)[]> {
const results: (File | Blob)[] = [];
for (let i = 0; i < files.length; i++) {
const compressed = await smartCompress(files[i]);
results.push(compressed);
if (onProgress) {
onProgress(i + 1, files.length);
}
}
return results;
}Handle various encoding scenarios for API parameters.
import { urlSafeBase64Encode, urlSafeBase64Decode } from "qiniu-js";
// Create complex watermark configurations
function createWatermarkConfig(options: {
text?: string;
imageUrl?: string;
fontFamily?: string;
color?: string;
}) {
const params: string[] = [];
if (options.text) {
params.push(`text/${urlSafeBase64Encode(options.text)}`);
}
if (options.imageUrl) {
params.push(`image/${urlSafeBase64Encode(options.imageUrl)}`);
}
if (options.fontFamily) {
params.push(`font/${urlSafeBase64Encode(options.fontFamily)}`);
}
if (options.color) {
params.push(`fill/${urlSafeBase64Encode(options.color)}`);
}
return params.join('/');
}
// Usage
const watermarkParams = createWatermarkConfig({
text: "版权所有 © 2024",
fontFamily: "微软雅黑",
color: "#FFFFFF"
});
// Parse processing URLs
function parseProcessingUrl(url: string): any {
const params = url.split('/');
const result: any = {};
for (let i = 0; i < params.length; i += 2) {
if (i + 1 < params.length) {
const key = params[i];
const value = params[i + 1];
// Decode base64 encoded values
if (['text', 'font', 'image', 'fill'].includes(key)) {
result[key] = urlSafeBase64Decode(value);
} else {
result[key] = value;
}
}
}
return result;
}Utility functions for managing upload configurations and validating settings.
// Validation helper for custom variables
function validateCustomVars(customVars: { [key: string]: string }): boolean {
return Object.keys(customVars).every(key => key.startsWith('x:'));
}
// Validation helper for metadata
function validateMetadata(metadata: { [key: string]: string }): boolean {
return Object.keys(metadata).every(key => key.startsWith('x-qn-meta-'));
}
// Configuration builder
class UploadConfigBuilder {
private config: any = {};
region(region: string) {
this.config.region = region;
return this;
}
retryCount(count: number) {
this.config.retryCount = Math.max(0, Math.min(10, count));
return this;
}
chunkSize(sizeMB: number) {
this.config.chunkSize = Math.max(1, Math.min(1000, sizeMB));
return this;
}
useCdn(enabled: boolean = true) {
this.config.useCdnDomain = enabled;
return this;
}
forceDirect(enabled: boolean = true) {
this.config.forceDirect = enabled;
return this;
}
build() {
return { ...this.config };
}
}
// Usage
const config = new UploadConfigBuilder()
.region('z0')
.retryCount(5)
.chunkSize(8)
.useCdn(true)
.build();
console.log(config);
// Output: {
// region: 'z0',
// retryCount: 5,
// chunkSize: 8,
// useCdnDomain: true
// }Install with Tessl CLI
npx tessl i tessl/npm-qiniu-js