native-update 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/CapacitorNativeUpdate.podspec +18 -0
  2. package/LICENSE +21 -0
  3. package/Readme.md +451 -0
  4. package/android/build.gradle +92 -0
  5. package/android/gradle/wrapper/gradle-wrapper.properties +8 -0
  6. package/android/gradle.properties +17 -0
  7. package/android/proguard-rules.pro +29 -0
  8. package/android/settings.gradle +2 -0
  9. package/android/src/main/AndroidManifest.xml +34 -0
  10. package/android/src/main/java/com/aoneahsan/nativeupdate/AppReviewPlugin.kt +153 -0
  11. package/android/src/main/java/com/aoneahsan/nativeupdate/AppUpdatePlugin.kt +275 -0
  12. package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundNotificationManager.kt +390 -0
  13. package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundUpdateManager.kt +46 -0
  14. package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundUpdatePlugin.kt +333 -0
  15. package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundUpdateWorker.kt +251 -0
  16. package/android/src/main/java/com/aoneahsan/nativeupdate/CapacitorNativeUpdatePlugin.kt +265 -0
  17. package/android/src/main/java/com/aoneahsan/nativeupdate/LiveUpdatePlugin.kt +526 -0
  18. package/android/src/main/java/com/aoneahsan/nativeupdate/NotificationActionReceiver.kt +99 -0
  19. package/android/src/main/java/com/aoneahsan/nativeupdate/SecurityManager.kt +249 -0
  20. package/dist/esm/__tests__/bundle-manager.test.d.ts +1 -0
  21. package/dist/esm/__tests__/bundle-manager.test.js +123 -0
  22. package/dist/esm/__tests__/bundle-manager.test.js.map +1 -0
  23. package/dist/esm/__tests__/config.test.d.ts +1 -0
  24. package/dist/esm/__tests__/config.test.js +69 -0
  25. package/dist/esm/__tests__/config.test.js.map +1 -0
  26. package/dist/esm/__tests__/integration.test.d.ts +1 -0
  27. package/dist/esm/__tests__/integration.test.js +78 -0
  28. package/dist/esm/__tests__/integration.test.js.map +1 -0
  29. package/dist/esm/__tests__/security.test.d.ts +1 -0
  30. package/dist/esm/__tests__/security.test.js +54 -0
  31. package/dist/esm/__tests__/security.test.js.map +1 -0
  32. package/dist/esm/__tests__/version-manager.test.d.ts +1 -0
  33. package/dist/esm/__tests__/version-manager.test.js +45 -0
  34. package/dist/esm/__tests__/version-manager.test.js.map +1 -0
  35. package/dist/esm/app-review/app-review-manager.d.ts +24 -0
  36. package/dist/esm/app-review/app-review-manager.js +195 -0
  37. package/dist/esm/app-review/app-review-manager.js.map +1 -0
  38. package/dist/esm/app-review/index.d.ts +5 -0
  39. package/dist/esm/app-review/index.js +6 -0
  40. package/dist/esm/app-review/index.js.map +1 -0
  41. package/dist/esm/app-review/platform-review-handler.d.ts +20 -0
  42. package/dist/esm/app-review/platform-review-handler.js +138 -0
  43. package/dist/esm/app-review/platform-review-handler.js.map +1 -0
  44. package/dist/esm/app-review/review-conditions-checker.d.ts +22 -0
  45. package/dist/esm/app-review/review-conditions-checker.js +155 -0
  46. package/dist/esm/app-review/review-conditions-checker.js.map +1 -0
  47. package/dist/esm/app-review/review-rate-limiter.d.ts +23 -0
  48. package/dist/esm/app-review/review-rate-limiter.js +164 -0
  49. package/dist/esm/app-review/review-rate-limiter.js.map +1 -0
  50. package/dist/esm/app-review/types.d.ts +41 -0
  51. package/dist/esm/app-review/types.js +2 -0
  52. package/dist/esm/app-review/types.js.map +1 -0
  53. package/dist/esm/app-update/app-update-checker.d.ts +13 -0
  54. package/dist/esm/app-update/app-update-checker.js +104 -0
  55. package/dist/esm/app-update/app-update-checker.js.map +1 -0
  56. package/dist/esm/app-update/app-update-installer.d.ts +19 -0
  57. package/dist/esm/app-update/app-update-installer.js +123 -0
  58. package/dist/esm/app-update/app-update-installer.js.map +1 -0
  59. package/dist/esm/app-update/app-update-manager.d.ts +28 -0
  60. package/dist/esm/app-update/app-update-manager.js +199 -0
  61. package/dist/esm/app-update/app-update-manager.js.map +1 -0
  62. package/dist/esm/app-update/app-update-notifier.d.ts +14 -0
  63. package/dist/esm/app-update/app-update-notifier.js +100 -0
  64. package/dist/esm/app-update/app-update-notifier.js.map +1 -0
  65. package/dist/esm/app-update/index.d.ts +6 -0
  66. package/dist/esm/app-update/index.js +7 -0
  67. package/dist/esm/app-update/index.js.map +1 -0
  68. package/dist/esm/app-update/platform-app-update.d.ts +19 -0
  69. package/dist/esm/app-update/platform-app-update.js +129 -0
  70. package/dist/esm/app-update/platform-app-update.js.map +1 -0
  71. package/dist/esm/app-update/types.d.ts +58 -0
  72. package/dist/esm/app-update/types.js +12 -0
  73. package/dist/esm/app-update/types.js.map +1 -0
  74. package/dist/esm/background-update/background-scheduler.d.ts +17 -0
  75. package/dist/esm/background-update/background-scheduler.js +195 -0
  76. package/dist/esm/background-update/background-scheduler.js.map +1 -0
  77. package/dist/esm/background-update/index.d.ts +3 -0
  78. package/dist/esm/background-update/index.js +3 -0
  79. package/dist/esm/background-update/index.js.map +1 -0
  80. package/dist/esm/background-update/notification-manager.d.ts +29 -0
  81. package/dist/esm/background-update/notification-manager.js +89 -0
  82. package/dist/esm/background-update/notification-manager.js.map +1 -0
  83. package/dist/esm/core/analytics.d.ts +70 -0
  84. package/dist/esm/core/analytics.js +137 -0
  85. package/dist/esm/core/analytics.js.map +1 -0
  86. package/dist/esm/core/cache-manager.d.ts +72 -0
  87. package/dist/esm/core/cache-manager.js +275 -0
  88. package/dist/esm/core/cache-manager.js.map +1 -0
  89. package/dist/esm/core/config.d.ts +48 -0
  90. package/dist/esm/core/config.js +83 -0
  91. package/dist/esm/core/config.js.map +1 -0
  92. package/dist/esm/core/errors.d.ts +51 -0
  93. package/dist/esm/core/errors.js +80 -0
  94. package/dist/esm/core/errors.js.map +1 -0
  95. package/dist/esm/core/logger.d.ts +21 -0
  96. package/dist/esm/core/logger.js +109 -0
  97. package/dist/esm/core/logger.js.map +1 -0
  98. package/dist/esm/core/performance.d.ts +53 -0
  99. package/dist/esm/core/performance.js +140 -0
  100. package/dist/esm/core/performance.js.map +1 -0
  101. package/dist/esm/core/plugin-manager.d.ts +66 -0
  102. package/dist/esm/core/plugin-manager.js +148 -0
  103. package/dist/esm/core/plugin-manager.js.map +1 -0
  104. package/dist/esm/core/security.d.ts +93 -0
  105. package/dist/esm/core/security.js +315 -0
  106. package/dist/esm/core/security.js.map +1 -0
  107. package/dist/esm/definitions.d.ts +639 -0
  108. package/dist/esm/definitions.js +103 -0
  109. package/dist/esm/definitions.js.map +1 -0
  110. package/dist/esm/index.d.ts +12 -0
  111. package/dist/esm/index.js +16 -0
  112. package/dist/esm/index.js.map +1 -0
  113. package/dist/esm/live-update/bundle-manager.d.ts +94 -0
  114. package/dist/esm/live-update/bundle-manager.js +310 -0
  115. package/dist/esm/live-update/bundle-manager.js.map +1 -0
  116. package/dist/esm/live-update/certificate-pinning.d.ts +38 -0
  117. package/dist/esm/live-update/certificate-pinning.js +78 -0
  118. package/dist/esm/live-update/certificate-pinning.js.map +1 -0
  119. package/dist/esm/live-update/download-manager.d.ts +67 -0
  120. package/dist/esm/live-update/download-manager.js +319 -0
  121. package/dist/esm/live-update/download-manager.js.map +1 -0
  122. package/dist/esm/live-update/update-manager.d.ts +52 -0
  123. package/dist/esm/live-update/update-manager.js +294 -0
  124. package/dist/esm/live-update/update-manager.js.map +1 -0
  125. package/dist/esm/live-update/version-manager.d.ts +84 -0
  126. package/dist/esm/live-update/version-manager.js +335 -0
  127. package/dist/esm/live-update/version-manager.js.map +1 -0
  128. package/dist/esm/plugin.d.ts +6 -0
  129. package/dist/esm/plugin.js +283 -0
  130. package/dist/esm/plugin.js.map +1 -0
  131. package/dist/esm/security/crypto.d.ts +25 -0
  132. package/dist/esm/security/crypto.js +70 -0
  133. package/dist/esm/security/crypto.js.map +1 -0
  134. package/dist/esm/security/validator.d.ts +60 -0
  135. package/dist/esm/security/validator.js +143 -0
  136. package/dist/esm/security/validator.js.map +1 -0
  137. package/dist/esm/web.d.ts +74 -0
  138. package/dist/esm/web.js +595 -0
  139. package/dist/esm/web.js.map +1 -0
  140. package/dist/plugin.cjs.js +2 -0
  141. package/dist/plugin.cjs.js.map +1 -0
  142. package/dist/plugin.esm.js +2 -0
  143. package/dist/plugin.esm.js.map +1 -0
  144. package/dist/plugin.js +3 -0
  145. package/dist/plugin.js.map +1 -0
  146. package/docs/APP_REVIEW_GUIDE.md +768 -0
  147. package/docs/BUNDLE_SIGNING.md +264 -0
  148. package/docs/LIVE_UPDATES_GUIDE.md +650 -0
  149. package/docs/MIGRATION.md +192 -0
  150. package/docs/NATIVE_UPDATES_GUIDE.md +694 -0
  151. package/docs/QUICK_START.md +606 -0
  152. package/docs/README.md +111 -0
  153. package/docs/REMAINING_FEATURES.md +139 -0
  154. package/docs/api/app-review-api.md +259 -0
  155. package/docs/api/app-update-api.md +238 -0
  156. package/docs/api/events-api.md +451 -0
  157. package/docs/api/live-update-api.md +265 -0
  158. package/docs/background-updates.md +392 -0
  159. package/docs/examples/advanced-scenarios.md +410 -0
  160. package/docs/examples/basic-usage.md +185 -0
  161. package/docs/features/app-reviews.md +975 -0
  162. package/docs/features/app-updates.md +785 -0
  163. package/docs/features/live-updates.md +633 -0
  164. package/docs/getting-started/configuration.md +468 -0
  165. package/docs/getting-started/installation.md +209 -0
  166. package/docs/getting-started/quick-start.md +379 -0
  167. package/docs/guides/deployment-guide.md +333 -0
  168. package/docs/guides/migration-from-codepush.md +142 -0
  169. package/docs/guides/security-best-practices.md +1057 -0
  170. package/docs/guides/testing-guide.md +373 -0
  171. package/docs/production-readiness.md +478 -0
  172. package/docs/security/certificate-pinning.md +122 -0
  173. package/docs/server-requirements.md +147 -0
  174. package/ios/Plugin/AppReview/AppReviewPlugin.swift +158 -0
  175. package/ios/Plugin/AppUpdate/AppUpdatePlugin.swift +234 -0
  176. package/ios/Plugin/BackgroundUpdate/BackgroundNotificationManager.swift +329 -0
  177. package/ios/Plugin/BackgroundUpdate/BackgroundUpdatePlugin.swift +396 -0
  178. package/ios/Plugin/CapacitorNativeUpdatePlugin.m +45 -0
  179. package/ios/Plugin/CapacitorNativeUpdatePlugin.swift +190 -0
  180. package/ios/Plugin/Info.plist +43 -0
  181. package/ios/Plugin/LiveUpdate/LiveUpdatePlugin.swift +689 -0
  182. package/ios/Plugin/LiveUpdate/WebViewConfiguration.swift +45 -0
  183. package/ios/Plugin/Security/SecurityManager.swift +289 -0
  184. package/package.json +90 -0
