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,153 @@
|
|
|
1
|
+
package com.aoneahsan.nativeupdate
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import com.getcapacitor.JSObject
|
|
6
|
+
import com.getcapacitor.PluginCall
|
|
7
|
+
import com.google.android.play.core.review.ReviewInfo
|
|
8
|
+
import com.google.android.play.core.review.ReviewManager
|
|
9
|
+
import com.google.android.play.core.review.ReviewManagerFactory
|
|
10
|
+
import java.util.concurrent.TimeUnit
|
|
11
|
+
|
|
12
|
+
class AppReviewPlugin(
|
|
13
|
+
private val activity: Activity,
|
|
14
|
+
private val context: Context
|
|
15
|
+
) {
|
|
16
|
+
private val reviewManager: ReviewManager = ReviewManagerFactory.create(context)
|
|
17
|
+
private var config: JSObject? = null
|
|
18
|
+
private val prefs = context.getSharedPreferences("native_update_review", Context.MODE_PRIVATE)
|
|
19
|
+
|
|
20
|
+
companion object {
|
|
21
|
+
private const val PREF_INSTALL_DATE = "install_date"
|
|
22
|
+
private const val PREF_LAST_REVIEW_REQUEST = "last_review_request"
|
|
23
|
+
private const val PREF_LAUNCH_COUNT = "launch_count"
|
|
24
|
+
private const val PREF_REVIEW_SHOWN_COUNT = "review_shown_count"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
init {
|
|
28
|
+
// Initialize install date if not set
|
|
29
|
+
if (!prefs.contains(PREF_INSTALL_DATE)) {
|
|
30
|
+
prefs.edit().putLong(PREF_INSTALL_DATE, System.currentTimeMillis()).apply()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Increment launch count
|
|
34
|
+
incrementLaunchCount()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fun configure(config: JSObject) {
|
|
38
|
+
this.config = config
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fun requestReview(call: PluginCall) {
|
|
42
|
+
// First check if we can request a review
|
|
43
|
+
val canRequestResult = checkCanRequestReview()
|
|
44
|
+
|
|
45
|
+
if (!canRequestResult.first) {
|
|
46
|
+
val result = JSObject()
|
|
47
|
+
result.put("shown", false)
|
|
48
|
+
result.put("error", canRequestResult.second)
|
|
49
|
+
call.resolve(result)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Request review info
|
|
54
|
+
val request = reviewManager.requestReviewFlow()
|
|
55
|
+
|
|
56
|
+
request.addOnCompleteListener { task ->
|
|
57
|
+
if (task.isSuccessful) {
|
|
58
|
+
val reviewInfo = task.result
|
|
59
|
+
launchReviewFlow(reviewInfo, call)
|
|
60
|
+
} else {
|
|
61
|
+
val result = JSObject()
|
|
62
|
+
result.put("shown", false)
|
|
63
|
+
result.put("error", "Failed to request review flow")
|
|
64
|
+
call.resolve(result)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fun canRequestReview(call: PluginCall) {
|
|
70
|
+
val (allowed, reason) = checkCanRequestReview()
|
|
71
|
+
|
|
72
|
+
val result = JSObject()
|
|
73
|
+
result.put("allowed", allowed)
|
|
74
|
+
if (!allowed) {
|
|
75
|
+
result.put("reason", reason)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
call.resolve(result)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private fun launchReviewFlow(reviewInfo: ReviewInfo, call: PluginCall) {
|
|
82
|
+
val flow = reviewManager.launchReviewFlow(activity, reviewInfo)
|
|
83
|
+
|
|
84
|
+
flow.addOnCompleteListener { _ ->
|
|
85
|
+
// The flow has finished. The API does not indicate whether the user
|
|
86
|
+
// reviewed or not, or even whether the review dialog was shown.
|
|
87
|
+
// Update last review request time
|
|
88
|
+
prefs.edit().putLong(PREF_LAST_REVIEW_REQUEST, System.currentTimeMillis()).apply()
|
|
89
|
+
|
|
90
|
+
// Increment shown count
|
|
91
|
+
val shownCount = prefs.getInt(PREF_REVIEW_SHOWN_COUNT, 0)
|
|
92
|
+
prefs.edit().putInt(PREF_REVIEW_SHOWN_COUNT, shownCount + 1).apply()
|
|
93
|
+
|
|
94
|
+
val result = JSObject()
|
|
95
|
+
result.put("shown", true)
|
|
96
|
+
call.resolve(result)
|
|
97
|
+
}.addOnFailureListener { e ->
|
|
98
|
+
val result = JSObject()
|
|
99
|
+
result.put("shown", false)
|
|
100
|
+
result.put("error", e.message)
|
|
101
|
+
call.resolve(result)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private fun checkCanRequestReview(): Pair<Boolean, String?> {
|
|
106
|
+
val now = System.currentTimeMillis()
|
|
107
|
+
val installDate = prefs.getLong(PREF_INSTALL_DATE, now)
|
|
108
|
+
val lastReviewRequest = prefs.getLong(PREF_LAST_REVIEW_REQUEST, 0)
|
|
109
|
+
val launchCount = prefs.getInt(PREF_LAUNCH_COUNT, 0)
|
|
110
|
+
val shownCount = prefs.getInt(PREF_REVIEW_SHOWN_COUNT, 0)
|
|
111
|
+
|
|
112
|
+
// Check if debug mode is enabled
|
|
113
|
+
val debugMode = config?.getBool("debugMode") ?: false
|
|
114
|
+
if (debugMode) {
|
|
115
|
+
return Pair(true, null)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Check minimum days since install
|
|
119
|
+
val minDaysSinceInstall = config?.getInteger("minimumDaysSinceInstall") ?: 7
|
|
120
|
+
val daysSinceInstall = TimeUnit.MILLISECONDS.toDays(now - installDate)
|
|
121
|
+
if (daysSinceInstall < minDaysSinceInstall) {
|
|
122
|
+
return Pair(false, "Not enough days since install")
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Check minimum days since last prompt
|
|
126
|
+
if (lastReviewRequest > 0) {
|
|
127
|
+
val minDaysSinceLastPrompt = config?.getInteger("minimumDaysSinceLastPrompt") ?: 90
|
|
128
|
+
val daysSinceLastPrompt = TimeUnit.MILLISECONDS.toDays(now - lastReviewRequest)
|
|
129
|
+
if (daysSinceLastPrompt < minDaysSinceLastPrompt) {
|
|
130
|
+
return Pair(false, "Too soon since last review request")
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Check minimum launch count
|
|
135
|
+
val minLaunchCount = config?.getInteger("minimumLaunchCount") ?: 3
|
|
136
|
+
if (launchCount < minLaunchCount) {
|
|
137
|
+
return Pair(false, "Not enough app launches")
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Google Play has internal quotas that we can't check directly
|
|
141
|
+
// But we can limit our own requests to be safe
|
|
142
|
+
if (shownCount >= 3) {
|
|
143
|
+
return Pair(false, "Review quota exceeded")
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return Pair(true, null)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private fun incrementLaunchCount() {
|
|
150
|
+
val currentCount = prefs.getInt(PREF_LAUNCH_COUNT, 0)
|
|
151
|
+
prefs.edit().putInt(PREF_LAUNCH_COUNT, currentCount + 1).apply()
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
package com.aoneahsan.nativeupdate
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.net.Uri
|
|
7
|
+
import com.getcapacitor.JSObject
|
|
8
|
+
import com.getcapacitor.PluginCall
|
|
9
|
+
import com.google.android.play.core.appupdate.AppUpdateInfo
|
|
10
|
+
import com.google.android.play.core.appupdate.AppUpdateManager
|
|
11
|
+
import com.google.android.play.core.appupdate.AppUpdateManagerFactory
|
|
12
|
+
import com.google.android.play.core.appupdate.AppUpdateOptions
|
|
13
|
+
import com.google.android.play.core.install.InstallState
|
|
14
|
+
import com.google.android.play.core.install.InstallStateUpdatedListener
|
|
15
|
+
import com.google.android.play.core.install.model.AppUpdateType
|
|
16
|
+
import com.google.android.play.core.install.model.InstallStatus
|
|
17
|
+
import com.google.android.play.core.install.model.UpdateAvailability
|
|
18
|
+
import com.google.android.play.core.ktx.isFlexibleUpdateAllowed
|
|
19
|
+
import com.google.android.play.core.ktx.isImmediateUpdateAllowed
|
|
20
|
+
import kotlinx.coroutines.tasks.await
|
|
21
|
+
|
|
22
|
+
class AppUpdatePlugin(
|
|
23
|
+
private val activity: Activity,
|
|
24
|
+
private val context: Context
|
|
25
|
+
) {
|
|
26
|
+
private val appUpdateManager: AppUpdateManager = AppUpdateManagerFactory.create(context)
|
|
27
|
+
private var config: JSObject? = null
|
|
28
|
+
private var updateInfo: AppUpdateInfo? = null
|
|
29
|
+
|
|
30
|
+
companion object {
|
|
31
|
+
const val REQUEST_CODE_UPDATE = 12345
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private val installStateUpdatedListener = InstallStateUpdatedListener { state ->
|
|
35
|
+
handleInstallState(state)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
init {
|
|
39
|
+
appUpdateManager.registerListener(installStateUpdatedListener)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fun configure(config: JSObject) {
|
|
43
|
+
this.config = config
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
fun getAppUpdateInfo(call: PluginCall) {
|
|
47
|
+
val appUpdateInfoTask = appUpdateManager.appUpdateInfo
|
|
48
|
+
|
|
49
|
+
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
|
|
50
|
+
this.updateInfo = appUpdateInfo
|
|
51
|
+
|
|
52
|
+
val result = JSObject()
|
|
53
|
+
result.put("updateAvailable", appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE)
|
|
54
|
+
result.put("currentVersion", getCurrentAppVersion())
|
|
55
|
+
result.put("availableVersion", appUpdateInfo.availableVersionCode().toString())
|
|
56
|
+
result.put("updatePriority", appUpdateInfo.updatePriority())
|
|
57
|
+
result.put("immediateUpdateAllowed", appUpdateInfo.isImmediateUpdateAllowed)
|
|
58
|
+
result.put("flexibleUpdateAllowed", appUpdateInfo.isFlexibleUpdateAllowed)
|
|
59
|
+
result.put("clientVersionStalenessDays", appUpdateInfo.clientVersionStalenessDays() ?: -1)
|
|
60
|
+
|
|
61
|
+
// Add install status if update is in progress
|
|
62
|
+
if (appUpdateInfo.installStatus() != InstallStatus.UNKNOWN) {
|
|
63
|
+
result.put("installStatus", getInstallStatusString(appUpdateInfo.installStatus()))
|
|
64
|
+
result.put("bytesDownloaded", appUpdateInfo.bytesDownloaded())
|
|
65
|
+
result.put("totalBytesToDownload", appUpdateInfo.totalBytesToDownload())
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
call.resolve(result)
|
|
69
|
+
}.addOnFailureListener { e ->
|
|
70
|
+
call.reject("UPDATE_CHECK_FAILED", e.message)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fun performImmediateUpdate(call: PluginCall) {
|
|
75
|
+
val appUpdateInfo = updateInfo
|
|
76
|
+
|
|
77
|
+
if (appUpdateInfo == null) {
|
|
78
|
+
// Need to check for updates first
|
|
79
|
+
appUpdateManager.appUpdateInfo.addOnSuccessListener { info ->
|
|
80
|
+
this.updateInfo = info
|
|
81
|
+
startImmediateUpdate(info, call)
|
|
82
|
+
}.addOnFailureListener { e ->
|
|
83
|
+
call.reject("UPDATE_CHECK_FAILED", e.message)
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
startImmediateUpdate(appUpdateInfo, call)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
fun startFlexibleUpdate(call: PluginCall) {
|
|
91
|
+
val appUpdateInfo = updateInfo
|
|
92
|
+
|
|
93
|
+
if (appUpdateInfo == null) {
|
|
94
|
+
// Need to check for updates first
|
|
95
|
+
appUpdateManager.appUpdateInfo.addOnSuccessListener { info ->
|
|
96
|
+
this.updateInfo = info
|
|
97
|
+
startFlexibleUpdateFlow(info, call)
|
|
98
|
+
}.addOnFailureListener { e ->
|
|
99
|
+
call.reject("UPDATE_CHECK_FAILED", e.message)
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
startFlexibleUpdateFlow(appUpdateInfo, call)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fun completeFlexibleUpdate(call: PluginCall) {
|
|
107
|
+
appUpdateManager.completeUpdate()
|
|
108
|
+
call.resolve()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
fun openAppStore(call: PluginCall) {
|
|
112
|
+
try {
|
|
113
|
+
val packageName = call.getString("appId") ?: context.packageName
|
|
114
|
+
|
|
115
|
+
// Try to open in Play Store app first
|
|
116
|
+
try {
|
|
117
|
+
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=$packageName"))
|
|
118
|
+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
119
|
+
context.startActivity(intent)
|
|
120
|
+
} catch (e: Exception) {
|
|
121
|
+
// Fallback to web browser
|
|
122
|
+
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=$packageName"))
|
|
123
|
+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
124
|
+
context.startActivity(intent)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
call.resolve()
|
|
128
|
+
} catch (e: Exception) {
|
|
129
|
+
call.reject("OPEN_STORE_FAILED", e.message)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private fun startImmediateUpdate(appUpdateInfo: AppUpdateInfo, call: PluginCall) {
|
|
134
|
+
if (!appUpdateInfo.isImmediateUpdateAllowed) {
|
|
135
|
+
call.reject("UPDATE_NOT_ALLOWED", "Immediate update is not allowed")
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
val updateOptions = AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build()
|
|
141
|
+
|
|
142
|
+
appUpdateManager.startUpdateFlow(
|
|
143
|
+
appUpdateInfo,
|
|
144
|
+
activity,
|
|
145
|
+
updateOptions
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
call.resolve()
|
|
149
|
+
} catch (e: Exception) {
|
|
150
|
+
call.reject("UPDATE_FAILED", e.message)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private fun startFlexibleUpdateFlow(appUpdateInfo: AppUpdateInfo, call: PluginCall) {
|
|
155
|
+
if (!appUpdateInfo.isFlexibleUpdateAllowed) {
|
|
156
|
+
call.reject("UPDATE_NOT_ALLOWED", "Flexible update is not allowed")
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
val updateOptions = AppUpdateOptions.newBuilder(AppUpdateType.FLEXIBLE).build()
|
|
162
|
+
|
|
163
|
+
appUpdateManager.startUpdateFlow(
|
|
164
|
+
appUpdateInfo,
|
|
165
|
+
activity,
|
|
166
|
+
updateOptions
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
call.resolve()
|
|
170
|
+
} catch (e: Exception) {
|
|
171
|
+
call.reject("UPDATE_FAILED", e.message)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private fun handleInstallState(state: InstallState) {
|
|
176
|
+
when (state.installStatus()) {
|
|
177
|
+
InstallStatus.DOWNLOADED -> {
|
|
178
|
+
// Update has been downloaded, prompt user to restart
|
|
179
|
+
popupSnackbarForCompleteUpdate()
|
|
180
|
+
}
|
|
181
|
+
InstallStatus.INSTALLED -> {
|
|
182
|
+
// Update installed, cleanup
|
|
183
|
+
appUpdateManager.unregisterListener(installStateUpdatedListener)
|
|
184
|
+
}
|
|
185
|
+
InstallStatus.FAILED -> {
|
|
186
|
+
// Update failed
|
|
187
|
+
}
|
|
188
|
+
else -> {
|
|
189
|
+
// Handle other states
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private fun popupSnackbarForCompleteUpdate() {
|
|
195
|
+
// In a real implementation, show a snackbar or notification
|
|
196
|
+
// For now, we'll just complete the update
|
|
197
|
+
appUpdateManager.completeUpdate()
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private fun getCurrentAppVersion(): String {
|
|
201
|
+
return try {
|
|
202
|
+
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
|
203
|
+
packageInfo.versionName ?: "Unknown"
|
|
204
|
+
} catch (e: Exception) {
|
|
205
|
+
"Unknown"
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private fun getInstallStatusString(status: Int): String {
|
|
210
|
+
return when (status) {
|
|
211
|
+
InstallStatus.PENDING -> "PENDING"
|
|
212
|
+
InstallStatus.DOWNLOADING -> "DOWNLOADING"
|
|
213
|
+
InstallStatus.DOWNLOADED -> "DOWNLOADED"
|
|
214
|
+
InstallStatus.INSTALLING -> "INSTALLING"
|
|
215
|
+
InstallStatus.INSTALLED -> "INSTALLED"
|
|
216
|
+
InstallStatus.FAILED -> "FAILED"
|
|
217
|
+
InstallStatus.CANCELED -> "CANCELED"
|
|
218
|
+
else -> "UNKNOWN"
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
fun onActivityResult(requestCode: Int, resultCode: Int) {
|
|
223
|
+
if (requestCode == REQUEST_CODE_UPDATE) {
|
|
224
|
+
when (resultCode) {
|
|
225
|
+
Activity.RESULT_OK -> {
|
|
226
|
+
// Update flow completed successfully
|
|
227
|
+
}
|
|
228
|
+
Activity.RESULT_CANCELED -> {
|
|
229
|
+
// User cancelled the update
|
|
230
|
+
}
|
|
231
|
+
else -> {
|
|
232
|
+
// Update flow failed
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
fun onResume() {
|
|
239
|
+
// Check if update is waiting to be installed
|
|
240
|
+
appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo ->
|
|
241
|
+
if (appUpdateInfo.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
|
|
242
|
+
// Resume the update
|
|
243
|
+
val updateOptions = AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build()
|
|
244
|
+
appUpdateManager.startUpdateFlow(
|
|
245
|
+
appUpdateInfo,
|
|
246
|
+
activity,
|
|
247
|
+
updateOptions
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Async method for background update checks
|
|
254
|
+
suspend fun getAppUpdateInfoAsync(): AppUpdateInfo? {
|
|
255
|
+
return try {
|
|
256
|
+
val appUpdateInfo = appUpdateManager.appUpdateInfo.await()
|
|
257
|
+
|
|
258
|
+
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
|
|
259
|
+
AppUpdateInfo(
|
|
260
|
+
updateAvailable = true,
|
|
261
|
+
currentVersion = getCurrentAppVersion(),
|
|
262
|
+
availableVersion = appUpdateInfo.availableVersionCode().toString()
|
|
263
|
+
)
|
|
264
|
+
} else {
|
|
265
|
+
AppUpdateInfo(
|
|
266
|
+
updateAvailable = false,
|
|
267
|
+
currentVersion = getCurrentAppVersion(),
|
|
268
|
+
availableVersion = null
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
} catch (e: Exception) {
|
|
272
|
+
null
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|