Saltar al contenido principal

Alternativa a cordova-plugin-file-transfer para Capacitor

Reemplaza cordova-plugin-file-transfer en apps Capacitor: @capacitor/file-transfer para transferencias simples, Capgo para uploader y downloader en background.

Alternativa a cordova-plugin-file-transfer para Capacitor

La mejor alternativa a cordova-plugin-file-transfer para una app Capacitor depende de una pregunta: ¿la transferencia debe seguir funcionando si la app pasa a background? Para uploads y downloads normales con progreso, usa el @capacitor/file-transfer plugin oficial. Para archivos grandes, redes inestables o transferencias que deben continuar cuando el usuario cambia de app, usa @capgo/capacitor-uploader y @capgo/capacitor-downloader, que se ejecutan sobre APIs nativas de transferencia en segundo plano. Esta guía los compara y mapea cada FileTransfer llamada.

¿Sigue deprecado cordova-plugin-file-transfer?

La historia es confusa, así que aquí tienes los hechos:

  • En 2017, el equipo de Apache Cordova anunció que planeaba deprecar el plugin y remitió a los desarrolladores a XMLHttpRequest Nivel 2.
  • El plugin nunca se marcó como deprecado en npm. La versión 2.0.0 se lanzó en septiembre de 2023, y la 2.0.1 en agosto de 2026. El repositorio de GitHub está activo.

Así que no está muerto. Pero en una app de Capacitor sigue siendo un plugin de Cordova que:

  • depende de cordova-plugin-file y su cdvfile:// y file:// modelo de ruta,
  • no encaja con los de Capacitor, Filesystem conversiones de URI y directorios,
  • has no background transfer support,
  • usa APIs de estilo callback.

Si estás migrando desde Cordova, reemplazarlo con plugins nativos de Capacitor elimina una de las últimas razones para mantenerlo. cordova-plugin-file.

Por qué “solo usa fetch” falla con archivos grandes

El reemplazo sugerido por Apache, XHR, funciona para JSON pequeños e imágenes. Pero falla en transferencias de archivos reales en una app híbrida:

  • Memoria. fetch y XHR te dan un Blob o ArrayBuffer en la WebView. Un video de 500 MB significa 500 MB en la memoria de la WebView, más una copia para escribirlo a través del bridge. En dispositivos Android de gama media, eso hace colapsar el renderer.
  • Suspensión. Cuando el usuario cambia de app, iOS suspende la WebView. Las peticiones en curso se bloquean o fallan.
  • Sin integración con el SO. Sin notificación de descarga del sistema, sin opción de solo Wi-Fi y sin reanudación tras una desconexión.

Los plugins nativos transmiten directamente entre el disco y la red, evitando que JavaScript interfiera en el flujo de datos.

Opciones comparadas

Necesidad cordova-plugin-file-transfer @capacitor/file-transfer @capgo/capacitor-uploader @capgo/capacitor-downloader
Descarga a disco Sí Sí No Sí
Subir desde el disco Sí Sí Sí No
Eventos de progreso Sí Sí (progress listener) Sí (events) Sí (downloadProgress)
Datos de formulario multipart Sí Sí (por defecto) Sí n/a
PUT binario puro (URLs presignadas) Sí Sí (configura Content-Type) Sí (uploadType: 'binary') n/a
Varios archivos en una sola petición No No Sí (files) n/a
Cancelar abort() No removeUpload stop
Pausar / reanudar No No No pause, resume
Reintentos No No maxRetries No
Sigue en segundo plano No Con la app activa Sí Sí
Solo Wi-Fi No No No network: 'wifi-only'
Detalles del error HTTP http_status, body httpStatus, body Estado code en eventos String de error en eventos
Implementación web No Sí Sí (basada en fetch) No

La mayoría de las apps usan @capacitor/file-transfer para transferencias pequeñas visibles para el usuario y los plugins Capgo para colas de upload de media y descargas offline.

Opción 1: @capacitor/file-transfer

bun add @capacitor/file-transfer @capacitor/filesystem
bunx cap sync

El plugin requiere una ruta nativa completa. Consíguela en @capacitor/filesystem.

Descargar

import { FileTransfer } from '@capacitor/file-transfer';
import { Filesystem, Directory } from '@capacitor/filesystem';

export async function downloadReport(url: string) {
  const { uri } = await Filesystem.getUri({
    directory: Directory.Data,
    path: 'reports/latest.pdf',
  });

  const progress = await FileTransfer.addListener('progress', (p) => {
    if (p.type === 'download' && p.lengthComputable) {
      console.log(`Downloaded ${Math.round((p.bytes / p.contentLength) * 100)}%`);
    }
  });

  try {
    const result = await FileTransfer.downloadFile({
      url,
      path: uri,
      progress: true,
      headers: { Authorization: 'Bearer <token>' },
    });
    return result.path;
  } catch (error: any) {
    if (error.code === 'OS-PLUG-FLTR-0010') {
      console.error('HTTP error', error.data?.httpStatus, error.data?.body);
    }
    throw error;
  } finally {
    await progress.remove();
  }
}

