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,45 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
import WebKit
|
|
4
|
+
|
|
5
|
+
extension NativeUpdatePlugin {
|
|
6
|
+
|
|
7
|
+
// This method should be called by the Capacitor app during WebView setup
|
|
8
|
+
@objc public func configureWebView(for webView: WKWebView) {
|
|
9
|
+
// Check if there's an active bundle to load
|
|
10
|
+
if let activeBundleId = UserDefaults.standard.string(forKey: "native_update_active_bundle"),
|
|
11
|
+
let bundles = UserDefaults.standard.dictionary(forKey: "native_update_bundles"),
|
|
12
|
+
let bundleInfo = bundles[activeBundleId] as? [String: Any],
|
|
13
|
+
let extractedPath = bundleInfo["extractedPath"] as? String {
|
|
14
|
+
|
|
15
|
+
// Verify the bundle still exists
|
|
16
|
+
let bundleURL = URL(fileURLWithPath: extractedPath)
|
|
17
|
+
let indexURL = bundleURL.appendingPathComponent("index.html")
|
|
18
|
+
|
|
19
|
+
if FileManager.default.fileExists(atPath: indexURL.path) {
|
|
20
|
+
// Configure WebView to load from the active bundle
|
|
21
|
+
webView.loadFileURL(indexURL, allowingReadAccessTo: bundleURL)
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// If no active bundle or bundle doesn't exist, clear the active bundle
|
|
27
|
+
UserDefaults.standard.removeObject(forKey: "native_update_active_bundle")
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Helper method to get the current bundle's base URL
|
|
31
|
+
@objc public func getCurrentBundleURL() -> URL? {
|
|
32
|
+
if let activeBundleId = UserDefaults.standard.string(forKey: "native_update_active_bundle"),
|
|
33
|
+
let bundles = UserDefaults.standard.dictionary(forKey: "native_update_bundles"),
|
|
34
|
+
let bundleInfo = bundles[activeBundleId] as? [String: Any],
|
|
35
|
+
let extractedPath = bundleInfo["extractedPath"] as? String {
|
|
36
|
+
|
|
37
|
+
let bundleURL = URL(fileURLWithPath: extractedPath)
|
|
38
|
+
if FileManager.default.fileExists(atPath: bundleURL.path) {
|
|
39
|
+
return bundleURL
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return nil
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Security
|
|
3
|
+
import CommonCrypto
|
|
4
|
+
import CryptoKit
|
|
5
|
+
|
|
6
|
+
class SecurityManager {
|
|
7
|
+
private var config: [String: Any]?
|
|
8
|
+
private let keychain = KeychainWrapper()
|
|
9
|
+
|
|
10
|
+
func configure(_ config: [String: Any]) throws {
|
|
11
|
+
self.config = config
|
|
12
|
+
|
|
13
|
+
// Validate security configuration
|
|
14
|
+
let enforceHttps = config["enforceHttps"] as? Bool ?? true
|
|
15
|
+
if !enforceHttps {
|
|
16
|
+
print("⚠️ SecurityManager: HTTPS enforcement is disabled. This is not recommended for production.")
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
func getSecurityInfo() -> [String: Any] {
|
|
21
|
+
return [
|
|
22
|
+
"enforceHttps": config?["enforceHttps"] as? Bool ?? true,
|
|
23
|
+
"certificatePinning": [
|
|
24
|
+
"enabled": (config?["certificatePinning"] as? [String: Any])?["enabled"] as? Bool ?? false,
|
|
25
|
+
"pins": getCertificatePins()
|
|
26
|
+
],
|
|
27
|
+
"validateInputs": config?["validateInputs"] as? Bool ?? true,
|
|
28
|
+
"secureStorage": config?["secureStorage"] as? Bool ?? true
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func getCertificatePins() -> [String: [String]] {
|
|
33
|
+
guard let pinningConfig = config?["certificatePinning"] as? [String: Any],
|
|
34
|
+
pinningConfig["enabled"] as? Bool == true,
|
|
35
|
+
let pins = pinningConfig["pins"] as? [[String: Any]] else {
|
|
36
|
+
return [:]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
var hostPins: [String: [String]] = [:]
|
|
40
|
+
|
|
41
|
+
for pin in pins {
|
|
42
|
+
guard let hostname = pin["hostname"] as? String,
|
|
43
|
+
let sha256Pins = pin["sha256"] as? [String] else {
|
|
44
|
+
continue
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
hostPins[hostname] = sha256Pins
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return hostPins
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
func validateUrl(_ url: String) -> Bool {
|
|
54
|
+
if !url.hasPrefix("https://") && isHttpsEnforced() {
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check against allowed hosts if configured
|
|
59
|
+
if let allowedHosts = getAllowedHosts(), !allowedHosts.isEmpty {
|
|
60
|
+
guard let urlComponents = URLComponents(string: url),
|
|
61
|
+
let host = urlComponents.host else {
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
return allowedHosts.contains(host)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return true
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
func verifySignature(data: Data, signature: String, publicKeyString: String) -> Bool {
|
|
71
|
+
// Handle PEM format or base64 encoded public key
|
|
72
|
+
let publicKeyData: Data
|
|
73
|
+
if publicKeyString.contains("-----BEGIN PUBLIC KEY-----") {
|
|
74
|
+
// Convert PEM to base64
|
|
75
|
+
let base64String = publicKeyString
|
|
76
|
+
.replacingOccurrences(of: "-----BEGIN PUBLIC KEY-----", with: "")
|
|
77
|
+
.replacingOccurrences(of: "-----END PUBLIC KEY-----", with: "")
|
|
78
|
+
.replacingOccurrences(of: "\n", with: "")
|
|
79
|
+
.replacingOccurrences(of: "\r", with: "")
|
|
80
|
+
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
81
|
+
|
|
82
|
+
guard let data = Data(base64Encoded: base64String) else {
|
|
83
|
+
print("Failed to decode PEM public key")
|
|
84
|
+
return false
|
|
85
|
+
}
|
|
86
|
+
publicKeyData = data
|
|
87
|
+
} else {
|
|
88
|
+
// Already base64 encoded
|
|
89
|
+
guard let data = Data(base64Encoded: publicKeyString) else {
|
|
90
|
+
print("Failed to decode base64 public key")
|
|
91
|
+
return false
|
|
92
|
+
}
|
|
93
|
+
publicKeyData = data
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
guard let signatureData = Data(base64Encoded: signature) else {
|
|
97
|
+
print("Failed to decode signature")
|
|
98
|
+
return false
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
do {
|
|
102
|
+
// Create SecKey from public key data
|
|
103
|
+
let attributes: [String: Any] = [
|
|
104
|
+
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
|
|
105
|
+
kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
|
|
106
|
+
kSecAttrKeySizeInBits as String: 2048
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
guard let secKey = SecKeyCreateWithData(publicKeyData as CFData, attributes as CFDictionary, nil) else {
|
|
110
|
+
print("Failed to create SecKey from public key data")
|
|
111
|
+
return false
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Verify signature using RSA-PSS (matching web implementation)
|
|
115
|
+
let algorithm = SecKeyAlgorithm.rsaSignatureMessagePSSSHA256
|
|
116
|
+
let result = SecKeyVerifySignature(secKey, algorithm, data as CFData, signatureData as CFData, nil)
|
|
117
|
+
|
|
118
|
+
if !result {
|
|
119
|
+
print("Signature verification failed")
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return result
|
|
123
|
+
} catch {
|
|
124
|
+
print("Signature verification error: \(error)")
|
|
125
|
+
return false
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
func calculateChecksum(for data: Data, algorithm: String = "SHA-256") -> String {
|
|
130
|
+
let digest: Data
|
|
131
|
+
|
|
132
|
+
switch algorithm {
|
|
133
|
+
case "SHA-256":
|
|
134
|
+
digest = SHA256.hash(data: data).data
|
|
135
|
+
case "SHA-512":
|
|
136
|
+
digest = SHA512.hash(data: data).data
|
|
137
|
+
default:
|
|
138
|
+
// Fallback to SHA-256
|
|
139
|
+
digest = SHA256.hash(data: data).data
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return digest.map { String(format: "%02x", $0) }.joined()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
func saveSecureData(key: String, value: String) {
|
|
146
|
+
if isSecureStorageEnabled() {
|
|
147
|
+
keychain.set(value, forKey: key)
|
|
148
|
+
} else {
|
|
149
|
+
// Fallback to UserDefaults (not recommended)
|
|
150
|
+
UserDefaults.standard.set(value, forKey: key)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
func getSecureData(key: String) -> String? {
|
|
155
|
+
if isSecureStorageEnabled() {
|
|
156
|
+
return keychain.string(forKey: key)
|
|
157
|
+
} else {
|
|
158
|
+
return UserDefaults.standard.string(forKey: key)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
func validatePath(_ path: String) -> Bool {
|
|
163
|
+
// Prevent directory traversal attacks
|
|
164
|
+
if path.contains("..") || path.contains("//") {
|
|
165
|
+
return false
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Ensure path is within app's sandbox
|
|
169
|
+
let url = URL(fileURLWithPath: path)
|
|
170
|
+
let standardizedPath = url.standardizedFileURL.path
|
|
171
|
+
|
|
172
|
+
// Check if path is within allowed directories
|
|
173
|
+
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.path
|
|
174
|
+
let cachesPath = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!.path
|
|
175
|
+
let tempPath = NSTemporaryDirectory()
|
|
176
|
+
|
|
177
|
+
return standardizedPath.hasPrefix(documentsPath) ||
|
|
178
|
+
standardizedPath.hasPrefix(cachesPath) ||
|
|
179
|
+
standardizedPath.hasPrefix(tempPath)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
func sanitizeInput(_ input: String) -> String {
|
|
183
|
+
// Remove potentially dangerous characters
|
|
184
|
+
let allowedCharacters = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "._-"))
|
|
185
|
+
return input.components(separatedBy: allowedCharacters.inverted).joined()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
func isHttpsEnforced() -> Bool {
|
|
189
|
+
return config?["enforceHttps"] as? Bool ?? true
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
func isSecureStorageEnabled() -> Bool {
|
|
193
|
+
return config?["secureStorage"] as? Bool ?? true
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
func isInputValidationEnabled() -> Bool {
|
|
197
|
+
return config?["validateInputs"] as? Bool ?? true
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
func logSecurityEvent(_ event: String, details: String? = nil) {
|
|
201
|
+
guard config?["logSecurityEvents"] as? Bool == true else { return }
|
|
202
|
+
|
|
203
|
+
let message = "Security Event: \(event) \(details ?? "")"
|
|
204
|
+
print("🔒 \(message)")
|
|
205
|
+
|
|
206
|
+
// In production, you might want to send these to a security monitoring service
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private func getAllowedHosts() -> [String]? {
|
|
210
|
+
var hosts: [String] = []
|
|
211
|
+
|
|
212
|
+
// Add hosts from live update config
|
|
213
|
+
if let liveUpdateConfig = config?["liveUpdate"] as? [String: Any],
|
|
214
|
+
let allowedHosts = liveUpdateConfig["allowedHosts"] as? [String] {
|
|
215
|
+
hosts.append(contentsOf: allowedHosts)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Add host from server URL
|
|
219
|
+
if let serverUrl = config?["serverUrl"] as? String,
|
|
220
|
+
let urlComponents = URLComponents(string: serverUrl),
|
|
221
|
+
let host = urlComponents.host {
|
|
222
|
+
hosts.append(host)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return hosts.isEmpty ? nil : Array(Set(hosts))
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// MARK: - Keychain Wrapper
|
|
230
|
+
|
|
231
|
+
private class KeychainWrapper {
|
|
232
|
+
private let serviceName = "com.aoneahsan.nativeupdate"
|
|
233
|
+
|
|
234
|
+
func set(_ value: String, forKey key: String) {
|
|
235
|
+
guard let data = value.data(using: .utf8) else { return }
|
|
236
|
+
|
|
237
|
+
let query: [String: Any] = [
|
|
238
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
239
|
+
kSecAttrService as String: serviceName,
|
|
240
|
+
kSecAttrAccount as String: key,
|
|
241
|
+
kSecValueData as String: data
|
|
242
|
+
]
|
|
243
|
+
|
|
244
|
+
// Delete any existing item
|
|
245
|
+
SecItemDelete(query as CFDictionary)
|
|
246
|
+
|
|
247
|
+
// Add new item
|
|
248
|
+
SecItemAdd(query as CFDictionary, nil)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
func string(forKey key: String) -> String? {
|
|
252
|
+
let query: [String: Any] = [
|
|
253
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
254
|
+
kSecAttrService as String: serviceName,
|
|
255
|
+
kSecAttrAccount as String: key,
|
|
256
|
+
kSecReturnData as String: true,
|
|
257
|
+
kSecMatchLimit as String: kSecMatchLimitOne
|
|
258
|
+
]
|
|
259
|
+
|
|
260
|
+
var dataTypeRef: AnyObject?
|
|
261
|
+
let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
|
|
262
|
+
|
|
263
|
+
guard status == errSecSuccess,
|
|
264
|
+
let data = dataTypeRef as? Data,
|
|
265
|
+
let string = String(data: data, encoding: .utf8) else {
|
|
266
|
+
return nil
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return string
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
func remove(forKey key: String) {
|
|
273
|
+
let query: [String: Any] = [
|
|
274
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
275
|
+
kSecAttrService as String: serviceName,
|
|
276
|
+
kSecAttrAccount as String: key
|
|
277
|
+
]
|
|
278
|
+
|
|
279
|
+
SecItemDelete(query as CFDictionary)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// MARK: - CryptoKit Extensions
|
|
284
|
+
|
|
285
|
+
extension Digest {
|
|
286
|
+
var data: Data {
|
|
287
|
+
Data(self)
|
|
288
|
+
}
|
|
289
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "native-update",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Foundation package for building a comprehensive update system for Capacitor apps. Provides architecture and interfaces but requires backend implementation.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/plugin.cjs.js",
|
|
7
|
+
"module": "dist/esm/index.js",
|
|
8
|
+
"types": "dist/esm/index.d.ts",
|
|
9
|
+
"unpkg": "dist/plugin.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"android/src/main/",
|
|
12
|
+
"android/build.gradle",
|
|
13
|
+
"android/variables.gradle",
|
|
14
|
+
"android/gradle.properties",
|
|
15
|
+
"android/gradle/",
|
|
16
|
+
"android/settings.gradle",
|
|
17
|
+
"android/proguard-rules.pro",
|
|
18
|
+
"dist/",
|
|
19
|
+
"ios/Plugin/",
|
|
20
|
+
"CapacitorNativeUpdate.podspec",
|
|
21
|
+
"docs/"
|
|
22
|
+
],
|
|
23
|
+
"author": {
|
|
24
|
+
"name": "Ahsan Mahmood",
|
|
25
|
+
"email": "aoneahsan@gmail.com",
|
|
26
|
+
"url": "https://aoneahsan.com"
|
|
27
|
+
},
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/aoneahsan/native-update.git"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/aoneahsan/native-update/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/aoneahsan/native-update#readme",
|
|
37
|
+
"keywords": [
|
|
38
|
+
"capacitor",
|
|
39
|
+
"plugin",
|
|
40
|
+
"native",
|
|
41
|
+
"app-updates",
|
|
42
|
+
"native-updates"
|
|
43
|
+
],
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "npm run clean && npm run tsc && rollup -c rollup.config.js",
|
|
46
|
+
"build:prod": "npm run clean && npm run tsc && NODE_ENV=production rollup -c rollup.config.js",
|
|
47
|
+
"clean": "rimraf ./dist",
|
|
48
|
+
"tsc": "tsc",
|
|
49
|
+
"watch": "tsc --watch",
|
|
50
|
+
"lint": "eslint . --ext ts",
|
|
51
|
+
"prettier": "prettier --write .",
|
|
52
|
+
"prepublishOnly": "npm run build:prod",
|
|
53
|
+
"swiftlint": "cd ios && swiftlint lint --fix --format --path Plugin --verbose",
|
|
54
|
+
"test": "vitest",
|
|
55
|
+
"test:ui": "vitest --ui",
|
|
56
|
+
"test:coverage": "vitest --coverage"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@capacitor/android": "^7.4.2",
|
|
60
|
+
"@capacitor/core": "^7.4.2",
|
|
61
|
+
"@capacitor/filesystem": "^7.1.4",
|
|
62
|
+
"@capacitor/ios": "^7.4.2",
|
|
63
|
+
"@capacitor/preferences": "^7.0.2",
|
|
64
|
+
"@rollup/plugin-json": "^6.1.0",
|
|
65
|
+
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
66
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
67
|
+
"@types/node": "^24.2.1",
|
|
68
|
+
"@typescript-eslint/eslint-plugin": "^8.39.1",
|
|
69
|
+
"@typescript-eslint/parser": "^8.39.1",
|
|
70
|
+
"@vitest/ui": "^3.2.4",
|
|
71
|
+
"eslint": "^9.33.0",
|
|
72
|
+
"happy-dom": "^18.0.1",
|
|
73
|
+
"prettier": "^3.6.2",
|
|
74
|
+
"rimraf": "^6.0.1",
|
|
75
|
+
"rollup": "^4.46.2",
|
|
76
|
+
"typescript": "^5.9.2",
|
|
77
|
+
"vitest": "^3.2.4"
|
|
78
|
+
},
|
|
79
|
+
"peerDependencies": {
|
|
80
|
+
"@capacitor/core": "^7.4.2"
|
|
81
|
+
},
|
|
82
|
+
"capacitor": {
|
|
83
|
+
"ios": {
|
|
84
|
+
"src": "ios"
|
|
85
|
+
},
|
|
86
|
+
"android": {
|
|
87
|
+
"src": "android"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|