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,396 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
import BackgroundTasks
|
|
4
|
+
import UserNotifications
|
|
5
|
+
|
|
6
|
+
@objc(BackgroundUpdatePlugin)
|
|
7
|
+
public class BackgroundUpdatePlugin: CAPPlugin {
|
|
8
|
+
|
|
9
|
+
private let backgroundTaskIdentifier = "com.aoneahsan.nativeupdate.background"
|
|
10
|
+
private var backgroundUpdateConfig: BackgroundUpdateConfig?
|
|
11
|
+
private var backgroundUpdateStatus: BackgroundUpdateStatus
|
|
12
|
+
private var notificationManager: BackgroundNotificationManager?
|
|
13
|
+
|
|
14
|
+
public override func load() {
|
|
15
|
+
super.load()
|
|
16
|
+
|
|
17
|
+
// Initialize background update status
|
|
18
|
+
backgroundUpdateStatus = BackgroundUpdateStatus(
|
|
19
|
+
enabled: false,
|
|
20
|
+
isRunning: false,
|
|
21
|
+
checkCount: 0,
|
|
22
|
+
failureCount: 0
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
// Initialize notification manager
|
|
26
|
+
notificationManager = BackgroundNotificationManager(plugin: self)
|
|
27
|
+
|
|
28
|
+
// Register background task
|
|
29
|
+
if #available(iOS 13.0, *) {
|
|
30
|
+
BGTaskScheduler.shared.register(forTaskWithIdentifier: backgroundTaskIdentifier, using: nil) { task in
|
|
31
|
+
self.handleBackgroundTask(task: task as! BGAppRefreshTask)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@objc func enableBackgroundUpdates(_ call: CAPPluginCall) {
|
|
37
|
+
guard let configData = call.options else {
|
|
38
|
+
call.reject("Missing configuration")
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
do {
|
|
43
|
+
let config = try BackgroundUpdateConfig.from(configData)
|
|
44
|
+
backgroundUpdateConfig = config
|
|
45
|
+
backgroundUpdateStatus.enabled = config.enabled
|
|
46
|
+
|
|
47
|
+
if config.enabled {
|
|
48
|
+
scheduleBackgroundTask(interval: config.checkInterval)
|
|
49
|
+
} else {
|
|
50
|
+
disableBackgroundUpdates()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
call.resolve()
|
|
54
|
+
} catch {
|
|
55
|
+
call.reject("Invalid configuration: \(error.localizedDescription)")
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@objc func disableBackgroundUpdates(_ call: CAPPluginCall) {
|
|
60
|
+
disableBackgroundUpdates()
|
|
61
|
+
call.resolve()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@objc func getBackgroundUpdateStatus(_ call: CAPPluginCall) {
|
|
65
|
+
call.resolve(backgroundUpdateStatus.toDictionary())
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@objc func scheduleBackgroundCheck(_ call: CAPPluginCall) {
|
|
69
|
+
guard let interval = call.getDouble("interval") else {
|
|
70
|
+
call.reject("Missing interval parameter")
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
scheduleBackgroundTask(interval: Int(interval))
|
|
75
|
+
call.resolve()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@objc func triggerBackgroundCheck(_ call: CAPPluginCall) {
|
|
79
|
+
Task {
|
|
80
|
+
let result = await performBackgroundCheck()
|
|
81
|
+
call.resolve(result.toDictionary())
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@objc func setNotificationPreferences(_ call: CAPPluginCall) {
|
|
86
|
+
guard let preferences = call.options else {
|
|
87
|
+
call.reject("Missing preferences")
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
notificationManager?.setPreferences(preferences)
|
|
92
|
+
call.resolve()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@objc func getNotificationPermissions(_ call: CAPPluginCall) {
|
|
96
|
+
notificationManager?.getPermissionStatus { status in
|
|
97
|
+
call.resolve(status.toDictionary())
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@objc func requestNotificationPermissions(_ call: CAPPluginCall) {
|
|
102
|
+
notificationManager?.requestPermissions { granted in
|
|
103
|
+
call.resolve(["granted": granted])
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// MARK: - Private Methods
|
|
108
|
+
|
|
109
|
+
private func disableBackgroundUpdates() {
|
|
110
|
+
backgroundUpdateStatus.enabled = false
|
|
111
|
+
backgroundUpdateStatus.isRunning = false
|
|
112
|
+
backgroundUpdateStatus.currentTaskId = nil
|
|
113
|
+
|
|
114
|
+
if #available(iOS 13.0, *) {
|
|
115
|
+
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: backgroundTaskIdentifier)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private func scheduleBackgroundTask(interval: Int) {
|
|
120
|
+
guard #available(iOS 13.0, *) else {
|
|
121
|
+
NSLog("BackgroundTasks framework not available on iOS < 13.0")
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let request = BGAppRefreshTaskRequest(identifier: backgroundTaskIdentifier)
|
|
126
|
+
request.earliestBeginDate = Date(timeIntervalSinceNow: TimeInterval(interval / 1000))
|
|
127
|
+
|
|
128
|
+
do {
|
|
129
|
+
try BGTaskScheduler.shared.submit(request)
|
|
130
|
+
backgroundUpdateStatus.nextCheckTime = Int(Date().timeIntervalSince1970 * 1000) + interval
|
|
131
|
+
NSLog("Background task scheduled for \(interval)ms from now")
|
|
132
|
+
} catch {
|
|
133
|
+
NSLog("Failed to schedule background task: \(error.localizedDescription)")
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@available(iOS 13.0, *)
|
|
138
|
+
private func handleBackgroundTask(task: BGAppRefreshTask) {
|
|
139
|
+
NSLog("Background task started")
|
|
140
|
+
|
|
141
|
+
// Schedule next task
|
|
142
|
+
if let config = backgroundUpdateConfig {
|
|
143
|
+
scheduleBackgroundTask(interval: config.checkInterval)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Set expiration handler
|
|
147
|
+
task.expirationHandler = {
|
|
148
|
+
NSLog("Background task expired")
|
|
149
|
+
self.backgroundUpdateStatus.isRunning = false
|
|
150
|
+
task.setTaskCompleted(success: false)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Perform background check
|
|
154
|
+
Task {
|
|
155
|
+
let result = await performBackgroundCheck()
|
|
156
|
+
|
|
157
|
+
// Notify listeners
|
|
158
|
+
await MainActor.run {
|
|
159
|
+
self.notifyListeners("backgroundUpdateProgress", data: [
|
|
160
|
+
"type": result.appUpdate != nil ? "app_update" : "live_update",
|
|
161
|
+
"status": result.success ? "completed" : "failed",
|
|
162
|
+
"percent": 100
|
|
163
|
+
])
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
task.setTaskCompleted(success: result.success)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private func performBackgroundCheck() async -> BackgroundCheckResult {
|
|
171
|
+
guard let config = backgroundUpdateConfig, config.enabled else {
|
|
172
|
+
return BackgroundCheckResult(
|
|
173
|
+
success: false,
|
|
174
|
+
updatesFound: false,
|
|
175
|
+
notificationSent: false,
|
|
176
|
+
error: UpdateError(code: "INVALID_CONFIG", message: "Background updates not enabled")
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
backgroundUpdateStatus.isRunning = true
|
|
181
|
+
backgroundUpdateStatus.checkCount += 1
|
|
182
|
+
backgroundUpdateStatus.lastCheckTime = Int(Date().timeIntervalSince1970 * 1000)
|
|
183
|
+
|
|
184
|
+
do {
|
|
185
|
+
var appUpdate: AppUpdateInfo?
|
|
186
|
+
var liveUpdate: LatestVersion?
|
|
187
|
+
|
|
188
|
+
// Check for app updates
|
|
189
|
+
if config.updateTypes.contains(.appUpdate) || config.updateTypes.contains(.both) {
|
|
190
|
+
appUpdate = await checkForAppUpdate()
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Check for live updates
|
|
194
|
+
if config.updateTypes.contains(.liveUpdate) || config.updateTypes.contains(.both) {
|
|
195
|
+
liveUpdate = await checkForLiveUpdate()
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let updatesFound = (appUpdate?.updateAvailable ?? false) || (liveUpdate?.available ?? false)
|
|
199
|
+
var notificationSent = false
|
|
200
|
+
|
|
201
|
+
if updatesFound {
|
|
202
|
+
notificationSent = await sendNotification(appUpdate: appUpdate, liveUpdate: liveUpdate)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
backgroundUpdateStatus.isRunning = false
|
|
206
|
+
backgroundUpdateStatus.lastError = nil
|
|
207
|
+
|
|
208
|
+
if updatesFound {
|
|
209
|
+
backgroundUpdateStatus.lastUpdateTime = Int(Date().timeIntervalSince1970 * 1000)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return BackgroundCheckResult(
|
|
213
|
+
success: true,
|
|
214
|
+
updatesFound: updatesFound,
|
|
215
|
+
appUpdate: appUpdate,
|
|
216
|
+
liveUpdate: liveUpdate,
|
|
217
|
+
notificationSent: notificationSent
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
} catch {
|
|
221
|
+
backgroundUpdateStatus.isRunning = false
|
|
222
|
+
backgroundUpdateStatus.failureCount += 1
|
|
223
|
+
|
|
224
|
+
let updateError = UpdateError(
|
|
225
|
+
code: "UNKNOWN_ERROR",
|
|
226
|
+
message: error.localizedDescription
|
|
227
|
+
)
|
|
228
|
+
backgroundUpdateStatus.lastError = updateError
|
|
229
|
+
|
|
230
|
+
return BackgroundCheckResult(
|
|
231
|
+
success: false,
|
|
232
|
+
updatesFound: false,
|
|
233
|
+
notificationSent: false,
|
|
234
|
+
error: updateError
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private func checkForAppUpdate() async -> AppUpdateInfo? {
|
|
240
|
+
// Create an instance of AppUpdatePlugin to check for updates
|
|
241
|
+
let appUpdatePlugin = AppUpdatePlugin(plugin: self)
|
|
242
|
+
return await appUpdatePlugin.getAppUpdateInfoAsync()
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private func checkForLiveUpdate() async -> LatestVersion? {
|
|
246
|
+
// Create an instance of LiveUpdatePlugin to check for updates
|
|
247
|
+
let liveUpdatePlugin = LiveUpdatePlugin(plugin: self)
|
|
248
|
+
return await liveUpdatePlugin.getLatestVersionAsync()
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private func sendNotification(appUpdate: AppUpdateInfo?, liveUpdate: LatestVersion?) async -> Bool {
|
|
252
|
+
guard let notificationManager = notificationManager else {
|
|
253
|
+
return false
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return await notificationManager.sendUpdateNotification(
|
|
257
|
+
appUpdate: appUpdate,
|
|
258
|
+
liveUpdate: liveUpdate
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// MARK: - Data Models
|
|
264
|
+
|
|
265
|
+
struct BackgroundUpdateConfig {
|
|
266
|
+
let enabled: Bool
|
|
267
|
+
let checkInterval: Int
|
|
268
|
+
let updateTypes: [BackgroundUpdateType]
|
|
269
|
+
let autoInstall: Bool
|
|
270
|
+
let notificationPreferences: NotificationPreferences?
|
|
271
|
+
let respectBatteryOptimization: Bool
|
|
272
|
+
let allowMeteredConnection: Bool
|
|
273
|
+
let minimumBatteryLevel: Int
|
|
274
|
+
let requireWifi: Bool
|
|
275
|
+
let maxRetries: Int
|
|
276
|
+
let retryDelay: Int
|
|
277
|
+
let taskIdentifier: String?
|
|
278
|
+
|
|
279
|
+
static func from(_ obj: [String: Any]) throws -> BackgroundUpdateConfig {
|
|
280
|
+
guard let enabled = obj["enabled"] as? Bool,
|
|
281
|
+
let checkInterval = obj["checkInterval"] as? Int,
|
|
282
|
+
let updateTypesArray = obj["updateTypes"] as? [String] else {
|
|
283
|
+
throw NSError(domain: "BackgroundUpdateConfig", code: 1, userInfo: [NSLocalizedDescriptionKey: "Missing required fields"])
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
let updateTypes = updateTypesArray.compactMap { BackgroundUpdateType(rawValue: $0) }
|
|
287
|
+
let notificationPreferences = obj["notificationPreferences"] as? [String: Any]
|
|
288
|
+
|
|
289
|
+
return BackgroundUpdateConfig(
|
|
290
|
+
enabled: enabled,
|
|
291
|
+
checkInterval: checkInterval,
|
|
292
|
+
updateTypes: updateTypes,
|
|
293
|
+
autoInstall: obj["autoInstall"] as? Bool ?? false,
|
|
294
|
+
notificationPreferences: notificationPreferences != nil ? NotificationPreferences.from(notificationPreferences!) : nil,
|
|
295
|
+
respectBatteryOptimization: obj["respectBatteryOptimization"] as? Bool ?? true,
|
|
296
|
+
allowMeteredConnection: obj["allowMeteredConnection"] as? Bool ?? false,
|
|
297
|
+
minimumBatteryLevel: obj["minimumBatteryLevel"] as? Int ?? 20,
|
|
298
|
+
requireWifi: obj["requireWifi"] as? Bool ?? false,
|
|
299
|
+
maxRetries: obj["maxRetries"] as? Int ?? 3,
|
|
300
|
+
retryDelay: obj["retryDelay"] as? Int ?? 5000,
|
|
301
|
+
taskIdentifier: obj["taskIdentifier"] as? String
|
|
302
|
+
)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
enum BackgroundUpdateType: String, CaseIterable {
|
|
307
|
+
case appUpdate = "app_update"
|
|
308
|
+
case liveUpdate = "live_update"
|
|
309
|
+
case both = "both"
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
struct BackgroundUpdateStatus {
|
|
313
|
+
var enabled: Bool
|
|
314
|
+
var lastCheckTime: Int?
|
|
315
|
+
var nextCheckTime: Int?
|
|
316
|
+
var lastUpdateTime: Int?
|
|
317
|
+
var currentTaskId: String?
|
|
318
|
+
var isRunning: Bool
|
|
319
|
+
var checkCount: Int
|
|
320
|
+
var failureCount: Int
|
|
321
|
+
var lastError: UpdateError?
|
|
322
|
+
|
|
323
|
+
func toDictionary() -> [String: Any] {
|
|
324
|
+
var obj: [String: Any] = [
|
|
325
|
+
"enabled": enabled,
|
|
326
|
+
"isRunning": isRunning,
|
|
327
|
+
"checkCount": checkCount,
|
|
328
|
+
"failureCount": failureCount
|
|
329
|
+
]
|
|
330
|
+
|
|
331
|
+
if let lastCheckTime = lastCheckTime {
|
|
332
|
+
obj["lastCheckTime"] = lastCheckTime
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if let nextCheckTime = nextCheckTime {
|
|
336
|
+
obj["nextCheckTime"] = nextCheckTime
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if let lastUpdateTime = lastUpdateTime {
|
|
340
|
+
obj["lastUpdateTime"] = lastUpdateTime
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if let currentTaskId = currentTaskId {
|
|
344
|
+
obj["currentTaskId"] = currentTaskId
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if let lastError = lastError {
|
|
348
|
+
obj["lastError"] = lastError.toDictionary()
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return obj
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
struct BackgroundCheckResult {
|
|
356
|
+
let success: Bool
|
|
357
|
+
let updatesFound: Bool
|
|
358
|
+
let appUpdate: AppUpdateInfo?
|
|
359
|
+
let liveUpdate: LatestVersion?
|
|
360
|
+
let notificationSent: Bool
|
|
361
|
+
let error: UpdateError?
|
|
362
|
+
|
|
363
|
+
func toDictionary() -> [String: Any] {
|
|
364
|
+
var obj: [String: Any] = [
|
|
365
|
+
"success": success,
|
|
366
|
+
"updatesFound": updatesFound,
|
|
367
|
+
"notificationSent": notificationSent
|
|
368
|
+
]
|
|
369
|
+
|
|
370
|
+
if let appUpdate = appUpdate {
|
|
371
|
+
obj["appUpdate"] = appUpdate.toDictionary()
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if let liveUpdate = liveUpdate {
|
|
375
|
+
obj["liveUpdate"] = liveUpdate.toDictionary()
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if let error = error {
|
|
379
|
+
obj["error"] = error.toDictionary()
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return obj
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
struct UpdateError {
|
|
387
|
+
let code: String
|
|
388
|
+
let message: String
|
|
389
|
+
|
|
390
|
+
func toDictionary() -> [String: Any] {
|
|
391
|
+
return [
|
|
392
|
+
"code": code,
|
|
393
|
+
"message": message
|
|
394
|
+
]
|
|
395
|
+
}
|
|
396
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
#import <Capacitor/Capacitor.h>
|
|
3
|
+
|
|
4
|
+
// Define the plugin using the CAP_PLUGIN Macro, and
|
|
5
|
+
// each method the plugin supports using the CAP_PLUGIN_METHOD macro.
|
|
6
|
+
CAP_PLUGIN(NativeUpdatePlugin, "CapacitorNativeUpdate",
|
|
7
|
+
CAP_PLUGIN_METHOD(configure, CAPPluginReturnPromise);
|
|
8
|
+
CAP_PLUGIN_METHOD(getSecurityInfo, CAPPluginReturnPromise);
|
|
9
|
+
|
|
10
|
+
// Live Update Methods
|
|
11
|
+
CAP_PLUGIN_METHOD(sync, CAPPluginReturnPromise);
|
|
12
|
+
CAP_PLUGIN_METHOD(download, CAPPluginReturnPromise);
|
|
13
|
+
CAP_PLUGIN_METHOD(set, CAPPluginReturnPromise);
|
|
14
|
+
CAP_PLUGIN_METHOD(reload, CAPPluginReturnPromise);
|
|
15
|
+
CAP_PLUGIN_METHOD(reset, CAPPluginReturnPromise);
|
|
16
|
+
CAP_PLUGIN_METHOD(current, CAPPluginReturnPromise);
|
|
17
|
+
CAP_PLUGIN_METHOD(list, CAPPluginReturnPromise);
|
|
18
|
+
CAP_PLUGIN_METHOD(delete, CAPPluginReturnPromise);
|
|
19
|
+
CAP_PLUGIN_METHOD(notifyAppReady, CAPPluginReturnPromise);
|
|
20
|
+
CAP_PLUGIN_METHOD(getLatest, CAPPluginReturnPromise);
|
|
21
|
+
CAP_PLUGIN_METHOD(setChannel, CAPPluginReturnPromise);
|
|
22
|
+
CAP_PLUGIN_METHOD(setUpdateUrl, CAPPluginReturnPromise);
|
|
23
|
+
CAP_PLUGIN_METHOD(validateUpdate, CAPPluginReturnPromise);
|
|
24
|
+
|
|
25
|
+
// App Update Methods
|
|
26
|
+
CAP_PLUGIN_METHOD(getAppUpdateInfo, CAPPluginReturnPromise);
|
|
27
|
+
CAP_PLUGIN_METHOD(performImmediateUpdate, CAPPluginReturnPromise);
|
|
28
|
+
CAP_PLUGIN_METHOD(startFlexibleUpdate, CAPPluginReturnPromise);
|
|
29
|
+
CAP_PLUGIN_METHOD(completeFlexibleUpdate, CAPPluginReturnPromise);
|
|
30
|
+
CAP_PLUGIN_METHOD(openAppStore, CAPPluginReturnPromise);
|
|
31
|
+
|
|
32
|
+
// App Review Methods
|
|
33
|
+
CAP_PLUGIN_METHOD(requestReview, CAPPluginReturnPromise);
|
|
34
|
+
CAP_PLUGIN_METHOD(canRequestReview, CAPPluginReturnPromise);
|
|
35
|
+
|
|
36
|
+
// Background Update Methods
|
|
37
|
+
CAP_PLUGIN_METHOD(enableBackgroundUpdates, CAPPluginReturnPromise);
|
|
38
|
+
CAP_PLUGIN_METHOD(disableBackgroundUpdates, CAPPluginReturnPromise);
|
|
39
|
+
CAP_PLUGIN_METHOD(getBackgroundUpdateStatus, CAPPluginReturnPromise);
|
|
40
|
+
CAP_PLUGIN_METHOD(scheduleBackgroundCheck, CAPPluginReturnPromise);
|
|
41
|
+
CAP_PLUGIN_METHOD(triggerBackgroundCheck, CAPPluginReturnPromise);
|
|
42
|
+
CAP_PLUGIN_METHOD(setNotificationPreferences, CAPPluginReturnPromise);
|
|
43
|
+
CAP_PLUGIN_METHOD(getNotificationPermissions, CAPPluginReturnPromise);
|
|
44
|
+
CAP_PLUGIN_METHOD(requestNotificationPermissions, CAPPluginReturnPromise);
|
|
45
|
+
)
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
|
|
4
|
+
@objc(NativeUpdatePlugin)
|
|
5
|
+
public class NativeUpdatePlugin: CAPPlugin {
|
|
6
|
+
private var liveUpdatePlugin: LiveUpdatePlugin!
|
|
7
|
+
private var appUpdatePlugin: AppUpdatePlugin!
|
|
8
|
+
private var appReviewPlugin: AppReviewPlugin!
|
|
9
|
+
private var backgroundUpdatePlugin: BackgroundUpdatePlugin!
|
|
10
|
+
private var securityManager: SecurityManager!
|
|
11
|
+
|
|
12
|
+
override public func load() {
|
|
13
|
+
super.load()
|
|
14
|
+
|
|
15
|
+
// Initialize sub-plugins
|
|
16
|
+
liveUpdatePlugin = LiveUpdatePlugin(plugin: self)
|
|
17
|
+
appUpdatePlugin = AppUpdatePlugin(plugin: self)
|
|
18
|
+
appReviewPlugin = AppReviewPlugin(plugin: self)
|
|
19
|
+
backgroundUpdatePlugin = BackgroundUpdatePlugin()
|
|
20
|
+
securityManager = SecurityManager()
|
|
21
|
+
|
|
22
|
+
// Set up listeners
|
|
23
|
+
liveUpdatePlugin.setProgressListener { [weak self] progress in
|
|
24
|
+
self?.notifyListeners("downloadProgress", data: progress)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
liveUpdatePlugin.setStateChangeListener { [weak self] state in
|
|
28
|
+
self?.notifyListeners("updateStateChanged", data: state)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@objc func configure(_ call: CAPPluginCall) {
|
|
33
|
+
guard let config = call.getObject("config") else {
|
|
34
|
+
call.reject("Configuration object is required")
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
do {
|
|
39
|
+
// Validate security settings
|
|
40
|
+
if let securityConfig = config["security"] as? [String: Any] {
|
|
41
|
+
try securityManager.configure(securityConfig)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Configure sub-plugins
|
|
45
|
+
if let liveUpdateConfig = config["liveUpdate"] as? [String: Any] {
|
|
46
|
+
try liveUpdatePlugin.configure(liveUpdateConfig)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if let appUpdateConfig = config["appUpdate"] as? [String: Any] {
|
|
50
|
+
try appUpdatePlugin.configure(appUpdateConfig)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if let appReviewConfig = config["appReview"] as? [String: Any] {
|
|
54
|
+
try appReviewPlugin.configure(appReviewConfig)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if let backgroundUpdateConfig = config["backgroundUpdate"] as? [String: Any] {
|
|
58
|
+
try backgroundUpdatePlugin.configure(backgroundUpdateConfig)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
call.resolve()
|
|
62
|
+
} catch {
|
|
63
|
+
call.reject("Configuration failed", error.localizedDescription)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@objc func getSecurityInfo(_ call: CAPPluginCall) {
|
|
68
|
+
call.resolve(securityManager.getSecurityInfo())
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// MARK: - Live Update Methods
|
|
72
|
+
|
|
73
|
+
@objc func sync(_ call: CAPPluginCall) {
|
|
74
|
+
liveUpdatePlugin.sync(call)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@objc func download(_ call: CAPPluginCall) {
|
|
78
|
+
liveUpdatePlugin.download(call)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@objc func set(_ call: CAPPluginCall) {
|
|
82
|
+
liveUpdatePlugin.set(call)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@objc func reload(_ call: CAPPluginCall) {
|
|
86
|
+
liveUpdatePlugin.reload(call)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@objc func reset(_ call: CAPPluginCall) {
|
|
90
|
+
liveUpdatePlugin.reset(call)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@objc func current(_ call: CAPPluginCall) {
|
|
94
|
+
liveUpdatePlugin.current(call)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@objc func list(_ call: CAPPluginCall) {
|
|
98
|
+
liveUpdatePlugin.list(call)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@objc func delete(_ call: CAPPluginCall) {
|
|
102
|
+
liveUpdatePlugin.delete(call)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@objc func notifyAppReady(_ call: CAPPluginCall) {
|
|
106
|
+
liveUpdatePlugin.notifyAppReady(call)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@objc func getLatest(_ call: CAPPluginCall) {
|
|
110
|
+
liveUpdatePlugin.getLatest(call)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@objc func setChannel(_ call: CAPPluginCall) {
|
|
114
|
+
liveUpdatePlugin.setChannel(call)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@objc func setUpdateUrl(_ call: CAPPluginCall) {
|
|
118
|
+
liveUpdatePlugin.setUpdateUrl(call)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@objc func validateUpdate(_ call: CAPPluginCall) {
|
|
122
|
+
liveUpdatePlugin.validateUpdate(call)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// MARK: - App Update Methods
|
|
126
|
+
|
|
127
|
+
@objc func getAppUpdateInfo(_ call: CAPPluginCall) {
|
|
128
|
+
appUpdatePlugin.getAppUpdateInfo(call)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@objc func performImmediateUpdate(_ call: CAPPluginCall) {
|
|
132
|
+
appUpdatePlugin.performImmediateUpdate(call)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@objc func startFlexibleUpdate(_ call: CAPPluginCall) {
|
|
136
|
+
appUpdatePlugin.startFlexibleUpdate(call)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@objc func completeFlexibleUpdate(_ call: CAPPluginCall) {
|
|
140
|
+
appUpdatePlugin.completeFlexibleUpdate(call)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@objc func openAppStore(_ call: CAPPluginCall) {
|
|
144
|
+
appUpdatePlugin.openAppStore(call)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// MARK: - App Review Methods
|
|
148
|
+
|
|
149
|
+
@objc func requestReview(_ call: CAPPluginCall) {
|
|
150
|
+
appReviewPlugin.requestReview(call)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@objc func canRequestReview(_ call: CAPPluginCall) {
|
|
154
|
+
appReviewPlugin.canRequestReview(call)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// MARK: - Background Update Methods
|
|
158
|
+
|
|
159
|
+
@objc func enableBackgroundUpdates(_ call: CAPPluginCall) {
|
|
160
|
+
backgroundUpdatePlugin.enableBackgroundUpdates(call)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
@objc func disableBackgroundUpdates(_ call: CAPPluginCall) {
|
|
164
|
+
backgroundUpdatePlugin.disableBackgroundUpdates(call)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@objc func getBackgroundUpdateStatus(_ call: CAPPluginCall) {
|
|
168
|
+
backgroundUpdatePlugin.getBackgroundUpdateStatus(call)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@objc func scheduleBackgroundCheck(_ call: CAPPluginCall) {
|
|
172
|
+
backgroundUpdatePlugin.scheduleBackgroundCheck(call)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@objc func triggerBackgroundCheck(_ call: CAPPluginCall) {
|
|
176
|
+
backgroundUpdatePlugin.triggerBackgroundCheck(call)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
@objc func setNotificationPreferences(_ call: CAPPluginCall) {
|
|
180
|
+
backgroundUpdatePlugin.setNotificationPreferences(call)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@objc func getNotificationPermissions(_ call: CAPPluginCall) {
|
|
184
|
+
backgroundUpdatePlugin.getNotificationPermissions(call)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
@objc func requestNotificationPermissions(_ call: CAPPluginCall) {
|
|
188
|
+
backgroundUpdatePlugin.requestNotificationPermissions(call)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>CFBundleDevelopmentRegion</key>
|
|
6
|
+
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
7
|
+
<key>CFBundleExecutable</key>
|
|
8
|
+
<string>$(EXECUTABLE_NAME)</string>
|
|
9
|
+
<key>CFBundleIdentifier</key>
|
|
10
|
+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
11
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
|
12
|
+
<string>6.0</string>
|
|
13
|
+
<key>CFBundleName</key>
|
|
14
|
+
<string>$(PRODUCT_NAME)</string>
|
|
15
|
+
<key>CFBundlePackageType</key>
|
|
16
|
+
<string>FMWK</string>
|
|
17
|
+
<key>CFBundleShortVersionString</key>
|
|
18
|
+
<string>1.0</string>
|
|
19
|
+
<key>CFBundleVersion</key>
|
|
20
|
+
<string>1</string>
|
|
21
|
+
<key>NSAppTransportSecurity</key>
|
|
22
|
+
<dict>
|
|
23
|
+
<key>NSAllowsArbitraryLoads</key>
|
|
24
|
+
<false/>
|
|
25
|
+
<key>NSAllowsArbitraryLoadsForMedia</key>
|
|
26
|
+
<false/>
|
|
27
|
+
<key>NSAllowsArbitraryLoadsInWebContent</key>
|
|
28
|
+
<false/>
|
|
29
|
+
<key>NSAllowsLocalNetworking</key>
|
|
30
|
+
<false/>
|
|
31
|
+
</dict>
|
|
32
|
+
<key>UIBackgroundModes</key>
|
|
33
|
+
<array>
|
|
34
|
+
<string>background-app-refresh</string>
|
|
35
|
+
<string>background-processing</string>
|
|
36
|
+
</array>
|
|
37
|
+
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
|
38
|
+
<array>
|
|
39
|
+
<string>com.aoneahsan.nativeupdate.background</string>
|
|
40
|
+
<string>com.aoneahsan.nativeupdate.processing</string>
|
|
41
|
+
</array>
|
|
42
|
+
</dict>
|
|
43
|
+
</plist>
|