パッケージの詳細

range-request-fetcher

cduque0MIT2.2.1

Reliable file fetcher with pause, resume, and abort controls for big files using chunked range requests, retries, and progress tracking

fetch, chunk, range-request, progress

readme

Range Request Fetcher

A JavaScript library for reliably fetching large files using chunked range requests, automatic retries, and progress tracking. Specifically designed to handle common network errors like ERR_HTTP2_PROTOCOL_ERROR and ERR_SSL_PROTOCOL_ERROR that frequently occur when downloading large files over unstable connections.

Handling download problems

This library specifically handles common network errors that occur during large file downloads:

ERR_HTTP2_PROTOCOL_ERROR

This error typically occurs when:

  • The HTTP/2 connection is disrupted or closed unexpectedly
  • The server terminates the stream prematurely
  • Network instability causes protocol-level issues

How range-request-fetcher helps:

  • Automatically retries failed requests with exponential backoff
  • Resumes downloads from the last successful byte position

ERR_SSL_PROTOCOL_ERROR

This error happens when:

  • SSL/TLS handshake fails or is interrupted
  • Certificate validation issues occur
  • Secure connection is dropped during transfer

How range-request-fetcher helps:

  • Implements intelligent retry logic for SSL failures
  • Maintains connection state to resume from interruption points
  • Uses chunked requests to minimize data loss on connection drops

These features make the library particularly reliable for downloading large files over unstable connections or from servers with intermittent issues

Features

  • Chunked fetching - Splits large files into chunks for improved reliability
  • Automatic retries - Automatically retries failed chunks
  • Progress tracking - Real-time callbacks for progress updates
  • Pause/Resume/Abort - Full download control with pause, resume, and abort functionality
  • Authentication support - Compatible with Bearer tokens and custom headers
  • Modern API - Uses File System Access API for saving files
  • Error handling - Robust handling of network and protocol errors

Installation

npm install range-request-fetcher

Basic Usage

import { rangeRequestFetcher } from 'range-request-fetcher';

// Basic fetch (Promise-based)
const download = rangeRequestFetcher({
  url: 'https://example.com/large-file.zip',
  fileName: 'my-download.zip'
});

await download.promise;
console.log('Download completed!');

Download Control Examples

Simple Progress Tracking

const download = rangeRequestFetcher({
  url: 'https://example.com/large-file.zip',
  fileName: 'my-download.zip',
  onProgress: (percent) => console.log(`Progress: ${percent}%`),
  onStatus: (status) => console.log(`Status: ${status}`)
});

await download.promise;

Pause and Resume

const download = rangeRequestFetcher({
  url: 'https://example.com/large-file.zip',
  fileName: 'my-download.zip',
  onProgress: (percent) => {
    console.log(`Progress: ${percent}%`);

    // Auto-pause at 50%
    if (percent === 50) {
      download.pause();
      console.log('Download paused at 50%');

      // Resume after 5 seconds
      setTimeout(() => {
        download.resume();
        console.log('Download resumed');
      }, 5000);
    }
  }
});

await download.promise;

Manual Control with UI

const download = rangeRequestFetcher({
  url: 'https://example.com/ubuntu-22.04.3-desktop-amd64.iso',
  fileName: 'ubuntu-22.04.3-desktop-amd64.iso',
  onProgress: (percent) => {
    document.getElementById('progress').style.width = `${percent}%`;
    document.getElementById('percent').textContent = `${percent}%`;
  },
  onStatus: (status) => {
    document.getElementById('status').textContent = status;
  }
});

// UI Controls
document.getElementById('pauseBtn').onclick = () => {
  download.pause();
  document.getElementById('pauseBtn').disabled = true;
  document.getElementById('resumeBtn').disabled = false;
};

document.getElementById('resumeBtn').onclick = () => {
  download.resume();
  document.getElementById('pauseBtn').disabled = false;
  document.getElementById('resumeBtn').disabled = true;
};

document.getElementById('abortBtn').onclick = () => {
  download.abort();
  console.log('Download cancelled');
};

// Check download state
setInterval(() => {
  console.log(`Paused: ${download.isPaused()}, Aborted: ${download.isAborted()}, Progress: ${download.getProgress()}%`);
}, 1000);

try {
  await download.promise;
  console.log('Download completed!');
} catch (error) {
  if (download.isAborted()) {
    console.log('Download was cancelled by user');
  } else {
    console.error('Download failed:', error.message);
  }
}

With Authentication