@@ -0,0 +1,329 @@
1
+ import Foundation
2
+ import Capacitor
3
+ import UserNotifications
4
+
5
+ class BackgroundNotificationManager: NSObject {
6
+
7
+ private weak var plugin: BackgroundUpdatePlugin?
8
+ private var preferences: NotificationPreferences
9
+ private let notificationCenter = UNUserNotificationCenter.current()
10
+
11
+ init(plugin: BackgroundUpdatePlugin) {
12
+ self.plugin = plugin
13
+ self.preferences = NotificationPreferences.default()
14
+ super.init()
15
+
16
+ // Set notification delegate
17
+ notificationCenter.delegate = self
18
+
19
+ // Setup notification categories
20
+ setupNotificationCategories()
21
+ }
22
+
23
+ func setPreferences(_ preferencesData: [String: Any]) {
24
+ preferences = NotificationPreferences.from(preferencesData)
25
+ }
26
+
27
+ func getPermissionStatus(completion: @escaping (NotificationPermissionStatus) -> Void) {
28
+ notificationCenter.getNotificationSettings { settings in
29
+ let status = NotificationPermissionStatus(
30
+ granted: settings.authorizationStatus == .authorized,
31
+ canRequest: settings.authorizationStatus == .notDetermined,
32
+ shouldShowRationale: settings.authorizationStatus == .denied
33
+ )
34
+ completion(status)
35
+ }
36
+ }
37
+
38
+ func requestPermissions(completion: @escaping (Bool) -> Void) {
39
+ notificationCenter.requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
40
+ if let error = error {
41
+ NSLog("Notification permission error: \(error.localizedDescription)")
42
+ }
43
+ completion(granted)
44
+ }
45
+ }
46
+
47
+ func sendUpdateNotification(appUpdate: AppUpdateInfo?, liveUpdate: LatestVersion?) async -> Bool {
48
+ // Check permissions first
49
+ let settings = await notificationCenter.notificationSettings()
50
+ guard settings.authorizationStatus == .authorized else {
51
+ return false
52
+ }
53
+
54
+ let content = createNotificationContent(appUpdate: appUpdate, liveUpdate: liveUpdate)
55
+ let identifier = "update_notification_\(Int(Date().timeIntervalSince1970))"
56
+
57
+ let request = UNNotificationRequest(
58
+ identifier: identifier,
59
+ content: content,
60
+ trigger: nil // Immediate delivery
61
+ )
62
+
63
+ do {
64
+ try await notificationCenter.add(request)
65
+
66
+ // Notify listeners
67
+ await MainActor.run {
68
+ self.plugin?.notifyListeners("backgroundUpdateNotification", data: [
69
+ "type": appUpdate?.updateAvailable == true ? "app_update" : "live_update",
70
+ "updateAvailable": true,
71
+ "version": appUpdate?.availableVersion ?? liveUpdate?.version ?? "unknown",
72
+ "action": "shown"
73
+ ])
74
+ }
75
+
76
+ return true
77
+ } catch {
78
+ NSLog("Failed to send notification: \(error.localizedDescription)")
79
+ return false
80
+ }
81
+ }
82
+
83
+ func cancelNotification(identifier: String) {
84
+ notificationCenter.removePendingNotificationRequests(withIdentifiers: [identifier])
85
+ notificationCenter.removeDeliveredNotifications(withIdentifiers: [identifier])
86
+ }
87
+
88
+ private func createNotificationContent(appUpdate: AppUpdateInfo?, liveUpdate: LatestVersion?) -> UNMutableNotificationContent {
89
+ let content = UNMutableNotificationContent()
90
+
91
+ // Determine title and body
92
+ var title = preferences.title ?? "App Update Available"
93
+ var body = preferences.description ?? "A new version of the app is available"
94
+
95
+ if let appUpdate = appUpdate, appUpdate.updateAvailable,
96
+ let liveUpdate = liveUpdate, liveUpdate.available {
97
+ title = "App Updates Available"
98
+ body = "App version \(appUpdate.availableVersion ?? "unknown") and content updates are available"
99
+ } else if let appUpdate = appUpdate, appUpdate.updateAvailable {
100
+ title = "App Update Available"
101
+ body = "Version \(appUpdate.availableVersion ?? "unknown") is available"
102
+ } else if let liveUpdate = liveUpdate, liveUpdate.available {
103
+ title = "Content Update Available"
104
+ body = "New content version \(liveUpdate.version ?? "unknown") is available"
105
+ }
106
+
107
+ content.title = title
108
+ content.body = body
109
+
110
+ // Set badge
111
+ content.badge = 1
112
+
113
+ // Set sound
114
+ if preferences.soundEnabled {
115
+ content.sound = .default
116
+ }
117
+
118
+ // Set category for actions
119
+ if preferences.showActions {
120
+ content.categoryIdentifier = "UPDATE_CATEGORY"
121
+ }
122
+
123
+ // Set user info
124
+ content.userInfo = [
125
+ "type": "update_available",
126
+ "appUpdate": appUpdate?.toDictionary() ?? [:],
127
+ "liveUpdate": liveUpdate?.toDictionary() ?? [:]
128
+ ]
129
+
130
+ return content
131
+ }
132
+
133
+ private func setupNotificationCategories() {
134
+ guard preferences.showActions else { return }
135
+
136
+ // Create actions
137
+ let updateNowAction = UNNotificationAction(
138
+ identifier: "UPDATE_NOW",
139
+ title: preferences.actionLabels?.updateNow ?? "Update Now",
140
+ options: [.foreground]
141
+ )
142
+
143
+ let updateLaterAction = UNNotificationAction(
144
+ identifier: "UPDATE_LATER",
145
+ title: preferences.actionLabels?.updateLater ?? "Later",
146
+ options: []
147
+ )
148
+
149
+ let dismissAction = UNNotificationAction(
150
+ identifier: "DISMISS",
151
+ title: preferences.actionLabels?.dismiss ?? "Dismiss",
152
+ options: []
153
+ )
154
+
155
+ // Create category
156
+ let updateCategory = UNNotificationCategory(
157
+ identifier: "UPDATE_CATEGORY",
158
+ actions: [updateNowAction, updateLaterAction, dismissAction],
159
+ intentIdentifiers: [],
160
+ options: []
161
+ )
162
+
163
+ // Set categories
164
+ notificationCenter.setNotificationCategories([updateCategory])
165
+ }
166
+ }
167
+
168
+ // MARK: - UNUserNotificationCenterDelegate
169
+
170
+ extension BackgroundNotificationManager: UNUserNotificationCenterDelegate {
171
+
172
+ func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
173
+ // Show notification even when app is in foreground
174
+ completionHandler([.banner, .sound, .badge])
175
+ }
176
+
177
+ func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
178
+
179
+ let userInfo = response.notification.request.content.userInfo
180
+
181
+ DispatchQueue.main.async {
182
+ self.plugin?.notifyListeners("backgroundUpdateNotification", data: [
183
+ "type": userInfo["type"] as? String ?? "unknown",
184
+ "updateAvailable": true,
185
+ "action": self.mapActionIdentifier(response.actionIdentifier)
186
+ ])
187
+ }
188
+
189
+ // Handle different actions
190
+ switch response.actionIdentifier {
191
+ case "UPDATE_NOW":
192
+ // Trigger immediate update
193
+ self.handleUpdateNowAction(userInfo: userInfo)
194
+ case "UPDATE_LATER":
195
+ // Schedule reminder or do nothing
196
+ break
197
+ case "DISMISS":
198
+ // Remove notification
199
+ break
200
+ default:
201
+ // Default tap action - open app
202
+ self.handleDefaultTapAction(userInfo: userInfo)
203
+ }
204
+
205
+ completionHandler()
206
+ }
207
+
208
+ private func mapActionIdentifier(_ identifier: String) -> String {
209
+ switch identifier {
210
+ case "UPDATE_NOW":
211
+ return "update_now"
212
+ case "UPDATE_LATER":
213
+ return "update_later"
214
+ case "DISMISS":
215
+ return "dismiss"
216
+ case UNNotificationDefaultActionIdentifier:
217
+ return "tapped"
218
+ default:
219
+ return "unknown"
220
+ }
221
+ }
222
+
223
+ private func handleUpdateNowAction(userInfo: [AnyHashable: Any]) {
224
+ // Here you would typically trigger the update process
225
+ // This could involve calling the app update or live update functionality
226
+ NSLog("Update now action triggered")
227
+ }
228
+
229
+ private func handleDefaultTapAction(userInfo: [AnyHashable: Any]) {
230
+ // App was opened via notification tap
231
+ NSLog("Notification tapped - app opened")
232
+ }
233
+ }
234
+
235
+ // MARK: - Data Models
236
+
237
+ struct NotificationPreferences {
238
+ let title: String?
239
+ let description: String?
240
+ let iconName: String?
241
+ let soundEnabled: Bool
242
+ let vibrationEnabled: Bool
243
+ let showActions: Bool
244
+ let actionLabels: ActionLabels?
245
+ let channelId: String?
246
+ let channelName: String?
247
+ let priority: NotificationPriority
248
+
249
+ static func `default`() -> NotificationPreferences {
250
+ return NotificationPreferences(
251
+ title: "App Update Available",
252
+ description: "A new version of the app is available",
253
+ iconName: nil,
254
+ soundEnabled: true,
255
+ vibrationEnabled: true,
256
+ showActions: true,
257
+ actionLabels: ActionLabels.default(),
258
+ channelId: "capacitor_native_update",
259
+ channelName: "App Updates",
260
+ priority: .default
261
+ )
262
+ }
263
+
264
+ static func from(_ obj: [String: Any]) -> NotificationPreferences {
265
+ let actionLabelsObj = obj["actionLabels"] as? [String: Any]
266
+
267
+ return NotificationPreferences(
268
+ title: obj["title"] as? String,
269
+ description: obj["description"] as? String,
270
+ iconName: obj["iconName"] as? String,
271
+ soundEnabled: obj["soundEnabled"] as? Bool ?? true,
272
+ vibrationEnabled: obj["vibrationEnabled"] as? Bool ?? true,
273
+ showActions: obj["showActions"] as? Bool ?? true,
274
+ actionLabels: actionLabelsObj != nil ? ActionLabels.from(actionLabelsObj!) : ActionLabels.default(),
275
+ channelId: obj["channelId"] as? String,
276
+ channelName: obj["channelName"] as? String,
277
+ priority: NotificationPriority(rawValue: obj["priority"] as? String ?? "default") ?? .default
278
+ )
279
+ }
280
+ }
281
+
282
+ struct ActionLabels {
283
+ let updateNow: String?
284
+ let updateLater: String?
285
+ let dismiss: String?
286
+
287
+ static func `default`() -> ActionLabels {
288
+ return ActionLabels(
289
+ updateNow: "Update Now",
290
+ updateLater: "Later",
291
+ dismiss: "Dismiss"
292
+ )
293
+ }
294
+
295
+ static func from(_ obj: [String: Any]) -> ActionLabels {
296
+ return ActionLabels(
297
+ updateNow: obj["updateNow"] as? String,
298
+ updateLater: obj["updateLater"] as? String,
299
+ dismiss: obj["dismiss"] as? String
300
+ )
301
+ }
302
+ }
303
+
304
+ enum NotificationPriority: String, CaseIterable {
305
+ case min = "min"
306
+ case low = "low"
307
+ case `default` = "default"
308
+ case high = "high"
309
+ case max = "max"
310
+ }
311
+
312
+ struct NotificationPermissionStatus {
313
+ let granted: Bool
314
+ let canRequest: Bool
315
+ let shouldShowRationale: Bool?
316
+
317
+ func toDictionary() -> [String: Any] {
318
+ var obj: [String: Any] = [
319
+ "granted": granted,
320
+ "canRequest": canRequest
321
+ ]
322
+
323
+ if let shouldShowRationale = shouldShowRationale {
324
+ obj["shouldShowRationale"] = shouldShowRationale
325
+ }
326
+
327
+ return obj
328
+ }
329
+ }