본문 바로가기

Capacitor를 위한 cordova-plugin-file-transfer 대안

cordova-plugin-file-transfer를 Capacitor 앱에서 대체하세요: 간단한 전송에는 @capacitor/file-transfer, 백그라운드 작업에는 Capgo uploader와 downloader를 사용하세요.

Cordova 앱을 Capacitor로 마이그레이션하는 일러스트, 파일 전송 플러그인 가이드용

Capacitor 앱에 가장 적합한 cordova-plugin-file-transfer 대안은 한 가지 질문에 달려 있습니다: 앱이 백그라운드로 전환돼도 전송이 계속돼야 하나요? 진행률 표시가 있는 일반 업로드와 다운로드에는 공식 @capacitor/file-transfer 플러그인을 사용하세요. 대용량 파일, 불안정한 네트워크, 또는 사용자가 앱을 전환해도 계속 진행되어야 하는 전송에는 @capgo/capacitor-uploader 및 @capgo/capacitor-downloader, 네이티브 백그라운드 전송 API로 동작합니다. 이 가이드에서는 이들을 비교하고 모든 FileTransfer 호출을 매핑합니다.

cordova-plugin-file-transfer는 아직 deprecated인가요?

그동안의 역사는 혼란스러우니, 사실만 정리하면 다음과 같습니다:

  • 2017년 Apache Cordova 팀은 해당 플러그인을 지원 중단(deprecate)할 계획이라고 발표하며 개발자들에게 XMLHttpRequest Level 2를 안내했습니다.
  • npm에서 이 플러그인이 지원 중단된 것으로 표시된 적은 없습니다. 2.0.0 버전은 2023년 9월에, 2.0.1 버전은 2026년 8월에 출시되었습니다. GitHub 저장소도 활발히 운영 중입니다.

따라서 완전히 중단된 것은 아닙니다. 하지만 Capacitor 앱에서는 여전히 다음과 같은 Cordova 플러그인입니다:

  • 에 의존하며 cordova-plugin-file 및 해당 cdvfile:// 및 file:// path 모델,
  • 은 Capacitor의 Filesystem 디렉터리 및 URI 변환,
  • 백그라운드 전송을 지원하지 않으며,
  • 콜백 스타일 API를 사용합니다.

Cordova에서 마이그레이션 중이라면, 이를 네이티브 Capacitor 플러그인으로 교체하면 유지해야 할 마지막 이유 중 하나가 사라집니다. cordova-plugin-file.

대용량 파일에서 “그냥 fetch 쓰면 되지”가 안 통하는 이유

Apache가 권장하는 대안인 XHR은 작은 JSON과 이미지에는 잘 작동합니다. 하지만 하이브리드 앱에서 실제 파일 전송 작업에는 실패합니다:

  • 메모리. fetch 그리고 XHR은 WebView에서 Blob 또는 ArrayBuffer 를 제공합니다. 500MB 영상은 WebView 메모리에 500MB를 차지한 뒤, 브리지를 통해 쓰기 위해 복사됩니다. 중급 Android 기기에서는 렌더러가 충돌합니다.
  • 일시 중단. 사용자가 앱을 전환하면 iOS는 WebView를 일시 중단합니다. 진행 중인 요청은 멈추거나 실패합니다.
  • OS 통합 없음. 시스템 다운로드 알림도, Wi-Fi 전용 옵션도, 연결이 끊긴 뒤 이어받기도 없습니다.

Native 플러그인은 디스크와 네트워크 사이에서 직접 스트리밍하므로 JavaScript가 데이터 경로에 끼어들지 않습니다.

옵션 비교

필요 기능 cordova-plugin-file-transfer @capacitor/file-transfer @capgo/capacitor-uploader @capgo/capacitor-downloader
디스크로 다운로드 지원 지원 아니요 지원
디스크에서 업로드 지원 지원 지원 아니요
진행 이벤트 지원 예 (progress 리스너) 예 (events) 예 (downloadProgress)
멀티파트 폼 데이터 지원 예 (기본값) 지원 해당 없음
Raw binary PUT (presigned URL) 지원 예 (설정 Content-Type) 예 (uploadType: 'binary') 해당 없음
한 번의 요청으로 여러 파일 아니요 아니요 예 (files) 해당 없음
취소 abort() 아니요 removeUpload stop
일시 중지 / 재개 아니요 아니요 아니요 pause, resume
재시도 아니요 아니요 maxRetries 아니요
백그라운드에서 계속 실행 아니요 앱이 활성화된 동안 지원 지원
Wi-Fi 전용 아니요 아니요 아니요 network: 'wifi-only'
HTTP 에러 상세 정보 http_status, body httpStatus, body 이벤트 내 code 상태 이벤트 내 에러 문자열
웹 구현 방식 아니요 지원 예 (fetch 기반) 아니요

대부분의 앱은 결국 @capacitor/file-transfer 사용자에게 보이는 소규모 전송과 미디어 업로드 큐, 오프라인 콘텐츠 다운로드에는 Capgo 플러그인을 사용합니다.

옵션 1: @capacitor/file-transfer

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

플러그인에는 전체 네이티브 경로가 필요합니다. 다음에서 가져오세요: @capacitor/filesystem.

다운로드

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();
  }
}

업로드

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);
}

기본적으로 업로드는 multipart입니다. 예를 들어 S3 presigned URL로 raw stream을 전송하려면 다음을 설정하세요: method: 'PUT' 그리고 명시적인 Content-Type 헤더 예: application/octet-stream.

