native-update 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapacitorNativeUpdate.podspec +18 -0
- package/LICENSE +21 -0
- package/Readme.md +451 -0
- package/android/build.gradle +92 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +8 -0
- package/android/gradle.properties +17 -0
- package/android/proguard-rules.pro +29 -0
- package/android/settings.gradle +2 -0
- package/android/src/main/AndroidManifest.xml +34 -0
- package/android/src/main/java/com/aoneahsan/nativeupdate/AppReviewPlugin.kt +153 -0
- package/android/src/main/java/com/aoneahsan/nativeupdate/AppUpdatePlugin.kt +275 -0
- package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundNotificationManager.kt +390 -0
- package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundUpdateManager.kt +46 -0
- package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundUpdatePlugin.kt +333 -0
- package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundUpdateWorker.kt +251 -0
- package/android/src/main/java/com/aoneahsan/nativeupdate/CapacitorNativeUpdatePlugin.kt +265 -0
- package/android/src/main/java/com/aoneahsan/nativeupdate/LiveUpdatePlugin.kt +526 -0
- package/android/src/main/java/com/aoneahsan/nativeupdate/NotificationActionReceiver.kt +99 -0
- package/android/src/main/java/com/aoneahsan/nativeupdate/SecurityManager.kt +249 -0
- package/dist/esm/__tests__/bundle-manager.test.d.ts +1 -0
- package/dist/esm/__tests__/bundle-manager.test.js +123 -0
- package/dist/esm/__tests__/bundle-manager.test.js.map +1 -0
- package/dist/esm/__tests__/config.test.d.ts +1 -0
- package/dist/esm/__tests__/config.test.js +69 -0
- package/dist/esm/__tests__/config.test.js.map +1 -0
- package/dist/esm/__tests__/integration.test.d.ts +1 -0
- package/dist/esm/__tests__/integration.test.js +78 -0
- package/dist/esm/__tests__/integration.test.js.map +1 -0
- package/dist/esm/__tests__/security.test.d.ts +1 -0
- package/dist/esm/__tests__/security.test.js +54 -0
- package/dist/esm/__tests__/security.test.js.map +1 -0
- package/dist/esm/__tests__/version-manager.test.d.ts +1 -0
- package/dist/esm/__tests__/version-manager.test.js +45 -0
- package/dist/esm/__tests__/version-manager.test.js.map +1 -0
- package/dist/esm/app-review/app-review-manager.d.ts +24 -0
- package/dist/esm/app-review/app-review-manager.js +195 -0
- package/dist/esm/app-review/app-review-manager.js.map +1 -0
- package/dist/esm/app-review/index.d.ts +5 -0
- package/dist/esm/app-review/index.js +6 -0
- package/dist/esm/app-review/index.js.map +1 -0
- package/dist/esm/app-review/platform-review-handler.d.ts +20 -0
- package/dist/esm/app-review/platform-review-handler.js +138 -0
- package/dist/esm/app-review/platform-review-handler.js.map +1 -0
- package/dist/esm/app-review/review-conditions-checker.d.ts +22 -0
- package/dist/esm/app-review/review-conditions-checker.js +155 -0
- package/dist/esm/app-review/review-conditions-checker.js.map +1 -0
- package/dist/esm/app-review/review-rate-limiter.d.ts +23 -0
- package/dist/esm/app-review/review-rate-limiter.js +164 -0
- package/dist/esm/app-review/review-rate-limiter.js.map +1 -0
- package/dist/esm/app-review/types.d.ts +41 -0
- package/dist/esm/app-review/types.js +2 -0
- package/dist/esm/app-review/types.js.map +1 -0
- package/dist/esm/app-update/app-update-checker.d.ts +13 -0
- package/dist/esm/app-update/app-update-checker.js +104 -0
- package/dist/esm/app-update/app-update-checker.js.map +1 -0
- package/dist/esm/app-update/app-update-installer.d.ts +19 -0
- package/dist/esm/app-update/app-update-installer.js +123 -0
- package/dist/esm/app-update/app-update-installer.js.map +1 -0
- package/dist/esm/app-update/app-update-manager.d.ts +28 -0
- package/dist/esm/app-update/app-update-manager.js +199 -0
- package/dist/esm/app-update/app-update-manager.js.map +1 -0
- package/dist/esm/app-update/app-update-notifier.d.ts +14 -0
- package/dist/esm/app-update/app-update-notifier.js +100 -0
- package/dist/esm/app-update/app-update-notifier.js.map +1 -0
- package/dist/esm/app-update/index.d.ts +6 -0
- package/dist/esm/app-update/index.js +7 -0
- package/dist/esm/app-update/index.js.map +1 -0
- package/dist/esm/app-update/platform-app-update.d.ts +19 -0
- package/dist/esm/app-update/platform-app-update.js +129 -0
- package/dist/esm/app-update/platform-app-update.js.map +1 -0
- package/dist/esm/app-update/types.d.ts +58 -0
- package/dist/esm/app-update/types.js +12 -0
- package/dist/esm/app-update/types.js.map +1 -0
- package/dist/esm/background-update/background-scheduler.d.ts +17 -0
- package/dist/esm/background-update/background-scheduler.js +195 -0
- package/dist/esm/background-update/background-scheduler.js.map +1 -0
- package/dist/esm/background-update/index.d.ts +3 -0
- package/dist/esm/background-update/index.js +3 -0
- package/dist/esm/background-update/index.js.map +1 -0
- package/dist/esm/background-update/notification-manager.d.ts +29 -0
- package/dist/esm/background-update/notification-manager.js +89 -0
- package/dist/esm/background-update/notification-manager.js.map +1 -0
- package/dist/esm/core/analytics.d.ts +70 -0
- package/dist/esm/core/analytics.js +137 -0
- package/dist/esm/core/analytics.js.map +1 -0
- package/dist/esm/core/cache-manager.d.ts +72 -0
- package/dist/esm/core/cache-manager.js +275 -0
- package/dist/esm/core/cache-manager.js.map +1 -0
- package/dist/esm/core/config.d.ts +48 -0
- package/dist/esm/core/config.js +83 -0
- package/dist/esm/core/config.js.map +1 -0
- package/dist/esm/core/errors.d.ts +51 -0
- package/dist/esm/core/errors.js +80 -0
- package/dist/esm/core/errors.js.map +1 -0
- package/dist/esm/core/logger.d.ts +21 -0
- package/dist/esm/core/logger.js +109 -0
- package/dist/esm/core/logger.js.map +1 -0
- package/dist/esm/core/performance.d.ts +53 -0
- package/dist/esm/core/performance.js +140 -0
- package/dist/esm/core/performance.js.map +1 -0
- package/dist/esm/core/plugin-manager.d.ts +66 -0
- package/dist/esm/core/plugin-manager.js +148 -0
- package/dist/esm/core/plugin-manager.js.map +1 -0
- package/dist/esm/core/security.d.ts +93 -0
- package/dist/esm/core/security.js +315 -0
- package/dist/esm/core/security.js.map +1 -0
- package/dist/esm/definitions.d.ts +639 -0
- package/dist/esm/definitions.js +103 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +12 -0
- package/dist/esm/index.js +16 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/live-update/bundle-manager.d.ts +94 -0
- package/dist/esm/live-update/bundle-manager.js +310 -0
- package/dist/esm/live-update/bundle-manager.js.map +1 -0
- package/dist/esm/live-update/certificate-pinning.d.ts +38 -0
- package/dist/esm/live-update/certificate-pinning.js +78 -0
- package/dist/esm/live-update/certificate-pinning.js.map +1 -0
- package/dist/esm/live-update/download-manager.d.ts +67 -0
- package/dist/esm/live-update/download-manager.js +319 -0
- package/dist/esm/live-update/download-manager.js.map +1 -0
- package/dist/esm/live-update/update-manager.d.ts +52 -0
- package/dist/esm/live-update/update-manager.js +294 -0
- package/dist/esm/live-update/update-manager.js.map +1 -0
- package/dist/esm/live-update/version-manager.d.ts +84 -0
- package/dist/esm/live-update/version-manager.js +335 -0
- package/dist/esm/live-update/version-manager.js.map +1 -0
- package/dist/esm/plugin.d.ts +6 -0
- package/dist/esm/plugin.js +283 -0
- package/dist/esm/plugin.js.map +1 -0
- package/dist/esm/security/crypto.d.ts +25 -0
- package/dist/esm/security/crypto.js +70 -0
- package/dist/esm/security/crypto.js.map +1 -0
- package/dist/esm/security/validator.d.ts +60 -0
- package/dist/esm/security/validator.js +143 -0
- package/dist/esm/security/validator.js.map +1 -0
- package/dist/esm/web.d.ts +74 -0
- package/dist/esm/web.js +595 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +2 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.esm.js +2 -0
- package/dist/plugin.esm.js.map +1 -0
- package/dist/plugin.js +3 -0
- package/dist/plugin.js.map +1 -0
- package/docs/APP_REVIEW_GUIDE.md +768 -0
- package/docs/BUNDLE_SIGNING.md +264 -0
- package/docs/LIVE_UPDATES_GUIDE.md +650 -0
- package/docs/MIGRATION.md +192 -0
- package/docs/NATIVE_UPDATES_GUIDE.md +694 -0
- package/docs/QUICK_START.md +606 -0
- package/docs/README.md +111 -0
- package/docs/REMAINING_FEATURES.md +139 -0
- package/docs/api/app-review-api.md +259 -0
- package/docs/api/app-update-api.md +238 -0
- package/docs/api/events-api.md +451 -0
- package/docs/api/live-update-api.md +265 -0
- package/docs/background-updates.md +392 -0
- package/docs/examples/advanced-scenarios.md +410 -0
- package/docs/examples/basic-usage.md +185 -0
- package/docs/features/app-reviews.md +975 -0
- package/docs/features/app-updates.md +785 -0
- package/docs/features/live-updates.md +633 -0
- package/docs/getting-started/configuration.md +468 -0
- package/docs/getting-started/installation.md +209 -0
- package/docs/getting-started/quick-start.md +379 -0
- package/docs/guides/deployment-guide.md +333 -0
- package/docs/guides/migration-from-codepush.md +142 -0
- package/docs/guides/security-best-practices.md +1057 -0
- package/docs/guides/testing-guide.md +373 -0
- package/docs/production-readiness.md +478 -0
- package/docs/security/certificate-pinning.md +122 -0
- package/docs/server-requirements.md +147 -0
- package/ios/Plugin/AppReview/AppReviewPlugin.swift +158 -0
- package/ios/Plugin/AppUpdate/AppUpdatePlugin.swift +234 -0
- package/ios/Plugin/BackgroundUpdate/BackgroundNotificationManager.swift +329 -0
- package/ios/Plugin/BackgroundUpdate/BackgroundUpdatePlugin.swift +396 -0
- package/ios/Plugin/CapacitorNativeUpdatePlugin.m +45 -0
- package/ios/Plugin/CapacitorNativeUpdatePlugin.swift +190 -0
- package/ios/Plugin/Info.plist +43 -0
- package/ios/Plugin/LiveUpdate/LiveUpdatePlugin.swift +689 -0
- package/ios/Plugin/LiveUpdate/WebViewConfiguration.swift +45 -0
- package/ios/Plugin/Security/SecurityManager.swift +289 -0
- package/package.json +90 -0
|
@@ -0,0 +1,694 @@
|
|
|
1
|
+
# Native App Updates Implementation Guide
|
|
2
|
+
|
|
3
|
+
This comprehensive guide explains how to implement Native App Updates (App Store and Google Play updates) in your Capacitor application using the CapacitorNativeUpdate plugin.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Overview](#overview)
|
|
8
|
+
- [Platform Differences](#platform-differences)
|
|
9
|
+
- [Setup Guide](#setup-guide)
|
|
10
|
+
- [Implementation Steps](#implementation-steps)
|
|
11
|
+
- [UI/UX Best Practices](#uiux-best-practices)
|
|
12
|
+
- [Testing](#testing)
|
|
13
|
+
- [Troubleshooting](#troubleshooting)
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
Native App Updates allow your app to:
|
|
18
|
+
|
|
19
|
+
- 🔄 Check for new versions in app stores
|
|
20
|
+
- 📥 Download updates within the app
|
|
21
|
+
- 🚀 Install updates seamlessly
|
|
22
|
+
- 📊 Track update adoption rates
|
|
23
|
+
|
|
24
|
+
### Benefits
|
|
25
|
+
|
|
26
|
+
- **User Experience**: Users update without leaving your app
|
|
27
|
+
- **Adoption Rate**: Higher update rates vs waiting for manual updates
|
|
28
|
+
- **Control**: Guide users through important updates
|
|
29
|
+
- **Flexibility**: Immediate vs flexible update strategies
|
|
30
|
+
|
|
31
|
+
## Platform Differences
|
|
32
|
+
|
|
33
|
+
### Android (Google Play)
|
|
34
|
+
|
|
35
|
+
- Uses Google Play Core Library
|
|
36
|
+
- Supports In-App Updates API
|
|
37
|
+
- Two update modes: Immediate and Flexible
|
|
38
|
+
- Requires Google Play Store (not available on other stores)
|
|
39
|
+
|
|
40
|
+
### iOS (App Store)
|
|
41
|
+
|
|
42
|
+
- Uses StoreKit framework
|
|
43
|
+
- Redirects to App Store for download
|
|
44
|
+
- Cannot install directly within app
|
|
45
|
+
- Shows update availability only
|
|
46
|
+
|
|
47
|
+
## Setup Guide
|
|
48
|
+
|
|
49
|
+
### Prerequisites
|
|
50
|
+
|
|
51
|
+
1. **Android**:
|
|
52
|
+
- App must be published on Google Play Store
|
|
53
|
+
- Minimum API level 21 (Android 5.0)
|
|
54
|
+
- Google Play Core library
|
|
55
|
+
|
|
56
|
+
2. **iOS**:
|
|
57
|
+
- App must be published on Apple App Store
|
|
58
|
+
- iOS 10.0 or higher
|
|
59
|
+
- Valid App Store ID
|
|
60
|
+
|
|
61
|
+
### Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm install capacitor-native-update
|
|
65
|
+
npx cap sync
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Android Configuration
|
|
69
|
+
|
|
70
|
+
1. **Add to `android/app/build.gradle`:**
|
|
71
|
+
|
|
72
|
+
```gradle
|
|
73
|
+
dependencies {
|
|
74
|
+
implementation 'com.google.android.play:core:2.1.0'
|
|
75
|
+
implementation 'com.google.android.play:core-ktx:1.8.1'
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
2. **Add to `AndroidManifest.xml`:**
|
|
80
|
+
|
|
81
|
+
```xml
|
|
82
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
83
|
+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### iOS Configuration
|
|
87
|
+
|
|
88
|
+
1. **Add to `Info.plist`:**
|
|
89
|
+
|
|
90
|
+
```xml
|
|
91
|
+
<key>LSApplicationQueriesSchemes</key>
|
|
92
|
+
<array>
|
|
93
|
+
<string>itms-apps</string>
|
|
94
|
+
</array>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
2. **Enable Capabilities in Xcode:**
|
|
98
|
+
- App Groups (for shared data)
|
|
99
|
+
- Background Modes > Background fetch
|
|
100
|
+
|
|
101
|
+
## Implementation Steps
|
|
102
|
+
|
|
103
|
+
### Step 1: Basic Implementation
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import { CapacitorNativeUpdate } from 'capacitor-native-update';
|
|
107
|
+
import { Capacitor } from '@capacitor/core';
|
|
108
|
+
|
|
109
|
+
export class NativeUpdateService {
|
|
110
|
+
async checkForAppUpdate() {
|
|
111
|
+
try {
|
|
112
|
+
// Check current platform
|
|
113
|
+
const platform = Capacitor.getPlatform();
|
|
114
|
+
|
|
115
|
+
// Check for native app updates
|
|
116
|
+
const result = await CapacitorNativeUpdate.checkAppUpdate();
|
|
117
|
+
|
|
118
|
+
if (result.updateAvailable) {
|
|
119
|
+
console.log(`Update available: ${result.availableVersion}`);
|
|
120
|
+
console.log(`Current version: ${result.currentVersion}`);
|
|
121
|
+
|
|
122
|
+
// Handle based on update type
|
|
123
|
+
if (result.immediateUpdateAllowed) {
|
|
124
|
+
await this.performImmediateUpdate();
|
|
125
|
+
} else if (result.flexibleUpdateAllowed) {
|
|
126
|
+
await this.performFlexibleUpdate();
|
|
127
|
+
} else if (platform === 'ios') {
|
|
128
|
+
await this.redirectToAppStore();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error('Update check failed:', error);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Step 2: Android In-App Updates
|
|
139
|
+
|
|
140
|
+
#### Immediate Update (Blocking)
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
export class AndroidImmediateUpdate {
|
|
144
|
+
async performImmediateUpdate() {
|
|
145
|
+
try {
|
|
146
|
+
// Start immediate update
|
|
147
|
+
const { started } = await CapacitorNativeUpdate.startImmediateUpdate();
|
|
148
|
+
|
|
149
|
+
if (started) {
|
|
150
|
+
// The app will be restarted automatically after update
|
|
151
|
+
console.log('Immediate update started');
|
|
152
|
+
}
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (error.code === 'UPDATE_CANCELED') {
|
|
155
|
+
// User canceled the update
|
|
156
|
+
// For immediate updates, you might want to close the app
|
|
157
|
+
await this.showMandatoryUpdateDialog();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async showMandatoryUpdateDialog() {
|
|
163
|
+
const alert = await this.alertController.create({
|
|
164
|
+
header: 'Update Required',
|
|
165
|
+
message: 'This update is required to continue using the app.',
|
|
166
|
+
backdropDismiss: false,
|
|
167
|
+
buttons: [
|
|
168
|
+
{
|
|
169
|
+
text: 'Update',
|
|
170
|
+
handler: () => {
|
|
171
|
+
this.performImmediateUpdate();
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
});
|
|
176
|
+
await alert.present();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
#### Flexible Update (Non-blocking)
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
export class AndroidFlexibleUpdate {
|
|
185
|
+
private updateDownloaded = false;
|
|
186
|
+
|
|
187
|
+
async performFlexibleUpdate() {
|
|
188
|
+
try {
|
|
189
|
+
// Start flexible update
|
|
190
|
+
await CapacitorNativeUpdate.startFlexibleUpdate();
|
|
191
|
+
|
|
192
|
+
// Listen for download progress
|
|
193
|
+
CapacitorNativeUpdate.addListener(
|
|
194
|
+
'onAppUpdateDownloadProgress',
|
|
195
|
+
(progress) => {
|
|
196
|
+
console.log(
|
|
197
|
+
`Download progress: ${progress.bytesDownloaded} / ${progress.totalBytesToDownload}`
|
|
198
|
+
);
|
|
199
|
+
this.updateProgressUI(progress);
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
// Listen for download completion
|
|
204
|
+
CapacitorNativeUpdate.addListener('onAppUpdateDownloaded', () => {
|
|
205
|
+
console.log('Update downloaded');
|
|
206
|
+
this.updateDownloaded = true;
|
|
207
|
+
this.showInstallPrompt();
|
|
208
|
+
});
|
|
209
|
+
} catch (error) {
|
|
210
|
+
console.error('Flexible update failed:', error);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async showInstallPrompt() {
|
|
215
|
+
const alert = await this.alertController.create({
|
|
216
|
+
header: 'Update Ready',
|
|
217
|
+
message:
|
|
218
|
+
'A new version has been downloaded. Would you like to install it now?',
|
|
219
|
+
buttons: [
|
|
220
|
+
{
|
|
221
|
+
text: 'Later',
|
|
222
|
+
role: 'cancel',
|
|
223
|
+
handler: () => {
|
|
224
|
+
// Install will happen on next app restart
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
text: 'Install',
|
|
229
|
+
handler: () => {
|
|
230
|
+
this.completeFlexibleUpdate();
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
});
|
|
235
|
+
await alert.present();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async completeFlexibleUpdate() {
|
|
239
|
+
try {
|
|
240
|
+
await CapacitorNativeUpdate.completeFlexibleUpdate();
|
|
241
|
+
// App will restart with new version
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.error('Failed to complete update:', error);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
updateProgressUI(progress: any) {
|
|
248
|
+
const percent = Math.round(
|
|
249
|
+
(progress.bytesDownloaded / progress.totalBytesToDownload) * 100
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
// Update your UI progress bar
|
|
253
|
+
this.downloadProgress = percent;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Step 3: iOS App Store Updates
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
export class iOSAppStoreUpdate {
|
|
262
|
+
async checkAndPromptUpdate() {
|
|
263
|
+
try {
|
|
264
|
+
const result = await CapacitorNativeUpdate.checkAppUpdate();
|
|
265
|
+
|
|
266
|
+
if (result.updateAvailable) {
|
|
267
|
+
await this.showiOSUpdateDialog(result);
|
|
268
|
+
}
|
|
269
|
+
} catch (error) {
|
|
270
|
+
console.error('iOS update check failed:', error);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async showiOSUpdateDialog(updateInfo: any) {
|
|
275
|
+
const alert = await this.alertController.create({
|
|
276
|
+
header: 'Update Available',
|
|
277
|
+
message: `Version ${updateInfo.availableVersion} is available on the App Store.`,
|
|
278
|
+
buttons: [
|
|
279
|
+
{
|
|
280
|
+
text: 'Later',
|
|
281
|
+
role: 'cancel',
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
text: 'Update',
|
|
285
|
+
handler: () => {
|
|
286
|
+
this.openAppStore();
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
});
|
|
291
|
+
await alert.present();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async openAppStore() {
|
|
295
|
+
try {
|
|
296
|
+
await CapacitorNativeUpdate.openAppStore();
|
|
297
|
+
} catch (error) {
|
|
298
|
+
// Fallback to browser
|
|
299
|
+
const appStoreUrl = `https://apps.apple.com/app/id${YOUR_APP_STORE_ID}`;
|
|
300
|
+
window.open(appStoreUrl, '_system');
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Step 4: Cross-Platform Implementation
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
import { Capacitor } from '@capacitor/core';
|
|
310
|
+
import { CapacitorNativeUpdate } from 'capacitor-native-update';
|
|
311
|
+
|
|
312
|
+
export class UnifiedUpdateService {
|
|
313
|
+
private platform = Capacitor.getPlatform();
|
|
314
|
+
|
|
315
|
+
async checkAndUpdateApp() {
|
|
316
|
+
try {
|
|
317
|
+
const updateInfo = await CapacitorNativeUpdate.checkAppUpdate();
|
|
318
|
+
|
|
319
|
+
if (!updateInfo.updateAvailable) {
|
|
320
|
+
console.log('App is up to date');
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Platform-specific handling
|
|
325
|
+
if (this.platform === 'android') {
|
|
326
|
+
await this.handleAndroidUpdate(updateInfo);
|
|
327
|
+
} else if (this.platform === 'ios') {
|
|
328
|
+
await this.handleiOSUpdate(updateInfo);
|
|
329
|
+
}
|
|
330
|
+
} catch (error) {
|
|
331
|
+
console.error('Update check failed:', error);
|
|
332
|
+
this.handleUpdateError(error);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private async handleAndroidUpdate(updateInfo: any) {
|
|
337
|
+
// Determine update priority
|
|
338
|
+
const priority = updateInfo.updatePriority || 0;
|
|
339
|
+
|
|
340
|
+
if (priority >= 4 || updateInfo.immediateUpdateAllowed) {
|
|
341
|
+
// High priority or immediate update required
|
|
342
|
+
await this.showUpdateDialog({
|
|
343
|
+
title: 'Important Update',
|
|
344
|
+
message: 'A critical update is available and must be installed.',
|
|
345
|
+
mandatory: true,
|
|
346
|
+
action: () => this.performImmediateUpdate(),
|
|
347
|
+
});
|
|
348
|
+
} else if (updateInfo.flexibleUpdateAllowed) {
|
|
349
|
+
// Normal priority - flexible update
|
|
350
|
+
await this.showUpdateDialog({
|
|
351
|
+
title: 'Update Available',
|
|
352
|
+
message: `Version ${updateInfo.availableVersion} is available with new features and improvements.`,
|
|
353
|
+
mandatory: false,
|
|
354
|
+
action: () => this.performFlexibleUpdate(),
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private async handleiOSUpdate(updateInfo: any) {
|
|
360
|
+
await this.showUpdateDialog({
|
|
361
|
+
title: 'Update Available',
|
|
362
|
+
message: `Version ${updateInfo.availableVersion} is available on the App Store.`,
|
|
363
|
+
mandatory: false,
|
|
364
|
+
action: () => CapacitorNativeUpdate.openAppStore(),
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private async showUpdateDialog(options: {
|
|
369
|
+
title: string;
|
|
370
|
+
message: string;
|
|
371
|
+
mandatory: boolean;
|
|
372
|
+
action: () => Promise<void>;
|
|
373
|
+
}) {
|
|
374
|
+
const buttons = options.mandatory
|
|
375
|
+
? [
|
|
376
|
+
{
|
|
377
|
+
text: 'Update Now',
|
|
378
|
+
handler: () => options.action(),
|
|
379
|
+
},
|
|
380
|
+
]
|
|
381
|
+
: [
|
|
382
|
+
{
|
|
383
|
+
text: 'Later',
|
|
384
|
+
role: 'cancel',
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
text: 'Update',
|
|
388
|
+
handler: () => options.action(),
|
|
389
|
+
},
|
|
390
|
+
];
|
|
391
|
+
|
|
392
|
+
const alert = await this.alertController.create({
|
|
393
|
+
header: options.title,
|
|
394
|
+
message: options.message,
|
|
395
|
+
backdropDismiss: !options.mandatory,
|
|
396
|
+
buttons,
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
await alert.present();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Step 5: Update Status Handling
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
export class UpdateStatusManager {
|
|
408
|
+
constructor() {
|
|
409
|
+
this.setupUpdateListeners();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
private setupUpdateListeners() {
|
|
413
|
+
// Installation status
|
|
414
|
+
CapacitorNativeUpdate.addListener('onAppUpdateInstallStatus', (status) => {
|
|
415
|
+
switch (status.status) {
|
|
416
|
+
case 'PENDING':
|
|
417
|
+
console.log('Update pending');
|
|
418
|
+
break;
|
|
419
|
+
case 'DOWNLOADING':
|
|
420
|
+
console.log('Downloading update');
|
|
421
|
+
break;
|
|
422
|
+
case 'INSTALLING':
|
|
423
|
+
console.log('Installing update');
|
|
424
|
+
break;
|
|
425
|
+
case 'INSTALLED':
|
|
426
|
+
console.log('Update installed');
|
|
427
|
+
this.notifyUpdateComplete();
|
|
428
|
+
break;
|
|
429
|
+
case 'FAILED':
|
|
430
|
+
console.error('Update failed:', status.error);
|
|
431
|
+
this.handleUpdateFailure(status.error);
|
|
432
|
+
break;
|
|
433
|
+
case 'CANCELED':
|
|
434
|
+
console.log('Update canceled by user');
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
// Download progress (Android flexible updates)
|
|
440
|
+
CapacitorNativeUpdate.addListener(
|
|
441
|
+
'onAppUpdateDownloadProgress',
|
|
442
|
+
(progress) => {
|
|
443
|
+
const percent = Math.round(
|
|
444
|
+
(progress.bytesDownloaded / progress.totalBytesToDownload) * 100
|
|
445
|
+
);
|
|
446
|
+
this.updateProgressBar(percent);
|
|
447
|
+
}
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
private updateProgressBar(percent: number) {
|
|
452
|
+
// Update your UI
|
|
453
|
+
const progressBar = document.querySelector('.update-progress');
|
|
454
|
+
if (progressBar) {
|
|
455
|
+
progressBar.setAttribute('value', percent.toString());
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## UI/UX Best Practices
|
|
462
|
+
|
|
463
|
+
### 1. Update Prompts
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
export class UpdateUIService {
|
|
467
|
+
async showSmartUpdatePrompt(updateInfo: any) {
|
|
468
|
+
const timeSinceLastPrompt = Date.now() - this.lastPromptTime;
|
|
469
|
+
const oneDayInMs = 24 * 60 * 60 * 1000;
|
|
470
|
+
|
|
471
|
+
// Don't prompt too frequently
|
|
472
|
+
if (timeSinceLastPrompt < oneDayInMs && !updateInfo.mandatory) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Custom UI based on update importance
|
|
477
|
+
if (updateInfo.updatePriority >= 4) {
|
|
478
|
+
await this.showCriticalUpdateUI(updateInfo);
|
|
479
|
+
} else if (updateInfo.releaseNotes?.includes('security')) {
|
|
480
|
+
await this.showSecurityUpdateUI(updateInfo);
|
|
481
|
+
} else {
|
|
482
|
+
await this.showStandardUpdateUI(updateInfo);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
this.lastPromptTime = Date.now();
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
private async showCriticalUpdateUI(updateInfo: any) {
|
|
489
|
+
// Full-screen modal for critical updates
|
|
490
|
+
const modal = await this.modalController.create({
|
|
491
|
+
component: CriticalUpdateComponent,
|
|
492
|
+
componentProps: { updateInfo },
|
|
493
|
+
backdropDismiss: false,
|
|
494
|
+
});
|
|
495
|
+
await modal.present();
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
private async showStandardUpdateUI(updateInfo: any) {
|
|
499
|
+
// Toast or small banner for standard updates
|
|
500
|
+
const toast = await this.toastController.create({
|
|
501
|
+
message: 'A new version is available',
|
|
502
|
+
duration: 5000,
|
|
503
|
+
position: 'top',
|
|
504
|
+
buttons: [
|
|
505
|
+
{
|
|
506
|
+
text: 'Update',
|
|
507
|
+
handler: () => this.startUpdate(),
|
|
508
|
+
},
|
|
509
|
+
],
|
|
510
|
+
});
|
|
511
|
+
await toast.present();
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### 2. Progress Indicators
|
|
517
|
+
|
|
518
|
+
```html
|
|
519
|
+
<!-- update-progress.component.html -->
|
|
520
|
+
<ion-card *ngIf="updateInProgress">
|
|
521
|
+
<ion-card-header>
|
|
522
|
+
<ion-card-title>Updating App</ion-card-title>
|
|
523
|
+
</ion-card-header>
|
|
524
|
+
<ion-card-content>
|
|
525
|
+
<ion-progress-bar [value]="downloadProgress / 100"></ion-progress-bar>
|
|
526
|
+
<p>{{ downloadProgress }}% complete</p>
|
|
527
|
+
<p class="update-status">{{ updateStatus }}</p>
|
|
528
|
+
</ion-card-content>
|
|
529
|
+
</ion-card>
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
### 3. Smart Update Scheduling
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
export class SmartUpdateScheduler {
|
|
536
|
+
async scheduleUpdate() {
|
|
537
|
+
const updateInfo = await CapacitorNativeUpdate.checkAppUpdate();
|
|
538
|
+
|
|
539
|
+
if (!updateInfo.updateAvailable) return;
|
|
540
|
+
|
|
541
|
+
// Check user preferences
|
|
542
|
+
const preferences = await this.getUpdatePreferences();
|
|
543
|
+
|
|
544
|
+
if (preferences.autoUpdate === 'wifi-only') {
|
|
545
|
+
const connection = await Network.getStatus();
|
|
546
|
+
if (connection.connectionType !== 'wifi') {
|
|
547
|
+
// Schedule for later when on WiFi
|
|
548
|
+
this.scheduleForWiFi();
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (preferences.autoUpdate === 'night-only') {
|
|
554
|
+
const hour = new Date().getHours();
|
|
555
|
+
if (hour >= 6 && hour <= 22) {
|
|
556
|
+
// Schedule for nighttime
|
|
557
|
+
this.scheduleForNight();
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Proceed with update
|
|
563
|
+
await this.performUpdate();
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
## Testing
|
|
569
|
+
|
|
570
|
+
### Android Testing
|
|
571
|
+
|
|
572
|
+
1. **Using Internal Test Track**:
|
|
573
|
+
|
|
574
|
+
```typescript
|
|
575
|
+
// Enable internal app sharing for testing
|
|
576
|
+
await CapacitorNativeUpdate.enableDebugMode({
|
|
577
|
+
enabled: true,
|
|
578
|
+
testMode: 'internal-test',
|
|
579
|
+
});
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
2. **Test Different Scenarios**:
|
|
583
|
+
|
|
584
|
+
```typescript
|
|
585
|
+
// Test immediate update
|
|
586
|
+
await CapacitorNativeUpdate.simulateUpdate({
|
|
587
|
+
type: 'immediate',
|
|
588
|
+
version: '2.0.0',
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
// Test flexible update
|
|
592
|
+
await CapacitorNativeUpdate.simulateUpdate({
|
|
593
|
+
type: 'flexible',
|
|
594
|
+
version: '1.1.0',
|
|
595
|
+
});
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### iOS Testing
|
|
599
|
+
|
|
600
|
+
1. **TestFlight Testing**:
|
|
601
|
+
- Upload build to TestFlight
|
|
602
|
+
- Install older version on device
|
|
603
|
+
- Check for updates in app
|
|
604
|
+
|
|
605
|
+
2. **Sandbox Testing**:
|
|
606
|
+
- Use sandbox App Store account
|
|
607
|
+
- Test update flow without affecting production
|
|
608
|
+
|
|
609
|
+
### Testing Checklist
|
|
610
|
+
|
|
611
|
+
- [ ] Update available detection
|
|
612
|
+
- [ ] Update not available scenario
|
|
613
|
+
- [ ] Immediate update flow (Android)
|
|
614
|
+
- [ ] Flexible update flow (Android)
|
|
615
|
+
- [ ] App Store redirect (iOS)
|
|
616
|
+
- [ ] Update cancellation handling
|
|
617
|
+
- [ ] Network error handling
|
|
618
|
+
- [ ] Update completion verification
|
|
619
|
+
- [ ] Rollback scenarios (if applicable)
|
|
620
|
+
|
|
621
|
+
## Troubleshooting
|
|
622
|
+
|
|
623
|
+
### Common Issues
|
|
624
|
+
|
|
625
|
+
#### 1. Update Not Detected
|
|
626
|
+
|
|
627
|
+
```typescript
|
|
628
|
+
// Debug update detection
|
|
629
|
+
const debug = await CapacitorNativeUpdate.getDebugInfo();
|
|
630
|
+
console.log('Debug info:', {
|
|
631
|
+
currentVersion: debug.currentVersion,
|
|
632
|
+
packageName: debug.packageName,
|
|
633
|
+
lastCheckTime: debug.lastCheckTime,
|
|
634
|
+
playServicesAvailable: debug.playServicesAvailable, // Android
|
|
635
|
+
});
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
#### 2. Update Fails to Install
|
|
639
|
+
|
|
640
|
+
```typescript
|
|
641
|
+
// Check update state
|
|
642
|
+
const state = await CapacitorNativeUpdate.getUpdateState();
|
|
643
|
+
if (state.status === 'FAILED') {
|
|
644
|
+
// Clear update data and retry
|
|
645
|
+
await CapacitorNativeUpdate.clearUpdateData();
|
|
646
|
+
await CapacitorNativeUpdate.checkAppUpdate();
|
|
647
|
+
}
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
#### 3. iOS App Store Not Opening
|
|
651
|
+
|
|
652
|
+
```typescript
|
|
653
|
+
// Verify App Store ID configuration
|
|
654
|
+
const config = await CapacitorNativeUpdate.getConfiguration();
|
|
655
|
+
console.log('App Store ID:', config.appStoreId);
|
|
656
|
+
|
|
657
|
+
// Manual fallback
|
|
658
|
+
if (!config.appStoreId) {
|
|
659
|
+
console.error('App Store ID not configured');
|
|
660
|
+
}
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
### Platform-Specific Issues
|
|
664
|
+
|
|
665
|
+
#### Android
|
|
666
|
+
|
|
667
|
+
- Ensure Google Play Services is updated
|
|
668
|
+
- Check Play Store app is installed
|
|
669
|
+
- Verify app is signed with release key
|
|
670
|
+
- Test on device with Play Store (not emulator)
|
|
671
|
+
|
|
672
|
+
#### iOS
|
|
673
|
+
|
|
674
|
+
- Verify App Store ID is correct
|
|
675
|
+
- Check iTunes lookup API response
|
|
676
|
+
- Ensure app is live on App Store
|
|
677
|
+
- Test on real device (not simulator)
|
|
678
|
+
|
|
679
|
+
## Best Practices Summary
|
|
680
|
+
|
|
681
|
+
1. **Always provide "Later" option** for non-critical updates
|
|
682
|
+
2. **Show release notes** when available
|
|
683
|
+
3. **Respect user preferences** for update timing
|
|
684
|
+
4. **Handle network conditions** appropriately
|
|
685
|
+
5. **Test thoroughly** on both platforms
|
|
686
|
+
6. **Monitor update metrics** to improve adoption
|
|
687
|
+
7. **Provide fallback options** for update failures
|
|
688
|
+
|
|
689
|
+
## Next Steps
|
|
690
|
+
|
|
691
|
+
- Learn about [Live Updates (OTA)](./LIVE_UPDATES_GUIDE.md)
|
|
692
|
+
- Implement [App Review Features](./APP_REVIEW_GUIDE.md)
|
|
693
|
+
- Review [Security Guidelines](./SECURITY.md)
|
|
694
|
+
- Check [API Reference](../API.md)
|