Hot Updates & Code Push for Mobile Applications
Hot updates and code push functionality allow mobile applications to update their JavaScript code, assets, and configurations without requiring users to download a new version from app stores. This capability is particularly valuable for React Native and hybrid applications.
Architecture Overview
Update Types
- JavaScript Bundle Updates: Core application logic
- Asset Updates: Images, fonts, configuration files
- Configuration Updates: API endpoints, feature flags
- Partial Updates: Delta updates for efficient bandwidth usage
- Critical Updates: Security patches and bug fixes
Implementation Strategies
1. Update Manager Architecture
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,
platform: deviceInfo.platform,
appVersion: deviceInfo.appVersion,
deviceId: deviceInfo.deviceId
});
if (update && this.shouldApplyUpdate(update)) {
return update;
}
return null;
} catch (error) {
console.error('Error checking for updates:', error);
return null;
}
}
async downloadUpdate(metadata: UpdateMetadata): Promise<void> {
this.notifyListeners({ status: 'downloading', availableVersion: metadata.version });
try {
// Download bundle
const bundlePath = await this.downloadWithProgress(
metadata.bundleUrl,
(progress) => this.notifyListeners({
status: 'downloading',
downloadProgress: progress,
availableVersion: metadata.version
})
);
// Download assets if available
let assetsPath;
if (metadata.assetsUrl) {
assetsPath = await this.downloadWithProgress(metadata.assetsUrl);
}
// Verify integrity
await this.validator.verifyUpdate(bundlePath, metadata);
// Store update
await this.storage.storeUpdate({
version: metadata.version,
bundlePath,
assetsPath,
metadata
});
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);
if (!storedUpdate) {
throw new Error('Update not found in storage');
}
// Create backup of current version
await this.storage.createBackup();
// Apply update
await this.applyUpdate(storedUpdate);
// Update current version
await this.storage.setCurrentVersion(version);
this.notifyListeners({ status: 'ready', currentVersion: version });
} catch (error) {
// Rollback on failure
await this.rollbackUpdate();
this.notifyListeners({ status: 'failed', error: error.message });
throw error;
}
}
private shouldApplyUpdate(metadata: UpdateMetadata): boolean {
// Check rollout percentage
const userId = this.getUserId();
const hash = this.hashString(userId);
if ((hash % 100) >= metadata.rolloutPercentage) {
return false;
}
// Check app version compatibility
const currentAppVersion = this.getAppVersion();
if (!this.isVersionCompatible(currentAppVersion, metadata.minAppVersion, metadata.maxAppVersion)) {
return false;
}
return true;
}
}
2. Delta Update System
typescript
// Delta update for efficient bandwidth usage
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,
toVersion,
operations: deltaOperations,
checksum: this.computeChecksum(deltaOperations)
};
}
async applyDelta(currentVersion: string, delta: DeltaPackage): Promise<Bundle> {
if (currentVersion !== delta.fromVersion) {
throw new Error('Version mismatch for delta update');
}
const currentBundle = await this.loadBundle(currentVersion);
const updatedBundle = this.applyDeltaOperations(currentBundle, delta.operations);
// Verify result
const checksum = this.computeChecksum(updatedBundle);
if (checksum !== delta.checksum) {
throw new Error('Delta application failed - checksum mismatch');
}
return updatedBundle;
}
private computeDelta(fromBundle: Bundle, toBundle: Bundle): DeltaOperation[] {
const operations: DeltaOperation[] = [];
// File additions
for (const [path, content] of toBundle.files) {
if (!fromBundle.files.has(path)) {
operations.push({
type: 'add',
path,
content: this.compressContent(content)
});
}
}
// File modifications
for (const [path, content] of toBundle.files) {
const oldContent = fromBundle.files.get(path);
if (oldContent && oldContent !== content) {
const patch = this.createBinaryPatch(oldContent, content);
operations.push({
type: 'modify',
path,
patch
});
}
}
// File deletions
for (const [path] of fromBundle.files) {
if (!toBundle.files.has(path)) {
operations.push({
type: 'delete',
path
});
}
}
return operations;
}
}
Platform-Specific Solutions
React Native Implementation
typescript
// React Native Code Push Implementation
import CodePush from 'react-native-code-push';
class ReactNativeUpdateManager {
private codePushOptions: CodePushOptions = {
checkFrequency: CodePush.CheckFrequency.ON_APP_RESUME,
updateDialog: {
title: 'Update Available',
optionalUpdateMessage: 'An update is available. Would you like to install it?',
optionalIgnoreButtonLabel: 'Later',
optionalInstallButtonLabel: 'Install',
mandatoryUpdateMessage: 'A mandatory update is available. The app will restart after installation.',
mandatoryContinueButtonLabel: 'Continue'
},
installMode: CodePush.InstallMode.ON_NEXT_RESTART
};
async checkForUpdates(): Promise<CodePushRemotePackage | null> {
try {
return await CodePush.checkForUpdate();
} catch (error) {
console.error('CodePush check failed:', 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.handleSyncStatus.bind(this),
this.handleDownloadProgress.bind(this)
);
}
private handleSyncStatus(status: CodePushSyncStatus) {
switch (status) {
case CodePush.SyncStatus.CHECKING_FOR_UPDATE:
console.log('Checking for updates...');
break;
case CodePush.SyncStatus.DOWNLOADING_PACKAGE:
console.log('Downloading update...');
break;
case CodePush.SyncStatus.INSTALLING_UPDATE:
console.log('Installing update...');
break;
case CodePush.SyncStatus.UP_TO_DATE:
console.log('App is up to date');
break;
case CodePush.SyncStatus.UPDATE_INSTALLED:
console.log('Update installed successfully');
break;
}
}
private handleDownloadProgress(progress: DownloadProgress) {
const percentage = (progress.receivedBytes / progress.totalBytes) * 100;
console.log(`Download progress: ${percentage}%`);
}
}
// Higher-Order Component for CodePush
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: 'Update Available',
optionalUpdateMessage: 'An update is available. Install now?',
optionalIgnoreButtonLabel: 'Later',
optionalInstallButtonLabel: 'Install'
}
})(WrappedComponent);
};
// Usage in App.js
const App: React.FC = () => {
useEffect(() => {
const updateManager = new ReactNativeUpdateManager();
updateManager.sync();
}, []);
return (
<NavigationContainer>
{/* Your app content */}
</NavigationContainer>
);
};
export default CodePushWrapper(App);
Expo Updates Implementation
typescript
// Expo Updates Implementation
import * as Updates from 'expo-updates';
class ExpoUpdateManager {
async checkForUpdates(): Promise<Updates.UpdateCheckResult> {
if (!Updates.isEnabled) {
throw new Error('Updates are not enabled in this build');
}
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();
if (update.isAvailable) {
console.log('Update available, downloading...');
const fetchResult = await this.downloadUpdate();
if (fetchResult.isNew) {
console.log('New update downloaded, reloading app...');
await this.reloadApp();
}
}
} catch (error) {
console.error('Update failed:', error);
}
}
}
// React Hook for Expo Updates
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);
setError(null);
const fetchResult = await updateManager.downloadUpdate();
if (fetchResult.isNew) {
await updateManager.reloadApp();
}
} catch (err) {
setError(err.message);
} finally {
setDownloading(false);
}
}, [updateManager]);
useEffect(() => {
checkForUpdates();
}, [checkForUpdates]);
return {
updateAvailable,
downloading,
error,
checkForUpdates,
downloadAndInstall
};
};
Cordova/PhoneGap Implementation
typescript
// Cordova Hot Code Push Implementation
declare var chcp: any;
class CordovaUpdateManager {
private isInitialized = false;
async initialize(): Promise<void> {
return new Promise((resolve, reject) => {
document.addEventListener('deviceready', () => {
if (typeof chcp !== 'undefined') {
this.setupEventListeners();
this.isInitialized = true;
resolve();
} else {
reject(new Error('Hot Code Push plugin not available'));
}
});
});
}
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('Update manager not initialized');
}
chcp.fetchUpdate((error: any) => {
if (error) {
console.error('Update check failed:', error);
}
});
}
async installUpdate(): Promise<void> {
chcp.installUpdate((error: any) => {
if (error) {
console.error('Update installation failed:', error);
}
});
}
private handleUpdateReady(): void {
console.log('Update is ready to install');
// Show user dialog
navigator.notification.confirm(
'A new update is available. Install now?',
(buttonIndex: number) => {
if (buttonIndex === 1) {
this.installUpdate();
}
},
'Update Available',
['Install', 'Later']
);
}
private handleUpdateFailed(event: any): void {
console.error('Update failed:', event.detail.error);
}
private handleNoUpdate(): void {
console.log('No updates available');
}
private handleUpdateInstalled(): void {
console.log('Update installed successfully');
}
}
Flutter Implementation
dart
// Flutter Hot Update Implementation
class FlutterUpdateManager {
static const MethodChannel _channel = MethodChannel('flutter_hot_update');
static Future<UpdateInfo?> checkForUpdates() async {
try {
final Map<String, dynamic>? result =
await _channel.invokeMethod('checkForUpdates');
if (result != null) {
return UpdateInfo.fromMap(result);
}
return null;
} catch (e) {
print('Error checking for updates: $e');
return null;
}
}
static Future<bool> downloadUpdate(
String updateUrl,
Function(double progress)? onProgress,
) async {
try {
final bool result = await _channel.invokeMethod('downloadUpdate', {
'updateUrl': updateUrl,
});
return result;
} catch (e) {
print('Error downloading update: $e');
return false;
}
}
static Future<bool> installUpdate() async {
try {
final bool result = await _channel.invokeMethod('installUpdate');
return result;
} catch (e) {
print('Error installing update: $e');
return false;
}
}
static Future<String?> getCurrentVersion() async {
try {
final String? version = await _channel.invokeMethod('getCurrentVersion');
return version;
} catch (e) {
print('Error getting current version: $e');
return null;
}
}
}
// Flutter Widget for Update Management
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,
barrierDismissible: !mandatory,
builder: (context) => AlertDialog(
title: Text('Update Available'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(_availableUpdate!.releaseNotes),
if (_isDownloading) ...[
SizedBox(height: 16),
LinearProgressIndicator(value: _downloadProgress),
Text('${(_downloadProgress * 100).toStringAsFixed(1)}%'),
],
],
),
actions: [
if (!mandatory && !_isDownloading)
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('Later'),
),
ElevatedButton(
onPressed: _isDownloading ? null : _downloadAndInstall,
child: Text(_isDownloading ? 'Downloading...' : 'Update'),
),
],
),
);
}
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'],
downloadUrl: map['downloadUrl'],
releaseNotes: map['releaseNotes'],
isMandatory: map['isMandatory'] ?? false,
size: map['size'] ?? 0,
);
}
}
Backend Infrastructure
Update Server Implementation
typescript
// Update Server with Express.js
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 {
// Get latest available version
const latestVersion = await this.getLatestVersion(request.platform);
if (!latestVersion || semver.lte(latestVersion.version, request.currentVersion)) {
return { updateAvailable: false };
}
// Check rollout eligibility
const rolloutInfo = await this.checkRolloutEligibility(
request.deviceId,
request.userId,
latestVersion
);
if (!rolloutInfo.eligible) {
return { updateAvailable: false, rolloutInfo };
}
// Generate signed URLs for download
const bundleUrl = await this.generateSignedUrl(latestVersion.bundlePath);
const assetsUrl = latestVersion.assetsPath
? await this.generateSignedUrl(latestVersion.assetsPath)
: undefined;
const metadata: UpdateMetadata = {
...latestVersion,
bundleUrl,
assetsUrl
};
return {
updateAvailable: true,
metadata,
rolloutInfo
};
} catch (error) {
console.error('Error checking for updates:', error);
throw error;
}
}
async publishUpdate(updateData: PublishUpdateRequest): Promise<void> {
// Upload bundle to S3
const bundleKey = `updates/${updateData.platform}/${updateData.version}/bundle.js`;
await this.s3.upload({
Bucket: process.env.UPDATES_BUCKET!,
Key: bundleKey,
Body: updateData.bundle,
ContentType: 'application/javascript'
}).promise();
// Upload assets if provided
let assetsKey;
if (updateData.assets) {
assetsKey = `updates/${updateData.platform}/${updateData.version}/assets.zip`;
await this.s3.upload({
Bucket: process.env.UPDATES_BUCKET!,
Key: assetsKey,
Body: updateData.assets,
ContentType: 'application/zip'
}).promise();
}
// Create signature
const signature = this.createSignature(updateData.bundle);
// Store metadata in DynamoDB
await this.dynamoDB.put({
TableName: 'Updates',
Item: {
platform: updateData.platform,
version: updateData.version,
bundlePath: bundleKey,
assetsPath: assetsKey,
checksum: crypto.createHash('sha256').update(updateData.bundle).digest('hex'),
signature,
releaseNotes: updateData.releaseNotes,
isMandatory: updateData.isMandatory,
rolloutPercentage: updateData.rolloutPercentage,
minAppVersion: updateData.minAppVersion,
maxAppVersion: updateData.maxAppVersion,
createdAt: new Date().toISOString()
}
}).promise();
}
private async getLatestVersion(platform: string): Promise<any> {
const result = await this.dynamoDB.query({
TableName: 'Updates',
KeyConditionExpression: 'platform = :platform',
ExpressionAttributeValues: {
':platform': platform
},
ScanIndexForward: false,
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,
rolloutPercentage: version.rolloutPercentage,
userHash: hashValue
};
}
private async generateSignedUrl(key: string): Promise<string> {
return this.s3.getSignedUrl('getObject', {
Bucket: process.env.UPDATES_BUCKET!,
Key: key,
Expires: 3600 // 1 hour
});
}
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 routes
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: 'Internal server error' });
}
});
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: 'Internal server error' });
}
});
CDN Configuration
yaml
# CloudFront Distribution Configuration
Resources:
UpdatesCDN:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Origins:
- DomainName: !GetAtt UpdatesBucket.DomainName
Id: S3Origin
S3OriginConfig:
OriginAccessIdentity: !Sub 'origin-access-identity/cloudfront/${OriginAccessIdentity}'
Enabled: true
DefaultCacheBehavior:
TargetOriginId: S3Origin
ViewerProtocolPolicy: redirect-to-https
AllowedMethods: [GET, HEAD, OPTIONS]
CachedMethods: [GET, HEAD]
Compress: true
TTL:
DefaultTTL: 86400
MaxTTL: 31536000
CacheBehaviors:
- PathPattern: '/updates/*'
TargetOriginId: S3Origin
ViewerProtocolPolicy: https-only
CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad # CachingOptimized
PriceClass: PriceClass_100
ViewerCertificate:
AcmCertificateArn: !Ref SSLCertificate
SslSupportMethod: sni-only
Security & Validation
1. Code Signing and Verification
typescript
// Update signature verification
import crypto from 'crypto';
class UpdateValidator {
private publicKey: string;
constructor(publicKey: string) {
this.publicKey = publicKey;
}
async verifyUpdate(bundlePath: string, metadata: UpdateMetadata): Promise<boolean> {
try {
// Verify file integrity
const bundleContent = await this.readFile(bundlePath);
const actualChecksum = crypto.createHash('sha256').update(bundleContent).digest('hex');
if (actualChecksum !== metadata.checksum) {
throw new Error('Checksum verification failed');
}
// Verify digital signature
const isSignatureValid = this.verifySignature(bundleContent, metadata.signature);
if (!isSignatureValid) {
throw new Error('Signature verification failed');
}
// Additional security checks
await this.performSecurityScan(bundleContent);
return true;
} catch (error) {
console.error('Update validation failed:', 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> {
// Scan for malicious patterns
const contentStr = content.toString();
const maliciousPatterns = [
/eval\s*\(/,
/Function\s*\(/,
/document\.write/,
/XMLHttpRequest/,
/__dirname/,
/__filename/,
/require\s*\(/
];
for (const pattern of maliciousPatterns) {
if (pattern.test(contentStr)) {
throw new Error(`Malicious pattern detected: ${pattern}`);
}
}
}
}
2. Runtime Protection
typescript
// Runtime security monitoring
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 {
// Check origin
const origin = new URL(url).origin;
if (!this.allowedOrigins.includes(origin)) {
throw new Error(`Unauthorized update origin: ${origin}`);
}
// Check size
if (size > this.maxUpdateSize) {
throw new Error(`Update size exceeds limit: ${size} > ${this.maxUpdateSize}`);
}
return true;
}
async monitorUpdateInstallation(updatePath: string): Promise<void> {
// Monitor file system changes
const watcher = require('fs').watch(updatePath, (eventType, filename) => {
console.log(`File system event: ${eventType} - ${filename}`);
// Log suspicious activities
if (this.isSuspiciousActivity(eventType, filename)) {
this.alertSecurity(`Suspicious activity detected: ${eventType} ${filename}`);
}
});
// Cleanup after installation
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(`[SECURITY ALERT] ${message}`);
// Send to monitoring service
fetch('/api/security/alert', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message,
timestamp: new Date().toISOString(),
severity: 'high'
})
});
}
}
Best Practices
1. Rollback Strategy
typescript
// Automatic rollback implementation
class RollbackManager {
private healthCheckInterval: number = 30000; // 30 seconds
private maxFailures: number = 3;
private currentFailures: number = 0;
async installWithRollback(update: StoredUpdate): Promise<void> {
// Create checkpoint before installation
const checkpoint = await this.createCheckpoint();
try {
// Install update
await this.installUpdate(update);
// Start health monitoring
await this.startHealthMonitoring();
} catch (error) {
// Immediate rollback on installation failure
await this.rollbackToCheckpoint(checkpoint);
throw error;
}
}
private async startHealthMonitoring(): Promise<void> {
const healthCheck = setInterval(async () => {
try {
const isHealthy = await this.performHealthCheck();
if (!isHealthy) {
this.currentFailures++;
if (this.currentFailures >= this.maxFailures) {
clearInterval(healthCheck);
await this.performEmergencyRollback();
}
} else {
// Reset failure count on successful health check
this.currentFailures = 0;
}
} catch (error) {
console.error('Health check failed:', error);
this.currentFailures++;
}
}, this.healthCheckInterval);
// Stop monitoring after 10 minutes
setTimeout(() => clearInterval(healthCheck), 600000);
}
private async performHealthCheck(): Promise<boolean> {
try {
// Check app responsiveness
const startTime = Date.now();
await fetch('/api/health', { timeout: 5000 });
const responseTime = Date.now() - startTime;
if (responseTime > 10000) {
return false;
}
// Check error rates
const errorRate = await this.getErrorRate();
if (errorRate > 0.05) { // 5% error rate threshold
return false;
}
return true;
} catch {
return false;
}
}
private async performEmergencyRollback(): Promise<void> {
console.log('Emergency rollback triggered');
const lastKnownGood = await this.getLastKnownGoodVersion();
if (lastKnownGood) {
await this.rollbackToVersion(lastKnownGood);
}
}
}
2. Performance Monitoring
typescript
// Update performance tracking
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;
// Send metrics to analytics
this.sendMetrics(this.metrics);
return this.metrics;
}
private sendMetrics(metrics: UpdateMetrics): void {
// Send to analytics service
fetch('/api/analytics/update-performance', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(metrics)
});
}
}
3. User Experience Guidelines
- Non-intrusive updates: Check for updates in background
- Progress indication: Show download/installation progress
- Offline handling: Queue updates when offline
- Battery awareness: Defer non-critical updates on low battery
- Network awareness: Use WiFi for large updates
- Graceful failures: Handle update failures gracefully
4. Testing Strategy
typescript
// Update testing framework
class UpdateTester {
async testUpdate(updatePackage: UpdatePackage): Promise<TestResult[]> {
const results: TestResult[] = [];
// Test update integrity
results.push(await this.testIntegrity(updatePackage));
// Test backward compatibility
results.push(await this.testBackwardCompatibility(updatePackage));
// Test performance impact
results.push(await this.testPerformanceImpact(updatePackage));
// Test rollback capability
results.push(await this.testRollback(updatePackage));
return results;
}
private async testIntegrity(updatePackage: UpdatePackage): Promise<TestResult> {
try {
const validator = new UpdateValidator(this.publicKey);
const isValid = await validator.verifyUpdate(updatePackage.path, updatePackage.metadata);
return {
test: 'integrity',
passed: isValid,
message: isValid ? 'Update integrity verified' : 'Integrity check failed'
};
} catch (error) {
return {
test: 'integrity',
passed: false,
message: error.message
};
}
}
}
Hot updates and code push capabilities provide significant value for mobile applications by enabling rapid bug fixes, feature updates, and A/B testing without app store dependencies. However, they require careful consideration of security, performance, and user experience to implement successfully.