svelte-firebase-upload
Version:
Enterprise-grade file upload manager for Svelte with Firebase Storage integration, featuring concurrent uploads, resumable transfers, validation, health monitoring, and plugin system
196 lines (195 loc) • 7.43 kB
JavaScript
export class BandwidthManager {
_config;
_throttleInfo;
_isThrottling = false;
_uploadQueue = [];
_currentBandwidth = 0;
_bandwidthHistory = [];
_throttleTimeout;
constructor(config = {}) {
this._config = this._validateConfig({
maxBandwidthMbps: 10, // 10 Mbps default
adaptiveBandwidth: true,
throttleInterval: 100, // 100ms intervals
...config
});
this._throttleInfo = {
bytesPerSecond: 0,
lastUpdate: Date.now(),
queue: []
};
}
_validateConfig(config) {
const warnings = [];
// Validate maxBandwidthMbps
if (typeof config.maxBandwidthMbps !== 'number' || config.maxBandwidthMbps < 0.1) {
warnings.push('maxBandwidthMbps must be at least 0.1');
config.maxBandwidthMbps = Math.max(0.1, config.maxBandwidthMbps || 10);
}
else if (config.maxBandwidthMbps > 1000) {
warnings.push('maxBandwidthMbps exceeds reasonable maximum (1000 Mbps)');
config.maxBandwidthMbps = 1000;
}
// Validate throttleInterval
if (typeof config.throttleInterval !== 'number' || config.throttleInterval < 10) {
warnings.push('throttleInterval must be at least 10ms');
config.throttleInterval = Math.max(10, config.throttleInterval || 100);
}
else if (config.throttleInterval > 5000) {
warnings.push('throttleInterval exceeds recommended maximum (5000ms)');
config.throttleInterval = 5000;
}
// Validate adaptiveBandwidth
if (typeof config.adaptiveBandwidth !== 'boolean') {
warnings.push('adaptiveBandwidth must be a boolean');
config.adaptiveBandwidth = true;
}
// Log warnings
if (warnings.length > 0) {
console.warn('[BandwidthManager] Configuration warnings:', warnings);
}
return config;
}
// Throttle upload based on bandwidth limits
async throttleUpload(bytesToUpload) {
if (!this._isThrottling) {
this._isThrottling = true;
this._startThrottling();
}
return new Promise((resolve) => {
this._uploadQueue.push(() => {
this._processUpload(bytesToUpload);
resolve();
});
});
}
// Update bandwidth usage
updateBandwidthUsage(bytesUploaded, timeMs) {
const bytesPerSecond = (bytesUploaded / timeMs) * 1000;
this._currentBandwidth = bytesPerSecond;
// Keep history for adaptive bandwidth
this._bandwidthHistory.push(bytesPerSecond);
if (this._bandwidthHistory.length > 10) {
this._bandwidthHistory.shift();
}
// Update throttle info
this._throttleInfo.bytesPerSecond = bytesPerSecond;
this._throttleInfo.lastUpdate = Date.now();
}
// Get current bandwidth usage
getCurrentBandwidth() {
return this._currentBandwidth;
}
// Get average bandwidth over time
getAverageBandwidth() {
if (this._bandwidthHistory.length === 0)
return 0;
const sum = this._bandwidthHistory.reduce((a, b) => a + b, 0);
return sum / this._bandwidthHistory.length;
}
// Check if we're within bandwidth limits
isWithinLimits() {
const maxBytesPerSecond = (this._config.maxBandwidthMbps * 1024 * 1024) / 8; // Convert Mbps to bytes/s
return this._currentBandwidth <= maxBytesPerSecond;
}
// Adaptive bandwidth adjustment
adjustBandwidth() {
if (!this._config.adaptiveBandwidth)
return;
const maxBytesPerSecond = (this._config.maxBandwidthMbps * 1024 * 1024) / 8;
const averageBandwidth = this.getAverageBandwidth();
// If we're consistently under the limit, we can increase
if (averageBandwidth < maxBytesPerSecond * 0.8) {
this._config.maxBandwidthMbps = Math.min(this._config.maxBandwidthMbps * 1.1, this._config.maxBandwidthMbps * 2 // Don't double it
);
}
// If we're consistently over the limit, decrease
else if (averageBandwidth > maxBytesPerSecond * 0.95) {
this._config.maxBandwidthMbps = Math.max(this._config.maxBandwidthMbps * 0.9, this._config.maxBandwidthMbps * 0.5 // Don't halve it
);
}
}
// Set bandwidth limit
setBandwidthLimit(mbps) {
this._config.maxBandwidthMbps = mbps;
}
// Get recommended chunk size based on bandwidth
getRecommendedChunkSize() {
const maxBytesPerSecond = (this._config.maxBandwidthMbps * 1024 * 1024) / 8;
const chunkTime = 2; // 2 seconds per chunk
return Math.min(maxBytesPerSecond * chunkTime, 5 * 1024 * 1024); // Max 5MB
}
// Pause throttling
pause() {
this._isThrottling = false;
}
// Resume throttling
resume() {
if (!this._isThrottling) {
this._isThrottling = true;
this._startThrottling();
}
}
// Cleanup and destroy
destroy() {
this._isThrottling = false;
// Clear timeout
if (this._throttleTimeout) {
clearTimeout(this._throttleTimeout);
this._throttleTimeout = undefined;
}
// Clear queues and history
this._uploadQueue.length = 0;
this._bandwidthHistory.length = 0;
this._currentBandwidth = 0;
// Reset throttle info
this._throttleInfo = {
bytesPerSecond: 0,
lastUpdate: Date.now(),
queue: []
};
}
// Get bandwidth statistics
getBandwidthStats() {
const maxBytesPerSecond = (this._config.maxBandwidthMbps * 1024 * 1024) / 8;
const peak = Math.max(...this._bandwidthHistory, 0);
const utilization = maxBytesPerSecond > 0 ? (this._currentBandwidth / maxBytesPerSecond) * 100 : 0;
return {
current: this._currentBandwidth,
average: this.getAverageBandwidth(),
peak,
limit: maxBytesPerSecond,
utilization
};
}
// Private methods
_startThrottling() {
const processQueue = () => {
if (!this._isThrottling)
return;
const maxBytesPerSecond = (this._config.maxBandwidthMbps * 1024 * 1024) / 8;
const now = Date.now();
const timeDiff = now - this._throttleInfo.lastUpdate;
// Calculate how many bytes we can process
const bytesAllowed = (maxBytesPerSecond * timeDiff) / 1000;
if (this._throttleInfo.bytesPerSecond <= bytesAllowed && this._uploadQueue.length > 0) {
const upload = this._uploadQueue.shift();
if (upload) {
upload();
}
}
this._throttleInfo.lastUpdate = now;
// Continue throttling
this._throttleTimeout = setTimeout(processQueue, this._config.throttleInterval);
};
processQueue();
}
_processUpload(bytes) {
// Update throttle info
this._throttleInfo.bytesPerSecond += bytes;
// Reset after a second
setTimeout(() => {
this._throttleInfo.bytesPerSecond = Math.max(0, this._throttleInfo.bytesPerSecond - bytes);
}, 1000);
}
}