Subir

export async function uploadAvatar(path: string) {
  const result = await FileTransfer.uploadFile({
    url: 'https://api.example.com/v1/avatar',
    path,
    fileKey: 'avatar',
    mimeType: 'image/jpeg',
    headers: { Authorization: 'Bearer <token>' },
    progress: true,
  });
  console.log(result.responseCode, result.response);
}

Las subidas son multipart por defecto. Para un stream raw, por ejemplo a una URL prefirmada de S3, establece method: 'PUT' y uno explícito Content-Type header explícito como application/octet-stream.

Los códigos de error siguen el OS-PLUG-FLTR-XXXX formato. Los más útiles: 0007 archivo no encontrado, 0008 conexión fallida, 0010 estado de error HTTP con httpStatus y body en error.data.

Opción 2: subidas en segundo plano con @capgo/capacitor-uploader

bun add @capgo/capacitor-uploader
bunx cap sync
import { Uploader } from '@capgo/capacitor-uploader';

await Uploader.addListener('events', async (event) => {
  switch (event.name) {
    case 'uploading':
      console.log(event.id, `${event.payload.percent}%`);
      break;
    case 'completed':
      console.log(event.id, 'done', event.payload.statusCode);
      break;
    case 'failed':
      console.error(event.id, event.payload.error);
      break;
  }
  // Completed and failed events are cached natively until acknowledged
  if (event.eventId) {
    await Uploader.acknowledgeEvent({ eventId: event.eventId });
  }
});

const { id } = await Uploader.startUpload({
  filePath: 'file:///path/to/video.mp4',
  serverUrl: 'https://api.example.com/v1/videos',
  method: 'POST',
  uploadType: 'multipart',
  fileField: 'video',
  parameters: { albumId: '7' },
  headers: { Authorization: 'Bearer <token>' },
  maxRetries: 3,
  notificationTitle: 'Uploading video', // Android only
});

Para URLs presignadas, usa method: 'PUT', que por defecto usa un cuerpo binario:

await Uploader.startUpload({
  filePath,
  serverUrl: presignedUrl,
  method: 'PUT',
  headers: { 'Content-Type': 'video/mp4' },
  mimeType: 'video/mp4',
});

Varios archivos en una sola petición multipart:

await Uploader.startUpload({
  serverUrl: 'https://api.example.com/v1/photos',
  method: 'POST',
  uploadType: 'multipart',
  files: [
    { filePath: photo1, fieldName: 'images[]', mimeType: 'image/jpeg' },
    { filePath: photo2, fieldName: 'images[]', mimeType: 'image/jpeg' },
  ],
  headers: { Authorization: 'Bearer <token>' },
});

Cancelar con Uploader.removeUpload({ id }).

¿Por qué confirmar los eventos? Los eventos completados y fallidos se guardan en una caché nativa persistente, para que puedan entregarse de nuevo si la app se cerró o pasó a segundo plano antes de procesarlos. Confirmarlos elimina el evento para que no se vuelva a emitir la próxima vez que se inicialice el plugin.

iOS setup. El uploader utiliza un proceso en segundo plano URLSession. Muchas apps no requieren configuración extra. Si las subidas deben continuar tras suspender la app, añade fetch a UIBackgroundModes. No lo añadas processing a menos que otra funcionalidad lo use BGTaskScheduler, ya que App Store Connect rechaza builds que lo declaren sin BGTaskSchedulerPermittedIdentifiers.

Opción 3: descargas en segundo plano con @capgo/capacitor-downloader

bun add @capgo/capacitor-downloader
bunx cap sync
import { CapacitorDownloader } from '@capgo/capacitor-downloader';

await CapacitorDownloader.addListener('downloadProgress', ({ id, progress }) => {
  console.log(id, `${progress}%`);
});
await CapacitorDownloader.addListener('downloadCompleted', ({ id }) => {
  console.log(id, 'completed');
});
await CapacitorDownloader.addListener('downloadFailed', ({ id, error }) => {
  console.error(id, error);
});

await CapacitorDownloader.download({
  id: 'course-42',
  url: 'https://cdn.example.com/courses/42.zip',
  destination: 'courses/42.zip',
  headers: { Authorization: 'Bearer <token>' },
  network: 'wifi-only',
  priority: 'normal',
  notification: 'progress', // Android: remove the system notification when done
});

// Later
await CapacitorDownloader.pause({ id: 'course-42' });
await CapacitorDownloader.resume({ id: 'course-42' });
const status = await CapacitorDownloader.checkStatus({ id: 'course-42' });
console.log(status.state, status.progress);

Una ruta relativa destination se resuelve en el directorio Documents en iOS y en el directorio de archivos externos de la app en Android. También puedes pasar una ruta absoluta o una file:// URL. Usa getFileInfo({ path }) para leer el tamaño y el MIME type tras la descarga, y stop({ id }) para cancelar y borrar datos parciales.

