@hff-ai/media-processor-client
v1.0.1
Published
TypeScript client for communicating with the HFF Media Processor service
Maintainers
Readme
@hff-ai/media-processor-client
A TypeScript client library for communicating with the HFF Media Processor service. Abstracts away AMQP and REST API details, providing both async/await and event-based APIs.
Features
- Dual API: Choose between async/await (
processS3*) or event-based (queueS3*) styles - Clean separation: Async requests don't emit events, preventing double-handling
- Progress callbacks: Track processing progress with optional callbacks
- Auto-reconnect: Automatic reconnection handling with configurable retry logic
- Type-safe: Full TypeScript support with comprehensive type definitions
- Zero-config MIME detection: Automatically detects MIME types from file extensions
Installation
npm install @hff-ai/media-processor-clientOr if using the package locally within the monorepo:
npm install ./packages/media-processor-clientQuick Start
Async/Await (Recommended)
import { media_processor, ProcessingError } from '@hff-ai/media-processor-client';
const processor = await media_processor.connect({
processorUrl: 'https://mp.hff.ai',
processorAmqp: 'amqp://user:[email protected]',
token: process.env.MEDIA_PROCESSOR_TOKEN!,
s3Details: {
provider: 'idrive',
endpoint: 'https://s3.us-east-1.idrivee2-27.com',
accessKey: process.env.S3_ACCESS_KEY!,
secretAccessKey: process.env.S3_SECRET_KEY!,
region: 'us-east-1',
bucketName: 'my-media-bucket',
},
system: 'my-application',
});
// Process and await result
try {
const result = await processor.processS3Image('uploads/photo.jpg', {
timeout: 30000,
onProgress: (p) => console.log(`${p.progress}%`),
});
console.log('Done:', result.result.thumbnailS3Key);
} catch (error) {
if (error instanceof ProcessingError) {
console.error(`Failed: ${error.errorCode}`);
}
}Event-Based (Fire-and-Forget)
Important: Set up handlers BEFORE connecting to capture queued messages.
import { media_processor, MediaResultMessage } from '@hff-ai/media-processor-client';
// Create without connecting
const processor = media_processor.create(config);
// Set up handlers FIRST
processor.on('image_completed', (msg: MediaResultMessage) => {
console.log('Image processed:', msg.result.s3Key);
});
// NOW connect - handlers ready for queued messages
await processor.connect();
// Queue (returns immediately)
const mediaId = processor.queueS3Image('uploads/photo.jpg');Configuration
MediaProcessorConfig
| Property | Type | Required | Description |
| --------------- | ------------------ | -------- | ---------------------------------------- |
| processorUrl | string | Yes | Base URL of the media processor REST API |
| processorAmqp | string | Yes | AMQP connection URL for RabbitMQ |
| token | string | Yes | JWT authentication token |
| s3Details | S3Config | Yes | S3 bucket configuration |
| system | string | Yes | System namespace for queue routing |
| options | ProcessorOptions | No | Optional processing defaults |
S3Config
| Property | Type | Required | Description |
| ----------------- | -------- | -------- | ------------------------------------------------------- |
| provider | string | Yes | S3-compatible provider (e.g., 'idrive', 'aws', 'minio') |
| endpoint | string | Yes | S3 endpoint URL |
| accessKey | string | Yes | S3 access key ID |
| secretAccessKey | string | Yes | S3 secret access key |
| region | string | Yes | S3 region |
| bucketName | string | Yes | Bucket name |
ProcessorOptions
| Property | Type | Default | Description |
| ---------------------- | --------- | ------------- | -------------------------------------------- |
| maxConcurrentJobs | number | 5 | Maximum concurrent processing jobs |
| defaultUserId | string | 'anonymous' | Default user ID for requests |
| autoReconnect | boolean | true | Auto-reconnect on connection loss |
| reconnectDelay | number | 5000 | Reconnection delay in milliseconds |
| maxReconnectAttempts | number | 10 | Maximum reconnection attempts (0 = infinite) |
| requestTimeout | number | 30000 | Request timeout in milliseconds |
API Reference
Factory Methods
media_processor.connect(config)
Create and connect to a MediaProcessor instance.
const processor = await media_processor.connect(config);media_processor.create(config)
Create a MediaProcessor instance without connecting. Useful for setting up event handlers before connecting.
const processor = media_processor.create(config);
processor.on('connected', () => console.log('Connected!'));
await processor.connect();Async Processing Methods (Promise-based)
These methods return a Promise that resolves when processing completes.
Note: Results from async methods are delivered only via the Promise - no events are emitted. This allows safe mixing of both API styles in the same application.
processor.processS3Image(s3Key, options?): Promise<MediaResultMessage>
Process an image and await the result.
const result = await processor.processS3Image('uploads/photo.jpg', {
userId: 'user-123',
timeout: 30000,
onProgress: (p) => console.log(`${p.progress}%`),
});
console.log('Thumbnail:', result.result.thumbnailS3Key);processor.processS3Video(s3Key, options?): Promise<MediaResultMessage>
Process a video and await the result.
const result = await processor.processS3Video('uploads/video.mp4', {
userId: 'user-123',
targetQualities: ['1080p', '720p', '480p'],
timeout: 300000, // 5 minutes
onProgress: (p) => updateProgressBar(p.progress),
});processor.processS3Zip(s3Key, options?): Promise<MediaResultMessage>
Process a zip archive and await the result.
const result = await processor.processS3Zip('uploads/photos.zip', {
userId: 'user-123',
processImages: true,
processVideos: true,
timeout: 120000,
});Queue Methods (Fire-and-Forget)
These methods return immediately. Results are delivered exclusively via events (*_completed, *_error, *_progress).
processor.queueS3Image(s3Key, options?): string
Queue an image for processing.
const mediaId = processor.queueS3Image('uploads/photo.jpg', {
userId: 'user-123',
});
// Handle result via processor.on('image_completed', ...)processor.queueS3Video(s3Key, options?): string
Queue a video for processing.
const mediaId = processor.queueS3Video('uploads/video.mp4', {
userId: 'user-123',
targetQualities: ['1080p', '720p'],
});processor.queueS3Zip(s3Key, options?): string
Queue a zip archive for processing.
const mediaId = processor.queueS3Zip('uploads/photos.zip', {
userId: 'user-123',
});Connection Methods
processor.disconnect()
Gracefully close all connections.
await processor.disconnect();processor.isConnected()
Check connection status.
if (processor.isConnected()) {
// Safe to submit requests
}processor.reconnect()
Manually trigger reconnection.
await processor.reconnect();processor.updateToken(token)
Update the authentication token (e.g., after token refresh).
processor.updateToken(newToken);processor.getSystemStatus()
Get current system status and statistics.
const status = await processor.getSystemStatus();
console.log(`Pending jobs: ${status.pendingJobs}`);Events
Processing Events
| Event | Payload | Description |
| ------------------ | ---------------------- | -------------------------- |
| image_progress | MediaProgressMessage | Image processing progress |
| image_completed | MediaResultMessage | Image processing completed |
| image_error | MediaErrorMessage | Image processing failed |
| video_progress | MediaProgressMessage | Video processing progress |
| video_completed | MediaResultMessage | Video processing completed |
| video_error | MediaErrorMessage | Video processing failed |
| zip_progress | MediaProgressMessage | Zip extraction progress |
| zip_completed | MediaResultMessage | Zip processing completed |
| zip_error | MediaErrorMessage | Zip processing failed |
| progress | MediaProgressMessage | Any media type progress |
| completed | MediaResultMessage | Any media type completed |
| processing_error | MediaErrorMessage | Any media type failed |
Connection Events
| Event | Payload | Description |
| -------------- | --------------------- | ----------------------- |
| connected | void | Successfully connected |
| disconnected | { reason: string } | Connection lost |
| reconnecting | { attempt: number } | Attempting to reconnect |
| error | Error | Connection-level error |
Warning Events
| Event | Payload | Description |
| ------------------- | ------------------------- | ----------------------------------------------- |
| unhandled_message | UnhandledMessagePayload | Orphaned or unhandled message (will be dropped) |
The unhandled_message event fires in two scenarios:
Orphaned async messages: A
processS3*()request from a previous process that died. These have acorrelationIdbut no matching pending promise. They are NOT sent to regular event handlers.Queue messages with no listeners: A
queueS3*()result where no event handler is registered.
processor.on('unhandled_message', (warning) => {
console.warn(`Dropped: ${warning.message}`);
// Optionally: save to dead-letter store, alert, etc.
});Error Handling
The library provides typed error classes for different scenarios:
Error Classes
import {
MediaProcessorError, // Base class
ConnectionError, // Connection-related errors
ConfigurationError, // Configuration errors
RequestError, // Queue request errors (sync)
ProcessingError, // Processing failures (async, from process* methods)
} from '@hff-ai/media-processor-client';Connection Errors
| Code | Description |
| ------------------- | ------------------------------ |
| CONNECTION_FAILED | Cannot reach the processor |
| CONNECTION_LOST | Connection dropped |
| AUTH_FAILED | Invalid or expired token |
| AMQP_ERROR | RabbitMQ error |
| REST_API_ERROR | REST API error |
| RECONNECT_FAILED | Exceeded reconnection attempts |
Processing Errors
| Code | Description |
| ------------------------ | --------------------------- |
| S3_ACCESS_DENIED | Insufficient S3 permissions |
| S3_NOT_FOUND | Source file not found |
| S3_UPLOAD_FAILED | Failed to upload results |
| INVALID_FORMAT | Invalid file format |
| UNSUPPORTED_CODEC | Unsupported video codec |
| FILE_TOO_LARGE | File exceeds size limit |
| FILE_CORRUPTED | Damaged file |
| PROCESSING_TIMEOUT | Processing timed out |
| TRANSCODING_FAILED | Video transcoding failed |
| ZIP_EXTRACTION_FAILED | Zip extraction failed |
| ZIP_PASSWORD_PROTECTED | Password-protected zip |
ProcessingError Codes (async methods)
| Code | Description |
| -------------------- | -------------------------------------------------------- |
| PROCESSING_FAILED | Server returned an error (check errorCode for details) |
| PROCESSING_TIMEOUT | Client-side timeout exceeded |
| DISCONNECTED | Connection lost during processing |
Error Handling Example
import {
ConnectionError,
ProcessingError,
MediaErrorMessage,
} from '@hff-ai/media-processor-client';
// Async error handling (try/catch)
try {
const result = await processor.processS3Image('uploads/photo.jpg', {
timeout: 30000,
});
} catch (error) {
if (error instanceof ProcessingError) {
console.error(`Processing failed: ${error.errorCode} - ${error.message}`);
if (error.errorCode === 'S3_NOT_FOUND') {
// Handle missing file
}
}
}
// Event-based error handling (for queue methods)
processor.on('image_error', (msg: MediaErrorMessage) => {
switch (msg.errorCode) {
case 'S3_NOT_FOUND':
console.error('File not found:', msg.details);
break;
case 'INVALID_FORMAT':
notifyUser('Invalid image format');
break;
default:
logError(msg);
}
});
// Connection errors
processor.on('error', (error: ConnectionError) => {
if (error.code === 'AUTH_FAILED') {
refreshToken().then((newToken) => {
processor.updateToken(newToken);
processor.reconnect();
});
}
});Complete Example
import {
media_processor,
MediaProcessorConfig,
MediaResultMessage,
MediaProgressMessage,
MediaErrorMessage,
ConnectionError,
} from '@hff-ai/media-processor-client';
const config: MediaProcessorConfig = {
processorUrl: process.env.MEDIA_PROCESSOR_URL!,
processorAmqp: process.env.MEDIA_PROCESSOR_AMQP!,
token: process.env.MEDIA_PROCESSOR_TOKEN!,
s3Details: {
provider: 'idrive',
endpoint: process.env.S3_ENDPOINT!,
accessKey: process.env.S3_ACCESS_KEY!,
secretAccessKey: process.env.S3_SECRET_KEY!,
region: process.env.S3_REGION || 'us-east-1',
bucketName: process.env.S3_BUCKET!,
},
system: 'my-application',
options: {
maxConcurrentJobs: 10,
autoReconnect: true,
},
};
async function main() {
// Create processor with event handlers before connecting
const processor = media_processor.create(config);
// Connection events
processor.on('connected', () => {
console.log('Connected to media processor');
});
processor.on('disconnected', ({ reason }) => {
console.warn('Disconnected:', reason);
});
processor.on('reconnecting', ({ attempt }) => {
console.log(`Reconnecting (attempt ${attempt})...`);
});
processor.on('error', (error: Error) => {
console.error('Connection error:', error.message);
});
// Processing events
processor.on('image_progress', (msg: MediaProgressMessage) => {
console.log(`Image ${msg.mediaId}: ${msg.progress}% - ${msg.message}`);
});
processor.on('image_completed', (msg: MediaResultMessage) => {
console.log(`Image completed: ${msg.mediaId}`);
// Save to database, update UI, etc.
});
processor.on('image_error', (msg: MediaErrorMessage) => {
console.error(`Image failed: ${msg.mediaId} - ${msg.error}`);
});
processor.on('video_progress', (msg: MediaProgressMessage) => {
updateProgressBar(msg.mediaId, msg.progress);
});
processor.on('video_completed', (msg: MediaResultMessage) => {
console.log(`Video completed: ${msg.mediaId}`);
});
// Connect
try {
await processor.connect();
} catch (error) {
if (error instanceof ConnectionError) {
console.error(`Failed to connect: ${error.code} - ${error.message}`);
}
process.exit(1);
}
// Process some media
try {
const imageId = processor.processS3Image('uploads/photo.jpg', {
userId: 'user-123',
});
console.log(`Processing image: ${imageId}`);
const videoId = processor.processS3Video('uploads/video.mp4', {
userId: 'user-123',
targetQualities: ['1080p', '720p'],
});
console.log(`Processing video: ${videoId}`);
} catch (error) {
console.error('Failed to submit request:', error);
}
// Graceful shutdown
process.on('SIGINT', async () => {
console.log('Shutting down...');
await processor.disconnect();
process.exit(0);
});
}
main();Type Guards
The library provides type guards for narrowing message types:
import {
isProgressMessage,
isResultMessage,
isErrorMessage,
isImageResult,
isVideoResult,
isZipResult,
} from '@hff-ai/media-processor-client';
processor.on('completed', (msg) => {
if (isImageResult(msg.result)) {
console.log('Image versions:', msg.result.metadata?.imageVersions);
} else if (isVideoResult(msg.result)) {
console.log('Duration:', msg.result.duration);
} else if (isZipResult(msg.result)) {
console.log('Extracted files:', msg.result.extractedMedia.length);
}
});Development
Building
cd packages/media-processor-client
npm install
npm run buildTesting
npm testLinting
npm run lint
npm run lint:fixLicense
MIT
