Capacitor OTA 업데이트 아키텍처: 전략과 GitHub 릴리스 활용
Capacitor는 Google Play나 App Store의 심사 없이 모바일 앱의 JavaScript 번들을 업데이트할 수 있게 해줍니다. 네이티브 셸은 그대로 유지하면서, 제품 코드는 ZIP 아카이브를 통해 교체됩니다. 이 시스템은 GitHub 릴리스를 버전 소스로 사용하며, 다운로드를 위해 @capgo/capacitor-updater를 활용합니다. 실패 시 자동 롤백은 앱 실행 후 10초 이내에 notifyAppReady()를 호출함으로써 보장됩니다.
법적 제약과 한계
Google Play는 앱의 주요 목적이 변경되지 않는 한 해석형 코드(JavaScript) 업데이트를 허용합니다. Apple은 기능이 유지되고 보안 메커니즘이 우회되지 않는 한 JS 번들 다운로드를 허용합니다. 명확한 기준은 다음과 같습니다: android/ 또는 ios/ 디렉토리의 변경은 스토어를 통한 배포가 필요합니다.
설치 방법:
npm install @capgo/capacitor-updater
npx cap sync
capacitor.config.ts에서 자동 업데이트 비활성화:
const config: CapacitorConfig = {
plugins: {
CapacitorUpdater: {
autoUpdate: false,
},
},
};
번들 빌드:
npm run build:dist
npx @capgo/cli bundle zip io.your.app --name app-bundle.zip
새로운 아키텍처: FSD와 포트 & 어댑터
코드는 src/features/app-update/(비즈니스 로직)와 src/shared/api/(어댑터)로 분리됩니다. 기능은 인터페이스를 통해 작동하며, Capgo나 GitHub에 의존하지 않습니다. Zustand 스토어가 상태를 관리하고, UI 컴포넌트는 독립적으로 구성됩니다.
구조:
features/app-update/model/types.ts— 상태(IDLE,CHECKING,AVAILABLE)와 전략(NONE,NATIVE,PATCH)features/app-update/model/contracts.ts— 의존성 인터페이스shared/api/app-updater/capgo-updater-runtime.ts— 플러그인 래퍼shared/api/github/github-runtime.ts— GitHub API 요청
Semver 기반 업데이트 전략
업데이트 유형 결정:
const isReleaseHasNativeUpdate = (current: SemVer, remote: SemVer): boolean =>
remote.major > current.major || remote.minor > current.minor;
const isReleaseHasPatchUpdate = (current: SemVer, remote: SemVer): boolean =>
remote.major === current.major &&
remote.minor === current.minor &&
remote.patch > current.patch;
1.1.0vs1.0.2→NATIVE(스토어)1.0.3vs1.0.2→PATCH(OTA)
단일 소스로서의 GitHub 릴리스
GitHub API는 태그, 변경 로그, app-bundle.zip 링크를 반환합니다. 유스케이스는 버전을 파싱하고 에셋을 선택합니다:
export const checkForAvailableUpdate = async (deps: AppUpdateDependencies): Promise<CheckResult> => {
const releaseResult = await deps.githubRuntime.getLatestReleaseInfo();
// ...
const bundleAsset = assets.find(a => a.name === 'app-bundle.zip');
// ...
};
GitHub 어댑터:
export const createGithubRuntime = (): GithubRuntime => ({
getLatestReleaseInfo: async () => {
const response = await fetch(config.APP_RELEASE_URL);
const data: GithubLatestReleaseResponse = await response.json();
return { status: true, data };
},
});
Capgo 어댑터와 이벤트 처리
인터페이스는 비즈니스 로직을 플러그인으로부터 분리합니다:
export const createCapgoUpdaterRuntime = (): CapgoUpdaterRuntime => ({
downloadBundle: (url, version) => CapacitorUpdater.download({ url, version }),
notifyAppReady: () => CapacitorUpdater.notifyAppReady(),
addDownloadListener: (listener) => CapacitorUpdater.addListener('download', listener),
});
Zustand 스토어는 진행 상황을 구독합니다:
startDownloadUpdate: async () => {
set({ status: APP_UPDATE_STATUSES.DOWNLOADING, downloadProgress: 0 });
await capgoRuntime.addDownloadListener(({ percent }) => {
set({ downloadProgress: percent });
});
const bundle = await capgoRuntime.downloadBundle(bundleDownloadUrl, remoteVersion);
},
적용: CapacitorUpdater.set(bundle) + App.minimizeApp()으로 부드러운 재시작.
초기화와 UI
main.tsx에서 렌더링 후:
await initializeUpdater();
initializeUpdater()는 의존성을 생성하고, notifyAppReady()를 호출하며, 업데이트를 확인합니다. AppUpdateInfo 컴포넌트는 상태(AVAILABLE, DOWNLOADING, DOWNLOADED)에 따라 조건부로 표시됩니다.
핵심 포인트:
- OTA는 패치 버전에만 적용; 메이저/마이너 업데이트는 스토어를 통해 진행
notifyAppReady()를 10초 이내에 호출하여 롤백 방지- GitHub 릴리스는 버전 관리, CDN, API를 한곳에 통합
- 포트/어댑터 아키텍처로 테스트와 공급자 교체 간소화
- 네이티브 플러그인 이벤트를 통한 다운로드 진행률 표시
— Editorial Team
아직 댓글이 없습니다.