@capgo/capacitor-updater 7.23.13 → 7.25.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/README.md +150 -127
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +49 -11
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +3 -3
- package/android/src/main/java/ee/forgr/capacitor_updater/DeviceIdHelper.java +221 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +1 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +1 -1
- package/dist/docs.json +4 -4
- package/dist/esm/definitions.d.ts +16 -6
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +32 -8
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +3 -3
- package/ios/Sources/CapacitorUpdaterPlugin/DeviceIdHelper.swift +120 -0
- package/package.json +2 -3
|
@@ -71,7 +71,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
71
71
|
private static final String[] BREAKING_EVENT_NAMES = { "breakingAvailable", "majorAvailable" };
|
|
72
72
|
private static final String LAST_FAILED_BUNDLE_PREF_KEY = "CapacitorUpdater.lastFailedBundle";
|
|
73
73
|
|
|
74
|
-
private final String
|
|
74
|
+
private final String pluginVersion = "7.25.0";
|
|
75
75
|
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
76
76
|
|
|
77
77
|
private SharedPreferences.Editor editor;
|
|
@@ -97,6 +97,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
97
97
|
private Boolean autoSplashscreenTimedOut = false;
|
|
98
98
|
private String directUpdateMode = "false";
|
|
99
99
|
private Boolean wasRecentlyInstalledOrUpdated = false;
|
|
100
|
+
private Boolean onLaunchDirectUpdateUsed = false;
|
|
100
101
|
Boolean shakeMenuEnabled = false;
|
|
101
102
|
private Boolean allowManualBundleError = false;
|
|
102
103
|
|
|
@@ -219,14 +220,30 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
219
220
|
this.implementation.activity = this.getActivity();
|
|
220
221
|
this.implementation.versionBuild = this.getConfig().getString("version", pInfo.versionName);
|
|
221
222
|
this.implementation.CAP_SERVER_PATH = WebView.CAP_SERVER_PATH;
|
|
222
|
-
this.implementation.
|
|
223
|
+
this.implementation.pluginVersion = this.pluginVersion;
|
|
223
224
|
this.implementation.versionCode = Integer.toString(pInfo.versionCode);
|
|
224
225
|
// Removed unused OkHttpClient creation - using shared client in DownloadService instead
|
|
225
226
|
// Handle directUpdate configuration - support string values and backward compatibility
|
|
226
227
|
String directUpdateConfig = this.getConfig().getString("directUpdate", null);
|
|
227
228
|
if (directUpdateConfig != null) {
|
|
228
229
|
this.directUpdateMode = directUpdateConfig;
|
|
229
|
-
this.implementation.directUpdate =
|
|
230
|
+
this.implementation.directUpdate =
|
|
231
|
+
directUpdateConfig.equals("always") || directUpdateConfig.equals("atInstall") || directUpdateConfig.equals("onLaunch");
|
|
232
|
+
// Validate directUpdate value
|
|
233
|
+
if (
|
|
234
|
+
!directUpdateConfig.equals("false") &&
|
|
235
|
+
!directUpdateConfig.equals("always") &&
|
|
236
|
+
!directUpdateConfig.equals("atInstall") &&
|
|
237
|
+
!directUpdateConfig.equals("onLaunch")
|
|
238
|
+
) {
|
|
239
|
+
logger.error(
|
|
240
|
+
"Invalid directUpdate value: \"" +
|
|
241
|
+
directUpdateConfig +
|
|
242
|
+
"\". Supported values are: false, \"always\", \"atInstall\", \"onLaunch\". Defaulting to false."
|
|
243
|
+
);
|
|
244
|
+
this.directUpdateMode = "false";
|
|
245
|
+
this.implementation.directUpdate = false;
|
|
246
|
+
}
|
|
230
247
|
} else {
|
|
231
248
|
Boolean directUpdateBool = this.getConfig().getBoolean("directUpdate", false);
|
|
232
249
|
if (directUpdateBool) {
|
|
@@ -308,12 +325,11 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
308
325
|
this.implementation.prefs = this.prefs;
|
|
309
326
|
this.implementation.editor = this.editor;
|
|
310
327
|
this.implementation.versionOs = Build.VERSION.RELEASE;
|
|
311
|
-
|
|
312
|
-
this.
|
|
313
|
-
this.editor.apply();
|
|
328
|
+
// Use DeviceIdHelper to get or create device ID that persists across reinstalls
|
|
329
|
+
this.implementation.deviceID = DeviceIdHelper.getOrCreateDeviceId(this.getContext(), this.prefs);
|
|
314
330
|
|
|
315
331
|
// Update User-Agent for shared OkHttpClient with OS version
|
|
316
|
-
DownloadService.updateUserAgent(this.implementation.appId, this.
|
|
332
|
+
DownloadService.updateUserAgent(this.implementation.appId, this.pluginVersion, this.implementation.versionOs);
|
|
317
333
|
|
|
318
334
|
if (Boolean.TRUE.equals(this.persistCustomId)) {
|
|
319
335
|
final String storedCustomId = this.prefs.getString(CUSTOM_ID_PREF_KEY, "");
|
|
@@ -626,7 +642,17 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
626
642
|
return true;
|
|
627
643
|
}
|
|
628
644
|
return false;
|
|
645
|
+
case "onLaunch":
|
|
646
|
+
if (!this.onLaunchDirectUpdateUsed) {
|
|
647
|
+
return true;
|
|
648
|
+
}
|
|
649
|
+
return false;
|
|
629
650
|
default:
|
|
651
|
+
logger.error(
|
|
652
|
+
"Invalid directUpdateMode: \"" +
|
|
653
|
+
this.directUpdateMode +
|
|
654
|
+
"\". Supported values are: \"false\", \"always\", \"atInstall\", \"onLaunch\". Defaulting to false behavior."
|
|
655
|
+
);
|
|
630
656
|
return false;
|
|
631
657
|
}
|
|
632
658
|
}
|
|
@@ -636,6 +662,10 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
636
662
|
}
|
|
637
663
|
|
|
638
664
|
private void directUpdateFinish(final BundleInfo latest) {
|
|
665
|
+
if ("onLaunch".equals(this.directUpdateMode)) {
|
|
666
|
+
this.onLaunchDirectUpdateUsed = true;
|
|
667
|
+
this.implementation.directUpdate = false;
|
|
668
|
+
}
|
|
639
669
|
CapacitorUpdaterPlugin.this.implementation.set(latest);
|
|
640
670
|
CapacitorUpdaterPlugin.this._reload();
|
|
641
671
|
sendReadyToJs(latest, "update installed", true);
|
|
@@ -810,7 +840,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
810
840
|
public void getPluginVersion(final PluginCall call) {
|
|
811
841
|
try {
|
|
812
842
|
final JSObject ret = new JSObject();
|
|
813
|
-
ret.put("version", this.
|
|
843
|
+
ret.put("version", this.pluginVersion);
|
|
814
844
|
call.resolve(ret);
|
|
815
845
|
} catch (final Exception e) {
|
|
816
846
|
logger.error("Could not get plugin version " + e.getMessage());
|
|
@@ -1940,9 +1970,17 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1940
1970
|
}
|
|
1941
1971
|
|
|
1942
1972
|
if (!this.shouldUseDirectUpdate()) {
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1973
|
+
if ("false".equals(this.directUpdateMode)) {
|
|
1974
|
+
logger.warn(
|
|
1975
|
+
"autoSplashscreen is enabled but directUpdate is not configured for immediate updates. Set directUpdate to 'always' or disable autoSplashscreen."
|
|
1976
|
+
);
|
|
1977
|
+
} else if ("atInstall".equals(this.directUpdateMode) || "onLaunch".equals(this.directUpdateMode)) {
|
|
1978
|
+
logger.info(
|
|
1979
|
+
"autoSplashscreen is enabled but directUpdate is set to \"" +
|
|
1980
|
+
this.directUpdateMode +
|
|
1981
|
+
"\". This is normal. Skipping autoSplashscreen logic."
|
|
1982
|
+
);
|
|
1983
|
+
}
|
|
1946
1984
|
canShowSplashscreen = false;
|
|
1947
1985
|
}
|
|
1948
1986
|
|
|
@@ -66,7 +66,7 @@ public class CapgoUpdater {
|
|
|
66
66
|
public File documentsDir;
|
|
67
67
|
public Boolean directUpdate = false;
|
|
68
68
|
public Activity activity;
|
|
69
|
-
public String
|
|
69
|
+
public String pluginVersion = "";
|
|
70
70
|
public String versionBuild = "";
|
|
71
71
|
public String versionCode = "";
|
|
72
72
|
public String versionOs = "";
|
|
@@ -353,7 +353,7 @@ public class CapgoUpdater {
|
|
|
353
353
|
manifest != null,
|
|
354
354
|
this.isEmulator(),
|
|
355
355
|
this.appId,
|
|
356
|
-
this.
|
|
356
|
+
this.pluginVersion
|
|
357
357
|
);
|
|
358
358
|
|
|
359
359
|
if (manifest != null) {
|
|
@@ -775,7 +775,7 @@ public class CapgoUpdater {
|
|
|
775
775
|
json.put("version_code", this.versionCode);
|
|
776
776
|
json.put("version_os", this.versionOs);
|
|
777
777
|
json.put("version_name", this.getCurrentBundle().getVersionName());
|
|
778
|
-
json.put("plugin_version", this.
|
|
778
|
+
json.put("plugin_version", this.pluginVersion);
|
|
779
779
|
json.put("is_emulator", this.isEmulator());
|
|
780
780
|
json.put("is_prod", this.isProd());
|
|
781
781
|
json.put("defaultChannel", this.defaultChannel);
|
|
@@ -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
|
+
}
|
|
@@ -63,7 +63,7 @@ public class DownloadService extends Worker {
|
|
|
63
63
|
public static final String PUBLIC_KEY = "publickey";
|
|
64
64
|
public static final String IS_MANIFEST = "is_manifest";
|
|
65
65
|
public static final String APP_ID = "app_id";
|
|
66
|
-
public static final String
|
|
66
|
+
public static final String pluginVersion = "plugin_version";
|
|
67
67
|
private static final String UPDATE_FILE = "update.dat";
|
|
68
68
|
|
|
69
69
|
// Shared OkHttpClient to prevent resource leaks
|
|
@@ -80,7 +80,7 @@ public class DownloadWorkerManager {
|
|
|
80
80
|
.putBoolean(DownloadService.IS_MANIFEST, isManifest)
|
|
81
81
|
.putString(DownloadService.PUBLIC_KEY, publicKey)
|
|
82
82
|
.putString(DownloadService.APP_ID, appId)
|
|
83
|
-
.putString(DownloadService.
|
|
83
|
+
.putString(DownloadService.pluginVersion, pluginVersion)
|
|
84
84
|
.build();
|
|
85
85
|
|
|
86
86
|
// Create network constraints - be more lenient on emulators
|
package/dist/docs.json
CHANGED
|
@@ -679,7 +679,7 @@
|
|
|
679
679
|
"text": "{Error}"
|
|
680
680
|
}
|
|
681
681
|
],
|
|
682
|
-
"docs": "Get unique ID used to identify device (sent to auto update server)
|
|
682
|
+
"docs": "Get unique ID used to identify device (sent to auto update server).\n\nThis ID is privacy-friendly and follows Apple and Google best practices:\n- Generated as a UUID and stored securely\n- Android: Uses EncryptedSharedPreferences with Auto Backup (persists across reinstalls)\n- iOS: Uses Keychain with kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly (persists across reinstalls)\n- Data stays on device (not synced to cloud on iOS)\n- Can be cleared by user via system settings (Android) or keychain access (iOS)\n\nThe device ID now persists between app reinstalls to maintain consistent device identity.",
|
|
683
683
|
"complexTypes": [
|
|
684
684
|
"DeviceId"
|
|
685
685
|
],
|
|
@@ -2559,9 +2559,9 @@
|
|
|
2559
2559
|
"name": "since"
|
|
2560
2560
|
}
|
|
2561
2561
|
],
|
|
2562
|
-
"docs": "Configure when the plugin should direct install updates. Only for autoUpdate mode.\nWorks well for apps less than 10MB and with uploads done using --partial flag.\nZip or apps more than 10MB will be relatively slow for users to update.\n- false: Never do direct updates (default behavior)\n- atInstall: Direct update only when app is installed
|
|
2562
|
+
"docs": "Configure when the plugin should direct install updates. Only for autoUpdate mode.\nWorks well for apps less than 10MB and with uploads done using --partial flag.\nZip or apps more than 10MB will be relatively slow for users to update.\n- false: Never do direct updates (use default behavior: download at start, set when backgrounded)\n- atInstall: Direct update only when app is installed, updated from store, otherwise act as directUpdate = false\n- onLaunch: Direct update only on app installed, updated from store or after app kill, otherwise act as directUpdate = false\n- always: Direct update in all previous cases (app installed, updated from store, after app kill or app resume), never act as directUpdate = false\n- true: (deprecated) Same as \"always\" for backward compatibility\n\nOnly available for Android and iOS.",
|
|
2563
2563
|
"complexTypes": [],
|
|
2564
|
-
"type": "boolean | 'always' | 'atInstall' | undefined"
|
|
2564
|
+
"type": "boolean | 'always' | 'atInstall' | 'onLaunch' | undefined"
|
|
2565
2565
|
},
|
|
2566
2566
|
{
|
|
2567
2567
|
"name": "autoSplashscreen",
|
|
@@ -2575,7 +2575,7 @@
|
|
|
2575
2575
|
"name": "since"
|
|
2576
2576
|
}
|
|
2577
2577
|
],
|
|
2578
|
-
"docs": "Automatically handle splashscreen hiding when using directUpdate. When enabled, the plugin will automatically hide the splashscreen after updates are applied or when no update is needed.\nThis removes the need to manually listen for appReady events and call SplashScreen.hide().\nOnly works when directUpdate is set to \"atInstall\", \"always\", or true.\nRequires the @capacitor/splash-screen plugin to be installed and configured with launchAutoHide: false.\nRequires autoUpdate and directUpdate to be enabled.\n\nOnly available for Android and iOS.",
|
|
2578
|
+
"docs": "Automatically handle splashscreen hiding when using directUpdate. When enabled, the plugin will automatically hide the splashscreen after updates are applied or when no update is needed.\nThis removes the need to manually listen for appReady events and call SplashScreen.hide().\nOnly works when directUpdate is set to \"atInstall\", \"always\", \"onLaunch\", or true.\nRequires the @capacitor/splash-screen plugin to be installed and configured with launchAutoHide: false.\nRequires autoUpdate and directUpdate to be enabled.\n\nOnly available for Android and iOS.",
|
|
2579
2579
|
"complexTypes": [],
|
|
2580
2580
|
"type": "boolean | undefined"
|
|
2581
2581
|
},
|
|
@@ -109,9 +109,10 @@ declare module '@capacitor/cli' {
|
|
|
109
109
|
* Configure when the plugin should direct install updates. Only for autoUpdate mode.
|
|
110
110
|
* Works well for apps less than 10MB and with uploads done using --partial flag.
|
|
111
111
|
* Zip or apps more than 10MB will be relatively slow for users to update.
|
|
112
|
-
* - false: Never do direct updates (default behavior)
|
|
113
|
-
* - atInstall: Direct update only when app is installed
|
|
114
|
-
* -
|
|
112
|
+
* - false: Never do direct updates (use default behavior: download at start, set when backgrounded)
|
|
113
|
+
* - atInstall: Direct update only when app is installed, updated from store, otherwise act as directUpdate = false
|
|
114
|
+
* - onLaunch: Direct update only on app installed, updated from store or after app kill, otherwise act as directUpdate = false
|
|
115
|
+
* - always: Direct update in all previous cases (app installed, updated from store, after app kill or app resume), never act as directUpdate = false
|
|
115
116
|
* - true: (deprecated) Same as "always" for backward compatibility
|
|
116
117
|
*
|
|
117
118
|
* Only available for Android and iOS.
|
|
@@ -119,11 +120,11 @@ declare module '@capacitor/cli' {
|
|
|
119
120
|
* @default false
|
|
120
121
|
* @since 5.1.0
|
|
121
122
|
*/
|
|
122
|
-
directUpdate?: boolean | 'atInstall' | 'always';
|
|
123
|
+
directUpdate?: boolean | 'atInstall' | 'always' | 'onLaunch';
|
|
123
124
|
/**
|
|
124
125
|
* Automatically handle splashscreen hiding when using directUpdate. When enabled, the plugin will automatically hide the splashscreen after updates are applied or when no update is needed.
|
|
125
126
|
* This removes the need to manually listen for appReady events and call SplashScreen.hide().
|
|
126
|
-
* Only works when directUpdate is set to "atInstall", "always", or true.
|
|
127
|
+
* Only works when directUpdate is set to "atInstall", "always", "onLaunch", or true.
|
|
127
128
|
* Requires the @capacitor/splash-screen plugin to be installed and configured with launchAutoHide: false.
|
|
128
129
|
* Requires autoUpdate and directUpdate to be enabled.
|
|
129
130
|
*
|
|
@@ -516,7 +517,16 @@ export interface CapacitorUpdaterPlugin {
|
|
|
516
517
|
*/
|
|
517
518
|
getBuiltinVersion(): Promise<BuiltinVersion>;
|
|
518
519
|
/**
|
|
519
|
-
* Get unique ID used to identify device (sent to auto update server)
|
|
520
|
+
* Get unique ID used to identify device (sent to auto update server).
|
|
521
|
+
*
|
|
522
|
+
* This ID is privacy-friendly and follows Apple and Google best practices:
|
|
523
|
+
* - Generated as a UUID and stored securely
|
|
524
|
+
* - Android: Uses EncryptedSharedPreferences with Auto Backup (persists across reinstalls)
|
|
525
|
+
* - iOS: Uses Keychain with kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly (persists across reinstalls)
|
|
526
|
+
* - Data stays on device (not synced to cloud on iOS)
|
|
527
|
+
* - Can be cleared by user via system settings (Android) or keychain access (iOS)
|
|
528
|
+
*
|
|
529
|
+
* The device ID now persists between app reinstalls to maintain consistent device identity.
|
|
520
530
|
*
|
|
521
531
|
* @returns {Promise<DeviceId>} A Promise with id for this device
|
|
522
532
|
* @throws {Error}
|