const download = rangeRequestFetcher({
  url: 'https://api.example.com/secure-file.zip',
  fileName: 'secure-file.zip',
  token: 'your-bearer-token',
  headers: {
    'X-API-Key': 'your-api-key'
  },
  onProgress: (percent) => console.log(`Progress: ${percent}%`)
});

// You can still control authenticated downloads
setTimeout(() => download.pause(), 5000);
setTimeout(() => download.resume(), 10000);

await download.promise;

API Reference

rangeRequestFetcher(options)

Downloads a file using chunked range requests with automatic retries and full download control.

Parameters

Parameter Type Required Default Description
url string - URL of the file to download
fileName string 'downloaded-file' Suggested name for the saved file
token string - Bearer token for authentication (automatically added to Authorization header)
headers object {} Custom HTTP headers
chunkSize number 104857600 (100MB) Size of each chunk in bytes
maxRetries number 10 Maximum number of retries per chunk
onProgress function () => {} Callback called with progress percentage (0-100)
onStatus function () => {} Callback called with status updates

Return Value

The function returns a control object with the following methods and properties:

Method/Property Type Description
promise Promise Main download promise to await
pause() function Pauses the download
resume() function Resumes the download
abort() function Cancels the download completely
isPaused() function Returns true if download is paused
isAborted() function Returns true if download was aborted
getProgress() function Returns current progress percentage (0-100)

onStatus States

  • 'preparing' - Getting file information
  • 'downloading' - Downloading chunks
  • 'paused' - Download is paused
  • 'aborted' - Download was cancelled
  • 'retrying ${start}-${end}, attempt ${retries}' - Retrying a specific chunk
  • 'finalizing' - Finalizing the download
  • 'done' - Download completed successfully
  • 'error' - Download error

Complete Example with Error Handling

import { rangeRequestFetcher } from 'range-request-fetcher';

const download = rangeRequestFetcher({
  url: 'https://releases.ubuntu.com/22.04/ubuntu-22.04.3-desktop-amd64.iso',
  fileName: 'ubuntu-22.04.3-desktop-amd64.iso',
  chunkSize: 50 * 1024 * 1024, // 50MB chunks
  maxRetries: 5,
  headers: {
    'User-Agent': 'MyApp/1.0'
  },
  onProgress: (percent) => {
    console.log(`Download progress: ${percent}%`);

    // Update UI
    const progressBar = document.getElementById('progress-bar');
    const progressText = document.getElementById('progress-text');

    if (progressBar) progressBar.style.width = `${percent}%`;
    if (progressText) progressText.textContent = `${percent}%`;
  },
  onStatus: (status) => {
    console.log(`Download status: ${status}`);

    const statusElement = document.getElementById('download-status');
    if (statusElement) statusElement.textContent = status;

    // Handle different states
    if (status === 'paused') {
      document.getElementById('pause-btn')?.setAttribute('disabled', 'true');
      document.getElementById('resume-btn')?.removeAttribute('disabled');
    } else if (status === 'downloading') {
      document.getElementById('pause-btn')?.removeAttribute('disabled');
      document.getElementById('resume-btn')?.setAttribute('disabled', 'true');
    }
  }
});

// Set up UI controls
document.getElementById('pause-btn')?.addEventListener('click', () => {
  download.pause();
  console.log('Download paused by user');
});

document.getElementById('resume-btn')?.addEventListener('click', () => {
  download.resume();
  console.log('Download resumed by user');
});

document.getElementById('cancel-btn')?.addEventListener('click', () => {
  download.abort();
  console.log('Download cancelled by user');
});

// Monitor download state
const monitor = setInterval(() => {
  const progress = download.getProgress();
  const paused = download.isPaused();
  const aborted = download.isAborted();

  console.log(`State - Progress: ${progress}%, Paused: ${paused}, Aborted: ${aborted}`);

  if (aborted || progress === 100) {
    clearInterval(monitor);
  }
}, 2000);

// Wait for completion
try {
  await download.promise;
  console.log('🎉 Download completed successfully!');
  clearInterval(monitor);
} catch (error) {
  clearInterval(monitor);

  if (download.isAborted()) {
    console.log('❌ Download was cancelled by user');
  } else {
    console.error('💥 Download failed:', error.message);

    // Show user-friendly error messages
    if (error.message.includes('ERR_HTTP2_PROTOCOL_ERROR')) {
      console.log('💡 Try again - HTTP/2 connection issue detected');
    } else if (error.message.includes('ERR_SSL_PROTOCOL_ERROR')) {
      console.log('💡 Try again - SSL connection issue detected');
    }
  }
}