에러 코드는 다음 OS-PLUG-FLTR-XXXX 형식을 따릅니다. 주요 에러: 0007 파일을 찾을 수 없음, 0008 연결 실패, 0010 다음이 포함된 HTTP 에러 상태: httpStatus 및 body 에서 error.data.

옵션 2: @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
});

presigned URL의 경우 method: 'PUT'을(를) 사용하세요. 기본값은 바이너리 바디입니다:

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

하나의 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>' },
});

다음으로 취소: Uploader.removeUpload({ id }).

왜 이벤트를 확인(acknowledge)해야 할까요? 완료 및 실패 이벤트는 영구 네이티브 캐시에 저장되므로, 앱이 이벤트를 처리하기 전에 종료되거나 백그라운드로 전환되면 다시 전달될 수 있습니다. Acknowledge하면 이벤트가 제거되어 플러그인이 다음에 초기화될 때 다시 브로드캐스트되지 않습니다.

iOS 설정. 업로더는 백그라운드 URLSession을 사용합니다. 많은 앱은 추가 설정이 필요하지 않습니다. 앱이 일시정지된 후에도 업로드를 계속하려면 fetch 대상 UIBackgroundModes를 추가하세요. 다음 항목은 추가하지 마세요: processing 단, 다른 기능이 BGTaskScheduler을 사용하는 경우는 예외입니다. 왜냐하면 App Store Connect는 해당 항목 없이 이를 선언한 빌드를 거부하기 때문입니다. BGTaskSchedulerPermittedIdentifiers.

옵션 3: @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);

상대 경로 destination 는 iOS에서 앱의 Documents 디렉터리, Android에서 앱 전용 외부 파일 디렉터리로 해석됩니다. 절대 경로 또는 file:// URL. 사용 getFileInfo({ path }) 다운로드 후 파일 크기와 MIME 타입을 읽고, stop({ id }) 부분 데이터를 취소하고 삭제하기 위해서입니다.

cordova-plugin-file-transfer에서 마이그레이션하기

다운로드

// 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>' },
});

업로드

// 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>' },
});

참고로, params 에서 @capacitor/file-transfer 는 URL 쿼리 파라미터입니다. 서버가 form 필드를 기대한다면 Capgo uploader의 필드를 사용하세요. parameters, 이 필드들은 multipart form 필드로 전송됩니다.

매핑 테이블

cordova-plugin-file-transfer @capacitor/file-transfer 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() 사용 불가 removeUpload({ id }) / stop({ id })
options.fileKey fileKey fileField 또는 files[].fieldName
options.params params (쿼리 문자열) parameters (폼 필드)
options.httpMethod method method (POST 또는 PUT)
options.chunkedMode chunkedMode 네이티브로 처리됨
cordova.file.dataDirectory Filesystem.getUri({ directory: Directory.Data }) 상대 경로 또는 file:// URL
FileTransferError.http_status error.data.httpStatus event.payload.statusCode

경로: 마이그레이션을 깨뜨리는 부분

Cordova code 전달 cdvfile:// 또는 cordova.file.* 여러 곳에 경로가 있습니다. Capacitor에서는:

  • 네이티브 경로를 가져오는 방법: Filesystem.getUri() 또는 파일을 생성한 플러그인(카메라, 파일 선택기, 녹음기)에서 가져옵니다.
  • WebView에서 다운로드된 이미지를 표시하려면 네이티브 경로를 변환해야 합니다: Capacitor.convertFileSrc(path).
  • Capgo에는 파일 플러그인 및 파일 선택기 만약 @capacitor/filesystem 해당되지 않는다면.

문제 해결

Android에서는 업로드가 작동하지만, iOS에서는 파일을 찾을 수 없다는 오류가 발생합니다. 전달한 값은 capacitor:// 네이티브가 아닌 WebView URL file:// 경로입니다. 다음 경로를 사용하세요: Filesystem.getUri().

서버가 415로 업로드를 거부합니다. Multipart와 raw body가 일치하지 않습니다. presigned URL을 사용하려면 PUT 명시적인 Content-Type.

iOS에서는 백그라운드 업로드가 중단됩니다. 사용자가 앱을 강제 종료해 백그라운드 세션이 취소되거나, fetch 필요할 때 백그라운드 모드가 빠져 있습니다.

Android에서는 다운로드 알림이 멈춘 채 표시됩니다. 설정 notification: 'progress' 또는 'hidden' 다운로더에서

대용량 다운로드 시 메모리 크래시가 발생합니다. 아직도 사용 중이거나 fetch 브릿지를 통해 파일을 base64로 읽고 있다면, 디스크에 저장하는 네이티브 다운로드로 전환하세요.

마이그레이션 배포하기

네이티브 플러그인을 교체하려면 스토어에 새 빌드를 업로드해야 하며, Capgo 빌드 iOS와 Android 모두에서 클라우드에서 실행할 수 있습니다. 이후 업로드와 다운로드를 제어하는 JavaScript 코드는 Capgo Live Update 사용하기로 수정 및 개선할 수 있습니다. Cordova에서 Capacitor로의 전환에 대한 나머지 내용은 마이그레이션 가이드을 참고하고, 기타 유료 또는 레거시 네이티브 SDK의 경우 Ionic Enterprise 플러그인 대안.

Capacitor 앱용 Live updates

웹 레이어 버그가 라이브 상태라면, 앱 스토어 승인을 며칠씩 기다리지 말고 Capgo를 통해 수정 사항을 배포하세요. 네이티브 변경 사항은 정상 리뷰 절차를 그대로 따르는 반면, 사용자는 백그라운드에서 업데이트를 받습니다.

블로그 최신 글

Capgo는 정말 프로페셔널한 모바일 앱을 만드는 데 필요한 최고의 인사이트를 제공합니다.