@capgo/capacitor-updater 7.0.22 → 7.0.26
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 +2 -2
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +1 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +84 -17
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +1 -1
- package/package.json +1 -1
|
@@ -14,7 +14,7 @@ Pod::Spec.new do |s|
|
|
|
14
14
|
s.ios.deployment_target = '14.0'
|
|
15
15
|
s.dependency 'Capacitor'
|
|
16
16
|
s.dependency 'SSZipArchive', '2.4.3'
|
|
17
|
-
s.dependency 'Alamofire'
|
|
18
|
-
s.dependency 'Version'
|
|
17
|
+
s.dependency 'Alamofire', '5.10.2'
|
|
18
|
+
s.dependency 'Version', '0.8.0'
|
|
19
19
|
s.swift_version = '5.1'
|
|
20
20
|
end
|
|
@@ -57,7 +57,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
57
57
|
private static final String statsUrlDefault = "https://plugin.capgo.app/stats";
|
|
58
58
|
private static final String channelUrlDefault = "https://plugin.capgo.app/channel_self";
|
|
59
59
|
|
|
60
|
-
private final String PLUGIN_VERSION = "7.0.
|
|
60
|
+
private final String PLUGIN_VERSION = "7.0.26";
|
|
61
61
|
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
62
62
|
|
|
63
63
|
private SharedPreferences.Editor editor;
|
|
@@ -16,6 +16,7 @@ import java.io.FileInputStream;
|
|
|
16
16
|
import java.net.HttpURLConnection;
|
|
17
17
|
import java.net.URL;
|
|
18
18
|
import java.nio.channels.FileChannel;
|
|
19
|
+
import java.nio.file.Files;
|
|
19
20
|
import java.security.MessageDigest;
|
|
20
21
|
import java.util.ArrayList;
|
|
21
22
|
import java.util.Arrays;
|
|
@@ -162,25 +163,38 @@ public class DownloadService extends Worker {
|
|
|
162
163
|
String fileHash = entry.getString("file_hash");
|
|
163
164
|
String downloadUrl = entry.getString("download_url");
|
|
164
165
|
|
|
166
|
+
if (!publicKey.isEmpty() && sessionKey != null && !sessionKey.isEmpty()) {
|
|
167
|
+
try {
|
|
168
|
+
fileHash = CryptoCipherV2.decryptChecksum(fileHash, publicKey);
|
|
169
|
+
} catch (Exception e) {
|
|
170
|
+
Log.e(TAG, "Error decrypting checksum for " + fileName, e);
|
|
171
|
+
hasError.set(true);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
final String finalFileHash = fileHash;
|
|
165
177
|
File targetFile = new File(destFolder, fileName);
|
|
166
|
-
File cacheFile = new File(cacheFolder,
|
|
178
|
+
File cacheFile = new File(cacheFolder, finalFileHash + "_" + new File(fileName).getName());
|
|
167
179
|
File builtinFile = new File(builtinFolder, fileName);
|
|
168
180
|
|
|
169
181
|
// Ensure parent directories of the target file exist
|
|
170
182
|
if (!Objects.requireNonNull(targetFile.getParentFile()).exists() && !targetFile.getParentFile().mkdirs()) {
|
|
171
|
-
|
|
183
|
+
Log.e(TAG, "Failed to create parent directory for: " + targetFile.getAbsolutePath());
|
|
184
|
+
hasError.set(true);
|
|
185
|
+
continue;
|
|
172
186
|
}
|
|
173
187
|
|
|
174
188
|
Future<?> future = executor.submit(() -> {
|
|
175
189
|
try {
|
|
176
|
-
if (builtinFile.exists() && verifyChecksum(builtinFile,
|
|
190
|
+
if (builtinFile.exists() && verifyChecksum(builtinFile, finalFileHash)) {
|
|
177
191
|
copyFile(builtinFile, targetFile);
|
|
178
192
|
Log.d(TAG, "using builtin file " + fileName);
|
|
179
|
-
} else if (cacheFile.exists() && verifyChecksum(cacheFile,
|
|
193
|
+
} else if (cacheFile.exists() && verifyChecksum(cacheFile, finalFileHash)) {
|
|
180
194
|
copyFile(cacheFile, targetFile);
|
|
181
195
|
Log.d(TAG, "already cached " + fileName);
|
|
182
196
|
} else {
|
|
183
|
-
downloadAndVerify(downloadUrl, targetFile, cacheFile,
|
|
197
|
+
downloadAndVerify(downloadUrl, targetFile, cacheFile, finalFileHash, sessionKey, publicKey);
|
|
184
198
|
}
|
|
185
199
|
|
|
186
200
|
long completed = completedFiles.incrementAndGet();
|
|
@@ -215,9 +229,11 @@ public class DownloadService extends Worker {
|
|
|
215
229
|
}
|
|
216
230
|
|
|
217
231
|
if (hasError.get()) {
|
|
232
|
+
Log.e(TAG, "One or more files failed to download");
|
|
218
233
|
throw new IOException("One or more files failed to download");
|
|
219
234
|
}
|
|
220
235
|
} catch (Exception e) {
|
|
236
|
+
Log.e(TAG, "Error in handleManifestDownload", e);
|
|
221
237
|
throw new RuntimeException(e.getLocalizedMessage());
|
|
222
238
|
}
|
|
223
239
|
}
|
|
@@ -392,18 +408,10 @@ public class DownloadService extends Worker {
|
|
|
392
408
|
decryptedExpectedHash = CryptoCipherV2.decryptChecksum(decryptedExpectedHash, publicKey);
|
|
393
409
|
}
|
|
394
410
|
|
|
395
|
-
//
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
FileOutputStream fos = new FileOutputStream(targetFile)
|
|
400
|
-
) {
|
|
401
|
-
byte[] buffer = new byte[8192];
|
|
402
|
-
int len;
|
|
403
|
-
while ((len = brotliInputStream.read(buffer)) != -1) {
|
|
404
|
-
fos.write(buffer, 0, len);
|
|
405
|
-
}
|
|
406
|
-
}
|
|
411
|
+
// Use new decompression method
|
|
412
|
+
byte[] compressedData = Files.readAllBytes(compressedFile.toPath());
|
|
413
|
+
byte[] decompressedData = decompressBrotli(compressedData, targetFile.getName());
|
|
414
|
+
Files.write(targetFile.toPath(), decompressedData);
|
|
407
415
|
|
|
408
416
|
// Delete the compressed file
|
|
409
417
|
compressedFile.delete();
|
|
@@ -459,4 +467,63 @@ public class DownloadService extends Worker {
|
|
|
459
467
|
}
|
|
460
468
|
return sb.toString();
|
|
461
469
|
}
|
|
470
|
+
|
|
471
|
+
private byte[] decompressBrotli(byte[] data, String fileName) throws IOException {
|
|
472
|
+
// Validate input
|
|
473
|
+
if (data == null) {
|
|
474
|
+
Log.e(TAG, "Error: Null data received for " + fileName);
|
|
475
|
+
throw new IOException("Null data received");
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Handle empty files
|
|
479
|
+
if (data.length == 0) {
|
|
480
|
+
return new byte[0];
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Handle the special EMPTY_BROTLI_STREAM case
|
|
484
|
+
if (data.length == 3 && data[0] == 0x1B && data[1] == 0x00 && data[2] == 0x06) {
|
|
485
|
+
return new byte[0];
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// For small files, check if it's a minimal Brotli wrapper
|
|
489
|
+
if (data.length > 3) {
|
|
490
|
+
try {
|
|
491
|
+
// Handle our minimal wrapper pattern
|
|
492
|
+
if (data[0] == 0x1B && data[1] == 0x00 && data[2] == 0x06 && data[data.length - 1] == 0x03) {
|
|
493
|
+
return Arrays.copyOfRange(data, 3, data.length - 1);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Handle brotli.compress minimal wrapper (quality 0)
|
|
497
|
+
if (data[0] == 0x0b && data[1] == 0x02 && data[2] == (byte) 0x80 && data[data.length - 1] == 0x03) {
|
|
498
|
+
return Arrays.copyOfRange(data, 3, data.length - 1);
|
|
499
|
+
}
|
|
500
|
+
} catch (ArrayIndexOutOfBoundsException e) {
|
|
501
|
+
Log.e(TAG, "Error: Malformed data for " + fileName);
|
|
502
|
+
throw new IOException("Malformed data structure");
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// For all other cases, try standard decompression
|
|
507
|
+
try (
|
|
508
|
+
ByteArrayInputStream bis = new ByteArrayInputStream(data);
|
|
509
|
+
BrotliInputStream brotliInputStream = new BrotliInputStream(bis);
|
|
510
|
+
ByteArrayOutputStream bos = new ByteArrayOutputStream()
|
|
511
|
+
) {
|
|
512
|
+
byte[] buffer = new byte[8192];
|
|
513
|
+
int len;
|
|
514
|
+
while ((len = brotliInputStream.read(buffer)) != -1) {
|
|
515
|
+
bos.write(buffer, 0, len);
|
|
516
|
+
}
|
|
517
|
+
return bos.toByteArray();
|
|
518
|
+
} catch (IOException e) {
|
|
519
|
+
Log.e(TAG, "Error: Brotli process failed for " + fileName + ". Status: " + e.getMessage());
|
|
520
|
+
// Add hex dump for debugging
|
|
521
|
+
StringBuilder hexDump = new StringBuilder();
|
|
522
|
+
for (int i = 0; i < Math.min(32, data.length); i++) {
|
|
523
|
+
hexDump.append(String.format("%02x ", data[i]));
|
|
524
|
+
}
|
|
525
|
+
Log.e(TAG, "Error: Raw data (" + fileName + "): " + hexDump.toString());
|
|
526
|
+
throw e;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
462
529
|
}
|
|
@@ -45,7 +45,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
45
45
|
CAPPluginMethod(name: "getNextBundle", returnType: CAPPluginReturnPromise)
|
|
46
46
|
]
|
|
47
47
|
public var implementation = CapacitorUpdater()
|
|
48
|
-
private let PLUGIN_VERSION: String = "7.0.
|
|
48
|
+
private let PLUGIN_VERSION: String = "7.0.26"
|
|
49
49
|
static let updateUrlDefault = "https://plugin.capgo.app/updates"
|
|
50
50
|
static let statsUrlDefault = "https://plugin.capgo.app/stats"
|
|
51
51
|
static let channelUrlDefault = "https://plugin.capgo.app/channel_self"
|