클라이언트 앱 업데이트 전략 가이드
작성일: 2026-04-06 Last Updated: 2026-04-06 대상: Browser Extension, Tauri Desktop, Flutter Mobile
목차
1. 자동 업데이트 전략 개요
업데이트 유형 분류
| 유형 | 설명 | 사용자 개입 | 예시 |
|---|---|---|---|
| Silent | 백그라운드 자동 설치 | 없음 | 보안 패치, 버그 수정 |
| Passive | 알림 표시 후 자동 적용 | 최소 (재시작) | 마이너 업데이트 |
| Active | 사용자 확인 필요 | 필수 (확인 버튼) | 메이저 업데이트 |
| Forced | 사용 차단 + 업데이트 강제 | 필수 | 보안 취약점, API 호환 |
업데이트 판단 플로우
앱 시작 / 주기적 체크
│
▼
[서버에 최신 버전 확인] ──(동일)──→ 종료
│ (새 버전 있음)
▼
[현재 버전 < 최소 요구 버전?]
│ │
(Yes) (No)
│ │
▼ ▼
[강제 업데이트 UI] [업데이트 유형 판단]
│
┌──────────┼──────────┐
▼ ▼ ▼
[Silent] [Passive] [Active]
업데이트 체크 주기
| 상황 | 체크 주기 | 비고 |
|---|---|---|
| 앱 시작 시 | 항상 | Cold Start |
| 포어그라운드 복귀 | 4시간 이상 경과 시 | Warm Start |
| 백그라운드 | 24시간 | 모바일 배터리 절약 |
| 수동 체크 | 설정 화면에서 | 즉시 |
2. 플랫폼별 업데이트 메커니즘
2.1 Browser Extension
스토어 자동 업데이트
| 항목 | Chrome | Firefox |
|---|---|---|
| 업데이트 주기 | 5시간마다 체크 (브라우저 관리) | 24시간마다 체크 |
| 업데이트 방식 | 자동 다운로 드 + 설치 | 자동 다운로드 + 설치 |
| 사용자 제어 | 브라우저 설정에서 비활성화 가능 | 마찬가지 |
| 개발자 제어 | CWS 배포 속도만 조절 가능 | AMO 배포 속도만 조절 |
| 적용 시점 | 확장 재시작 시 (브라우저 재시작 불필요) | 확장 재시작 시 |
자체 업데이트 감지 (보조)
// background.ts - 스토어 업데이트 후 감지
chrome.runtime.onInstalled.addListener((details) => {
if (details.reason === 'update') {
const previousVersion = details.previousVersion;
const currentVersion = chrome.runtime.getManifest().version;
console.log(`Updated: ${previousVersion} → ${currentVersion}`);
// 데이터 마이그레이션 실행
migrateData(previousVersion, currentVersion);
// 업데이트 완료 알림 (선택)
showUpdateNotification(currentVersion);
}
});
앱 내 버전 체크 (추가 알림용)
// 서버에서 최신 버전 확인 (강제 업데이트 체크)
async function checkForUpdate(): Promise<UpdateInfo | null> {
const response = await fetch(`${API_URL}/client/version-check`, {
method: 'POST',
body: JSON.stringify({
platform: 'browser_ext',
currentVersion: chrome.runtime.getManifest().version,
browser: detectBrowser(), // chrome, firefox
}),
});
return response.json();
}
2.2 Tauri Desktop
tauri-plugin-updater 기반 자체 업데이트
| 항목 | 설명 |
|---|---|
| 프로토콜 | HTTPS JSON manifest + 바이너리 다운로드 |
| 서명 | Ed25519 서명 검증 (필수) |
| 서버 옵션 | GitHub Releases, S3/R2, 자체 서버 |
| 업데이트 단위 | 전체 바이너리 교체 |
| 차분 업데이트 | 지원 안 됨 (전체 다운로드) |
Tauri 업데이트 설정
// tauri.conf.json
{
"plugins": {
"updater": {
"active": true,
"dialog": false,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQgLi4u",
"endpoints": [
"https://releases.example.com/{{target}}/{{arch}}/{{current_version}}"
],
"windows": {
"installMode": "passive"
}
}
}
}
업데이트 서버 응답 형식
// GET /releases/{target}/{arch}/{current_version}
// 업데이트 없음: 204 No Content
// 업데이트 있음: 200 OK
{
"version": "2.4.0",
"notes": "새로운 기능 추가 및 버그 수정",
"pub_date": "2026-04-06T12:00:00Z",
"url": "https://releases.example.com/download/v2.4.0/app-2.4.0-x86_64.msi",
"signature": "dW50cnVzdGVkIGNvbW1lbnQgLi4u"
}
Rust 업데이트 구현
use tauri_plugin_updater::UpdaterExt;
// 업데이트 체크 + 다운로드 + 설치
async fn check_and_update(app: tauri::AppHandle) -> Result<(), Box<dyn std::error::Error>> {
let updater = app.updater_builder().build()?;
match updater.check().await? {
Some(update) => {
// 프론트엔드에 알림
app.emit("update-available", &update.version)?;
// 사용자 확인 후 다운로드 + 설치
let mut downloaded = 0;
update.download_and_install(
|chunk_length, content_length| {
downloaded += chunk_length;
// 프론트엔드에 진행률 전송
let _ = app.emit("update-progress", (downloaded, content_length));
},
|| {
// 설치 완료 → 재시작 안내
let _ = app.emit("update-installed", ());
},
).await?;
}
None => {
// 최신 버전
}
}
Ok(())
}
GitHub Releases 업데이트 서버
# Tauri가 GitHub Releases를 직접 지원
# tauri.conf.json endpoints 설정:
"endpoints": [
"https://github.com/{owner}/{repo}/releases/latest/download/latest.json"
]
# CI에서 latest.json 자동 생성 (tauri-action이 처리)
2.3 Flutter Mobile
스토어 업데이트 (기본)
| 항목 | Google Play | Apple App Store |
|---|---|---|
| 자동 업데이트 | 사용자 설정에 따라 | 사용자 설정에 따라 |
| 강제 업데이트 | 자체 구현 필요 | 자체 구현 필요 |
| 인앱 업데이트 | Play In-App Updates API | 지원 안 됨 |
| 심사 기간 | 수 시간 ~ 3일 | 1 ~ 7일 |
Google Play In-App Updates
import 'package:in_app_update/in_app_update.dart';
class UpdateService {
Future<void> checkForUpdate() async {
final updateInfo = await InAppUpdate.checkForUpdate();
if (updateInfo.updateAvailability == UpdateAvailability.updateAvailable) {
if (updateInfo.immediateUpdateAllowed) {
// 강제 업데이트 (전체 화면)
await InAppUpdate.performImmediateUpdate();
} else if (updateInfo.flexibleUpdateAllowed) {
// 유연한 업데이트 (백그라운드 다운로드)
await InAppUpdate.startFlexibleUpdate();
// 다운로드 완료 후
await InAppUpdate.completeFlexibleUpdate();
}
}
}
}
OTA 업데이트 (CodePush 유사)
| 솔루션 | 설명 | 제약 |
|---|---|---|
| Shorebird (Dart 전용) | Dart 코드 패치 배포 | 네이티브 코드 변경 불가 |
| 자체 번들 서버 | 에셋/설정 업데이트 | 코드 변경 불가 |
// Shorebird를 사용한 OTA 업데이트
// pubspec.yaml에 shorebird 추가 후:
// shorebird release --platforms android,ios
// shorebird patch --platforms android,ios
// 앱 내에서는 자동으로 패치 적용 (Shorebird 런타임이 처리)
// 별도 코드 불필요
주의: Apple App Store 가이드라인 3.3.2에 따르면 OTA로 앱의 주요 기능을 변경하는 것은 금지됨. Shorebird는 Dart VM 수준 패치로 이 규정을 준수하지만, 큰 기능 변경은 반드시 스토어 업데이트를 통해야 함.
3. 강제 업데이트 (최소 버전 체크)
3.1 서버 API 설계
GET /api/client/version-check
요청
{
"platform": "flutter_ios", // browser_ext_chrome, tauri_windows, flutter_android, etc.
"currentVersion": "2.3.1",
"buildNumber": 142
}
응답
{
"latestVersion": "2.4.0",
"minimumVersion": "2.2.0",
"updateRequired": false,
"updateAvailable": true,
"updateUrl": "https://apps.apple.com/app/id123456",
"releaseNotes": "새 기능: 다크 모드 지원",
"maintenanceMode": false,
"maintenanceMessage": null
}
3.2 Laravel 백엔드 구현
// app/Http/Controllers/Api/ClientVersionController.php
class ClientVersionController extends Controller
{
public function check(Request $request): JsonResponse
{
$validated = $request->validate([
'platform' => 'required|string',
'currentVersion' => 'required|string',
'buildNumber' => 'nullable|integer',
]);
$config = ClientVersionConfig::where('platform', $validated['platform'])->first();
if (!$config) {
return response()->json(['error' => 'Unknown platform'], 400);
}
$currentVersion = $validated['currentVersion'];
$updateRequired = version_compare($currentVersion, $config->minimum_version, '<');
$updateAvailable = version_compare($currentVersion, $config->latest_version, '<');
return response()->json([
'latestVersion' => $config->latest_version,
'minimumVersion' => $config->minimum_version,
'updateRequired' => $updateRequired,
'updateAvailable' => $updateAvailable,
'updateUrl' => $config->store_url,
'releaseNotes' => $config->release_notes,
'maintenanceMode' => $config->maintenance_mode,
'maintenanceMessage' => $config->maintenance_message,
]);
}
}