Migración desde cordova-plugin-file-transfer

Descargar

// Before
const ft = new FileTransfer();
ft.onprogress = (e) => e.lengthComputable && setProgress(e.loaded / e.total);
ft.download(
  encodeURI(url),
  cordova.file.dataDirectory + 'report.pdf',
  (entry) => console.log(entry.toURL()),
  (err) => console.error(err.code, err.http_status),
  false,
  { headers: { Authorization: 'Bearer <token>' } },
);
// After
const { uri } = await Filesystem.getUri({ directory: Directory.Data, path: 'report.pdf' });
await FileTransfer.downloadFile({
  url,
  path: uri,
  progress: true,
  headers: { Authorization: 'Bearer <token>' },
});

Subir

// Before
const options = new FileUploadOptions();
options.fileKey = 'file';
options.fileName = 'photo.jpg';
options.mimeType = 'image/jpeg';
options.params = { albumId: '7' };
options.headers = { Authorization: 'Bearer <token>' };
new FileTransfer().upload(fileUrl, encodeURI(serverUrl), onSuccess, onError, options);
// After, foreground
await FileTransfer.uploadFile({
  url: serverUrl,
  path: fileUrl,
  fileKey: 'file',
  mimeType: 'image/jpeg',
  params: { albumId: '7' },
  headers: { Authorization: 'Bearer <token>' },
});

// After, background
await Uploader.startUpload({
  filePath: fileUrl,
  serverUrl,
  uploadType: 'multipart',
  fileField: 'file',
  parameters: { albumId: '7' },
  headers: { Authorization: 'Bearer <token>' },
});

Ten en cuenta que params en @capacitor/file-transfer son parámetros de consulta URL. Si tu servidor espera campos de formulario, usa el uploader de Capgo, parametersque se envían como campos multipart form.

Tabla de mapeo

cordova-plugin-file-transfer @capacitor/file-transfer plugins de Capgo
download(source, target, ...) downloadFile({ url, path }) CapacitorDownloader.download({ id, url, destination })
upload(file, server, ...) uploadFile({ url, path }) Uploader.startUpload({ filePath, serverUrl })
onprogress addListener('progress') addListener('events') / addListener('downloadProgress')
abort() no disponible removeUpload({ id }) / stop({ id })
options.fileKey fileKey fileField o files[].fieldName
options.params params (query string) parameters (campos de formulario)
options.httpMethod method method (POST o PUT)
options.chunkedMode chunkedMode Gestionado de forma nativa
cordova.file.dataDirectory Filesystem.getUri({ directory: Directory.Data }) Ruta relativa o file:// URL
FileTransferError.http_status error.data.httpStatus event.payload.statusCode

Rutas: lo que rompe las migraciones

Cordova code pasa cdvfile:// o cordova.file.* rutas por todas partes. En Capacitor:

  • Obtén rutas nativas de Filesystem.getUri() o del plugin que creó el archivo (camera, file picker, recorder).
  • Para mostrar una imagen descargada en la WebView, convierte la ruta nativa con Capacitor.convertFileSrc(path).
  • Capgo tiene un plugin de archivos y un file picker si @capacitor/filesystem does not cover your case.

Solución de problemas

El upload funciona en Android pero falla en iOS con un error de archivo no encontrado. Pasaste una capacitor:// URL de WebView en lugar de una file:// ruta nativa. Usa la ruta de Filesystem.getUri().

El servidor rechaza el upload con un 415. Discrepancia entre Multipart y raw body. Para URLs presigned usa PUT con un explícito Content-Type.

Las subidas en segundo plano fallan en iOS. El usuario cierra la app a la fuerza, cancelando las sesiones en segundo plano, o fetch falta el modo background cuando lo necesitas.

Android muestra una notificación de descarga bloqueada. Configurar notification: 'progress' o 'hidden' en el downloader.

Crasheos de memoria en descargas grandes. You are still using fetch o leyendo el archivo como base64 a través del bridge. Cambia a una descarga nativa a disco.

Lanza la migración

Cambiar plugins nativos requiere un nuevo build de la tienda, lo cual Build con Capgo puede ejecutarse en la nube para iOS y Android. Después, el JavaScript que gestiona los uploads y downloads puede corregirse y mejorarse con Live Updates de Capgo. Para el resto de la migración de Cordova a Capacitor, consulta nuestra guía de migración, y para otros SDK nativos de pago o legacy, las alternativas de plugins de Ionic enterprise.

Live Updates para apps de Capacitor

Si hay un bug en la capa web, publica el fix con Capgo en lugar de esperar días a la aprobación de la app store. Los usuarios reciben la actualización en segundo plano mientras los cambios nativos siguen el proceso de revisión normal.

Lo último de nuestro blog

Capgo te da los mejores insights para crear una app móvil realmente profesional.