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,975 @@
1
+ # App Reviews
2
+
3
+ The App Reviews feature provides intelligent in-app review prompts that help you gather user feedback without interrupting the user experience. It integrates with native store review systems and includes smart timing algorithms to maximize positive reviews.
4
+
5
+ ## Overview
6
+
7
+ App reviews are crucial for app store visibility and user trust. This feature helps you:
8
+
9
+ - Request reviews at optimal moments
10
+ - Avoid review fatigue and spam
11
+ - Comply with platform guidelines
12
+ - Maximize positive review rates
13
+
14
+ ## Platform Integration
15
+
16
+ ### iOS - StoreKit
17
+
18
+ - Uses `SKStoreReviewController` for native review prompts
19
+ - Automatically handles App Store guidelines
20
+ - No user redirection required
21
+
22
+ ### Android - Play Core
23
+
24
+ - Uses Google Play Core Library for in-app reviews
25
+ - Seamless integration with Play Store
26
+ - Follows Google Play policies
27
+
28
+ ### Web - Fallback
29
+
30
+ - Custom review prompts
31
+ - Redirects to appropriate stores
32
+ - Graceful degradation
33
+
34
+ ## Key Features
35
+
36
+ ### 🎯 Smart Timing
37
+
38
+ - Analyzes user behavior patterns
39
+ - Triggers after positive interactions
40
+ - Respects platform limitations
41
+
42
+ ### 📊 Review Intelligence
43
+
44
+ - Tracks review history
45
+ - Prevents over-prompting
46
+ - Optimizes timing based on user segments
47
+
48
+ ### 🔧 Customizable Rules
49
+
50
+ - Flexible triggering conditions
51
+ - Custom event tracking
52
+ - A/B testing support
53
+
54
+ ### 🚀 Platform Compliance
55
+
56
+ - Follows App Store guidelines
57
+ - Respects Play Store policies
58
+ - Handles platform restrictions
59
+
60
+ ## Implementation Guide
61
+
62
+ ### Basic Implementation
63
+
64
+ ```typescript
65
+ // Configure app reviews
66
+ await CapacitorNativeUpdate.configure({
67
+ appReview: {
68
+ minimumDaysSinceInstall: 7,
69
+ minimumDaysSinceLastPrompt: 60,
70
+ minimumLaunchCount: 3,
71
+ },
72
+ });
73
+
74
+ // Request review at appropriate moment
75
+ async function requestReview() {
76
+ try {
77
+ // Check if we can request a review
78
+ const canRequest = await CapacitorNativeUpdate.AppReview.canRequestReview();
79
+
80
+ if (canRequest.allowed) {
81
+ // Request the review
82
+ const result = await CapacitorNativeUpdate.AppReview.requestReview();
83
+
84
+ if (result.shown) {
85
+ console.log('Review dialog was shown');
86
+ } else {
87
+ console.log('Review dialog not shown:', result.reason);
88
+ }
89
+ } else {
90
+ console.log('Cannot request review:', canRequest.reason);
91
+ }
92
+ } catch (error) {
93
+ console.error('Review request failed:', error);
94
+ }
95
+ }
96
+ ```
97
+
98
+ ### Advanced Implementation
99
+
100
+ ```typescript
101
+ class AppReviewManager {
102
+ private userEvents: Map<string, number> = new Map();
103
+ private positiveActions: string[] = [];
104
+
105
+ async initialize() {
106
+ // Configure with advanced settings
107
+ await CapacitorNativeUpdate.configure({
108
+ appReview: {
109
+ minimumDaysSinceInstall: 14,
110
+ minimumDaysSinceLastPrompt: 90,
111
+ minimumLaunchCount: 5,
112
+ minimumSignificantEvents: 3,
113
+ customTriggers: [
114
+ 'purchase_completed',
115
+ 'level_completed',
116
+ 'task_finished',
117
+ 'positive_feedback',
118
+ ],
119
+ requirePositiveEvents: true,
120
+ maxPromptsPerVersion: 1,
121
+ },
122
+ });
123
+
124
+ // Set up event tracking
125
+ this.setupEventTracking();
126
+ }
127
+
128
+ private setupEventTracking() {
129
+ // Track app launches
130
+ this.trackEvent('app_launch');
131
+
132
+ // Track positive user actions
133
+ this.trackPositiveAction('app_opened');
134
+ }
135
+
136
+ // Track custom events
137
+ trackEvent(eventName: string) {
138
+ const count = this.userEvents.get(eventName) || 0;
139
+ this.userEvents.set(eventName, count + 1);
140
+
141
+ // Check if this event should trigger review
142
+ this.checkReviewTrigger(eventName);
143
+ }
144
+
145
+ // Track positive user actions
146
+ trackPositiveAction(action: string) {
147
+ this.positiveActions.push(action);
148
+ this.trackEvent(action);
149
+ }
150
+
151
+ private async checkReviewTrigger(eventName: string) {
152
+ // Check if this event is a review trigger
153
+ const triggers = [
154
+ 'purchase_completed',
155
+ 'level_completed',
156
+ 'task_finished',
157
+ 'positive_feedback',
158
+ ];
159
+
160
+ if (triggers.includes(eventName)) {
161
+ // Add delay to avoid interrupting user flow
162
+ setTimeout(() => {
163
+ this.considerReviewRequest();
164
+ }, 2000);
165
+ }
166
+ }
167
+
168
+ private async considerReviewRequest() {
169
+ try {
170
+ // Check if review is appropriate
171
+ const analysis = await this.analyzeReviewReadiness();
172
+
173
+ if (analysis.shouldRequest) {
174
+ await this.requestReview();
175
+ } else {
176
+ console.log('Review not requested:', analysis.reason);
177
+ }
178
+ } catch (error) {
179
+ console.error('Review analysis failed:', error);
180
+ }
181
+ }
182
+
183
+ private async analyzeReviewReadiness() {
184
+ // Check basic eligibility
185
+ const canRequest = await CapacitorNativeUpdate.AppReview.canRequestReview();
186
+
187
+ if (!canRequest.allowed) {
188
+ return {
189
+ shouldRequest: false,
190
+ reason: canRequest.reason,
191
+ };
192
+ }
193
+
194
+ // Check user sentiment
195
+ const sentiment = this.analyzeUserSentiment();
196
+
197
+ if (sentiment.score < 0.7) {
198
+ return {
199
+ shouldRequest: false,
200
+ reason: 'User sentiment too low',
201
+ };
202
+ }
203
+
204
+ // Check recent app usage
205
+ const usage = await this.analyzeAppUsage();
206
+
207
+ if (!usage.isActive) {
208
+ return {
209
+ shouldRequest: false,
210
+ reason: 'User not actively using app',
211
+ };
212
+ }
213
+
214
+ return {
215
+ shouldRequest: true,
216
+ reason: 'All conditions met',
217
+ };
218
+ }
219
+
220
+ private analyzeUserSentiment() {
221
+ // Simple sentiment analysis based on actions
222
+ const positiveWeight = this.positiveActions.length * 0.3;
223
+ const negativeWeight = this.getNegativeActions().length * 0.7;
224
+
225
+ const score = Math.max(0, Math.min(1, positiveWeight - negativeWeight));
226
+
227
+ return {
228
+ score,
229
+ confidence: Math.min(1, this.positiveActions.length / 10),
230
+ };
231
+ }
232
+
233
+ private async analyzeAppUsage() {
234
+ const launchCount = this.userEvents.get('app_launch') || 0;
235
+ const sessionDuration = await this.getAverageSessionDuration();
236
+
237
+ return {
238
+ isActive: launchCount >= 5 && sessionDuration > 120000, // 2 minutes
239
+ engagement: Math.min(1, launchCount / 20),
240
+ retention: await this.calculateRetention(),
241
+ };
242
+ }
243
+
244
+ async requestReview() {
245
+ try {
246
+ const result = await CapacitorNativeUpdate.AppReview.requestReview();
247
+
248
+ // Track the request
249
+ this.trackReviewRequest(result);
250
+
251
+ if (result.shown) {
252
+ // Review dialog was shown
253
+ console.log('Review prompt shown successfully');
254
+
255
+ // Optional: Show thank you message
256
+ setTimeout(() => {
257
+ this.showThankYouMessage();
258
+ }, 5000);
259
+ }
260
+
261
+ return result;
262
+ } catch (error) {
263
+ console.error('Review request failed:', error);
264
+ throw error;
265
+ }
266
+ }
267
+
268
+ private trackReviewRequest(result: ReviewResult) {
269
+ // Track analytics
270
+ this.trackEvent('review_requested');
271
+
272
+ if (result.shown) {
273
+ this.trackEvent('review_shown');
274
+ }
275
+
276
+ // Log for debugging
277
+ console.log('Review request result:', result);
278
+ }
279
+ }
280
+ ```
281
+
282
+ ## Smart Timing Strategies
283
+
284
+ ### Optimal Moments
285
+
286
+ ```typescript
287
+ class ReviewTimingOptimizer {
288
+ // After successful task completion
289
+ async onTaskCompleted(taskType: string) {
290
+ const isPositive = await this.isPositiveTask(taskType);
291
+
292
+ if (isPositive) {
293
+ // Wait for user to feel satisfaction
294
+ setTimeout(() => {
295
+ this.considerReview('task_completion');
296
+ }, 1500);
297
+ }
298
+ }
299
+
300
+ // After positive app interaction
301
+ async onPositiveInteraction(interaction: string) {
302
+ const interactions = [
303
+ 'feature_liked',
304
+ 'content_shared',
305
+ 'positive_rating_given',
306
+ 'upgrade_purchased',
307
+ ];
308
+
309
+ if (interactions.includes(interaction)) {
310
+ this.considerReview('positive_interaction');
311
+ }
312
+ }
313
+
314
+ // After user shows engagement
315
+ async onEngagementMilestone(milestone: string) {
316
+ const milestones = {
317
+ daily_streak_7: 0.8,
318
+ goals_completed_10: 0.9,
319
+ features_explored_5: 0.7,
320
+ time_spent_milestone: 0.6,
321
+ };
322
+
323
+ const probability = milestones[milestone];
324
+
325
+ if (probability && Math.random() < probability) {
326
+ this.considerReview('engagement_milestone');
327
+ }
328
+ }
329
+
330
+ private async considerReview(trigger: string) {
331
+ // Analyze context
332
+ const context = await this.analyzeContext();
333
+
334
+ if (context.appropriate) {
335
+ // Small delay to avoid interrupting user flow
336
+ setTimeout(() => {
337
+ this.requestReview();
338
+ }, 2000);
339
+ }
340
+ }
341
+
342
+ private async analyzeContext() {
343
+ // Check if user is in a good state
344
+ const checks = [
345
+ this.isUserInGoodMood(),
346
+ this.isAppPerformingWell(),
347
+ this.isFeatureWorkingCorrectly(),
348
+ this.hasUserTimeForReview(),
349
+ ];
350
+
351
+ const results = await Promise.all(checks);
352
+
353
+ return {
354
+ appropriate: results.every((check) => check),
355
+ confidence: results.filter((check) => check).length / results.length,
356
+ };
357
+ }
358
+ }
359
+ ```
360
+
361
+ ### Avoiding Review Fatigue
362
+
363
+ ```typescript
364
+ class ReviewFatigueManager {
365
+ private reviewHistory: ReviewAttempt[] = [];
366
+
367
+ async canRequestReview(): Promise<boolean> {
368
+ // Check platform limits
369
+ const platformCheck =
370
+ await CapacitorNativeUpdate.AppReview.canRequestReview();
371
+
372
+ if (!platformCheck.allowed) {
373
+ return false;
374
+ }
375
+
376
+ // Check our custom rules
377
+ const customChecks = [
378
+ this.checkFrequencyLimit(),
379
+ this.checkUserResponseHistory(),
380
+ this.checkAppVersionHistory(),
381
+ this.checkUserSegment(),
382
+ ];
383
+
384
+ return customChecks.every((check) => check);
385
+ }
386
+
387
+ private checkFrequencyLimit(): boolean {
388
+ const lastRequest = this.getLastReviewRequest();
389
+
390
+ if (!lastRequest) return true;
391
+
392
+ const daysSinceLastRequest = this.daysSince(lastRequest.timestamp);
393
+ const minimumDays = this.getMinimumDaysBetweenRequests();
394
+
395
+ return daysSinceLastRequest >= minimumDays;
396
+ }
397
+
398
+ private checkUserResponseHistory(): boolean {
399
+ const recentResponses = this.reviewHistory
400
+ .filter((attempt) => this.daysSince(attempt.timestamp) <= 180)
401
+ .map((attempt) => attempt.response);
402
+
403
+ // If user dismissed last 2 requests, wait longer
404
+ const recentDismissals = recentResponses.filter(
405
+ (r) => r === 'dismissed'
406
+ ).length;
407
+
408
+ return recentDismissals < 2;
409
+ }
410
+
411
+ private getMinimumDaysBetweenRequests(): number {
412
+ const baseMinimum = 60; // 60 days base
413
+ const dismissalPenalty = this.getDismissalPenalty();
414
+
415
+ return baseMinimum + dismissalPenalty;
416
+ }
417
+
418
+ private getDismissalPenalty(): number {
419
+ // Increase delay for users who dismiss frequently
420
+ const dismissals = this.reviewHistory.filter(
421
+ (r) => r.response === 'dismissed'
422
+ ).length;
423
+ return Math.min(dismissals * 30, 180); // Max 180 days penalty
424
+ }
425
+ }
426
+ ```
427
+
428
+ ## Custom Review Triggers
429
+
430
+ ### Event-Based Triggers
431
+
432
+ ```typescript
433
+ class CustomReviewTriggers {
434
+ private triggerEvents: Map<string, TriggerConfig> = new Map();
435
+
436
+ setupCustomTriggers() {
437
+ // E-commerce triggers
438
+ this.triggerEvents.set('purchase_completed', {
439
+ weight: 0.9,
440
+ delay: 3000,
441
+ conditions: ['payment_successful', 'no_recent_issues'],
442
+ });
443
+
444
+ // Gaming triggers
445
+ this.triggerEvents.set('level_completed', {
446
+ weight: 0.7,
447
+ delay: 2000,
448
+ conditions: ['level_difficulty_high', 'no_deaths'],
449
+ });
450
+
451
+ // Productivity triggers
452
+ this.triggerEvents.set('goal_achieved', {
453
+ weight: 0.8,
454
+ delay: 1500,
455
+ conditions: ['streak_active', 'feature_used_recently'],
456
+ });
457
+
458
+ // Social triggers
459
+ this.triggerEvents.set('content_shared', {
460
+ weight: 0.6,
461
+ delay: 5000,
462
+ conditions: ['sharing_successful', 'positive_engagement'],
463
+ });
464
+ }
465
+
466
+ async handleTriggerEvent(eventName: string, metadata: any) {
467
+ const config = this.triggerEvents.get(eventName);
468
+
469
+ if (!config) return;
470
+
471
+ // Check conditions
472
+ const conditionsMet = await this.checkConditions(
473
+ config.conditions,
474
+ metadata
475
+ );
476
+
477
+ if (!conditionsMet) {
478
+ console.log(`Conditions not met for trigger: ${eventName}`);
479
+ return;
480
+ }
481
+
482
+ // Apply probability
483
+ if (Math.random() > config.weight) {
484
+ console.log(`Probability check failed for trigger: ${eventName}`);
485
+ return;
486
+ }
487
+
488
+ // Add delay
489
+ setTimeout(() => {
490
+ this.requestReview(`trigger_${eventName}`);
491
+ }, config.delay);
492
+ }
493
+
494
+ private async checkConditions(
495
+ conditions: string[],
496
+ metadata: any
497
+ ): Promise<boolean> {
498
+ const conditionChecks = {
499
+ payment_successful: () => metadata.paymentStatus === 'success',
500
+ no_recent_issues: () => !this.hasRecentIssues(),
501
+ level_difficulty_high: () => metadata.difficulty >= 3,
502
+ no_deaths: () => metadata.deaths === 0,
503
+ streak_active: () => this.getUserStreak() > 0,
504
+ feature_used_recently: () => this.wasFeatureUsedRecently(),
505
+ sharing_successful: () => metadata.shareResult === 'success',
506
+ positive_engagement: () => metadata.likes > 0,
507
+ };
508
+
509
+ return conditions.every((condition) => {
510
+ const check = conditionChecks[condition];
511
+ return check ? check() : false;
512
+ });
513
+ }
514
+ }
515
+ ```
516
+
517
+ ### Behavioral Triggers
518
+
519
+ ```typescript
520
+ class BehavioralTriggers {
521
+ private behaviorTracker = new Map<string, number>();
522
+
523
+ // Track user behavior patterns
524
+ trackBehavior(behavior: string, value: number = 1) {
525
+ const current = this.behaviorTracker.get(behavior) || 0;
526
+ this.behaviorTracker.set(behavior, current + value);
527
+
528
+ this.checkBehavioralTriggers(behavior);
529
+ }
530
+
531
+ private checkBehavioralTriggers(behavior: string) {
532
+ const triggers = {
533
+ app_launches: {
534
+ threshold: 10,
535
+ probability: 0.3,
536
+ },
537
+ feature_usage: {
538
+ threshold: 15,
539
+ probability: 0.6,
540
+ },
541
+ session_duration: {
542
+ threshold: 1800, // 30 minutes
543
+ probability: 0.8,
544
+ },
545
+ content_created: {
546
+ threshold: 5,
547
+ probability: 0.9,
548
+ },
549
+ };
550
+
551
+ const config = triggers[behavior];
552
+ if (!config) return;
553
+
554
+ const currentValue = this.behaviorTracker.get(behavior) || 0;
555
+
556
+ if (
557
+ currentValue >= config.threshold &&
558
+ Math.random() < config.probability
559
+ ) {
560
+ this.requestReview(`behavioral_${behavior}`);
561
+ }
562
+ }
563
+
564
+ // Advanced behavioral analysis
565
+ async analyzeBehavioralPatterns() {
566
+ const patterns = {
567
+ engagement: this.calculateEngagementScore(),
568
+ retention: await this.calculateRetentionRate(),
569
+ satisfaction: this.calculateSatisfactionScore(),
570
+ growth: this.calculateGrowthMetrics(),
571
+ };
572
+
573
+ // Determine if user is in a good state for review
574
+ const overallScore =
575
+ patterns.engagement * 0.3 +
576
+ patterns.retention * 0.2 +
577
+ patterns.satisfaction * 0.4 +
578
+ patterns.growth * 0.1;
579
+
580
+ return {
581
+ score: overallScore,
582
+ recommendReview: overallScore > 0.7,
583
+ patterns,
584
+ };
585
+ }
586
+ }
587
+ ```
588
+
589
+ ## Platform-Specific Implementation
590
+
591
+ ### iOS StoreKit Integration
592
+
593
+ ```typescript
594
+ // iOS-specific review handling
595
+ class iOSReviewManager {
596
+ async requestReview(): Promise<ReviewResult> {
597
+ try {
598
+ // Use StoreKit review controller
599
+ const result = await CapacitorNativeUpdate.AppReview.requestReview();
600
+
601
+ // iOS handles everything natively
602
+ return {
603
+ shown: result.shown,
604
+ platform: 'ios',
605
+ method: 'storekit',
606
+ };
607
+ } catch (error) {
608
+ console.error('iOS review request failed:', error);
609
+
610
+ // Fallback to App Store redirect
611
+ return this.fallbackToAppStore();
612
+ }
613
+ }
614
+
615
+ private async fallbackToAppStore() {
616
+ // Open App Store page
617
+ await CapacitorNativeUpdate.AppUpdate.openAppStore();
618
+
619
+ return {
620
+ shown: true,
621
+ platform: 'ios',
622
+ method: 'appstore_redirect',
623
+ };
624
+ }
625
+ }
626
+ ```
627
+
628
+ ### Android Play Core Integration
629
+
630
+ ```typescript
631
+ // Android-specific review handling
632
+ class AndroidReviewManager {
633
+ async requestReview(): Promise<ReviewResult> {
634
+ try {
635
+ // Use Play Core in-app review
636
+ const result = await CapacitorNativeUpdate.AppReview.requestReview();
637
+
638
+ return {
639
+ shown: result.shown,
640
+ platform: 'android',
641
+ method: 'play_core',
642
+ };
643
+ } catch (error) {
644
+ console.error('Android review request failed:', error);
645
+
646
+ // Fallback to Play Store
647
+ return this.fallbackToPlayStore();
648
+ }
649
+ }
650
+
651
+ private async fallbackToPlayStore() {
652
+ // Open Play Store page
653
+ await CapacitorNativeUpdate.AppUpdate.openAppStore();
654
+
655
+ return {
656
+ shown: true,
657
+ platform: 'android',
658
+ method: 'playstore_redirect',
659
+ };
660
+ }
661
+ }
662
+ ```
663
+
664
+ ### Web Platform Fallback
665
+
666
+ ```typescript
667
+ // Web platform review handling
668
+ class WebReviewManager {
669
+ async requestReview(): Promise<ReviewResult> {
670
+ // Show custom review dialog
671
+ const userChoice = await this.showCustomReviewDialog();
672
+
673
+ if (userChoice === 'review') {
674
+ // Redirect to appropriate store
675
+ this.redirectToStore();
676
+ }
677
+
678
+ return {
679
+ shown: true,
680
+ platform: 'web',
681
+ method: 'custom_dialog',
682
+ userChoice,
683
+ };
684
+ }
685
+
686
+ private async showCustomReviewDialog() {
687
+ return new Promise((resolve) => {
688
+ // Create custom modal
689
+ const modal = this.createReviewModal();
690
+
691
+ modal.onChoice = (choice) => {
692
+ modal.close();
693
+ resolve(choice);
694
+ };
695
+
696
+ modal.show();
697
+ });
698
+ }
699
+
700
+ private redirectToStore() {
701
+ const platform = this.detectPlatform();
702
+
703
+ const urls = {
704
+ ios: 'https://apps.apple.com/app/id123456789',
705
+ android: 'https://play.google.com/store/apps/details?id=com.myapp',
706
+ web: 'https://myapp.com/feedback',
707
+ };
708
+
709
+ window.open(urls[platform] || urls.web, '_blank');
710
+ }
711
+ }
712
+ ```
713
+
714
+ ## Analytics and Monitoring
715
+
716
+ ### Review Metrics Tracking
717
+
718
+ ```typescript
719
+ class ReviewAnalytics {
720
+ async trackReviewMetrics(event: string, data: any) {
721
+ const metrics = {
722
+ timestamp: Date.now(),
723
+ event,
724
+ ...data,
725
+ userSegment: await this.getUserSegment(),
726
+ appVersion: await this.getAppVersion(),
727
+ platform: this.getPlatform(),
728
+ };
729
+
730
+ // Send to analytics service
731
+ await this.analytics.track('app_review_' + event, metrics);
732
+ }
733
+
734
+ // Track review funnel
735
+ async trackReviewFunnel(step: string, success: boolean) {
736
+ const funnelData = {
737
+ step,
738
+ success,
739
+ timestamp: Date.now(),
740
+ sessionId: this.getSessionId(),
741
+ };
742
+
743
+ await this.trackReviewMetrics('funnel_step', funnelData);
744
+ }
745
+
746
+ // Monitor review performance
747
+ async getReviewPerformance() {
748
+ const metrics = await this.analytics.query({
749
+ events: [
750
+ 'app_review_requested',
751
+ 'app_review_shown',
752
+ 'app_review_completed',
753
+ ],
754
+ timeRange: '30d',
755
+ });
756
+
757
+ return {
758
+ requestRate: metrics.requested / metrics.eligible,
759
+ showRate: metrics.shown / metrics.requested,
760
+ completionRate: metrics.completed / metrics.shown,
761
+ overallConversion: metrics.completed / metrics.eligible,
762
+ };
763
+ }
764
+ }
765
+ ```
766
+
767
+ ### A/B Testing Review Strategies
768
+
769
+ ```typescript
770
+ class ReviewABTesting {
771
+ private experiments = new Map<string, ABTest>();
772
+
773
+ async setupReviewExperiment(
774
+ experimentName: string,
775
+ variations: ABVariation[]
776
+ ) {
777
+ const experiment = {
778
+ name: experimentName,
779
+ variations,
780
+ allocation: this.allocateUsers(variations),
781
+ metrics: ['show_rate', 'completion_rate', 'user_satisfaction'],
782
+ };
783
+
784
+ this.experiments.set(experimentName, experiment);
785
+ }
786
+
787
+ async getReviewStrategy(userId: string): Promise<ReviewStrategy> {
788
+ const experiment = this.experiments.get('review_timing');
789
+
790
+ if (!experiment) {
791
+ return this.getDefaultStrategy();
792
+ }
793
+
794
+ const variation = this.getUserVariation(userId, experiment);
795
+
796
+ return {
797
+ timing: variation.timing,
798
+ frequency: variation.frequency,
799
+ triggers: variation.triggers,
800
+ ui: variation.ui,
801
+ };
802
+ }
803
+
804
+ private getUserVariation(userId: string, experiment: ABTest): ABVariation {
805
+ const hash = this.hashUserId(userId);
806
+ const bucket = hash % 100;
807
+
808
+ let cumulative = 0;
809
+ for (const variation of experiment.variations) {
810
+ cumulative += variation.allocation;
811
+ if (bucket < cumulative) {
812
+ return variation;
813
+ }
814
+ }
815
+
816
+ return experiment.variations[0]; // Fallback
817
+ }
818
+ }
819
+ ```
820
+
821
+ ## Best Practices
822
+
823
+ ### 1. Respect User Experience
824
+
825
+ ```typescript
826
+ // Never interrupt critical user flows
827
+ class UserFlowProtection {
828
+ private criticalFlows = [
829
+ 'payment_processing',
830
+ 'form_submission',
831
+ 'content_creation',
832
+ 'authentication',
833
+ ];
834
+
835
+ async isUserInCriticalFlow(): Promise<boolean> {
836
+ const currentFlow = await this.getCurrentUserFlow();
837
+ return this.criticalFlows.includes(currentFlow);
838
+ }
839
+
840
+ async requestReviewSafely() {
841
+ if (await this.isUserInCriticalFlow()) {
842
+ // Schedule for later
843
+ this.scheduleReviewForLater();
844
+ return;
845
+ }
846
+
847
+ // Safe to request review
848
+ await this.requestReview();
849
+ }
850
+ }
851
+ ```
852
+
853
+ ### 2. Personalize Review Requests
854
+
855
+ ```typescript
856
+ // Tailor review requests to user segments
857
+ class PersonalizedReviews {
858
+ async getPersonalizedMessage(user: User): Promise<string> {
859
+ const segment = await this.getUserSegment(user);
860
+
861
+ const messages = {
862
+ new_user:
863
+ "You've been using our app for a week now. How's your experience?",
864
+ power_user:
865
+ "You're one of our most active users! Would you mind sharing your thoughts?",
866
+ premium_user: 'As a premium member, your feedback is invaluable to us.',
867
+ casual_user:
868
+ "Hope you're enjoying the app! A quick review would help us improve.",
869
+ };
870
+
871
+ return messages[segment] || messages['casual_user'];
872
+ }
873
+ }
874
+ ```
875
+
876
+ ### 3. Handle Edge Cases
877
+
878
+ ```typescript
879
+ // Handle various edge cases gracefully
880
+ class EdgeCaseHandler {
881
+ async handleReviewRequest() {
882
+ try {
883
+ // Check for edge cases
884
+ if (await this.isAppInBackground()) {
885
+ return { shown: false, reason: 'App in background' };
886
+ }
887
+
888
+ if (await this.isDeviceInDoNotDisturb()) {
889
+ return { shown: false, reason: 'Do not disturb mode' };
890
+ }
891
+
892
+ if (await this.isUserInCall()) {
893
+ return { shown: false, reason: 'User in call' };
894
+ }
895
+
896
+ // Normal review request
897
+ return await this.requestReview();
898
+ } catch (error) {
899
+ console.error('Review request failed:', error);
900
+ return { shown: false, reason: 'Error occurred' };
901
+ }
902
+ }
903
+ }
904
+ ```
905
+
906
+ ## Testing Review Flows
907
+
908
+ ### Development Testing
909
+
910
+ ```typescript
911
+ // Enable debug mode for testing
912
+ const debugConfig = {
913
+ appReview: {
914
+ debugMode: true, // Bypass all time restrictions
915
+ minimumDaysSinceInstall: 0,
916
+ minimumDaysSinceLastPrompt: 0,
917
+ minimumLaunchCount: 0,
918
+ },
919
+ };
920
+
921
+ // Test different scenarios
922
+ async function testReviewScenarios() {
923
+ // Test immediate request
924
+ await CapacitorNativeUpdate.AppReview.requestReview();
925
+
926
+ // Test eligibility check
927
+ const canRequest = await CapacitorNativeUpdate.AppReview.canRequestReview();
928
+ console.log('Can request review:', canRequest);
929
+
930
+ // Test custom triggers
931
+ await testCustomTriggers();
932
+ }
933
+ ```
934
+
935
+ ### User Testing
936
+
937
+ ```typescript
938
+ // A/B test different review strategies
939
+ const strategies = [
940
+ {
941
+ name: 'aggressive',
942
+ config: { minimumDaysSinceInstall: 3, minimumLaunchCount: 2 },
943
+ },
944
+ {
945
+ name: 'balanced',
946
+ config: { minimumDaysSinceInstall: 7, minimumLaunchCount: 5 },
947
+ },
948
+ {
949
+ name: 'conservative',
950
+ config: { minimumDaysSinceInstall: 14, minimumLaunchCount: 10 },
951
+ },
952
+ ];
953
+
954
+ // Monitor success rates
955
+ async function monitorReviewSuccess() {
956
+ const metrics = await analytics.getReviewMetrics();
957
+
958
+ return {
959
+ showRate: metrics.shown / metrics.requested,
960
+ completionRate: metrics.completed / metrics.shown,
961
+ userSatisfaction: metrics.positiveRatings / metrics.totalRatings,
962
+ };
963
+ }
964
+ ```
965
+
966
+ ## Next Steps
967
+
968
+ - Implement [Security Best Practices](../guides/security-best-practices.md)
969
+ - Set up [Analytics Tracking](../examples/analytics-integration.md)
970
+ - Configure [Live Updates](./live-updates.md)
971
+ - Review [API Reference](../api/app-review-api.md)
972
+
973
+ ---
974
+
975
+ Made with ❤️ by Ahsan Mahmood