Anlık Güncellemeler ve Kod Gönderme (Hot Updates & Code Push)
Anlık güncellemeler (hot update) ve kod gönderme (code push) işlevleri, mobil uygulamaların JavaScript kodunu, varlıklarını ve yapılandırmalarını kullanıcıların uygulama mağazasından yeni bir sürüm indirmesine gerek kalmadan güncelleyebilmesini sağlar. Bu yetenek özellikle React Native ve hibrit uygulamalar için çok değerlidir.
Mimari Genel Bakış
Güncelleme Türleri
- JavaScript Paket Güncellemeleri: Uygulamanın ana mantığı
- Varlık Güncellemeleri: Görseller, fontlar, konfigürasyon dosyaları
- Yapılandırma Güncellemeleri: API uç noktaları, feature flag'ler
- Kısmi Güncellemeler: Delta güncellemeleri ile bant genişliği optimizasyonu
- Kritik Güncellemeler: Güvenlik yamaları ve hata düzeltmeleri
Uygulama Stratejileri
1. Güncelleme Yöneticisi Mimarisi
typescript
interface UpdateMetadata {
version: string;
bundleUrl: string;
assetsUrl?: string;
checksum: string;
signature: string;
releaseNotes: string;
isMandatory: boolean;
rolloutPercentage: number;
minAppVersion: string;
maxAppVersion?: string;
targetPlatform: 'ios' | 'android' | 'both';
releaseDate: Date;
}
interface UpdateStatus {
currentVersion: string;
availableVersion?: string;
downloadProgress?: number;
status: 'checking' | 'downloading' | 'installing' | 'ready' | 'failed';
error?: string;
}
class UpdateManager {
private updateService: UpdateService;
private storage: UpdateStorage;
private validator: UpdateValidator;
private listeners: UpdateListener[] = [];
constructor() {
this.updateService = new UpdateService();
this.storage = new UpdateStorage();
this.validator = new UpdateValidator();
}
async checkForUpdates(): Promise<UpdateMetadata | null> {
try {
const currentVersion = await this.getCurrentVersion();
const deviceInfo = await this.getDeviceInfo();
const update = await this.updateService.checkForUpdates({
currentVersion,
deviceId: deviceInfo.deviceId
});
if (update && this.shouldApplyUpdate(update)) {
return update;
}
return null;
} catch (error) {
console.error('Güncellemeler kontrol edilirken hata:', error);
return null;
}
}
async downloadUpdate(metadata: UpdateMetadata): Promise<void> {
this.notifyListeners({ status: 'downloading', availableVersion: metadata.version });
try {
// Bundle indir
const bundlePath = await this.downloadWithProgress(
metadata.bundleUrl,
})
);
// Varlıklar varsa indir
let assetsPath;
if (metadata.assetsUrl) {
assetsPath = await this.downloadWithProgress(metadata.assetsUrl);
}
this.notifyListeners({ status: 'ready', availableVersion: metadata.version });
} catch (error) {
this.notifyListeners({ status: 'failed', error: error.message });
throw error;
}
}
async installUpdate(version: string): Promise<void> {
this.notifyListeners({ status: 'installing' });
try {
const storedUpdate = await this.storage.getUpdate(version);
this.notifyListeners({ status: 'ready', currentVersion: version });
} catch (error) {
// Başarısızlıkta rollback
throw error;
}
}
private shouldApplyUpdate(metadata: UpdateMetadata): boolean {
// Rollout yüzdesi kontrolü
const userId = this.getUserId();
const hash = this.hashString(userId);
if ((hash % 100) >= metadata.rolloutPercentage) {
return false;
}
// Uygulama sürüm uyumluluğu kontrolü
const currentAppVersion = this.getAppVersion();
if (!this.isVersionCompatible(currentAppVersion, metadata.minAppVersion, metadata.maxAppVersion)) {
return false;
}
return true;
}
}
2. Delta Güncelleme Sistemi
typescript
// Delta güncelleme ile bant genişliği optimizasyonu
class DeltaUpdateManager {
async createDelta(fromVersion: string, toVersion: string): Promise<DeltaPackage> {
const fromBundle = await this.loadBundle(fromVersion);
const toBundle = await this.loadBundle(toVersion);
const deltaOperations = this.computeDelta(fromBundle, toBundle);
return {
fromVersion,
checksum: this.computeChecksum(deltaOperations)
};
}
async applyDelta(currentVersion: string, delta: DeltaPackage): Promise<Bundle> {
if (currentVersion !== delta.fromVersion) {
throw new Error('Delta güncelleme için sürüm uyuşmazlığı');
}
const currentBundle = await this.loadBundle(currentVersion);
const updatedBundle = this.applyDeltaOperations(currentBundle, delta.operations);
// Sonucu doğrula
const checksum = this.computeChecksum(updatedBundle);
if (checksum !== delta.checksum) {
throw new Error('Delta uygulaması başarısız - checksum uyuşmazlığı');
}
return updatedBundle;
}
private computeDelta(fromBundle: Bundle, toBundle: Bundle): DeltaOperation[] {
const operations: DeltaOperation[] = [];
// ... Dosya ekleme, değiştirme, silme işlemleri ...
return operations;
}
}
Platforma Özel Çözümler
React Native
typescript
// React Native Code Push implementasyonu
import CodePush from 'react-native-code-push';
class ReactNativeUpdateManager {
private codePushOptions: CodePushOptions = {
checkFrequency: CodePush.CheckFrequency.ON_APP_RESUME,
updateDialog: {
title: 'Güncelleme Mevcut',
mandatoryContinueButtonLabel: 'Devam'
},
installMode: CodePush.InstallMode.ON_NEXT_RESTART
};
async checkForUpdates(): Promise<CodePushRemotePackage | null> {
try {
return await CodePush.checkForUpdate();
} catch (error) {
console.error('CodePush kontrolü başarısız:', error);
return null;
}
}
async downloadUpdate(
remotePackage: CodePushRemotePackage,
onProgress?: (progress: DownloadProgress) => void
): Promise<CodePushLocalPackage> {
return await remotePackage.download(onProgress);
}
async installUpdate(localPackage: CodePushLocalPackage, installMode?: InstallMode): Promise<void> {
await localPackage.install(installMode || this.codePushOptions.installMode);
}
async getUpdateMetadata(): Promise<CodePushLocalPackage | null> {
return await CodePush.getUpdateMetadata();
}
async sync(): Promise<CodePushSyncStatus> {
return await CodePush.sync(
this.codePushOptions,
this.handleDownloadProgress.bind(this)
);
}
private handleSyncStatus(status: CodePushSyncStatus) {
switch (status) {
case CodePush.SyncStatus.CHECKING_FOR_UPDATE:
// ...
}
}
private handleDownloadProgress(progress: DownloadProgress) {
const percentage = (progress.receivedBytes / progress.totalBytes) * 100;
console.log(`İndirme ilerlemesi: ${percentage}%`);
}
}
// CodePush için Higher-Order Component
const CodePushWrapper = (WrappedComponent: React.ComponentType) => {
return CodePush({
checkFrequency: CodePush.CheckFrequency.ON_APP_RESUME,
installMode: CodePush.InstallMode.ON_NEXT_RESTART,
mandatoryInstallMode: CodePush.InstallMode.IMMEDIATE,
updateDialog: {
title: 'Güncelleme Mevcut',
optionalInstallButtonLabel: 'Yükle'
}
})(WrappedComponent);
};
// App.js'de kullanım
const App: React.FC = () => {
useEffect(() => {
const updateManager = new ReactNativeUpdateManager();
updateManager.sync();
}, []);
return (
<NavigationContainer>
{/* Uygulama içeriği */}
</NavigationContainer>
);
};
export default CodePushWrapper(App);
Expo
typescript
// Expo Updates implementasyonu
import * as Updates from 'expo-updates';
class ExpoUpdateManager {
async checkForUpdates(): Promise<Updates.UpdateCheckResult> {
if (!Updates.isEnabled) {
throw new Error('Bu derlemede güncellemeler etkin değil');
}
return await Updates.checkForUpdateAsync();
}
async downloadUpdate(): Promise<Updates.UpdateFetchResult> {
return await Updates.fetchUpdateAsync();
}
async reloadApp(): Promise<void> {
await Updates.reloadAsync();
}
getCurrentVersion(): string | null {
return Updates.updateId || Updates.releaseChannel;
}
isUpdateAvailable(): boolean {
return Updates.isUpdateAvailable;
}
async handleUpdate(): Promise<void> {
try {
const update = await this.checkForUpdates();
// ...
} catch (error) {
console.error('Güncelleme başarısız:', error);
}
}
}
// Expo Updates için React Hook
export const useExpoUpdates = () => {
const [updateAvailable, setUpdateAvailable] = useState(false);
const [downloading, setDownloading] = useState(false);
const [error, setError] = useState<string | null>(null);
const updateManager = useMemo(() => new ExpoUpdateManager(), []);
const checkForUpdates = useCallback(async () => {
try {
const result = await updateManager.checkForUpdates();
setUpdateAvailable(result.isAvailable);
} catch (err) {
setError(err.message);
}
}, [updateManager]);
const downloadAndInstall = useCallback(async () => {
try {
setDownloading(true);
// ...
} catch (err) {
setError(err.message);
} finally {
setDownloading(false);
}
}, [updateManager]);
useEffect(() => {
checkForUpdates();
}, [checkForUpdates]);
return {
updateAvailable,
downloading,
error,
checkForUpdates,
downloadAndInstall
};
};
Cordova/PhoneGap
typescript
// Cordova Hot Code Push implementasyonu
declare var chcp: any;
class CordovaUpdateManager {
private isInitialized = false;
async initialize(): Promise<void> {
return new Promise((resolve, reject) => {
document.addEventListener('deviceready', () => {…});
});
}
private setupEventListeners(): void {
document.addEventListener('chcp_updateIsReadyToInstall', this.handleUpdateReady.bind(this));
document.addEventListener('chcp_updateLoadFailed', this.handleUpdateFailed.bind(this));
document.addEventListener('chcp_nothingToUpdate', this.handleNoUpdate.bind(this));
document.addEventListener('chcp_updateInstalled', this.handleUpdateInstalled.bind(this));
}
async checkForUpdates(): Promise<void> {
if (!this.isInitialized) {
throw new Error('Güncelleme yöneticisi başlatılmadı');
}
chcp.fetchUpdate((error: any) => {
if (error) {…}
});
}
async installUpdate(): Promise<void> {
chcp.installUpdate((error: any) => {
if (error) {…}
});
}
private handleUpdateReady(): void {
console.log('Güncelleme yüklemeye hazır');
navigator.notification.confirm(
'Yeni bir güncelleme mevcut. Şimdi yükle?',
['Yükle', 'Daha Sonra']
);
}
private handleUpdateFailed(event: any): void {
console.error('Güncelleme başarısız:', event.detail.error);
}
private handleNoUpdate(): void {
console.log('Güncelleme yok');
}
private handleUpdateInstalled(): void {
console.log('Güncelleme başarıyla yüklendi');
}
}
Flutter
dart
// Flutter Hot Update implementasyonu
class FlutterUpdateManager {
static const MethodChannel _channel = MethodChannel('flutter_hot_update');
static Future<UpdateInfo?> checkForUpdates() async {
try {
final Map<String, dynamic>? result =
return null;
} catch (e) {
print('Güncellemeler kontrol edilirken hata: $e');
return null;
}
}
static Future<bool> downloadUpdate(
String updateUrl,
Function(double progress)? onProgress,
) async {
try {
final bool result = await _channel.invokeMethod('downloadUpdate', {
return result;
} catch (e) {
print('Güncelleme indirilirken hata: $e');
return false;
}
}
static Future<bool> installUpdate() async {
try {
final bool result = await _channel.invokeMethod('installUpdate');
return result;
} catch (e) {
print('Güncelleme yüklenirken hata: $e');
return false;
}
}
static Future<String?> getCurrentVersion() async {
try {
final String? version = await _channel.invokeMethod('getCurrentVersion');
return version;
} catch (e) {
print('Mevcut sürüm alınırken hata: $e');
return null;
}
}
}
// Flutter için güncelleme yönetim widget'ı
class UpdateWidget extends StatefulWidget {
final Widget child;
const UpdateWidget({Key? key, required this.child}) : super(key: key);
@override
_UpdateWidgetState createState() => _UpdateWidgetState();
}
class _UpdateWidgetState extends State<UpdateWidget> {
bool _isChecking = false;
bool _isDownloading = false;
double _downloadProgress = 0.0;
UpdateInfo? _availableUpdate;
@override
void initState() {
super.initState();
_checkForUpdates();
}
Future<void> _checkForUpdates() async {
setState(() => _isChecking = true);
final update = await FlutterUpdateManager.checkForUpdates();
setState(() {
_isChecking = false;
_availableUpdate = update;
});
if (update != null && update.isMandatory) {
_showUpdateDialog(mandatory: true);
} else if (update != null) {
_showUpdateDialog(mandatory: false);
}
}
void _showUpdateDialog({required bool mandatory}) {
showDialog(
context: context,
),
);
}
Future<void> _downloadAndInstall() async {
setState(() => _isDownloading = true);
final success = await FlutterUpdateManager.downloadUpdate(
_availableUpdate!.downloadUrl,
(progress) => setState(() => _downloadProgress = progress),
);
if (success) {
await FlutterUpdateManager.installUpdate();
}
setState(() => _isDownloading = false);
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}
class UpdateInfo {
final String version;
final String downloadUrl;
final String releaseNotes;
final bool isMandatory;
final int size;
UpdateInfo({
required this.version,
required this.downloadUrl,
required this.releaseNotes,
required this.isMandatory,
required this.size,
});
factory UpdateInfo.fromMap(Map<String, dynamic> map) {
return UpdateInfo(
version: map['version'],
size: map['size'] ?? 0,
);
}
}
Sunucu Altyapısı
Güncelleme Sunucusu Uygulaması
typescript
// Express.js ile güncelleme sunucusu
import express from 'express';
import AWS from 'aws-sdk';
import crypto from 'crypto';
import semver from 'semver';
interface UpdateRequest {
platform: 'ios' | 'android';
currentVersion: string;
appVersion: string;
deviceId: string;
userId?: string;
}
interface UpdateResponse {
updateAvailable: boolean;
metadata?: UpdateMetadata;
rolloutInfo?: RolloutInfo;
}
class UpdateServer {
private s3: AWS.S3;
private dynamoDB: AWS.DynamoDB.DocumentClient;
constructor() {
this.s3 = new AWS.S3();
this.dynamoDB = new AWS.DynamoDB.DocumentClient();
}
async checkForUpdates(request: UpdateRequest): Promise<UpdateResponse> {
try {
// En son mevcut sürümü al
};
} catch (error) {
console.error('Güncellemeler kontrol edilirken hata:', error);
throw error;
}
}
async publishUpdate(updateData: PublishUpdateRequest): Promise<void> {
// Bundle'ı S3'e yükle
const bundleKey = `updates/${updateData.platform}/${updateData.version}/bundle.js`;
await this.s3.upload({
Bucket: process.env.UPDATES_BUCKET!,
ContentType: 'application/javascript'
}).promise();
// Varlıklar varsa yükle
let assetsKey;
if (updateData.assets) {
assetsKey = `updates/${updateData.platform}/${updateData.version}/assets.zip`;
}).promise();
}
// İmza oluştur
const signature = this.createSignature(updateData.bundle);
// Metadata'yı DynamoDB'ye kaydet
await this.dynamoDB.put({
TableName: 'Updates',
}
}).promise();
}
private async getLatestVersion(platform: string): Promise<any> {
const result = await this.dynamoDB.query({
TableName: 'Updates',
Limit: 1
}).promise();
return result.Items?.[0];
}
private async checkRolloutEligibility(
deviceId: string,
userId: string | undefined,
version: any
): Promise<RolloutInfo> {
const identifier = userId || deviceId;
const hash = crypto.createHash('md5').update(identifier).digest('hex');
const hashValue = parseInt(hash.substr(0, 8), 16) % 100;
const eligible = hashValue < version.rolloutPercentage;
return {
eligible,
userHash: hashValue
};
}
private async generateSignedUrl(key: string): Promise<string> {
return this.s3.getSignedUrl('getObject', {
Bucket: process.env.UPDATES_BUCKET!,
Expires: 3600 // 1 saat
});
}
private createSignature(content: Buffer): string {
const privateKey = process.env.SIGNING_PRIVATE_KEY!;
const sign = crypto.createSign('RSA-SHA256');
sign.update(content);
return sign.sign(privateKey, 'base64');
}
}
// Express rotaları
const app = express();
const updateServer = new UpdateServer();
app.post('/api/updates/check', async (req, res) => {
try {
const result = await updateServer.checkForUpdates(req.body);
res.json(result);
} catch (error) {
res.status(500).json({ error: 'Sunucu hatası' });
}
});
app.post('/api/updates/publish', async (req, res) => {
try {
await updateServer.publishUpdate(req.body);
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: 'Sunucu hatası' });
}
});
CDN Yapılandırması
yaml
# CloudFront Dağıtım Yapılandırması
Resources:
UpdatesCDN:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Güvenlik ve Doğrulama
1. Kod İmzalama ve Doğrulama
typescript
// Güncelleme imza doğrulama
import crypto from 'crypto';
class UpdateValidator {
private publicKey: string;
constructor(publicKey: string) {
this.publicKey = publicKey;
}
async verifyUpdate(bundlePath: string, metadata: UpdateMetadata): Promise<boolean> {
try {
// Dosya bütünlüğünü doğrula
return true;
} catch (error) {
console.error('Güncelleme doğrulama başarısız:', error);
return false;
}
}
private verifySignature(content: Buffer, signature: string): boolean {
const verify = crypto.createVerify('RSA-SHA256');
verify.update(content);
return verify.verify(this.publicKey, signature, 'base64');
}
private async performSecurityScan(content: Buffer): Promise<void> {
// Zararlı desenler için tarama
const contentStr = content.toString();
const maliciousPatterns = [
/eval\s*\(/,
/require\s*\(/
];
for (const pattern of maliciousPatterns) {
if (pattern.test(contentStr)) {…}
}
}
}
2. Çalışma Zamanı Koruması
typescript
// Çalışma zamanı güvenlik izleme
class SecurityMonitor {
private allowedOrigins: string[];
private maxUpdateSize: number;
constructor(config: SecurityConfig) {
this.allowedOrigins = config.allowedOrigins;
this.maxUpdateSize = config.maxUpdateSize;
}
validateUpdateRequest(url: string, size: number): boolean {
// Kaynak kontrolü
const origin = new URL(url).origin;
if (!this.allowedOrigins.includes(origin)) {
throw new Error(`Yetkisiz güncelleme kaynağı: ${origin}`);
}
// Boyut kontrolü
if (size > this.maxUpdateSize) {
throw new Error(`Güncelleme boyutu sınırı aşıldı: ${size} > ${this.maxUpdateSize}`);
}
return true;
}
async monitorUpdateInstallation(updatePath: string): Promise<void> {
// Dosya sistemi değişikliklerini izle
const watcher = require('fs').watch(updatePath, (eventType, filename) => {
console.log(`Dosya sistemi olayı: ${eventType} - ${filename}`);
}
});
// Kurulumdan sonra temizle
setTimeout(() => watcher.close(), 60000);
}
private isSuspiciousActivity(eventType: string, filename: string): boolean {
const suspiciousFiles = ['.env', 'config.json', 'secrets.txt'];
return suspiciousFiles.some(file => filename.includes(file));
}
private alertSecurity(message: string): void {
console.error(`[GÜVENLİK UYARISI] ${message}`);
// İzleme servisine gönder
fetch('/api/security/alert', {
method: 'POST',
})
});
}
}
En İyi Uygulamalar
1. Rollback Stratejisi
typescript
// Otomatik rollback implementasyonu
class RollbackManager {
private healthCheckInterval: number = 30000; // 30 saniye
private maxFailures: number = 3;
private currentFailures: number = 0;
async installWithRollback(update: StoredUpdate): Promise<void> {
// Kurulumdan önce checkpoint oluştur
const checkpoint = await this.createCheckpoint();
try {
// Güncellemeyi kur
await this.startHealthMonitoring();
} catch (error) {
// Kurulum hatasında anında rollback
throw error;
}
}
private async startHealthMonitoring(): Promise<void> {
const healthCheck = setInterval(async () => {
try {…}
}, this.healthCheckInterval);
// 10 dakika sonra izlemeyi durdur
setTimeout(() => clearInterval(healthCheck), 600000);
}
private async performHealthCheck(): Promise<boolean> {
try {
// Uygulama yanıt veriyor mu kontrol et
return true;
} catch {
return false;
}
}
private async performEmergencyRollback(): Promise<void> {
console.log('Acil rollback tetiklendi');
const lastKnownGood = await this.getLastKnownGoodVersion();
if (lastKnownGood) {
await this.rollbackToVersion(lastKnownGood);
}
}
}
2. Performans İzleme
typescript
// Güncelleme performans takibi
class UpdatePerformanceMonitor {
private metrics: UpdateMetrics = {
downloadTime: 0,
installTime: 0,
verificationTime: 0,
totalUpdateTime: 0,
bundleSize: 0,
compressionRatio: 0
};
startTracking(): void {
this.metrics.startTime = Date.now();
}
trackDownload(size: number, compressedSize: number): void {
this.metrics.downloadTime = Date.now() - (this.metrics.downloadStartTime || 0);
this.metrics.bundleSize = size;
this.metrics.compressionRatio = compressedSize / size;
}
trackInstallation(): void {
this.metrics.installTime = Date.now() - (this.metrics.installStartTime || 0);
}
finishTracking(): UpdateMetrics {
this.metrics.totalUpdateTime = Date.now() - this.metrics.startTime;
// Analitik servisine gönder
this.sendMetrics(this.metrics);
return this.metrics;
}
private sendMetrics(metrics: UpdateMetrics): void {
fetch('/api/analytics/update-performance', {
method: 'POST',
body: JSON.stringify(metrics)
});
}
}
3. Kullanıcı Deneyimi Rehberi
- Kullanıcıyı rahatsız etmeyen güncellemeler: Arka planda güncelleme kontrolü
- İlerleme göstergesi: İndirme/kurulum ilerlemesini gösterin
- Çevrimdışı senaryolar: Güncellemeleri çevrimdışıyken kuyruğa alın
- Pil farkındalığı: Kritik olmayan güncellemeleri düşük pilde erteleyin
- Ağ farkındalığı: Büyük güncellemeler için WiFi kullanın
- Hatalı durumlar: Güncelleme hatalarını zarifçe yönetin
4. Test Stratejisi
typescript
// Güncelleme test framework'ü
class UpdateTester {
async testUpdate(updatePackage: UpdatePackage): Promise<TestResult[]> {
const results: TestResult[] = [];
// Bütünlük testi
results.push(await this.testIntegrity(updatePackage));
// Geriye dönük uyumluluk testi
results.push(await this.testBackwardCompatibility(updatePackage));
// Performans etkisi testi
results.push(await this.testPerformanceImpact(updatePackage));
// Rollback testi
results.push(await this.testRollback(updatePackage));
return results;
}
private async testIntegrity(updatePackage: UpdatePackage): Promise<TestResult> {
try {
const validator = new UpdateValidator(this.publicKey);
// ...
} catch (error) {
return {…};
}
}
}
Anlık güncellemeler ve kod gönderme, mobil uygulamalarda hızlı hata düzeltmeleri, özellik güncellemeleri ve A/B testleri için büyük avantaj sağlar. Ancak, güvenlik, performans ve kullanıcı deneyimi açısından dikkatli bir şekilde uygulanmalıdır.