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.
Files changed (184) hide show
  1. package/CapacitorNativeUpdate.podspec +18 -0
  2. package/LICENSE +21 -0
  3. package/Readme.md +451 -0
  4. package/android/build.gradle +92 -0
  5. package/android/gradle/wrapper/gradle-wrapper.properties +8 -0
  6. package/android/gradle.properties +17 -0
  7. package/android/proguard-rules.pro +29 -0
  8. package/android/settings.gradle +2 -0
  9. package/android/src/main/AndroidManifest.xml +34 -0
  10. package/android/src/main/java/com/aoneahsan/nativeupdate/AppReviewPlugin.kt +153 -0
  11. package/android/src/main/java/com/aoneahsan/nativeupdate/AppUpdatePlugin.kt +275 -0
  12. package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundNotificationManager.kt +390 -0
  13. package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundUpdateManager.kt +46 -0
  14. package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundUpdatePlugin.kt +333 -0
  15. package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundUpdateWorker.kt +251 -0
  16. package/android/src/main/java/com/aoneahsan/nativeupdate/CapacitorNativeUpdatePlugin.kt +265 -0
  17. package/android/src/main/java/com/aoneahsan/nativeupdate/LiveUpdatePlugin.kt +526 -0
  18. package/android/src/main/java/com/aoneahsan/nativeupdate/NotificationActionReceiver.kt +99 -0
  19. package/android/src/main/java/com/aoneahsan/nativeupdate/SecurityManager.kt +249 -0
  20. package/dist/esm/__tests__/bundle-manager.test.d.ts +1 -0
  21. package/dist/esm/__tests__/bundle-manager.test.js +123 -0
  22. package/dist/esm/__tests__/bundle-manager.test.js.map +1 -0
  23. package/dist/esm/__tests__/config.test.d.ts +1 -0
  24. package/dist/esm/__tests__/config.test.js +69 -0
  25. package/dist/esm/__tests__/config.test.js.map +1 -0
  26. package/dist/esm/__tests__/integration.test.d.ts +1 -0
  27. package/dist/esm/__tests__/integration.test.js +78 -0
  28. package/dist/esm/__tests__/integration.test.js.map +1 -0
  29. package/dist/esm/__tests__/security.test.d.ts +1 -0
  30. package/dist/esm/__tests__/security.test.js +54 -0
  31. package/dist/esm/__tests__/security.test.js.map +1 -0
  32. package/dist/esm/__tests__/version-manager.test.d.ts +1 -0
  33. package/dist/esm/__tests__/version-manager.test.js +45 -0
  34. package/dist/esm/__tests__/version-manager.test.js.map +1 -0
  35. package/dist/esm/app-review/app-review-manager.d.ts +24 -0
  36. package/dist/esm/app-review/app-review-manager.js +195 -0
  37. package/dist/esm/app-review/app-review-manager.js.map +1 -0
  38. package/dist/esm/app-review/index.d.ts +5 -0
  39. package/dist/esm/app-review/index.js +6 -0
  40. package/dist/esm/app-review/index.js.map +1 -0
  41. package/dist/esm/app-review/platform-review-handler.d.ts +20 -0
  42. package/dist/esm/app-review/platform-review-handler.js +138 -0
  43. package/dist/esm/app-review/platform-review-handler.js.map +1 -0
  44. package/dist/esm/app-review/review-conditions-checker.d.ts +22 -0
  45. package/dist/esm/app-review/review-conditions-checker.js +155 -0
  46. package/dist/esm/app-review/review-conditions-checker.js.map +1 -0
  47. package/dist/esm/app-review/review-rate-limiter.d.ts +23 -0
  48. package/dist/esm/app-review/review-rate-limiter.js +164 -0
  49. package/dist/esm/app-review/review-rate-limiter.js.map +1 -0
  50. package/dist/esm/app-review/types.d.ts +41 -0
  51. package/dist/esm/app-review/types.js +2 -0
  52. package/dist/esm/app-review/types.js.map +1 -0
  53. package/dist/esm/app-update/app-update-checker.d.ts +13 -0
  54. package/dist/esm/app-update/app-update-checker.js +104 -0
  55. package/dist/esm/app-update/app-update-checker.js.map +1 -0
  56. package/dist/esm/app-update/app-update-installer.d.ts +19 -0
  57. package/dist/esm/app-update/app-update-installer.js +123 -0
  58. package/dist/esm/app-update/app-update-installer.js.map +1 -0
  59. package/dist/esm/app-update/app-update-manager.d.ts +28 -0
  60. package/dist/esm/app-update/app-update-manager.js +199 -0
  61. package/dist/esm/app-update/app-update-manager.js.map +1 -0
  62. package/dist/esm/app-update/app-update-notifier.d.ts +14 -0
  63. package/dist/esm/app-update/app-update-notifier.js +100 -0
  64. package/dist/esm/app-update/app-update-notifier.js.map +1 -0
  65. package/dist/esm/app-update/index.d.ts +6 -0
  66. package/dist/esm/app-update/index.js +7 -0
  67. package/dist/esm/app-update/index.js.map +1 -0
  68. package/dist/esm/app-update/platform-app-update.d.ts +19 -0
  69. package/dist/esm/app-update/platform-app-update.js +129 -0
  70. package/dist/esm/app-update/platform-app-update.js.map +1 -0
  71. package/dist/esm/app-update/types.d.ts +58 -0
  72. package/dist/esm/app-update/types.js +12 -0
  73. package/dist/esm/app-update/types.js.map +1 -0
  74. package/dist/esm/background-update/background-scheduler.d.ts +17 -0
  75. package/dist/esm/background-update/background-scheduler.js +195 -0
  76. package/dist/esm/background-update/background-scheduler.js.map +1 -0
  77. package/dist/esm/background-update/index.d.ts +3 -0
  78. package/dist/esm/background-update/index.js +3 -0
  79. package/dist/esm/background-update/index.js.map +1 -0
  80. package/dist/esm/background-update/notification-manager.d.ts +29 -0
  81. package/dist/esm/background-update/notification-manager.js +89 -0
  82. package/dist/esm/background-update/notification-manager.js.map +1 -0
  83. package/dist/esm/core/analytics.d.ts +70 -0
  84. package/dist/esm/core/analytics.js +137 -0
  85. package/dist/esm/core/analytics.js.map +1 -0
  86. package/dist/esm/core/cache-manager.d.ts +72 -0
  87. package/dist/esm/core/cache-manager.js +275 -0
  88. package/dist/esm/core/cache-manager.js.map +1 -0
  89. package/dist/esm/core/config.d.ts +48 -0
  90. package/dist/esm/core/config.js +83 -0
  91. package/dist/esm/core/config.js.map +1 -0
  92. package/dist/esm/core/errors.d.ts +51 -0
  93. package/dist/esm/core/errors.js +80 -0
  94. package/dist/esm/core/errors.js.map +1 -0
  95. package/dist/esm/core/logger.d.ts +21 -0
  96. package/dist/esm/core/logger.js +109 -0
  97. package/dist/esm/core/logger.js.map +1 -0
  98. package/dist/esm/core/performance.d.ts +53 -0
  99. package/dist/esm/core/performance.js +140 -0
  100. package/dist/esm/core/performance.js.map +1 -0
  101. package/dist/esm/core/plugin-manager.d.ts +66 -0
  102. package/dist/esm/core/plugin-manager.js +148 -0
  103. package/dist/esm/core/plugin-manager.js.map +1 -0
  104. package/dist/esm/core/security.d.ts +93 -0
  105. package/dist/esm/core/security.js +315 -0
  106. package/dist/esm/core/security.js.map +1 -0
  107. package/dist/esm/definitions.d.ts +639 -0
  108. package/dist/esm/definitions.js +103 -0
  109. package/dist/esm/definitions.js.map +1 -0
  110. package/dist/esm/index.d.ts +12 -0
  111. package/dist/esm/index.js +16 -0
  112. package/dist/esm/index.js.map +1 -0
  113. package/dist/esm/live-update/bundle-manager.d.ts +94 -0
  114. package/dist/esm/live-update/bundle-manager.js +310 -0
  115. package/dist/esm/live-update/bundle-manager.js.map +1 -0
  116. package/dist/esm/live-update/certificate-pinning.d.ts +38 -0
  117. package/dist/esm/live-update/certificate-pinning.js +78 -0
  118. package/dist/esm/live-update/certificate-pinning.js.map +1 -0
  119. package/dist/esm/live-update/download-manager.d.ts +67 -0
  120. package/dist/esm/live-update/download-manager.js +319 -0
  121. package/dist/esm/live-update/download-manager.js.map +1 -0
  122. package/dist/esm/live-update/update-manager.d.ts +52 -0
  123. package/dist/esm/live-update/update-manager.js +294 -0
  124. package/dist/esm/live-update/update-manager.js.map +1 -0
  125. package/dist/esm/live-update/version-manager.d.ts +84 -0
  126. package/dist/esm/live-update/version-manager.js +335 -0
  127. package/dist/esm/live-update/version-manager.js.map +1 -0
  128. package/dist/esm/plugin.d.ts +6 -0
  129. package/dist/esm/plugin.js +283 -0
  130. package/dist/esm/plugin.js.map +1 -0
  131. package/dist/esm/security/crypto.d.ts +25 -0
  132. package/dist/esm/security/crypto.js +70 -0
  133. package/dist/esm/security/crypto.js.map +1 -0
  134. package/dist/esm/security/validator.d.ts +60 -0
  135. package/dist/esm/security/validator.js +143 -0
  136. package/dist/esm/security/validator.js.map +1 -0
  137. package/dist/esm/web.d.ts +74 -0
  138. package/dist/esm/web.js +595 -0
  139. package/dist/esm/web.js.map +1 -0
  140. package/dist/plugin.cjs.js +2 -0
  141. package/dist/plugin.cjs.js.map +1 -0
  142. package/dist/plugin.esm.js +2 -0
  143. package/dist/plugin.esm.js.map +1 -0
  144. package/dist/plugin.js +3 -0
  145. package/dist/plugin.js.map +1 -0
  146. package/docs/APP_REVIEW_GUIDE.md +768 -0
  147. package/docs/BUNDLE_SIGNING.md +264 -0
  148. package/docs/LIVE_UPDATES_GUIDE.md +650 -0
  149. package/docs/MIGRATION.md +192 -0
  150. package/docs/NATIVE_UPDATES_GUIDE.md +694 -0
  151. package/docs/QUICK_START.md +606 -0
  152. package/docs/README.md +111 -0
  153. package/docs/REMAINING_FEATURES.md +139 -0
  154. package/docs/api/app-review-api.md +259 -0
  155. package/docs/api/app-update-api.md +238 -0
  156. package/docs/api/events-api.md +451 -0
  157. package/docs/api/live-update-api.md +265 -0
  158. package/docs/background-updates.md +392 -0
  159. package/docs/examples/advanced-scenarios.md +410 -0
  160. package/docs/examples/basic-usage.md +185 -0
  161. package/docs/features/app-reviews.md +975 -0
  162. package/docs/features/app-updates.md +785 -0
  163. package/docs/features/live-updates.md +633 -0
  164. package/docs/getting-started/configuration.md +468 -0
  165. package/docs/getting-started/installation.md +209 -0
  166. package/docs/getting-started/quick-start.md +379 -0
  167. package/docs/guides/deployment-guide.md +333 -0
  168. package/docs/guides/migration-from-codepush.md +142 -0
  169. package/docs/guides/security-best-practices.md +1057 -0
  170. package/docs/guides/testing-guide.md +373 -0
  171. package/docs/production-readiness.md +478 -0
  172. package/docs/security/certificate-pinning.md +122 -0
  173. package/docs/server-requirements.md +147 -0
  174. package/ios/Plugin/AppReview/AppReviewPlugin.swift +158 -0
  175. package/ios/Plugin/AppUpdate/AppUpdatePlugin.swift +234 -0
  176. package/ios/Plugin/BackgroundUpdate/BackgroundNotificationManager.swift +329 -0
  177. package/ios/Plugin/BackgroundUpdate/BackgroundUpdatePlugin.swift +396 -0
  178. package/ios/Plugin/CapacitorNativeUpdatePlugin.m +45 -0
  179. package/ios/Plugin/CapacitorNativeUpdatePlugin.swift +190 -0
  180. package/ios/Plugin/Info.plist +43 -0
  181. package/ios/Plugin/LiveUpdate/LiveUpdatePlugin.swift +689 -0
  182. package/ios/Plugin/LiveUpdate/WebViewConfiguration.swift +45 -0
  183. package/ios/Plugin/Security/SecurityManager.swift +289 -0
  184. package/package.json +90 -0
