@capgo/capacitor-updater 8.0.1 → 8.1.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/CapgoCapacitorUpdater.podspec +7 -5
- package/Package.swift +9 -7
- package/README.md +984 -215
- package/android/build.gradle +24 -12
- package/android/proguard-rules.pro +22 -5
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleInfo.java +110 -22
- package/android/src/main/java/ee/forgr/capacitor_updater/Callback.java +2 -2
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +1310 -488
- package/android/src/main/java/ee/forgr/capacitor_updater/{CapacitorUpdater.java → CapgoUpdater.java} +640 -203
- package/android/src/main/java/ee/forgr/capacitor_updater/{CryptoCipherV2.java → CryptoCipher.java} +119 -33
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayCondition.java +0 -3
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayUpdateUtils.java +260 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DeviceIdHelper.java +221 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +497 -133
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +80 -25
- package/android/src/main/java/ee/forgr/capacitor_updater/Logger.java +338 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeDetector.java +72 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +169 -0
- package/dist/docs.json +873 -154
- package/dist/esm/definitions.d.ts +881 -114
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/history.d.ts +1 -0
- package/dist/esm/history.js +283 -0
- package/dist/esm/history.js.map +1 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +12 -1
- package/dist/esm/web.js +29 -2
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +311 -2
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +311 -2
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/AES.swift +69 -0
- package/ios/Sources/CapacitorUpdaterPlugin/BigInt.swift +55 -0
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/BundleInfo.swift +37 -10
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/BundleStatus.swift +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +1605 -0
- package/ios/{Plugin/CapacitorUpdater.swift → Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift} +523 -230
- package/ios/Sources/CapacitorUpdaterPlugin/CryptoCipher.swift +267 -0
- package/ios/Sources/CapacitorUpdaterPlugin/DelayUpdateUtils.swift +220 -0
- package/ios/Sources/CapacitorUpdaterPlugin/DeviceIdHelper.swift +120 -0
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/InternalUtils.swift +53 -0
- package/ios/Sources/CapacitorUpdaterPlugin/Logger.swift +310 -0
- package/ios/Sources/CapacitorUpdaterPlugin/RSA.swift +274 -0
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +112 -0
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/UserDefaultsExtension.swift +0 -2
- package/package.json +21 -19
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +0 -975
- package/ios/Plugin/CryptoCipherV2.swift +0 -310
- /package/{LICENCE → LICENSE} +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/DelayCondition.swift +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/DelayUntilNext.swift +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/Info.plist +0 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
package ee.forgr.capacitor_updater;
|
|
8
|
+
|
|
9
|
+
import android.content.Context;
|
|
10
|
+
import android.content.SharedPreferences;
|
|
11
|
+
import android.os.Build;
|
|
12
|
+
import android.security.keystore.KeyGenParameterSpec;
|
|
13
|
+
import android.security.keystore.KeyProperties;
|
|
14
|
+
import java.io.IOException;
|
|
15
|
+
import java.nio.charset.StandardCharsets;
|
|
16
|
+
import java.security.KeyStore;
|
|
17
|
+
import java.security.KeyStoreException;
|
|
18
|
+
import java.security.NoSuchAlgorithmException;
|
|
19
|
+
import java.security.NoSuchProviderException;
|
|
20
|
+
import java.security.UnrecoverableEntryException;
|
|
21
|
+
import java.security.cert.CertificateException;
|
|
22
|
+
import java.util.UUID;
|
|
23
|
+
import javax.crypto.Cipher;
|
|
24
|
+
import javax.crypto.KeyGenerator;
|
|
25
|
+
import javax.crypto.SecretKey;
|
|
26
|
+
import javax.crypto.spec.GCMParameterSpec;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Helper class to manage device ID persistence across app installations.
|
|
30
|
+
* Uses Android Keystore to persist the device ID across reinstalls.
|
|
31
|
+
*
|
|
32
|
+
* The device ID is a random UUID stored in the Android Keystore, which persists
|
|
33
|
+
* even after app uninstall/reinstall on Android 6.0+ (API 23+).
|
|
34
|
+
*/
|
|
35
|
+
public class DeviceIdHelper {
|
|
36
|
+
|
|
37
|
+
private static final String KEYSTORE_ALIAS = "capgo_device_id_key";
|
|
38
|
+
private static final String ANDROID_KEYSTORE = "AndroidKeyStore";
|
|
39
|
+
private static final String LEGACY_PREFS_KEY = "appUUID";
|
|
40
|
+
private static final String DEVICE_ID_PREFS = "capgo_device_id";
|
|
41
|
+
private static final String DEVICE_ID_KEY = "deviceId";
|
|
42
|
+
private static final String IV_KEY = "iv";
|
|
43
|
+
private static final int GCM_TAG_LENGTH = 128;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Gets or creates a device ID that persists across reinstalls.
|
|
47
|
+
*
|
|
48
|
+
* This method:
|
|
49
|
+
* 1. First checks for an existing ID in Keystore-encrypted storage (persists across reinstalls)
|
|
50
|
+
* 2. Falls back to legacy SharedPreferences (for migration)
|
|
51
|
+
* 3. Generates a new UUID if neither exists
|
|
52
|
+
* 4. Stores the ID in Keystore-encrypted storage for future use
|
|
53
|
+
*
|
|
54
|
+
* @param context Application context
|
|
55
|
+
* @param legacyPrefs Legacy SharedPreferences (for migration)
|
|
56
|
+
* @return Device ID as a lowercase UUID string
|
|
57
|
+
*/
|
|
58
|
+
public static String getOrCreateDeviceId(Context context, SharedPreferences legacyPrefs) {
|
|
59
|
+
// API 23+ required for Android Keystore
|
|
60
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
|
61
|
+
return getFallbackDeviceId(legacyPrefs);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
// Try to get device ID from Keystore storage
|
|
66
|
+
String deviceId = getDeviceIdFromKeystore(context);
|
|
67
|
+
|
|
68
|
+
if (deviceId != null && !deviceId.isEmpty()) {
|
|
69
|
+
return deviceId.toLowerCase();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Migration: Check legacy SharedPreferences for existing device ID
|
|
73
|
+
deviceId = legacyPrefs.getString(LEGACY_PREFS_KEY, null);
|
|
74
|
+
|
|
75
|
+
if (deviceId == null || deviceId.isEmpty()) {
|
|
76
|
+
// Generate new device ID if none exists
|
|
77
|
+
deviceId = UUID.randomUUID().toString();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Ensure lowercase for consistency
|
|
81
|
+
deviceId = deviceId.toLowerCase();
|
|
82
|
+
|
|
83
|
+
// Save to Keystore storage
|
|
84
|
+
saveDeviceIdToKeystore(context, deviceId);
|
|
85
|
+
|
|
86
|
+
return deviceId;
|
|
87
|
+
} catch (Exception e) {
|
|
88
|
+
// Fallback to legacy method if Keystore fails
|
|
89
|
+
return getFallbackDeviceId(legacyPrefs);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Retrieves the device ID from Keystore-encrypted storage.
|
|
95
|
+
*
|
|
96
|
+
* @param context Application context
|
|
97
|
+
* @return Device ID string or null if not found
|
|
98
|
+
*/
|
|
99
|
+
private static String getDeviceIdFromKeystore(Context context) throws Exception {
|
|
100
|
+
SharedPreferences prefs = context.getSharedPreferences(DEVICE_ID_PREFS, Context.MODE_PRIVATE);
|
|
101
|
+
String encryptedDeviceId = prefs.getString(DEVICE_ID_KEY, null);
|
|
102
|
+
String ivString = prefs.getString(IV_KEY, null);
|
|
103
|
+
|
|
104
|
+
if (encryptedDeviceId == null || ivString == null) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Get the encryption key from Keystore
|
|
109
|
+
SecretKey key = getOrCreateKey();
|
|
110
|
+
if (key == null) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Decrypt the device ID
|
|
115
|
+
byte[] encryptedBytes = android.util.Base64.decode(encryptedDeviceId, android.util.Base64.DEFAULT);
|
|
116
|
+
byte[] iv = android.util.Base64.decode(ivString, android.util.Base64.DEFAULT);
|
|
117
|
+
|
|
118
|
+
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
|
119
|
+
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
|
|
120
|
+
cipher.init(Cipher.DECRYPT_MODE, key, spec);
|
|
121
|
+
|
|
122
|
+
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
|
|
123
|
+
return new String(decryptedBytes, StandardCharsets.UTF_8);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Saves the device ID to Keystore-encrypted storage.
|
|
128
|
+
*
|
|
129
|
+
* The device ID is encrypted using AES/GCM with a key stored in Android Keystore.
|
|
130
|
+
* The Keystore key persists across reinstalls on Android 6.0+ (API 23+).
|
|
131
|
+
*
|
|
132
|
+
* @param context Application context
|
|
133
|
+
* @param deviceId The device ID to save
|
|
134
|
+
*/
|
|
135
|
+
private static void saveDeviceIdToKeystore(Context context, String deviceId) throws Exception {
|
|
136
|
+
// Get or create encryption key in Keystore
|
|
137
|
+
SecretKey key = getOrCreateKey();
|
|
138
|
+
if (key == null) {
|
|
139
|
+
throw new Exception("Failed to get encryption key");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Encrypt the device ID
|
|
143
|
+
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
|
144
|
+
cipher.init(Cipher.ENCRYPT_MODE, key);
|
|
145
|
+
|
|
146
|
+
byte[] iv = cipher.getIV();
|
|
147
|
+
byte[] encryptedBytes = cipher.doFinal(deviceId.getBytes(StandardCharsets.UTF_8));
|
|
148
|
+
|
|
149
|
+
// Store encrypted device ID and IV in SharedPreferences
|
|
150
|
+
SharedPreferences prefs = context.getSharedPreferences(DEVICE_ID_PREFS, Context.MODE_PRIVATE);
|
|
151
|
+
prefs
|
|
152
|
+
.edit()
|
|
153
|
+
.putString(DEVICE_ID_KEY, android.util.Base64.encodeToString(encryptedBytes, android.util.Base64.DEFAULT))
|
|
154
|
+
.putString(IV_KEY, android.util.Base64.encodeToString(iv, android.util.Base64.DEFAULT))
|
|
155
|
+
.apply();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Gets or creates the encryption key in Android Keystore.
|
|
160
|
+
*
|
|
161
|
+
* The key is configured to persist across reinstalls and not require user authentication.
|
|
162
|
+
*
|
|
163
|
+
* @return SecretKey from Keystore or null if failed
|
|
164
|
+
*/
|
|
165
|
+
private static SecretKey getOrCreateKey() {
|
|
166
|
+
try {
|
|
167
|
+
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
|
|
168
|
+
keyStore.load(null);
|
|
169
|
+
|
|
170
|
+
// Check if key already exists
|
|
171
|
+
if (keyStore.containsAlias(KEYSTORE_ALIAS)) {
|
|
172
|
+
KeyStore.SecretKeyEntry entry = (KeyStore.SecretKeyEntry) keyStore.getEntry(KEYSTORE_ALIAS, null);
|
|
173
|
+
return entry.getSecretKey();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Create new key
|
|
177
|
+
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE);
|
|
178
|
+
|
|
179
|
+
KeyGenParameterSpec keySpec = new KeyGenParameterSpec.Builder(
|
|
180
|
+
KEYSTORE_ALIAS,
|
|
181
|
+
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT
|
|
182
|
+
)
|
|
183
|
+
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
|
184
|
+
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
|
185
|
+
.setKeySize(256)
|
|
186
|
+
.setRandomizedEncryptionRequired(true)
|
|
187
|
+
.build();
|
|
188
|
+
|
|
189
|
+
keyGenerator.init(keySpec);
|
|
190
|
+
return keyGenerator.generateKey();
|
|
191
|
+
} catch (
|
|
192
|
+
KeyStoreException
|
|
193
|
+
| CertificateException
|
|
194
|
+
| NoSuchAlgorithmException
|
|
195
|
+
| IOException
|
|
196
|
+
| NoSuchProviderException
|
|
197
|
+
| UnrecoverableEntryException e
|
|
198
|
+
) {
|
|
199
|
+
return null;
|
|
200
|
+
} catch (Exception e) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Fallback method using legacy SharedPreferences if Keystore fails or API < 23.
|
|
207
|
+
*
|
|
208
|
+
* @param legacyPrefs Legacy SharedPreferences
|
|
209
|
+
* @return Device ID string
|
|
210
|
+
*/
|
|
211
|
+
private static String getFallbackDeviceId(SharedPreferences legacyPrefs) {
|
|
212
|
+
String deviceId = legacyPrefs.getString(LEGACY_PREFS_KEY, null);
|
|
213
|
+
|
|
214
|
+
if (deviceId == null || deviceId.isEmpty()) {
|
|
215
|
+
deviceId = UUID.randomUUID().toString();
|
|
216
|
+
legacyPrefs.edit().putString(LEGACY_PREFS_KEY, deviceId).apply();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return deviceId.toLowerCase();
|
|
220
|
+
}
|
|
221
|
+
}
|