Under the Hood: How We Built 104 Tools That Never Touch Our Servers
Dive deep into the technical architecture behind ConvertAll.io's 104 privacy-first tools: WebAssembly, Web Workers, client-side security, and the engineering challenges of building powerful tools that never see your data.

This technical deep dive explores the engineering behind ConvertAll.io's 104 browser-based tools, covering WebAssembly implementation, Web Workers for parallel processing, client-side security architecture, memory management, cross-browser compatibility challenges, and privacy-preserving performance monitoring with Grafana Faro. Learn how we solved complex technical challenges to build powerful tools that process everything locally while maintaining enterprise-grade performance and security.
Under the Hood: How We Built 104 Tools That Never Touch Our Servers

Building 104 functional, performant tools that run entirely in the browser while never touching our servers wasn't just an engineering challenge—it was a complete reimagining of how web applications should work. This technical deep dive reveals the architecture, engineering decisions, and solutions that power ConvertAll.io's privacy-first platform.
🏗️ Browser-Based Architecture Overview
The Core Philosophy: Client-Side Everything

// Traditional approach (what we DON'T do)
const processFile = async (file) => {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/process', {
method: 'POST',
body: formData
});
return response.blob();
};// Our approach (everything local)
const processFile = async (file) => {
const buffer = await file.arrayBuffer();
const result = await wasmModule.processInBrowser(buffer);
return new Blob([result]);
};
Multi-Tier Processing Architecture
Our architecture consists of four primary processing tiers:
1. JavaScript Layer: UI, file handling, and lightweight operations 2. WebAssembly (WASM) Layer: High-performance computing and complex algorithms 3. Web Workers Layer: Parallel processing and background tasks 4. Service Worker Layer: Offline capabilities and cachingThis tiered approach allows us to optimize for both performance and user experience while maintaining security isolation.
⚡ WebAssembly: The Performance Powerhouse
Why WebAssembly Was Non-Negotiable
When you're processing 100MB+ files or performing complex cryptographic operations entirely in the browser, JavaScript alone isn't enough. WebAssembly gives us near-native performance for compute-intensive tasks.
WASM Implementation Strategy
We compiled critical libraries from C/C++ and Rust to WebAssembly:
// Example: Image processing in Rust compiled to WASM
use wasm_bindgen::prelude::*;#[wasm_bindgen]
pub struct ImageProcessor {
width: u32,
height: u32,
data: Vec,
}#[wasm_bindgen]
impl ImageProcessor {
#[wasm_bindgen(constructor)]
pub fn new(width: u32, height: u32) -> ImageProcessor {
ImageProcessor {
width,
height,
data: vec![0; (width height 4) as usize],
}
} #[wasm_bindgen]
pub fn resize(&mut self, new_width: u32, new_height: u32) -> Vec {
// High-performance image resizing algorithm
// Returns processed image data
self.lanczos_resize(new_width, new_height)
}
}
Performance Benchmarks
Our WASM implementations show significant performance improvements:
Memory Management in WASM
One of the biggest challenges was managing memory efficiently across the JavaScript-WASM boundary:
class WASMMemoryManager {
constructor(wasmModule) {
this.module = wasmModule;
this.allocatedPointers = new Set();
}allocate(size) {
const ptr = this.module._malloc(size);
this.allocatedPointers.add(ptr);
return ptr;
}
free(ptr) {
if (this.allocatedPointers.has(ptr)) {
this.module._free(ptr);
this.allocatedPointers.delete(ptr);
}
}
cleanup() {
// Prevent memory leaks by freeing all allocated memory
this.allocatedPointers.forEach(ptr => this.module._free(ptr));
this.allocatedPointers.clear();
}
}
🔄 Web Workers: Parallel Processing Architecture
The Challenge of Main Thread Blocking
Processing large files on the main thread freezes the UI. Our solution: a sophisticated Web Worker system that handles heavy lifting while keeping the interface responsive.
Worker Pool Management
class WorkerPool {
constructor(workerScript, poolSize = navigator.hardwareConcurrency || 4) {
this.workers = [];
this.available = [];
this.tasks = [];
for (let i = 0; i < poolSize; i++) {
const worker = new Worker(workerScript);
worker.onmessage = this.handleWorkerMessage.bind(this);
this.workers.push(worker);
this.available.push(worker);
}
}async execute(task) {
return new Promise((resolve, reject) => {
this.tasks.push({ task, resolve, reject });
this.processQueue();
});
}
processQueue() {
if (this.tasks.length === 0 || this.available.length === 0) return;
const worker = this.available.pop();
const { task, resolve, reject } = this.tasks.shift();
const taskId = Math.random().toString(36);
worker.postMessage({ taskId, ...task });
// Store resolve/reject for async handling
worker.currentTask = { taskId, resolve, reject };
}
}
Chunked Processing for Large Files
For files larger than available memory, we implemented streaming chunk processing:
class ChunkedProcessor {
constructor(chunkSize = 8 1024 1024) { // 8MB chunks
this.chunkSize = chunkSize;
this.workerPool = new WorkerPool('/workers/chunk-processor.js');
} async processLargeFile(file) {
const chunks = Math.ceil(file.size / this.chunkSize);
const results = [];
for (let i = 0; i < chunks; i++) {
const start = i * this.chunkSize;
const end = Math.min(start + this.chunkSize, file.size);
const chunk = file.slice(start, end);
const result = await this.workerPool.execute({
type: 'PROCESS_CHUNK',
data: await chunk.arrayBuffer(),
chunkIndex: i,
isLastChunk: i === chunks - 1
});
results[i] = result;
}
// Combine results
return this.combineChunks(results);
}
}
🔒 Client-Side Security Implementation
Cryptographic Operations Without Server Trust
All cryptographic operations use the Web Crypto API, ensuring keys never exist outside the secure browser environment:
class SecureCrypto {
static async generateKeyPair(algorithm = 'RSA-PSS', keySize = 2048) {
const keyPair = await crypto.subtle.generateKey(
{
name: algorithm,
modulusLength: keySize,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256',
},
true, // extractable
['sign', 'verify']
);
return keyPair;
} static async secureMemoryWipe(buffer) {
// Overwrite sensitive data with random values
if (buffer instanceof ArrayBuffer) {
const view = new Uint8Array(buffer);
crypto.getRandomValues(view);
}
}
}
Input Validation and Sanitization
Every file upload goes through multiple validation layers:
class FileValidator {
static validate(file, expectedType) {
// Size validation
if (file.size > this.MAX_FILE_SIZE) {
throw new Error('File too large');
}
// MIME type validation
if (!this.isValidMimeType(file.type, expectedType)) {
throw new Error('Invalid file type');
}
// Magic number validation
return this.validateMagicNumbers(file);
} static async validateMagicNumbers(file) {
const header = await file.slice(0, 16).arrayBuffer();
const view = new Uint8Array(header);
// Check file signatures
const signatures = {
pdf: [0x25, 0x50, 0x44, 0x46], // %PDF
png: [0x89, 0x50, 0x4E, 0x47], // PNG
jpeg: [0xFF, 0xD8, 0xFF] // JPEG
};
// Validate against expected signatures
// Implementation details...
}
}
🗄️ Memory Management and Cleanup
The Browser Memory Challenge
Processing large files in browsers requires careful memory management to prevent crashes and maintain performance.
Streaming and Progressive Processing
class MemoryEfficientProcessor {
constructor() {
this.memoryThreshold = this.calculateMemoryThreshold();
this.processingQueue = [];
} calculateMemoryThreshold() {
// Estimate available memory (heuristic approach)
const totalMemory = performance.memory?.totalJSHeapSize ||
(navigator.deviceMemory || 4) 1024 1024 * 1024;
return Math.floor(totalMemory * 0.3); // Use 30% of available memory
}async processWithMemoryManagement(file) {
if (file.size > this.memoryThreshold) {
return this.streamingProcess(file);
} else {
return this.directProcess(file);
}
}
async streamingProcess(file) {
const stream = file.stream();
const reader = stream.getReader();
let result = new Uint8Array(0);
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
// Process chunk and append to result
const processed = await this.processChunk(value);
result = this.concatenateArrays(result, processed);
// Force garbage collection hint
if (typeof window !== 'undefined' && window.gc) {
window.gc();
}
}
} finally {
reader.releaseLock();
}
return result;
}
}
Garbage Collection Optimization
class GarbageCollectionManager {
static scheduleCleanup(callback) {
// Use requestIdleCallback for non-blocking cleanup
if (window.requestIdleCallback) {
window.requestIdleCallback(() => {
callback();
// Hint to browser for garbage collection
if (window.gc) window.gc();
}, { timeout: 1000 });
} else {
setTimeout(callback, 0);
}
} static clearSensitiveData(objects) {
objects.forEach(obj => {
if (obj instanceof ArrayBuffer) {
// Overwrite with zeros
new Uint8Array(obj).fill(0);
} else if (obj && typeof obj === 'object') {
// Clear object properties
Object.keys(obj).forEach(key => delete obj[key]);
}
});
}
}
🌍 Cross-Browser Compatibility Challenges
The Browser Fragmentation Problem
Supporting 104 tools across all modern browsers required solving numerous compatibility issues:
Feature Detection and Polyfills
class BrowserCompatibility {
static checkSupport() {
const features = {
webassembly: typeof WebAssembly !== 'undefined',
webworkers: typeof Worker !== 'undefined',
webcrypto: !!(window.crypto && window.crypto.subtle),
fileapi: !!(window.File && window.FileReader && window.FileList),
streams: !!(window.ReadableStream),
offscreencanvas: typeof OffscreenCanvas !== 'undefined'
};const unsupported = Object.entries(features)
.filter(([, supported]) => !supported)
.map(([feature]) => feature);
if (unsupported.length > 0) {
console.warn('Unsupported features:', unsupported);
return this.loadPolyfills(unsupported);
}
return Promise.resolve();
}
static async loadPolyfills(features) {
const polyfills = {
webcrypto: () => import('./polyfills/webcrypto-polyfill'),
streams: () => import('./polyfills/streams-polyfill'),
// More polyfills...
};
await Promise.all(
features
.filter(feature => polyfills[feature])
.map(feature => polyfills[feature]())
);
}
}
📊 Performance Optimization Techniques
Bundle Splitting and Lazy Loading
Each tool is loaded on-demand to minimize initial bundle size:
class ToolLoader {
static async loadTool(toolId) {
const toolMap = {
'pdf-converter': () => import('./tools/pdf/converter'),
'image-resizer': () => import('./tools/image/resizer'),
'audio-converter': () => import('./tools/audio/converter'),
// 101 more tools...
};if (!toolMap[toolId]) {
throw new Error(Tool ${toolId} not found
);
}
const toolModule = await toolMap[toolId]();
return new toolModule.default();
}
}
WebAssembly Module Caching
class WASMCache {
static cache = new Map();static async loadModule(moduleName) {
if (this.cache.has(moduleName)) {
return this.cache.get(moduleName);
}
const response = await fetch(/wasm/${moduleName}.wasm
);
const bytes = await response.arrayBuffer();
const module = await WebAssembly.compile(bytes);
this.cache.set(moduleName, module);
return module;
}
static async instantiate(moduleName, imports = {}) {
const module = await this.loadModule(moduleName);
return WebAssembly.instantiate(module, imports);
}
}
🔍 Performance Monitoring Without Privacy Invasion
Grafana Faro Integration: Observability That Respects Privacy
We implemented comprehensive performance monitoring using Grafana Faro while maintaining our privacy-first principles:
import { initializeFaro } from '@grafana/faro-web-sdk';class PrivacyPreservingTelemetry {
constructor() {
this.faro = initializeFaro({
url: process.env.GRAFANA_FARO_URL,
app: {
name: 'ConvertAll.io',
version: process.env.APP_VERSION,
},
instrumentations: [
// Only essential instrumentations
new TracingInstrumentation(),
new ErrorsInstrumentation(),
new WebVitalsInstrumentation(),
],
beforeSend: this.sanitizeData.bind(this),
});
}
sanitizeData(event) {
// Remove or mask sensitive information
if (event.meta?.file?.name) {
// Replace actual filename with generic pattern
event.meta.file.name = file.${event.meta.file.name.split('.').pop()}
;
}
// Round file sizes to protect privacy
if (event.meta?.file?.size) {
event.meta.file.size = Math.round(event.meta.file.size / 1024) * 1024;
}
// Remove user-specific identifiers
delete event.meta?.user;
delete event.meta?.session?.userAgent;
return event;
}
trackConversion(toolId, operationType, fileSize, duration) {
this.faro.api.pushEvent('conversion_completed', {
tool: toolId,
operation: operationType,
fileSize: this.roundSize(fileSize),
duration: Math.round(duration),
timestamp: Date.now(),
});
}
roundSize(bytes) {
// Round to nearest KB to preserve privacy
return Math.round(bytes / 1024) * 1024;
}
}
Custom Performance Metrics
class PerformanceTracker {
static trackToolPerformance(toolId, operation) {
const startTime = performance.now();
const startMemory = this.getMemoryUsage();return {
end: () => {
const endTime = performance.now();
const endMemory = this.getMemoryUsage();
const metrics = {
duration: endTime - startTime,
memoryDelta: endMemory - startMemory,
tool: toolId,
operation: operation,
};
// Send to Faro with privacy sanitization
window.telemetry.trackConversion(
toolId,
operation,
0, // No file size in performance metrics
metrics.duration
);
return metrics;
}
};
}
static getMemoryUsage() {
return performance.memory?.usedJSHeapSize || 0;
}
}
🛠️ Tool-Specific Technical Challenges
PDF Processing: The Complexity Beast
PDF files are notoriously complex. Our solution involved compiling PDF.js and custom C++ libraries to WebAssembly:
class PDFProcessor {
constructor() {
this.pdfLib = null;
this.initPromise = this.initialize();
}async initialize() {
// Load PDF processing WASM module
const wasmModule = await WASMCache.instantiate('pdf-processor');
this.pdfLib = wasmModule.instance.exports;
}
async convertToImage(pdfBuffer, options = {}) {
await this.initPromise;
const { dpi = 150, format = 'png' } = options;
// Allocate WASM memory
const inputPtr = this.pdfLib.malloc(pdfBuffer.length);
const inputView = new Uint8Array(
this.pdfLib.memory.buffer,
inputPtr,
pdfBuffer.length
);
inputView.set(new Uint8Array(pdfBuffer));
try {
// Call WASM function
const resultPtr = this.pdfLib.pdf_to_image(inputPtr, pdfBuffer.length, dpi);
const resultSize = this.pdfLib.get_result_size();
// Copy result from WASM memory
const result = new Uint8Array(
this.pdfLib.memory.buffer,
resultPtr,
resultSize
).slice();
return result;
} finally {
// Clean up WASM memory
this.pdfLib.free(inputPtr);
}
}
}
Image Processing: Multi-Format Support
Supporting dozens of image formats required a comprehensive codec system:
class ImageCodecManager {
static codecs = new Map();static async getCodec(format) {
if (!this.codecs.has(format)) {
const codec = await this.loadCodec(format);
this.codecs.set(format, codec);
}
return this.codecs.get(format);
}
static async loadCodec(format) {
const codecMap = {
'webp': () => WASMCache.instantiate('libwebp'),
'avif': () => WASMCache.instantiate('libavif'),
'heic': () => WASMCache.instantiate('libheif'),
'raw': () => WASMCache.instantiate('libraw'),
};
if (!codecMap[format]) {
throw new Error(Unsupported format: ${format}
);
}
return codecMap[format]();
}
}
Audio Processing: Real-Time Capabilities
Audio tools required real-time processing capabilities using Web Audio API combined with WebAssembly:
class AudioProcessor {
constructor() {
this.audioContext = new AudioContext();
this.processorNode = null;
}async createProcessor(algorithm) {
// Load audio processing WASM
const wasmModule = await WASMCache.instantiate('audio-processor');
// Create AudioWorklet processor
await this.audioContext.audioWorklet.addModule('/worklets/audio-processor.js');
this.processorNode = new AudioWorkletNode(
this.audioContext,
'audio-processor',
{
processorOptions: {
wasmModule: wasmModule,
algorithm: algorithm
}
}
);
return this.processorNode;
}
}
🚀 Offline Functionality Implementation
Service Worker Strategy
We implemented a sophisticated caching strategy for offline functionality:
// service-worker.js
class OfflineStrategy {
constructor() {
this.CACHE_NAME = 'convertall-v1';
this.CRITICAL_RESOURCES = [
'/',
'/js/app.js',
'/css/app.css',
'/wasm/core-processors.wasm'
];
}async install() {
const cache = await caches.open(this.CACHE_NAME);
await cache.addAll(this.CRITICAL_RESOURCES);
}
async fetch(request) {
// Network first for API calls
if (request.url.includes('/api/')) {
return this.networkFirst(request);
}
// Cache first for static resources
if (request.url.includes('/wasm/') || request.url.includes('/js/')) {
return this.cacheFirst(request);
}
// Stale while revalidate for tools
return this.staleWhileRevalidate(request);
}
async cacheFirst(request) {
const cached = await caches.match(request);
if (cached) return cached;
try {
const response = await fetch(request);
const cache = await caches.open(this.CACHE_NAME);
cache.put(request, response.clone());
return response;
} catch (error) {
return new Response('Offline', { status: 503 });
}
}
}
📈 Scalability Considerations
Tool Registration System
With 104 tools, we needed a scalable registration and discovery system:
class ToolRegistry {
static tools = new Map();
static categories = new Map();static register(toolConfig) {
const {
id,
name,
category,
description,
inputFormats,
outputFormats,
wasmModules,
workers,
loader
} = toolConfig;
this.tools.set(id, {
...toolConfig,
isLoaded: false,
instance: null
});
// Update category index
if (!this.categories.has(category)) {
this.categories.set(category, []);
}
this.categories.get(category).push(id);
}
static async getTool(id) {
const toolConfig = this.tools.get(id);
if (!toolConfig) {
throw new Error(Tool ${id} not found
);
}
if (!toolConfig.isLoaded) {
toolConfig.instance = await toolConfig.loader();
toolConfig.isLoaded = true;
}
return toolConfig.instance;
}
}
🔮 Future Technical Innovations
WebGPU Integration
We're preparing for WebGPU adoption for even more powerful processing:
class WebGPUProcessor {
constructor() {
this.device = null;
this.adapter = null;
}async initialize() {
if (!navigator.gpu) {
throw new Error('WebGPU not supported');
}
this.adapter = await navigator.gpu.requestAdapter();
this.device = await this.adapter.requestDevice();
}
async processWithGPU(data, shader) {
// GPU-accelerated processing for supported operations
const buffer = this.device.createBuffer({
size: data.byteLength,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
});
// Implementation details for GPU processing...
}
}
Progressive Web App Enhancements
We're continuously improving PWA capabilities:
// Enhanced PWA features
class PWAManager {
static async registerServiceWorker() {
if ('serviceWorker' in navigator) {
const registration = await navigator.serviceWorker.register('/sw.js');
// Background sync for failed operations
if ('sync' in window.ServiceWorkerRegistration.prototype) {
registration.sync.register('background-process');
} // Push notifications for long-running operations
if ('showNotification' in ServiceWorkerRegistration.prototype) {
// Request notification permission
const permission = await Notification.requestPermission();
if (permission === 'granted') {
this.setupNotifications(registration);
}
}
}
}
}
🎯 Engineering Lessons Learned
Performance Is Privacy
One unexpected insight: optimizing for performance often aligned perfectly with privacy goals. By processing everything locally, we eliminated network latency and gained fine-grained control over computational resources.
The Browser Is More Powerful Than You Think
Modern browsers are incredibly capable platforms. With WebAssembly, Web Workers, and modern APIs, you can build applications that rival desktop software in functionality while maintaining web accessibility.
Memory Management Is Critical
Browser memory management required careful consideration at every level. Streaming processing, aggressive cleanup, and smart caching strategies were essential for handling large files reliably.
User Experience Drives Technical Decisions
Every technical choice was evaluated through the lens of user experience. WebAssembly compilation times, Worker pool sizing, and progressive loading strategies all prioritized keeping the interface responsive.
🏆 The Technical Achievement
Building 104 tools that never touch our servers required solving problems at the intersection of performance, security, privacy, and user experience. The result is a platform that demonstrates what's possible when you prioritize user agency over data collection.
Our architecture proves that powerful, privacy-respecting applications aren't just possible—they're the future of web development. By leveraging WebAssembly, Web Workers, and modern browser APIs, we've created tools that are often faster and more secure than their server-based counterparts.
The technical foundation we've built supports not just our current 104 tools, but provides a scalable platform for the next generation of privacy-first web applications.
---
Want to explore the code behind these tools? Visit ConvertAll.io and open your browser's developer tools to see client-side processing in action. Every operation happens in your browser—no servers required.Related Posts
Discover the cutting-edge technologies revolutionizing online tools and transforming how we work. From AI-powered automation to privacy-first innovations, explore the game-changing developments every tech enthusiast needs to know about.
Introducing ConvertAll.io's comprehensive SSL certificate toolkit: generate self-signed certificates, convert private keys between formats, parse and validate certificates, and generate secure key pairs - all with privacy-first, browser-based processing.
Go behind the scenes with the ConvertAll.io team as they reflect on reaching 104 privacy-first tools, discuss technical challenges, and share what's coming next in this exclusive interview.
Ready to experience the tools mentioned in this post? Explore our complete toolkit of privacy-first conversion and manipulation tools.
Explore All Tools