@@ -0,0 +1,768 @@
1
+ # App Review Implementation Guide
2
+
3
+ This comprehensive guide explains how to implement in-app review functionality in your Capacitor application using the CapacitorNativeUpdate plugin.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Overview](#overview)
8
+ - [Why In-App Reviews Matter](#why-in-app-reviews-matter)
9
+ - [Platform Guidelines](#platform-guidelines)
10
+ - [Setup Guide](#setup-guide)
11
+ - [Implementation Steps](#implementation-steps)
12
+ - [Best Practices](#best-practices)
13
+ - [Analytics Integration](#analytics-integration)
14
+ - [Testing](#testing)
15
+ - [Troubleshooting](#troubleshooting)
16
+
17
+ ## Overview
18
+
19
+ The App Review feature allows users to rate and review your app without leaving it, significantly improving the likelihood of receiving feedback.
20
+
21
+ ### Key Benefits
22
+
23
+ - 📈 **Higher Review Rates**: 2-3x more reviews than traditional methods
24
+ - 🎯 **Better Timing**: Ask for reviews at optimal moments
25
+ - 😊 **Improved UX**: No app switching required
26
+ - 📊 **Better Ratings**: Happy users are more likely to rate
27
+
28
+ ### Platform Support
29
+
30
+ - **Android**: Google Play In-App Review API (Android 5.0+)
31
+ - **iOS**: SKStoreReviewController (iOS 10.3+)
32
+ - **Web**: Fallback to custom UI or redirect
33
+
34
+ ## Why In-App Reviews Matter
35
+
36
+ ### Statistics
37
+
38
+ - Apps with 4+ star ratings see **2x more downloads**
39
+ - **70% of users** look at ratings before downloading
40
+ - In-app review prompts have **4-5x higher engagement** than external links
41
+
42
+ ### Impact on ASO (App Store Optimization)
43
+
44
+ - Higher ratings improve search ranking
45
+ - More reviews increase credibility
46
+ - Recent reviews show active development
47
+
48
+ ## Platform Guidelines
49
+
50
+ ### Apple App Store Guidelines
51
+
52
+ 1. **Frequency Limits**:
53
+ - Maximum 3 prompts per 365 days
54
+ - System may show prompt less frequently
55
+ - Cannot check if review was submitted
56
+
57
+ 2. **Requirements**:
58
+ - Cannot incentivize reviews
59
+ - Cannot gate features behind reviews
60
+ - Must use official API (no custom UI)
61
+
62
+ ### Google Play Guidelines
63
+
64
+ 1. **Frequency Limits**:
65
+ - No hard limit, but be reasonable
66
+ - System may throttle excessive requests
67
+ - Cannot check if review was submitted
68
+
69
+ 2. **Requirements**:
70
+ - Cannot incentivize reviews
71
+ - Must follow Play Store policies
72
+ - Review flow must complete in-app
73
+
74
+ ## Setup Guide
75
+
76
+ ### Installation
77
+
78
+ ```bash
79
+ npm install capacitor-native-update
80
+ npx cap sync
81
+ ```
82
+
83
+ ### Android Configuration
84
+
85
+ No additional configuration required. The plugin automatically includes the Play Core library.
86
+
87
+ ### iOS Configuration
88
+
89
+ No additional configuration required. The plugin automatically uses StoreKit.
90
+
91
+ ### Capacitor Configuration
92
+
93
+ ```json
94
+ {
95
+ "plugins": {
96
+ "CapacitorNativeUpdate": {
97
+ "appStoreId": "YOUR_APP_STORE_ID",
98
+ "reviewPromptDelay": 2000,
99
+ "reviewDebugMode": false
100
+ }
101
+ }
102
+ }
103
+ ```
104
+
105
+ ## Implementation Steps
106
+
107
+ ### Step 1: Basic Implementation
108
+
109
+ ```typescript
110
+ import { CapacitorNativeUpdate } from 'capacitor-native-update';
111
+
112
+ export class AppReviewService {
113
+ async requestReview() {
114
+ try {
115
+ const result = await CapacitorNativeUpdate.requestReview();
116
+
117
+ if (result.displayed) {
118
+ console.log('Review prompt was displayed');
119
+ // Track that prompt was shown
120
+ await this.analytics.track('review_prompt_displayed');
121
+ } else {
122
+ console.log('Review prompt was not displayed (system throttled)');
123
+ // Maybe try alternative feedback method
124
+ await this.showAlternativeFeedback();
125
+ }
126
+ } catch (error) {
127
+ console.error('Review request failed:', error);
128
+ // Fallback to external review
129
+ await this.openExternalReview();
130
+ }
131
+ }
132
+ }
133
+ ```
134
+
135
+ ### Step 2: Smart Review Triggers
136
+
137
+ ```typescript
138
+ export class SmartReviewManager {
139
+ private readonly REVIEW_CONDITIONS = {
140
+ minSessions: 5,
141
+ minDaysInstalled: 3,
142
+ minActionsCompleted: 10,
143
+ positiveExperienceRequired: true,
144
+ };
145
+
146
+ async checkAndRequestReview() {
147
+ // Check if we should ask for review
148
+ const shouldAsk = await this.shouldAskForReview();
149
+
150
+ if (!shouldAsk) {
151
+ return;
152
+ }
153
+
154
+ // Check if user had positive experience
155
+ const hasPositiveExperience = await this.checkPositiveExperience();
156
+
157
+ if (!hasPositiveExperience) {
158
+ // Ask for feedback instead
159
+ await this.askForFeedback();
160
+ return;
161
+ }
162
+
163
+ // Request review
164
+ await this.requestReview();
165
+ }
166
+
167
+ private async shouldAskForReview(): Promise<boolean> {
168
+ const stats = await this.getUserStats();
169
+
170
+ // Check all conditions
171
+ const conditions = [
172
+ stats.sessionCount >= this.REVIEW_CONDITIONS.minSessions,
173
+ stats.daysSinceInstall >= this.REVIEW_CONDITIONS.minDaysInstalled,
174
+ stats.completedActions >= this.REVIEW_CONDITIONS.minActionsCompleted,
175
+ !stats.hasReviewedBefore,
176
+ !stats.hasDeclinedRecently,
177
+ this.isGoodMoment(),
178
+ ];
179
+
180
+ return conditions.every((condition) => condition);
181
+ }
182
+
183
+ private isGoodMoment(): boolean {
184
+ // Don't interrupt critical flows
185
+ const currentRoute = this.router.url;
186
+ const badMoments = ['/checkout', '/payment', '/onboarding', '/support'];
187
+
188
+ return !badMoments.some((route) => currentRoute.includes(route));
189
+ }
190
+
191
+ private async checkPositiveExperience(): Promise<boolean> {
192
+ // Check recent user actions
193
+ const recentActions = await this.getRecentUserActions();
194
+
195
+ const positiveSignals = [
196
+ recentActions.includes('task_completed'),
197
+ recentActions.includes('content_shared'),
198
+ recentActions.includes('milestone_achieved'),
199
+ !recentActions.includes('error_encountered'),
200
+ !recentActions.includes('support_contacted'),
201
+ ];
202
+
203
+ // Need at least 3 positive signals
204
+ return positiveSignals.filter((signal) => signal).length >= 3;
205
+ }
206
+ }
207
+ ```
208
+
209
+ ### Step 3: Review Trigger Points
210
+
211
+ ```typescript
212
+ export class ReviewTriggerPoints {
213
+ constructor(
214
+ private reviewService: SmartReviewManager,
215
+ private analytics: AnalyticsService
216
+ ) {}
217
+
218
+ // After completing important action
219
+ async onTaskCompleted() {
220
+ await this.incrementPositiveAction('task_completed');
221
+
222
+ // Check if this is a milestone
223
+ const taskCount = await this.getCompletedTaskCount();
224
+ if (taskCount % 5 === 0) {
225
+ // Every 5 tasks
226
+ await this.reviewService.checkAndRequestReview();
227
+ }
228
+ }
229
+
230
+ // After successful transaction
231
+ async onPurchaseCompleted() {
232
+ await this.incrementPositiveAction('purchase_completed');
233
+
234
+ // Wait a bit before asking
235
+ setTimeout(() => {
236
+ this.reviewService.checkAndRequestReview();
237
+ }, 5000);
238
+ }
239
+
240
+ // After achieving milestone
241
+ async onMilestoneAchieved(milestone: string) {
242
+ await this.incrementPositiveAction('milestone_achieved');
243
+
244
+ // Show achievement first
245
+ await this.showAchievementToast(milestone);
246
+
247
+ // Then ask for review
248
+ setTimeout(() => {
249
+ this.reviewService.checkAndRequestReview();
250
+ }, 3000);
251
+ }
252
+
253
+ // After positive feedback
254
+ async onPositiveFeedback() {
255
+ // User already indicated satisfaction
256
+ await this.reviewService.requestReview();
257
+ }
258
+
259
+ // App foregrounded (for time-based triggers)
260
+ async onAppForegrounded() {
261
+ const lastPrompt = await this.getLastReviewPromptDate();
262
+ const daysSinceLastPrompt = this.daysSince(lastPrompt);
263
+
264
+ // Check every 30 days
265
+ if (daysSinceLastPrompt >= 30) {
266
+ await this.reviewService.checkAndRequestReview();
267
+ }
268
+ }
269
+ }
270
+ ```
271
+
272
+ ### Step 4: Two-Step Review Flow
273
+
274
+ ```typescript
275
+ export class TwoStepReviewFlow {
276
+ async initiateReviewFlow() {
277
+ // Step 1: Ask if they enjoy the app
278
+ const enjoys = await this.askEnjoyment();
279
+
280
+ if (enjoys) {
281
+ // Step 2: Ask for review
282
+ await this.askForReview();
283
+ } else {
284
+ // Ask for feedback instead
285
+ await this.askForFeedback();
286
+ }
287
+ }
288
+
289
+ private async askEnjoyment(): Promise<boolean> {
290
+ return new Promise((resolve) => {
291
+ const alert = this.alertController.create({
292
+ header: 'Enjoying the app?',
293
+ message: 'How has your experience been so far?',
294
+ buttons: [
295
+ {
296
+ text: 'Not really',
297
+ handler: () => {
298
+ this.analytics.track('review_enjoyment_negative');
299
+ resolve(false);
300
+ },
301
+ },
302
+ {
303
+ text: 'Yes!',
304
+ handler: () => {
305
+ this.analytics.track('review_enjoyment_positive');
306
+ resolve(true);
307
+ },
308
+ },
309
+ ],
310
+ });
311
+ alert.present();
312
+ });
313
+ }
314
+
315
+ private async askForReview() {
316
+ const alert = await this.alertController.create({
317
+ header: 'Awesome!',
318
+ message: 'Would you mind rating us on the app store?',
319
+ buttons: [
320
+ {
321
+ text: 'No thanks',
322
+ role: 'cancel',
323
+ handler: () => {
324
+ this.analytics.track('review_declined');
325
+ this.markDeclined();
326
+ },
327
+ },
328
+ {
329
+ text: 'Sure!',
330
+ handler: async () => {
331
+ this.analytics.track('review_accepted');
332
+ await CapacitorNativeUpdate.requestReview();
333
+ },
334
+ },
335
+ ],
336
+ });
337
+ await alert.present();
338
+ }
339
+
340
+ private async askForFeedback() {
341
+ const alert = await this.alertController.create({
342
+ header: 'We appreciate your feedback',
343
+ message: 'What can we do to improve your experience?',
344
+ inputs: [
345
+ {
346
+ name: 'feedback',
347
+ type: 'textarea',
348
+ placeholder: 'Your feedback...',
349
+ },
350
+ ],
351
+ buttons: [
352
+ {
353
+ text: 'Cancel',
354
+ role: 'cancel',
355
+ },
356
+ {
357
+ text: 'Send',
358
+ handler: (data) => {
359
+ this.submitFeedback(data.feedback);
360
+ this.showThankYou();
361
+ },
362
+ },
363
+ ],
364
+ });
365
+ await alert.present();
366
+ }
367
+ }
368
+ ```
369
+
370
+ ### Step 5: Alternative Review Methods
371
+
372
+ ```typescript
373
+ export class AlternativeReviewMethods {
374
+ // For when in-app review is not available
375
+ async openExternalReview() {
376
+ const platform = Capacitor.getPlatform();
377
+
378
+ if (platform === 'ios') {
379
+ await this.openAppStore();
380
+ } else if (platform === 'android') {
381
+ await this.openPlayStore();
382
+ } else {
383
+ await this.openWebReview();
384
+ }
385
+ }
386
+
387
+ private async openAppStore() {
388
+ const appStoreId = 'YOUR_APP_STORE_ID';
389
+ const reviewUrl = `https://apps.apple.com/app/id${appStoreId}?action=write-review`;
390
+
391
+ try {
392
+ await Browser.open({ url: reviewUrl });
393
+ this.analytics.track('external_review_opened', { platform: 'ios' });
394
+ } catch (error) {
395
+ console.error('Failed to open App Store:', error);
396
+ }
397
+ }
398
+
399
+ private async openPlayStore() {
400
+ const packageName = 'com.example.app';
401
+ const playStoreUrl = `https://play.google.com/store/apps/details?id=${packageName}`;
402
+
403
+ try {
404
+ await Browser.open({ url: playStoreUrl });
405
+ this.analytics.track('external_review_opened', { platform: 'android' });
406
+ } catch (error) {
407
+ console.error('Failed to open Play Store:', error);
408
+ }
409
+ }
410
+
411
+ private async openWebReview() {
412
+ // Custom web review form or third-party service
413
+ const modal = await this.modalController.create({
414
+ component: WebReviewComponent,
415
+ });
416
+ await modal.present();
417
+ }
418
+ }
419
+ ```
420
+
421
+ ## Best Practices
422
+
423
+ ### 1. Timing is Everything
424
+
425
+ ```typescript
426
+ export class ReviewTimingStrategy {
427
+ // Good moments to ask
428
+ private readonly GOOD_MOMENTS = [
429
+ 'after_milestone_achieved',
430
+ 'after_successful_transaction',
431
+ 'after_positive_interaction',
432
+ 'after_content_shared',
433
+ 'after_returning_user_session',
434
+ ];
435
+
436
+ // Bad moments to avoid
437
+ private readonly BAD_MOMENTS = [
438
+ 'during_onboarding',
439
+ 'after_error',
440
+ 'during_payment_flow',
441
+ 'immediately_after_install',
442
+ 'after_support_contact',
443
+ 'during_critical_task',
444
+ ];
445
+
446
+ async isGoodMomentForReview(): Promise<boolean> {
447
+ const currentContext = await this.getCurrentUserContext();
448
+
449
+ // Check if it's a bad moment
450
+ if (this.BAD_MOMENTS.includes(currentContext.state)) {
451
+ return false;
452
+ }
453
+
454
+ // Check if it's explicitly a good moment
455
+ if (this.GOOD_MOMENTS.includes(currentContext.state)) {
456
+ return true;
457
+ }
458
+
459
+ // Additional checks
460
+ return this.additionalTimingChecks(currentContext);
461
+ }
462
+
463
+ private async additionalTimingChecks(context: any): Promise<boolean> {
464
+ // Don't ask too soon after install
465
+ if (context.daysSinceInstall < 3) return false;
466
+
467
+ // Don't ask if user is in a hurry
468
+ if (context.sessionDuration < 60) return false;
469
+
470
+ // Don't ask if user had recent issues
471
+ if (context.recentErrors > 0) return false;
472
+
473
+ return true;
474
+ }
475
+ }
476
+ ```
477
+
478
+ ### 2. Respect User Choice
479
+
480
+ ```typescript
481
+ export class ReviewPreferenceManager {
482
+ private readonly PREFERENCE_KEY = 'review_preferences';
483
+
484
+ async userDeclinedReview() {
485
+ const prefs = await this.getPreferences();
486
+ prefs.declineCount++;
487
+ prefs.lastDeclineDate = new Date().toISOString();
488
+
489
+ // After 3 declines, stop asking
490
+ if (prefs.declineCount >= 3) {
491
+ prefs.permanentlyDeclined = true;
492
+ }
493
+
494
+ await this.savePreferences(prefs);
495
+ }
496
+
497
+ async canAskForReview(): Promise<boolean> {
498
+ const prefs = await this.getPreferences();
499
+
500
+ // Never ask if permanently declined
501
+ if (prefs.permanentlyDeclined) return false;
502
+
503
+ // Wait longer after each decline
504
+ const daysSinceDecline = this.daysSince(prefs.lastDeclineDate);
505
+ const requiredDays = prefs.declineCount * 30; // 30, 60, 90 days
506
+
507
+ return daysSinceDecline >= requiredDays;
508
+ }
509
+
510
+ async resetAfterPositiveReview() {
511
+ const prefs = await this.getPreferences();
512
+ prefs.hasReviewed = true;
513
+ prefs.reviewDate = new Date().toISOString();
514
+ prefs.declineCount = 0;
515
+ await this.savePreferences(prefs);
516
+ }
517
+ }
518
+ ```
519
+
520
+ ### 3. A/B Testing
521
+
522
+ ```typescript
523
+ export class ReviewABTesting {
524
+ async getReviewStrategy(): Promise<string> {
525
+ const userId = await this.getUserId();
526
+ const variant = this.hashUserId(userId) % 3;
527
+
528
+ switch (variant) {
529
+ case 0:
530
+ return 'immediate'; // Direct review request
531
+ case 1:
532
+ return 'two-step'; // Ask enjoyment first
533
+ case 2:
534
+ return 'contextual'; // Wait for positive moment
535
+ }
536
+ }
537
+
538
+ async executeStrategy(strategy: string) {
539
+ this.analytics.track('review_strategy_assigned', { strategy });
540
+
541
+ switch (strategy) {
542
+ case 'immediate':
543
+ await CapacitorNativeUpdate.requestReview();
544
+ break;
545
+ case 'two-step':
546
+ await this.twoStepFlow.initiateReviewFlow();
547
+ break;
548
+ case 'contextual':
549
+ await this.contextualFlow.waitForPositiveMoment();
550
+ break;
551
+ }
552
+ }
553
+ }
554
+ ```
555
+
556
+ ## Analytics Integration
557
+
558
+ ### Track Everything
559
+
560
+ ```typescript
561
+ export class ReviewAnalytics {
562
+ private events = {
563
+ // Prompt events
564
+ PROMPT_TRIGGERED: 'review_prompt_triggered',
565
+ PROMPT_DISPLAYED: 'review_prompt_displayed',
566
+ PROMPT_DISMISSED: 'review_prompt_dismissed',
567
+
568
+ // User actions
569
+ REVIEW_ACCEPTED: 'review_accepted',
570
+ REVIEW_DECLINED: 'review_declined',
571
+ REVIEW_COMPLETED: 'review_completed', // Best guess
572
+
573
+ // Feedback events
574
+ FEEDBACK_PROVIDED: 'feedback_provided',
575
+ EXTERNAL_REVIEW: 'external_review_opened',
576
+ };
577
+
578
+ async trackReviewFlow(stage: string, properties?: any) {
579
+ await this.analytics.track(this.events[stage], {
580
+ ...properties,
581
+ timestamp: new Date().toISOString(),
582
+ sessionId: this.sessionId,
583
+ userId: this.userId,
584
+ platform: Capacitor.getPlatform(),
585
+ appVersion: this.appVersion,
586
+ });
587
+ }
588
+
589
+ async generateReviewReport(): Promise<ReviewMetrics> {
590
+ const metrics = await this.analytics.query({
591
+ events: Object.values(this.events),
592
+ timeframe: 'last_30_days',
593
+ });
594
+
595
+ return {
596
+ promptsShown: metrics[this.events.PROMPT_DISPLAYED],
597
+ acceptanceRate:
598
+ metrics[this.events.REVIEW_ACCEPTED] /
599
+ metrics[this.events.PROMPT_DISPLAYED],
600
+ declineRate:
601
+ metrics[this.events.REVIEW_DECLINED] /
602
+ metrics[this.events.PROMPT_DISPLAYED],
603
+ estimatedCompletionRate: this.estimateCompletionRate(metrics),
604
+ feedbackRate:
605
+ metrics[this.events.FEEDBACK_PROVIDED] /
606
+ metrics[this.events.PROMPT_DISPLAYED],
607
+ };
608
+ }
609
+ }
610
+ ```
611
+
612
+ ## Testing
613
+
614
+ ### Development Testing
615
+
616
+ ```typescript
617
+ export class ReviewTestingUtils {
618
+ async enableTestMode() {
619
+ // Enable debug mode
620
+ await CapacitorNativeUpdate.setReviewDebugMode({ enabled: true });
621
+
622
+ // Reset all preferences
623
+ await this.clearReviewPreferences();
624
+
625
+ // Set up test conditions
626
+ await this.setupTestConditions();
627
+ }
628
+
629
+ async simulateReviewFlow() {
630
+ console.log('Simulating review flow...');
631
+
632
+ // Force display of review prompt
633
+ const result = await CapacitorNativeUpdate.requestReview({
634
+ force: true, // Only works in debug mode
635
+ });
636
+
637
+ console.log('Review simulation result:', result);
638
+ }
639
+
640
+ async testDifferentScenarios() {
641
+ const scenarios = [
642
+ { name: 'First time user', daysSinceInstall: 0 },
643
+ { name: 'Happy user', positiveActions: 10 },
644
+ { name: 'Frustrated user', errors: 5 },
645
+ { name: 'Returning user', sessions: 20 },
646
+ ];
647
+
648
+ for (const scenario of scenarios) {
649
+ await this.setupScenario(scenario);
650
+ const shouldShow = await this.reviewManager.shouldAskForReview();
651
+ console.log(
652
+ `Scenario "${scenario.name}": ${shouldShow ? 'SHOW' : 'HIDE'}`
653
+ );
654
+ }
655
+ }
656
+ }
657
+ ```
658
+
659
+ ### Platform-Specific Testing
660
+
661
+ #### iOS Testing
662
+
663
+ 1. Use development build (not TestFlight)
664
+ 2. Reviews work in debug mode
665
+ 3. Can test multiple times
666
+ 4. Check console for SKStoreReviewController logs
667
+
668
+ #### Android Testing
669
+
670
+ 1. Use internal test track
671
+ 2. Sign in with test account
672
+ 3. Clear Play Store cache between tests
673
+ 4. Check Play Console for metrics
674
+
675
+ ## Troubleshooting
676
+
677
+ ### Common Issues
678
+
679
+ #### 1. Review Prompt Not Showing
680
+
681
+ ```typescript
682
+ // Debug checklist
683
+ async function debugReviewPrompt() {
684
+ // Check if available on platform
685
+ const isAvailable = await CapacitorNativeUpdate.isReviewAvailable();
686
+ console.log('Review available:', isAvailable);
687
+
688
+ // Check system throttling
689
+ const debugInfo = await CapacitorNativeUpdate.getReviewDebugInfo();
690
+ console.log('Debug info:', debugInfo);
691
+
692
+ // Check your conditions
693
+ const conditions = await this.checkAllConditions();
694
+ console.log('App conditions:', conditions);
695
+ }
696
+ ```
697
+
698
+ #### 2. Low Review Rates
699
+
700
+ ```typescript
701
+ // Optimize review strategy
702
+ class ReviewOptimizer {
703
+ async analyzeAndOptimize() {
704
+ const metrics = await this.getReviewMetrics();
705
+
706
+ if (metrics.promptsShown < 100) {
707
+ console.log('Not enough data yet');
708
+ return;
709
+ }
710
+
711
+ if (metrics.acceptanceRate < 0.1) {
712
+ // Less than 10% accepting
713
+ console.log('Consider:');
714
+ console.log('- Improving timing');
715
+ console.log('- Using two-step flow');
716
+ console.log('- Checking for negative experiences');
717
+ }
718
+
719
+ if (metrics.promptsShown / metrics.eligibleUsers < 0.5) {
720
+ console.log('Not reaching enough users');
721
+ console.log('- Relax conditions');
722
+ console.log('- Add more trigger points');
723
+ }
724
+ }
725
+ }
726
+ ```
727
+
728
+ #### 3. Platform-Specific Issues
729
+
730
+ ```typescript
731
+ // Platform fallbacks
732
+ async function requestReviewWithFallback() {
733
+ try {
734
+ const result = await CapacitorNativeUpdate.requestReview();
735
+
736
+ if (!result.displayed) {
737
+ // System didn't show prompt
738
+ if (Capacitor.getPlatform() === 'web') {
739
+ await this.showWebReviewUI();
740
+ } else {
741
+ // Try again later
742
+ await this.scheduleRetry();
743
+ }
744
+ }
745
+ } catch (error) {
746
+ // API not available
747
+ await this.openExternalReview();
748
+ }
749
+ }
750
+ ```
751
+
752
+ ## Summary
753
+
754
+ Key takeaways for implementing app reviews:
755
+
756
+ 1. **Time it right** - Ask when users are happy
757
+ 2. **Be respectful** - Don't ask too often
758
+ 3. **Track everything** - Measure and optimize
759
+ 4. **Handle all cases** - Have fallbacks ready
760
+ 5. **Follow guidelines** - Respect platform rules
761
+ 6. **Test thoroughly** - Use debug modes
762
+ 7. **Iterate** - Continuously improve based on data
763
+
764
+ ## Next Steps
765
+
766
+ - Review the [Quick Start Guide](./QUICK_START.md)
767
+ - Check the [API Reference](../API.md)
768
+ - See [Example Implementation](../example/src/services/review.service.ts)