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,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
|