native-update
Version:
Foundation package for building a comprehensive update system for Capacitor apps. Provides architecture and interfaces but requires backend implementation.
643 lines (508 loc) • 14.6 kB
Markdown
> **⚠️ Backend Infrastructure Required**
>
> Live Updates require a complete backend infrastructure that you must build:
> - **Update Server**: Host and serve update bundles
> - **Bundle Generation**: CI/CD pipeline to create and sign bundles
> - **Version Management**: Track versions, channels, and rollbacks
> - **Security Infrastructure**: Implement signing and verification
> - **CDN/Storage**: Distribute bundles globally
>
> This plugin provides the client-side implementation only. Without the backend, Live Updates will not function.
Live Updates, also known as Over-The-Air (OTA) updates, allow you to deploy JavaScript, HTML, and CSS changes to your app instantly without going through app store review processes. This feature is perfect for bug fixes, content updates, and minor feature additions.
1. **Bundle Creation**: Your web assets are packaged into a bundle with metadata
2. **Version Check**: The app checks for new versions on your update server
3. **Download**: New bundles are downloaded in the background
4. **Verification**: Checksums and signatures are validated for security
5. **Installation**: The update is applied based on your configured strategy
6. **Activation**: The app reloads with the new bundle
- Deploy updates within minutes
- No app store review delays
- Fix critical bugs immediately
- Cryptographic signature verification
- Checksum validation
- HTTPS enforcement
- Certificate pinning support
- Automatic cleanup of old bundles
- Storage usage optimization
- Version history tracking
- Rollback capabilities
- Multiple update channels
- Gradual rollouts
- A/B testing support
- User segmentation
```typescript
// 1. Configure live updates
await NativeUpdate.configure({
liveUpdate: {
appId: 'com.myapp.example',
serverUrl: 'https://updates.myserver.com',
autoUpdate: true,
},
});
// 2. Sync updates (automatic with autoUpdate: true)
const result = await NativeUpdate.sync();
// 3. Handle the result
switch (result.status) {
case 'UPDATE_INSTALLED':
console.log('Update installed:', result.bundle.version);
break;
case 'UP_TO_DATE':
console.log('App is up to date');
break;
}
```
```typescript
class LiveUpdateManager {
private updateAvailable = false;
async initialize() {
// Configure with advanced options
await NativeUpdate.configure({
liveUpdate: {
appId: 'com.myapp.example',
serverUrl: 'https://updates.myserver.com',
channel: this.getUpdateChannel(),
updateStrategy: 'BACKGROUND',
publicKey: this.getPublicKey(),
requireSignature: true,
checksumAlgorithm: 'SHA-512',
},
});
// Set up listeners
this.setupUpdateListeners();
// Initial check
await this.checkForUpdates();
}
private setupUpdateListeners() {
// Download progress
NativeUpdate.addListener(
'downloadProgress',
(progress) => {
this.updateDownloadProgress(progress.percent);
}
);
// State changes
NativeUpdate.addListener(
'updateStateChanged',
(event) => {
this.handleStateChange(event);
}
);
}
async checkForUpdates() {
try {
const latest = await NativeUpdate.getLatest();
const current = await NativeUpdate.current();
if (this.isNewerVersion(latest.version, current.version)) {
this.updateAvailable = true;
this.notifyUpdateAvailable(latest);
}
} catch (error) {
console.error('Update check failed:', error);
}
}
async downloadUpdate() {
if (!this.updateAvailable) return;
try {
const bundle = await NativeUpdate.download({
version: 'latest',
onProgress: (progress) => {
console.log(`Download: ${progress.percent}%`);
},
});
// Validate the bundle
const validation = await NativeUpdate.validateUpdate({
bundleId: bundle.bundleId,
});
if (validation.valid) {
await this.installUpdate(bundle);
} else {
throw new Error('Bundle validation failed');
}
} catch (error) {
this.handleUpdateError(error);
}
}
async installUpdate(bundle: BundleInfo) {
// Set the bundle as active
await NativeUpdate.set(bundle);
// Notify app is ready (important for rollback mechanism)
await NativeUpdate.notifyAppReady();
// Schedule reload based on user preference
this.scheduleReload();
}
private scheduleReload() {
// Option 1: Immediate reload
// await NativeUpdate.reload();
// Option 2: Reload on next app resume
App.addListener('appStateChange', ({ isActive }) => {
if (isActive) {
NativeUpdate.reload();
}
});
// Option 3: User-triggered reload
this.showUpdateReadyDialog();
}
}
```
Updates are downloaded and applied immediately when detected.
```typescript
{
updateStrategy: 'IMMEDIATE',
mandatoryInstallMode: 'IMMEDIATE'
}
```
**Use when:**
- Critical bug fixes
- Security patches
- Time-sensitive content
Updates download in the background and install based on install mode.
```typescript
{
updateStrategy: 'BACKGROUND',
mandatoryInstallMode: 'ON_NEXT_RESTART',
optionalInstallMode: 'ON_NEXT_RESUME'
}
```
**Use when:**
- Regular feature updates
- Non-critical improvements
- Large bundle sizes
Full control over download and installation timing.
```typescript
{
updateStrategy: 'MANUAL',
autoUpdate: false
}
```
**Use when:**
- User-initiated updates
- Specific update windows
- Custom update UI
The plugin uses semantic versioning (MAJOR.MINOR.PATCH):
```typescript
// Version comparison
const current = await NativeUpdate.current();
console.log(current.version); // "1.2.3"
// Check if update is major/minor/patch
function getUpdateType(oldVersion: string, newVersion: string) {
const [oldMajor, oldMinor, oldPatch] = oldVersion.split('.').map(Number);
const [newMajor, newMinor, newPatch] = newVersion.split('.').map(Number);
if (newMajor > oldMajor) return 'major';
if (newMinor > oldMinor) return 'minor';
if (newPatch > oldPatch) return 'patch';
return 'none';
}
```
```typescript
// List all downloaded bundles
const bundles = await NativeUpdate.list();
bundles.forEach((bundle) => {
console.log(`Version: ${bundle.version}`);
console.log(`Downloaded: ${new Date(bundle.downloadTime)}`);
console.log(`Size: ${bundle.size} bytes`);
console.log(`Status: ${bundle.status}`);
});
// Clean up old bundles
await NativeUpdate.delete({
keepNewest: 3, // Keep only 3 most recent bundles
});
```
The plugin includes automatic rollback for failed updates.
```typescript
// Mark app as ready after successful startup
async function onAppReady() {
try {
// Verify app is working correctly
await performHealthCheck();
// Notify that app started successfully
await NativeUpdate.notifyAppReady();
} catch (error) {
// Don't call notifyAppReady() - automatic rollback will occur
console.error('App startup failed:', error);
}
}
```
```typescript
// Reset to original bundle
await NativeUpdate.reset();
// Or rollback to previous version
const bundles = await NativeUpdate.list();
const previousBundle = bundles[bundles.length - 2];
if (previousBundle) {
await NativeUpdate.set(previousBundle);
await NativeUpdate.reload();
}
```
Use channels to manage different release tracks.
```typescript
// Set channel based on user preference
const channel = getUserPreference('updateChannel') || 'production';
await NativeUpdate.setChannel(channel);
// Available channels examples:
// - 'production': Stable releases
// - 'beta': Beta testing
// - 'alpha': Early access
// - 'development': Dev builds
// - 'staging': Pre-production
```
```typescript
// Enable features based on channel
async function getFeatureFlags() {
const bundle = await NativeUpdate.current();
switch (bundle.metadata?.channel) {
case 'alpha':
return { experimentalFeatures: true, debugMode: true };
case 'beta':
return { experimentalFeatures: true, debugMode: false };
default:
return { experimentalFeatures: false, debugMode: false };
}
}
```
1. **Minimize bundle size**:
```bash
npm run build -- --mode production
gzip -9 bundle.js
```
2. **Implement delta updates** (coming soon):
```typescript
// Future API
// Note: Delta updates are handled automatically by the sync() method
// when configured on the server. Direct delta download is not available
const bundle = await NativeUpdate.download({
version: latest.version,
});
```
```typescript
// Configure optimal download settings
{
liveUpdate: {
// Download only on WiFi
downloadOnWifiOnly: true,
// Limit concurrent downloads
maxConcurrentDownloads: 1,
// Set reasonable timeout
timeout: 30000,
// Enable resume capability
resumableDownloads: true
}
}
```
```typescript
// Monitor bundle count and clean up old versions
const bundles = await NativeUpdate.list();
console.log(`Total bundles: ${bundles.length}`);
// Clean up old bundles (keep only recent 5)
if (bundles.length > 5) {
// Sort by downloadTime and keep the 5 most recent
const sortedBundles = bundles.sort((a, b) =>
new Date(b.downloadTime).getTime() - new Date(a.downloadTime).getTime()
);
const toDelete = sortedBundles.slice(5);
for (const bundle of toDelete) {
await NativeUpdate.delete({
bundleId: bundle.bundleId,
});
}
}
```
```typescript
try {
await NativeUpdate.sync();
} catch (error) {
switch (error.code) {
case 'NETWORK_ERROR':
// No internet connection
showOfflineMessage();
break;
case 'SERVER_ERROR':
// Update server is down
scheduleRetry();
break;
case 'CHECKSUM_ERROR':
// Bundle corrupted
await NativeUpdate.delete({
bundleId: error.bundleId,
});
break;
case 'SIGNATURE_ERROR':
// Security validation failed
logSecurityEvent(error);
break;
case 'STORAGE_ERROR':
// Not enough space
await cleanupStorage();
break;
case 'VERSION_MISMATCH':
// Incompatible update
promptForAppUpdate();
break;
}
}
```
```typescript
class UpdateRetryManager {
private retryCount = 0;
private maxRetries = 3;
async syncWithRetry() {
try {
await NativeUpdate.sync();
this.retryCount = 0; // Reset on success
} catch (error) {
if (this.shouldRetry(error)) {
this.retryCount++;
const delay = this.getRetryDelay();
console.log(
`Retry ${this.retryCount}/${this.maxRetries} in ${delay}ms`
);
setTimeout(() => {
this.syncWithRetry();
}, delay);
} else {
throw error; // Don't retry
}
}
}
private shouldRetry(error: any): boolean {
return (
this.retryCount < this.maxRetries &&
['NETWORK_ERROR', 'TIMEOUT_ERROR', 'SERVER_ERROR'].includes(error.code)
);
}
private getRetryDelay(): number {
// Exponential backoff
return Math.min(1000 * Math.pow(2, this.retryCount), 30000);
}
}
```
```typescript
// Enable development mode
const devConfig = {
liveUpdate: {
appId: 'com.myapp.dev',
serverUrl: 'http://localhost:3000',
channel: 'development',
allowEmulator: true,
requireSignature: false, // Disable in dev
autoUpdate: false, // Manual control
},
};
// Force update check
await NativeUpdate.sync();
// Simulate different scenarios
await testUpdateScenarios();
```
Set up a local update server for testing:
```bash
cd server-example
npm install
npm run dev
```
1. **Successful update**
2. **Network interruption**
3. **Corrupted bundle**
4. **Version rollback**
5. **Storage full**
6. **Signature mismatch**
```typescript
// Roll out to percentage of users
function shouldReceiveUpdate(userId: string, percentage: number): boolean {
const hash = hashCode(userId);
return hash % 100 < percentage;
}
if (shouldReceiveUpdate(user.id, 10)) {
// 10% rollout
await NativeUpdate.setChannel('beta');
}
```
```typescript
// Notify users about updates
NativeUpdate.addListener('updateStateChanged', (event) => {
if (event.status === 'READY') {
showNotification({
title: 'Update Ready',
body: 'A new version is available. Restart to apply.',
actions: [
{ id: 'restart', title: 'Restart Now' },
{ id: 'later', title: 'Later' },
],
});
}
});
```
```typescript
// Track update metrics
async function trackUpdateMetrics(result: SyncResult) {
const metrics = {
event: 'live_update',
status: result.status,
version: result.bundle?.version,
downloadTime: result.downloadTime,
installTime: result.installTime,
bundleSize: result.bundle?.size,
};
await analytics.track(metrics);
}
```
```typescript
// Implement fallback for critical features
async function loadFeature(featureName: string) {
try {
// Try loading from update bundle
return await import(`./features/${featureName}`);
} catch (error) {
// Fall back to shipped version
console.warn(`Loading fallback for ${featureName}`);
return await import(`./features-fallback/${featureName}`);
}
}
```
- Set up your update server (see backend-template folder)
- Implement [Security Best Practices](../guides/security-best-practices.md)
- Configure [App Updates](./app-updates.md) for native changes
- Explore [API Reference](../api/live-update-api.md)
---
Made with ❤️ by Ahsan Mahmood