@capgo/capacitor-updater 6.0.69 → 6.0.70
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 +1 -0
- package/README.md +5 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdater.java +137 -41
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +29 -4
- package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipher.java +20 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +23 -2
- package/dist/docs.json +32 -0
- package/dist/esm/definitions.d.ts +15 -0
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Plugin/CapacitorUpdater.swift +81 -4
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +26 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -218,6 +218,7 @@ CapacitorUpdater can be configured with these options:
|
|
|
218
218
|
| **`localSupaAnon`** | <code>string</code> | Configure the CLI to use a local server for testing. | <code>undefined</code> | 4.17.48 |
|
|
219
219
|
| **`allowModifyUrl`** | <code>boolean</code> | Allow the plugin to modify the updateUrl, statsUrl and channelUrl dynamically from the JavaScript side. | <code>false</code> | 5.4.0 |
|
|
220
220
|
| **`defaultChannel`** | <code>string</code> | Set the default channel for the app in the config. | <code>undefined</code> | 5.5.0 |
|
|
221
|
+
| **`signKey`** | <code>string</code> | Public key used for bundle signing. | <code>undefined</code> | 6.1.0 |
|
|
221
222
|
|
|
222
223
|
### Examples
|
|
223
224
|
|
|
@@ -245,7 +246,8 @@ In `capacitor.config.json`:
|
|
|
245
246
|
"localSupa": undefined,
|
|
246
247
|
"localSupaAnon": undefined,
|
|
247
248
|
"allowModifyUrl": undefined,
|
|
248
|
-
"defaultChannel": undefined
|
|
249
|
+
"defaultChannel": undefined,
|
|
250
|
+
"signKey": undefined
|
|
249
251
|
}
|
|
250
252
|
}
|
|
251
253
|
}
|
|
@@ -280,6 +282,7 @@ const config: CapacitorConfig = {
|
|
|
280
282
|
localSupaAnon: undefined,
|
|
281
283
|
allowModifyUrl: undefined,
|
|
282
284
|
defaultChannel: undefined,
|
|
285
|
+
signKey: undefined,
|
|
283
286
|
},
|
|
284
287
|
},
|
|
285
288
|
};
|
|
@@ -934,6 +937,7 @@ Listen for app ready event in the App, let you know when app is ready to use
|
|
|
934
937
|
| **`version`** | <code>string</code> | The version code/name of this bundle/version | | |
|
|
935
938
|
| **`sessionKey`** | <code>string</code> | The session key for the update | <code>undefined</code> | 4.0.0 |
|
|
936
939
|
| **`checksum`** | <code>string</code> | The checksum for the update | <code>undefined</code> | 4.0.0 |
|
|
940
|
+
| **`signature`** | <code>string</code> | The signature of the update. Can be generated using capgo CLI | <code>undefined</code> | 6.1.0 |
|
|
937
941
|
|
|
938
942
|
|
|
939
943
|
#### BundleId
|
|
@@ -40,9 +40,13 @@ import java.io.InputStream;
|
|
|
40
40
|
import java.io.UnsupportedEncodingException;
|
|
41
41
|
import java.net.URL;
|
|
42
42
|
import java.net.URLConnection;
|
|
43
|
+
import java.nio.charset.StandardCharsets;
|
|
43
44
|
import java.security.GeneralSecurityException;
|
|
45
|
+
import java.security.MessageDigest;
|
|
44
46
|
import java.security.PrivateKey;
|
|
47
|
+
import java.security.PublicKey;
|
|
45
48
|
import java.security.SecureRandom;
|
|
49
|
+
import java.security.Signature;
|
|
46
50
|
import java.util.ArrayList;
|
|
47
51
|
import java.util.Date;
|
|
48
52
|
import java.util.Iterator;
|
|
@@ -51,6 +55,7 @@ import java.util.Objects;
|
|
|
51
55
|
import java.util.zip.CRC32;
|
|
52
56
|
import java.util.zip.ZipEntry;
|
|
53
57
|
import java.util.zip.ZipInputStream;
|
|
58
|
+
import javax.crypto.Cipher;
|
|
54
59
|
import javax.crypto.SecretKey;
|
|
55
60
|
import org.json.JSONException;
|
|
56
61
|
import org.json.JSONObject;
|
|
@@ -89,6 +94,7 @@ public class CapacitorUpdater {
|
|
|
89
94
|
public String privateKey = "";
|
|
90
95
|
public String deviceID = "";
|
|
91
96
|
public int timeout = 20000;
|
|
97
|
+
public PublicKey signKey = null;
|
|
92
98
|
|
|
93
99
|
private final FilenameFilter filter = (f, name) -> {
|
|
94
100
|
// ignore directories generated by mac os x
|
|
@@ -283,6 +289,7 @@ public class CapacitorUpdater {
|
|
|
283
289
|
String sessionKey = bundle.getString(DownloadService.SESSIONKEY);
|
|
284
290
|
String checksum = bundle.getString(DownloadService.CHECKSUM);
|
|
285
291
|
String error = bundle.getString(DownloadService.ERROR);
|
|
292
|
+
String signature = bundle.getString(DownloadService.SIGNATURE);
|
|
286
293
|
Log.i(
|
|
287
294
|
CapacitorUpdater.TAG,
|
|
288
295
|
"res " +
|
|
@@ -296,6 +303,8 @@ public class CapacitorUpdater {
|
|
|
296
303
|
" " +
|
|
297
304
|
checksum +
|
|
298
305
|
" " +
|
|
306
|
+
signature +
|
|
307
|
+
" " +
|
|
299
308
|
error
|
|
300
309
|
);
|
|
301
310
|
if (dest == null) {
|
|
@@ -318,6 +327,7 @@ public class CapacitorUpdater {
|
|
|
318
327
|
version,
|
|
319
328
|
sessionKey,
|
|
320
329
|
checksum,
|
|
330
|
+
signature,
|
|
321
331
|
true
|
|
322
332
|
);
|
|
323
333
|
} else {
|
|
@@ -333,14 +343,63 @@ public class CapacitorUpdater {
|
|
|
333
343
|
String version,
|
|
334
344
|
String sessionKey,
|
|
335
345
|
String checksumRes,
|
|
346
|
+
String signature,
|
|
336
347
|
Boolean setNext
|
|
337
348
|
) {
|
|
349
|
+
File downloaded = null;
|
|
350
|
+
String checksum;
|
|
351
|
+
|
|
338
352
|
try {
|
|
339
|
-
|
|
353
|
+
this.notifyDownload(id, 71);
|
|
354
|
+
downloaded = new File(this.documentsDir, dest);
|
|
355
|
+
|
|
356
|
+
boolean valid = verifyBundleSignature(version, downloaded, signature);
|
|
357
|
+
if (!valid) {
|
|
358
|
+
Log.e(
|
|
359
|
+
CapacitorUpdater.TAG,
|
|
360
|
+
"Invalid signature, cannot accept download"
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
this.sendStats("invalid_signature", version);
|
|
364
|
+
throw new GeneralSecurityException(
|
|
365
|
+
"Signature is not valid, cannot accept update"
|
|
366
|
+
);
|
|
367
|
+
} else {
|
|
368
|
+
Log.i(CapacitorUpdater.TAG, "Valid signature");
|
|
369
|
+
}
|
|
370
|
+
|
|
340
371
|
this.decryptFile(downloaded, sessionKey, version);
|
|
341
|
-
final String checksum;
|
|
342
372
|
checksum = this.calcChecksum(downloaded);
|
|
343
|
-
|
|
373
|
+
if (
|
|
374
|
+
checksumRes != null &&
|
|
375
|
+
!checksumRes.isEmpty() &&
|
|
376
|
+
!checksumRes.equals(checksum)
|
|
377
|
+
) {
|
|
378
|
+
Log.e(
|
|
379
|
+
CapacitorUpdater.TAG,
|
|
380
|
+
"Error checksum " + checksumRes + " " + checksum
|
|
381
|
+
);
|
|
382
|
+
this.sendStats("checksum_fail");
|
|
383
|
+
throw new IOException("Checksum failed: " + id);
|
|
384
|
+
}
|
|
385
|
+
} catch (IOException | GeneralSecurityException e) {
|
|
386
|
+
final Boolean res = this.delete(id);
|
|
387
|
+
if (!res) {
|
|
388
|
+
Log.i(CapacitorUpdater.TAG, "Double error, cannot cleanup: " + version);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
final JSObject ret = new JSObject();
|
|
392
|
+
ret.put(
|
|
393
|
+
"version",
|
|
394
|
+
CapacitorUpdater.this.getCurrentBundle().getVersionName()
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
CapacitorUpdater.this.notifyListeners("downloadFailed", ret);
|
|
398
|
+
CapacitorUpdater.this.sendStats("download_fail");
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
try {
|
|
344
403
|
final File unzipped = this.unzip(id, downloaded, this.randomString());
|
|
345
404
|
downloaded.delete();
|
|
346
405
|
this.notifyDownload(id, 91);
|
|
@@ -356,25 +415,7 @@ public class CapacitorUpdater {
|
|
|
356
415
|
checksum
|
|
357
416
|
);
|
|
358
417
|
this.saveBundleInfo(id, next);
|
|
359
|
-
|
|
360
|
-
checksumRes != null &&
|
|
361
|
-
!checksumRes.isEmpty() &&
|
|
362
|
-
!checksumRes.equals(checksum)
|
|
363
|
-
) {
|
|
364
|
-
Log.e(
|
|
365
|
-
CapacitorUpdater.TAG,
|
|
366
|
-
"Error checksum " + checksumRes + " " + checksum
|
|
367
|
-
);
|
|
368
|
-
this.sendStats("checksum_fail");
|
|
369
|
-
final Boolean res = this.delete(id);
|
|
370
|
-
if (res) {
|
|
371
|
-
Log.i(
|
|
372
|
-
CapacitorUpdater.TAG,
|
|
373
|
-
"Failed bundle deleted: " + next.getVersionName()
|
|
374
|
-
);
|
|
375
|
-
}
|
|
376
|
-
throw new IOException("Checksum failed: " + id);
|
|
377
|
-
}
|
|
418
|
+
|
|
378
419
|
final JSObject ret = new JSObject();
|
|
379
420
|
ret.put("bundle", next.toJSON());
|
|
380
421
|
CapacitorUpdater.this.notifyListeners("updateAvailable", ret);
|
|
@@ -406,7 +447,8 @@ public class CapacitorUpdater {
|
|
|
406
447
|
final String version,
|
|
407
448
|
final String sessionKey,
|
|
408
449
|
final String checksum,
|
|
409
|
-
final String dest
|
|
450
|
+
final String dest,
|
|
451
|
+
final String signature
|
|
410
452
|
) {
|
|
411
453
|
Intent intent = new Intent(this.activity, DownloadService.class);
|
|
412
454
|
intent.putExtra(DownloadService.URL, url);
|
|
@@ -419,6 +461,7 @@ public class CapacitorUpdater {
|
|
|
419
461
|
intent.putExtra(DownloadService.VERSION, version);
|
|
420
462
|
intent.putExtra(DownloadService.SESSIONKEY, sessionKey);
|
|
421
463
|
intent.putExtra(DownloadService.CHECKSUM, checksum);
|
|
464
|
+
intent.putExtra(DownloadService.SIGNATURE, signature);
|
|
422
465
|
this.activity.startService(intent);
|
|
423
466
|
}
|
|
424
467
|
|
|
@@ -502,6 +545,47 @@ public class CapacitorUpdater {
|
|
|
502
545
|
}
|
|
503
546
|
}
|
|
504
547
|
|
|
548
|
+
private boolean verifyBundleSignature(
|
|
549
|
+
final String version,
|
|
550
|
+
final File file,
|
|
551
|
+
final String signatureStr
|
|
552
|
+
) throws GeneralSecurityException, IOException {
|
|
553
|
+
if (this.signKey == null) {
|
|
554
|
+
Log.i(TAG, "Signing not configured");
|
|
555
|
+
return true;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (signatureStr.isEmpty()) {
|
|
559
|
+
Log.i(TAG, "Signature required but none provided");
|
|
560
|
+
this.sendStats("signature_not_provided", version);
|
|
561
|
+
throw new GeneralSecurityException(
|
|
562
|
+
"Signature was required but none was provided"
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
byte[] providedSignatureBytes = Base64.decode(
|
|
567
|
+
signatureStr.getBytes(StandardCharsets.UTF_8),
|
|
568
|
+
Base64.DEFAULT
|
|
569
|
+
);
|
|
570
|
+
|
|
571
|
+
Signature signature = Signature.getInstance("SHA512withRSA");
|
|
572
|
+
signature.initVerify(this.signKey);
|
|
573
|
+
|
|
574
|
+
byte[] content = new byte[(int) file.length()];
|
|
575
|
+
|
|
576
|
+
try (
|
|
577
|
+
final FileInputStream fis = new FileInputStream(file);
|
|
578
|
+
final BufferedInputStream bis = new BufferedInputStream(fis);
|
|
579
|
+
final DataInputStream dis = new DataInputStream(bis)
|
|
580
|
+
) {
|
|
581
|
+
dis.readFully(content);
|
|
582
|
+
dis.close();
|
|
583
|
+
|
|
584
|
+
signature.update(content);
|
|
585
|
+
return signature.verify(providedSignatureBytes);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
505
589
|
private void decryptFile(
|
|
506
590
|
final File file,
|
|
507
591
|
final String ivSessionKey,
|
|
@@ -560,7 +644,8 @@ public class CapacitorUpdater {
|
|
|
560
644
|
final String url,
|
|
561
645
|
final String version,
|
|
562
646
|
final String sessionKey,
|
|
563
|
-
final String checksum
|
|
647
|
+
final String checksum,
|
|
648
|
+
final String signature
|
|
564
649
|
) {
|
|
565
650
|
final String id = this.randomString();
|
|
566
651
|
this.saveBundleInfo(
|
|
@@ -581,7 +666,8 @@ public class CapacitorUpdater {
|
|
|
581
666
|
version,
|
|
582
667
|
sessionKey,
|
|
583
668
|
checksum,
|
|
584
|
-
this.randomString()
|
|
669
|
+
this.randomString(),
|
|
670
|
+
signature
|
|
585
671
|
);
|
|
586
672
|
}
|
|
587
673
|
|
|
@@ -589,7 +675,8 @@ public class CapacitorUpdater {
|
|
|
589
675
|
final String url,
|
|
590
676
|
final String version,
|
|
591
677
|
final String sessionKey,
|
|
592
|
-
final String checksum
|
|
678
|
+
final String checksum,
|
|
679
|
+
final String signature
|
|
593
680
|
) throws IOException {
|
|
594
681
|
final String id = this.randomString();
|
|
595
682
|
this.saveBundleInfo(
|
|
@@ -607,7 +694,15 @@ public class CapacitorUpdater {
|
|
|
607
694
|
final String dest = this.randomString();
|
|
608
695
|
this.downloadFile(id, url, dest);
|
|
609
696
|
final Boolean finished =
|
|
610
|
-
this.finishDownload(
|
|
697
|
+
this.finishDownload(
|
|
698
|
+
id,
|
|
699
|
+
dest,
|
|
700
|
+
version,
|
|
701
|
+
sessionKey,
|
|
702
|
+
checksum,
|
|
703
|
+
signature,
|
|
704
|
+
false
|
|
705
|
+
);
|
|
611
706
|
final BundleStatus status = finished
|
|
612
707
|
? BundleStatus.PENDING
|
|
613
708
|
: BundleStatus.ERROR;
|
|
@@ -659,8 +754,17 @@ public class CapacitorUpdater {
|
|
|
659
754
|
return false;
|
|
660
755
|
}
|
|
661
756
|
|
|
662
|
-
public Boolean delete(final String id)
|
|
663
|
-
|
|
757
|
+
public Boolean delete(final String id) {
|
|
758
|
+
try {
|
|
759
|
+
return this.delete(id, true);
|
|
760
|
+
} catch (IOException e) {
|
|
761
|
+
e.printStackTrace();
|
|
762
|
+
Log.i(
|
|
763
|
+
CapacitorUpdater.TAG,
|
|
764
|
+
"Failed to delete bundle (" + id + ")" + "\nError:\n" + e.toString()
|
|
765
|
+
);
|
|
766
|
+
return false;
|
|
767
|
+
}
|
|
664
768
|
}
|
|
665
769
|
|
|
666
770
|
private File getBundleDirectory(final String id) {
|
|
@@ -725,19 +829,11 @@ public class CapacitorUpdater {
|
|
|
725
829
|
"Version successfully loaded: " + bundle.getVersionName()
|
|
726
830
|
);
|
|
727
831
|
if (autoDeletePrevious && !fallback.isBuiltin()) {
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
Log.i(
|
|
732
|
-
CapacitorUpdater.TAG,
|
|
733
|
-
"Deleted previous bundle: " + fallback.getVersionName()
|
|
734
|
-
);
|
|
735
|
-
}
|
|
736
|
-
} catch (final IOException e) {
|
|
737
|
-
Log.e(
|
|
832
|
+
final Boolean res = this.delete(fallback.getId());
|
|
833
|
+
if (res) {
|
|
834
|
+
Log.i(
|
|
738
835
|
CapacitorUpdater.TAG,
|
|
739
|
-
"
|
|
740
|
-
e
|
|
836
|
+
"Deleted previous bundle: " + fallback.getVersionName()
|
|
741
837
|
);
|
|
742
838
|
}
|
|
743
839
|
}
|
|
@@ -30,6 +30,7 @@ import java.io.IOException;
|
|
|
30
30
|
import java.lang.reflect.Type;
|
|
31
31
|
import java.net.MalformedURLException;
|
|
32
32
|
import java.net.URL;
|
|
33
|
+
import java.security.PublicKey;
|
|
33
34
|
import java.text.SimpleDateFormat;
|
|
34
35
|
import java.util.ArrayList;
|
|
35
36
|
import java.util.Date;
|
|
@@ -55,7 +56,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
55
56
|
private static final String channelUrlDefault =
|
|
56
57
|
"https://api.capgo.app/channel_self";
|
|
57
58
|
|
|
58
|
-
private final String PLUGIN_VERSION = "6.0.
|
|
59
|
+
private final String PLUGIN_VERSION = "6.0.70";
|
|
59
60
|
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
60
61
|
|
|
61
62
|
private SharedPreferences.Editor editor;
|
|
@@ -137,6 +138,13 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
137
138
|
this.implementation.requestQueue = Volley.newRequestQueue(
|
|
138
139
|
this.getContext()
|
|
139
140
|
);
|
|
141
|
+
final String signKeyStr = this.getConfig().getString("signKey", "");
|
|
142
|
+
if (signKeyStr.length() > 0) {
|
|
143
|
+
this.implementation.signKey = CryptoCipher.stringToPublicKey(
|
|
144
|
+
signKeyStr
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
140
148
|
this.implementation.directUpdate = this.getConfig()
|
|
141
149
|
.getBoolean("directUpdate", false);
|
|
142
150
|
this.currentVersionNative = new Version(
|
|
@@ -574,6 +582,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
574
582
|
final String version = call.getString("version");
|
|
575
583
|
final String sessionKey = call.getString("sessionKey", "");
|
|
576
584
|
final String checksum = call.getString("checksum", "");
|
|
585
|
+
final String signature = call.getString("signature", "");
|
|
577
586
|
if (url == null) {
|
|
578
587
|
Log.e(CapacitorUpdater.TAG, "Download called without url");
|
|
579
588
|
call.reject("Download called without url");
|
|
@@ -584,6 +593,17 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
584
593
|
call.reject("Download called without version");
|
|
585
594
|
return;
|
|
586
595
|
}
|
|
596
|
+
if (
|
|
597
|
+
this.implementation.signKey != null &&
|
|
598
|
+
(signature == null || signature.isEmpty())
|
|
599
|
+
) {
|
|
600
|
+
Log.e(
|
|
601
|
+
CapacitorUpdater.TAG,
|
|
602
|
+
"Signature required but none provided for download call"
|
|
603
|
+
);
|
|
604
|
+
call.reject("Signature required but none provided");
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
587
607
|
try {
|
|
588
608
|
Log.i(CapacitorUpdater.TAG, "Downloading " + url);
|
|
589
609
|
startNewThread(() -> {
|
|
@@ -593,7 +613,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
593
613
|
url,
|
|
594
614
|
version,
|
|
595
615
|
sessionKey,
|
|
596
|
-
checksum
|
|
616
|
+
checksum,
|
|
617
|
+
signature
|
|
597
618
|
);
|
|
598
619
|
if (downloaded.isErrorStatus()) {
|
|
599
620
|
throw new RuntimeException(
|
|
@@ -834,7 +855,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
834
855
|
return;
|
|
835
856
|
}
|
|
836
857
|
final Timer timer = new Timer();
|
|
837
|
-
timer.
|
|
858
|
+
timer.schedule(
|
|
838
859
|
new TimerTask() {
|
|
839
860
|
@Override
|
|
840
861
|
public void run() {
|
|
@@ -1261,11 +1282,15 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1261
1282
|
final String checksum = res.has("checksum")
|
|
1262
1283
|
? res.getString("checksum")
|
|
1263
1284
|
: "";
|
|
1285
|
+
final String signature = res.has("signature")
|
|
1286
|
+
? res.getString("signature")
|
|
1287
|
+
: "";
|
|
1264
1288
|
CapacitorUpdaterPlugin.this.implementation.downloadBackground(
|
|
1265
1289
|
url,
|
|
1266
1290
|
latestVersionName,
|
|
1267
1291
|
sessionKey,
|
|
1268
|
-
checksum
|
|
1292
|
+
checksum,
|
|
1293
|
+
signature
|
|
1269
1294
|
);
|
|
1270
1295
|
} catch (final Exception e) {
|
|
1271
1296
|
Log.e(CapacitorUpdater.TAG, "error downloading file", e);
|
|
@@ -12,15 +12,18 @@ package ee.forgr.capacitor_updater;
|
|
|
12
12
|
* references: http://stackoverflow.com/questions/12471999/rsa-encryption-decryption-in-android
|
|
13
13
|
*/
|
|
14
14
|
import android.util.Base64;
|
|
15
|
+
import android.util.Log;
|
|
15
16
|
import java.security.GeneralSecurityException;
|
|
16
17
|
import java.security.InvalidAlgorithmParameterException;
|
|
17
18
|
import java.security.InvalidKeyException;
|
|
18
19
|
import java.security.KeyFactory;
|
|
19
20
|
import java.security.NoSuchAlgorithmException;
|
|
20
21
|
import java.security.PrivateKey;
|
|
22
|
+
import java.security.PublicKey;
|
|
21
23
|
import java.security.spec.InvalidKeySpecException;
|
|
22
24
|
import java.security.spec.MGF1ParameterSpec;
|
|
23
25
|
import java.security.spec.PKCS8EncodedKeySpec;
|
|
26
|
+
import java.security.spec.X509EncodedKeySpec;
|
|
24
27
|
import javax.crypto.BadPaddingException;
|
|
25
28
|
import javax.crypto.Cipher;
|
|
26
29
|
import javax.crypto.IllegalBlockSizeException;
|
|
@@ -142,4 +145,21 @@ public class CryptoCipher {
|
|
|
142
145
|
// extract the private key
|
|
143
146
|
return readPkcs1PrivateKey(pkcs1EncodedBytes);
|
|
144
147
|
}
|
|
148
|
+
|
|
149
|
+
public static PublicKey stringToPublicKey(String publicKey) {
|
|
150
|
+
byte[] encoded = Base64.decode(publicKey, Base64.DEFAULT);
|
|
151
|
+
|
|
152
|
+
KeyFactory keyFactory = null;
|
|
153
|
+
try {
|
|
154
|
+
keyFactory = KeyFactory.getInstance("RSA");
|
|
155
|
+
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
|
|
156
|
+
return keyFactory.generatePublic(keySpec);
|
|
157
|
+
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
|
158
|
+
Log.i(
|
|
159
|
+
"Capacitor-updater",
|
|
160
|
+
"stringToPublicKey fail\nError:\n" + e.toString()
|
|
161
|
+
);
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
145
165
|
}
|
|
@@ -27,6 +27,7 @@ public class DownloadService extends IntentService {
|
|
|
27
27
|
public static final String VERSION = "version";
|
|
28
28
|
public static final String SESSIONKEY = "sessionkey";
|
|
29
29
|
public static final String CHECKSUM = "checksum";
|
|
30
|
+
public static final String SIGNATURE = "signature";
|
|
30
31
|
public static final String NOTIFICATION = "service receiver";
|
|
31
32
|
public static final String PERCENTDOWNLOAD = "percent receiver";
|
|
32
33
|
|
|
@@ -53,6 +54,7 @@ public class DownloadService extends IntentService {
|
|
|
53
54
|
String version = intent.getStringExtra(VERSION);
|
|
54
55
|
String sessionKey = intent.getStringExtra(SESSIONKEY);
|
|
55
56
|
String checksum = intent.getStringExtra(CHECKSUM);
|
|
57
|
+
String signature = intent.getStringExtra(SIGNATURE);
|
|
56
58
|
|
|
57
59
|
try {
|
|
58
60
|
final URL u = new URL(url);
|
|
@@ -84,12 +86,28 @@ public class DownloadService extends IntentService {
|
|
|
84
86
|
}
|
|
85
87
|
bytesRead += length;
|
|
86
88
|
}
|
|
87
|
-
publishResults(
|
|
89
|
+
publishResults(
|
|
90
|
+
dest,
|
|
91
|
+
id,
|
|
92
|
+
version,
|
|
93
|
+
checksum,
|
|
94
|
+
sessionKey,
|
|
95
|
+
signature,
|
|
96
|
+
""
|
|
97
|
+
);
|
|
88
98
|
}
|
|
89
99
|
}
|
|
90
100
|
} catch (OutOfMemoryError e) {
|
|
91
101
|
e.printStackTrace();
|
|
92
|
-
publishResults(
|
|
102
|
+
publishResults(
|
|
103
|
+
"",
|
|
104
|
+
id,
|
|
105
|
+
version,
|
|
106
|
+
checksum,
|
|
107
|
+
sessionKey,
|
|
108
|
+
signature,
|
|
109
|
+
"low_mem_fail"
|
|
110
|
+
);
|
|
93
111
|
} catch (Exception e) {
|
|
94
112
|
e.printStackTrace();
|
|
95
113
|
publishResults(
|
|
@@ -98,6 +116,7 @@ public class DownloadService extends IntentService {
|
|
|
98
116
|
version,
|
|
99
117
|
checksum,
|
|
100
118
|
sessionKey,
|
|
119
|
+
signature,
|
|
101
120
|
e.getLocalizedMessage()
|
|
102
121
|
);
|
|
103
122
|
}
|
|
@@ -117,6 +136,7 @@ public class DownloadService extends IntentService {
|
|
|
117
136
|
String version,
|
|
118
137
|
String checksum,
|
|
119
138
|
String sessionKey,
|
|
139
|
+
String signature,
|
|
120
140
|
String error
|
|
121
141
|
) {
|
|
122
142
|
Intent intent = new Intent(NOTIFICATION);
|
|
@@ -132,6 +152,7 @@ public class DownloadService extends IntentService {
|
|
|
132
152
|
intent.putExtra(SESSIONKEY, sessionKey);
|
|
133
153
|
intent.putExtra(CHECKSUM, checksum);
|
|
134
154
|
intent.putExtra(ERROR, error);
|
|
155
|
+
intent.putExtra(SIGNATURE, signature);
|
|
135
156
|
sendBroadcast(intent);
|
|
136
157
|
}
|
|
137
158
|
}
|
package/dist/docs.json
CHANGED
|
@@ -1091,6 +1091,22 @@
|
|
|
1091
1091
|
"docs": "The checksum for the update",
|
|
1092
1092
|
"complexTypes": [],
|
|
1093
1093
|
"type": "string | undefined"
|
|
1094
|
+
},
|
|
1095
|
+
{
|
|
1096
|
+
"name": "signature",
|
|
1097
|
+
"tags": [
|
|
1098
|
+
{
|
|
1099
|
+
"text": "6.1.0",
|
|
1100
|
+
"name": "since"
|
|
1101
|
+
},
|
|
1102
|
+
{
|
|
1103
|
+
"text": "undefined",
|
|
1104
|
+
"name": "default"
|
|
1105
|
+
}
|
|
1106
|
+
],
|
|
1107
|
+
"docs": "The signature of the update. Can be generated using capgo CLI",
|
|
1108
|
+
"complexTypes": [],
|
|
1109
|
+
"type": "string | undefined"
|
|
1094
1110
|
}
|
|
1095
1111
|
]
|
|
1096
1112
|
},
|
|
@@ -2047,6 +2063,22 @@
|
|
|
2047
2063
|
"docs": "Set the default channel for the app in the config.",
|
|
2048
2064
|
"complexTypes": [],
|
|
2049
2065
|
"type": "string | undefined"
|
|
2066
|
+
},
|
|
2067
|
+
{
|
|
2068
|
+
"name": "signKey",
|
|
2069
|
+
"tags": [
|
|
2070
|
+
{
|
|
2071
|
+
"text": "undefined",
|
|
2072
|
+
"name": "default"
|
|
2073
|
+
},
|
|
2074
|
+
{
|
|
2075
|
+
"text": "6.1.0",
|
|
2076
|
+
"name": "since"
|
|
2077
|
+
}
|
|
2078
|
+
],
|
|
2079
|
+
"docs": "Public key used for bundle signing.",
|
|
2080
|
+
"complexTypes": [],
|
|
2081
|
+
"type": "string | undefined"
|
|
2050
2082
|
}
|
|
2051
2083
|
],
|
|
2052
2084
|
"docs": "CapacitorUpdater can be configured with these options:"
|
|
@@ -170,6 +170,15 @@ declare module "@capacitor/cli" {
|
|
|
170
170
|
* @since 5.5.0
|
|
171
171
|
*/
|
|
172
172
|
defaultChannel?: string;
|
|
173
|
+
/**
|
|
174
|
+
* Public key used for bundle signing.
|
|
175
|
+
*
|
|
176
|
+
*
|
|
177
|
+
*
|
|
178
|
+
* @default undefined
|
|
179
|
+
* @since 6.1.0
|
|
180
|
+
*/
|
|
181
|
+
signKey?: string;
|
|
173
182
|
};
|
|
174
183
|
}
|
|
175
184
|
}
|
|
@@ -598,6 +607,12 @@ export interface DownloadOptions {
|
|
|
598
607
|
* @default undefined
|
|
599
608
|
*/
|
|
600
609
|
checksum?: string;
|
|
610
|
+
/**
|
|
611
|
+
* The signature of the update. Can be generated using capgo CLI
|
|
612
|
+
* @since 6.1.0
|
|
613
|
+
* @default undefined
|
|
614
|
+
*/
|
|
615
|
+
signature?: string;
|
|
601
616
|
}
|
|
602
617
|
export interface BundleId {
|
|
603
618
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAAA;;;;GAIG","sourcesContent":["/*\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/.\n */\n\n/// <reference types=\"@capacitor/cli\" />\n\nimport type { PluginListenerHandle } from \"@capacitor/core\";\n\ndeclare module \"@capacitor/cli\" {\n export interface PluginsConfig {\n /**\n * CapacitorUpdater can be configured with these options:\n */\n CapacitorUpdater?: {\n /**\n * Configure the number of milliseconds the native plugin should wait before considering an update 'failed'.\n *\n * Only available for Android and iOS.\n *\n * @default 10000 // (10 seconds)\n * @example 1000 // (1 second)\n */\n appReadyTimeout?: number;\n /**\n * Configure the number of milliseconds the native plugin should wait before considering API timeout.\n *\n * Only available for Android and iOS.\n *\n * @default 20 // (20 second)\n * @example 10 // (10 second)\n */\n responseTimeout?: number;\n /**\n * Configure whether the plugin should use automatically delete failed bundles.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n autoDeleteFailed?: boolean;\n\n /**\n * Configure whether the plugin should use automatically delete previous bundles after a successful update.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n autoDeletePrevious?: boolean;\n\n /**\n * Configure whether the plugin should use Auto Update via an update server.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n autoUpdate?: boolean;\n\n /**\n * Automatically delete previous downloaded bundles when a newer native app bundle is installed to the device.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n resetWhenUpdate?: boolean;\n\n /**\n * Configure the URL / endpoint to which update checks are sent.\n *\n * Only available for Android and iOS.\n *\n * @default https://api.capgo.app/updates\n * @example https://example.com/api/auto_update\n */\n updateUrl?: string;\n\n /**\n * Configure the URL / endpoint to which update statistics are sent.\n *\n * Only available for Android and iOS. Set to \"\" to disable stats reporting.\n *\n * @default https://api.capgo.app/stats\n * @example https://example.com/api/stats\n */\n statsUrl?: string;\n /**\n * Configure the private key for end to end live update encryption.\n *\n * Only available for Android and iOS.\n *\n * @default undefined\n */\n privateKey?: string;\n\n /**\n * Configure the current version of the app. This will be used for the first update request.\n * If not set, the plugin will get the version from the native code.\n *\n * Only available for Android and iOS.\n *\n * @default undefined\n * @since 4.17.48\n */\n version?: string;\n /**\n * Make the plugin direct install the update when the app what just updated/installed. Only for autoUpdate mode.\n *\n * Only available for Android and iOS.\n *\n * @default undefined\n * @since 5.1.0\n */\n directUpdate?: boolean;\n\n /**\n * Configure the delay period for period update check. the unit is in seconds.\n *\n * Only available for Android and iOS.\n * Cannot be less than 600 seconds (10 minutes).\n *\n * @default 600 // (10 minutes)\n */\n periodCheckDelay?: number;\n\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localS3?: boolean;\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localHost?: string;\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localWebHost?: string;\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localSupa?: string;\n /**\n * Configure the CLI to use a local server for testing.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localSupaAnon?: string;\n\n /**\n * Allow the plugin to modify the updateUrl, statsUrl and channelUrl dynamically from the JavaScript side.\n *\n *\n * @default false\n * @since 5.4.0\n */\n allowModifyUrl?: boolean;\n\n /**\n * Set the default channel for the app in the config.\n *\n *\n *\n * @default undefined\n * @since 5.5.0\n */\n defaultChannel?: string;\n };\n }\n}\n\nexport interface CapacitorUpdaterPlugin {\n /**\n * Notify Capacitor Updater that the current bundle is working (a rollback will occur if this method is not called on every app launch)\n * By default this method should be called in the first 10 sec after app launch, otherwise a rollback will occur.\n * Change this behaviour with {@link appReadyTimeout}\n *\n * @returns {Promise<AppReadyResult>} an Promise resolved directly\n * @throws {Error}\n */\n notifyAppReady(): Promise<AppReadyResult>;\n\n /**\n * Set the updateUrl for the app, this will be used to check for updates.\n *\n * @param options contains the URL to use for checking for updates.\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 5.4.0\n */\n setUpdateUrl(options: UpdateUrl): Promise<void>;\n\n /**\n * Set the statsUrl for the app, this will be used to send statistics. Passing an empty string will disable statistics gathering.\n *\n * @param options contains the URL to use for sending statistics.\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 5.4.0\n */\n setStatsUrl(options: StatsUrl): Promise<void>;\n\n /**\n * Set the channelUrl for the app, this will be used to set the channel.\n *\n * @param options contains the URL to use for setting the channel.\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 5.4.0\n */\n setChannelUrl(options: ChannelUrl): Promise<void>;\n\n /**\n * Download a new bundle from the provided URL, it should be a zip file, with files inside or with a unique id inside with all your files\n *\n * @example const bundle = await CapacitorUpdater.download({ url: `https://example.com/versions/${version}/dist.zip`, version });\n * @returns {Promise<BundleInfo>} The {@link BundleInfo} for the specified bundle.\n * @param options The {@link DownloadOptions} for downloading a new bundle zip.\n */\n download(options: DownloadOptions): Promise<BundleInfo>;\n\n /**\n * Set the next bundle to be used when the app is reloaded.\n *\n * @param options Contains the ID of the next Bundle to set on next app launch. {@link BundleInfo.id}\n * @returns {Promise<BundleInfo>} The {@link BundleInfo} for the specified bundle id.\n * @throws {Error} When there is no index.html file inside the bundle folder.\n */\n next(options: BundleId): Promise<BundleInfo>;\n\n /**\n * Set the current bundle and immediately reloads the app.\n *\n * @param options A {@link BundleId} object containing the new bundle id to set as current.\n * @returns {Promise<void>}\n * @throws {Error} When there are is no index.html file inside the bundle folder.\n */\n set(options: BundleId): Promise<void>;\n\n /**\n * Deletes the specified bundle from the native app storage. Use with {@link list} to get the stored Bundle IDs.\n *\n * @param options A {@link BundleId} object containing the ID of a bundle to delete (note, this is the bundle id, NOT the version name)\n * @returns {Promise<void>} When the bundle is deleted\n * @throws {Error}\n */\n delete(options: BundleId): Promise<void>;\n\n /**\n * Get all locally downloaded bundles in your app\n *\n * @returns {Promise<BundleListResult>} A Promise containing the {@link BundleListResult.bundles}\n * @throws {Error}\n */\n list(): Promise<BundleListResult>;\n\n /**\n * Reset the app to the `builtin` bundle (the one sent to Apple App Store / Google Play Store ) or the last successfully loaded bundle.\n *\n * @param options Containing {@link ResetOptions.toLastSuccessful}, `true` resets to the builtin bundle and `false` will reset to the last successfully loaded bundle.\n * @returns {Promise<void>}\n * @throws {Error}\n */\n reset(options?: ResetOptions): Promise<void>;\n\n /**\n * Get the current bundle, if none are set it returns `builtin`. currentNative is the original bundle installed on the device\n *\n * @returns {Promise<CurrentBundleResult>} A Promise evaluating to the {@link CurrentBundleResult}\n * @throws {Error}\n */\n current(): Promise<CurrentBundleResult>;\n\n /**\n * Reload the view\n *\n * @returns {Promise<void>} A Promise which is resolved when the view is reloaded\n * @throws {Error}\n */\n reload(): Promise<void>;\n\n /**\n * Sets a {@link DelayCondition} array containing conditions that the Plugin will use to delay the update.\n * After all conditions are met, the update process will run start again as usual, so update will be installed after a backgrounding or killing the app.\n * For the `date` kind, the value should be an iso8601 date string.\n * For the `background` kind, the value should be a number in milliseconds.\n * For the `nativeVersion` kind, the value should be the version number.\n * For the `kill` kind, the value is not used.\n * The function has unconsistent behavior the option kill do trigger the update after the first kill and not after the next background like other options. This will be fixed in a future major release.\n *\n * @example\n * // Delay the update after the user kills the app or after a background of 300000 ms (5 minutes)\n * await CapacitorUpdater.setMultiDelay({ delayConditions: [{ kind: 'kill' }, { kind: 'background', value: '300000' }] })\n * @example\n * // Delay the update after the specific iso8601 date is expired\n * await CapacitorUpdater.setMultiDelay({ delayConditions: [{ kind: 'date', value: '2022-09-14T06:14:11.920Z' }] })\n * @example\n * // Delay the update after the first background (default behaviour without setting delay)\n * await CapacitorUpdater.setMultiDelay({ delayConditions: [{ kind: 'background' }] })\n * @param options Containing the {@link MultiDelayConditions} array of conditions to set\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 4.3.0\n */\n setMultiDelay(options: MultiDelayConditions): Promise<void>;\n\n /**\n * Cancels a {@link DelayCondition} to process an update immediately.\n *\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 4.0.0\n */\n cancelDelay(): Promise<void>;\n\n /**\n * Get Latest bundle available from update Url\n *\n * @returns {Promise<LatestVersion>} A Promise resolved when url is loaded\n * @throws {Error}\n * @since 4.0.0\n */\n getLatest(): Promise<LatestVersion>;\n\n /**\n * Sets the channel for this device. The channel has to allow for self assignment for this to work.\n * Do not use this method to set the channel at boot when `autoUpdate` is enabled in the {@link PluginsConfig}.\n * This method is to set the channel after the app is ready.\n *\n * @param options Is the {@link SetChannelOptions} channel to set\n * @returns {Promise<ChannelRes>} A Promise which is resolved when the new channel is set\n * @throws {Error}\n * @since 4.7.0\n */\n setChannel(options: SetChannelOptions): Promise<ChannelRes>;\n\n /**\n * Unset the channel for this device. The device will then return to the default channel\n *\n * @returns {Promise<ChannelRes>} A Promise resolved when channel is set\n * @throws {Error}\n * @since 4.7.0\n */\n unsetChannel(options: UnsetChannelOptions): Promise<void>;\n\n /**\n * Get the channel for this device\n *\n * @returns {Promise<ChannelRes>} A Promise that resolves with the channel info\n * @throws {Error}\n * @since 4.8.0\n */\n getChannel(): Promise<GetChannelRes>;\n\n /**\n * Set a custom ID for this device\n *\n * @param options is the {@link SetCustomIdOptions} customId to set\n * @returns {Promise<void>} an Promise resolved instantly\n * @throws {Error}\n * @since 4.9.0\n */\n setCustomId(options: SetCustomIdOptions): Promise<void>;\n\n /**\n * Get the native app version or the builtin version if set in config\n *\n * @returns {Promise<BuiltinVersion>} A Promise with version for this device\n * @since 5.2.0\n */\n getBuiltinVersion(): Promise<BuiltinVersion>;\n\n /**\n * Get unique ID used to identify device (sent to auto update server)\n *\n * @returns {Promise<DeviceId>} A Promise with id for this device\n * @throws {Error}\n */\n getDeviceId(): Promise<DeviceId>;\n\n /**\n * Get the native Capacitor Updater plugin version (sent to auto update server)\n *\n * @returns {Promise<PluginVersion>} A Promise with Plugin version\n * @throws {Error}\n */\n getPluginVersion(): Promise<PluginVersion>;\n\n /**\n * Get the state of auto update config.\n *\n * @returns {Promise<AutoUpdateEnabled>} The status for auto update. Evaluates to `false` in manual mode.\n * @throws {Error}\n */\n isAutoUpdateEnabled(): Promise<AutoUpdateEnabled>;\n\n /**\n * Remove all listeners for this plugin.\n *\n * @since 1.0.0\n */\n removeAllListeners(): Promise<void>;\n\n /**\n * Listen for bundle download event in the App. Fires once a download has started, during downloading and when finished.\n *\n * @since 2.0.11\n */\n addListener(\n eventName: \"download\",\n listenerFunc: (state: DownloadEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for no need to update event, useful when you want force check every time the app is launched\n *\n * @since 4.0.0\n */\n addListener(\n eventName: \"noNeedUpdate\",\n listenerFunc: (state: NoNeedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for available update event, useful when you want to force check every time the app is launched\n *\n * @since 4.0.0\n */\n addListener(\n eventName: \"updateAvailable\",\n listenerFunc: (state: UpdateAvailableEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for downloadComplete events.\n *\n * @since 4.0.0\n */\n addListener(\n eventName: \"downloadComplete\",\n listenerFunc: (state: DownloadCompleteEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for Major update event in the App, let you know when major update is blocked by setting disableAutoUpdateBreaking\n *\n * @since 2.3.0\n */\n addListener(\n eventName: \"majorAvailable\",\n listenerFunc: (state: MajorAvailableEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for update fail event in the App, let you know when update has fail to install at next app start\n *\n * @since 2.3.0\n */\n addListener(\n eventName: \"updateFailed\",\n listenerFunc: (state: UpdateFailedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for download fail event in the App, let you know when a bundle download has failed\n *\n * @since 4.0.0\n */\n addListener(\n eventName: \"downloadFailed\",\n listenerFunc: (state: DownloadFailedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for reload event in the App, let you know when reload has happened\n *\n * @since 4.3.0\n */\n addListener(\n eventName: \"appReloaded\",\n listenerFunc: () => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for app ready event in the App, let you know when app is ready to use\n *\n * @since 5.1.0\n */\n addListener(\n eventName: \"appReady\",\n listenerFunc: (state: AppReadyEvent) => void,\n ): Promise<PluginListenerHandle>;\n}\n\nexport type BundleStatus = \"success\" | \"error\" | \"pending\" | \"downloading\";\n\nexport type DelayUntilNext = \"background\" | \"kill\" | \"nativeVersion\" | \"date\";\n\nexport interface NoNeedEvent {\n /**\n * Current status of download, between 0 and 100.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface UpdateAvailableEvent {\n /**\n * Current status of download, between 0 and 100.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface ChannelRes {\n /**\n * Current status of set channel\n *\n * @since 4.7.0\n */\n status: string;\n error?: any;\n message?: any;\n}\n\nexport interface GetChannelRes {\n /**\n * Current status of get channel\n *\n * @since 4.8.0\n */\n channel?: string;\n error?: any;\n message?: any;\n status?: string;\n allowSet?: boolean;\n}\n\nexport interface DownloadEvent {\n /**\n * Current status of download, between 0 and 100.\n *\n * @since 4.0.0\n */\n percent: number;\n bundle: BundleInfo;\n}\n\nexport interface MajorAvailableEvent {\n /**\n * Emit when a new major bundle is available.\n *\n * @since 4.0.0\n */\n version: string;\n}\n\nexport interface DownloadFailedEvent {\n /**\n * Emit when a download fail.\n *\n * @since 4.0.0\n */\n version: string;\n}\n\nexport interface DownloadCompleteEvent {\n /**\n * Emit when a new update is available.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface UpdateFailedEvent {\n /**\n * Emit when a update failed to install.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface AppReadyEvent {\n /**\n * Emitted when the app is ready to use.\n *\n * @since 5.2.0\n */\n bundle: BundleInfo;\n status: string;\n}\n\nexport interface LatestVersion {\n /**\n * Result of getLatest method\n *\n * @since 4.0.0\n */\n version: string;\n major?: boolean;\n message?: string;\n sessionKey?: string;\n error?: string;\n old?: string;\n url?: string;\n}\n\nexport interface BundleInfo {\n id: string;\n version: string;\n downloaded: string;\n checksum: string;\n status: BundleStatus;\n}\n\nexport interface SetChannelOptions {\n channel: string;\n triggerAutoUpdate?: boolean;\n}\n\nexport interface UnsetChannelOptions {\n triggerAutoUpdate?: boolean;\n}\n\nexport interface SetCustomIdOptions {\n customId: string;\n}\n\nexport interface DelayCondition {\n /**\n * Set up delay conditions in setMultiDelay\n * @param value is useless for @param kind \"kill\", optional for \"background\" (default value: \"0\") and required for \"nativeVersion\" and \"date\"\n */\n kind: DelayUntilNext;\n value?: string;\n}\n\nexport interface AppReadyResult {\n bundle: BundleInfo;\n}\n\nexport interface UpdateUrl {\n url: string;\n}\n\nexport interface StatsUrl {\n url: string;\n}\n\nexport interface ChannelUrl {\n url: string;\n}\n\nexport interface DownloadOptions {\n /**\n * The URL of the bundle zip file (e.g: dist.zip) to be downloaded. (This can be any URL. E.g: Amazon S3, a GitHub tag, any other place you've hosted your bundle.)\n */\n url: string;\n /**\n * The version code/name of this bundle/version\n */\n version: string;\n /**\n * The session key for the update\n * @since 4.0.0\n * @default undefined\n */\n sessionKey?: string;\n /**\n * The checksum for the update\n * @since 4.0.0\n * @default undefined\n */\n checksum?: string;\n}\n\nexport interface BundleId {\n id: string;\n}\n\nexport interface BundleListResult {\n bundles: BundleInfo[];\n}\n\nexport interface ResetOptions {\n toLastSuccessful: boolean;\n}\n\nexport interface CurrentBundleResult {\n bundle: BundleInfo;\n native: string;\n}\n\nexport interface MultiDelayConditions {\n delayConditions: DelayCondition[];\n}\n\nexport interface BuiltinVersion {\n version: string;\n}\n\nexport interface DeviceId {\n deviceId: string;\n}\n\nexport interface PluginVersion {\n version: string;\n}\n\nexport interface AutoUpdateEnabled {\n enabled: boolean;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAAA;;;;GAIG","sourcesContent":["/*\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/.\n */\n\n/// <reference types=\"@capacitor/cli\" />\n\nimport type { PluginListenerHandle } from \"@capacitor/core\";\n\ndeclare module \"@capacitor/cli\" {\n export interface PluginsConfig {\n /**\n * CapacitorUpdater can be configured with these options:\n */\n CapacitorUpdater?: {\n /**\n * Configure the number of milliseconds the native plugin should wait before considering an update 'failed'.\n *\n * Only available for Android and iOS.\n *\n * @default 10000 // (10 seconds)\n * @example 1000 // (1 second)\n */\n appReadyTimeout?: number;\n /**\n * Configure the number of milliseconds the native plugin should wait before considering API timeout.\n *\n * Only available for Android and iOS.\n *\n * @default 20 // (20 second)\n * @example 10 // (10 second)\n */\n responseTimeout?: number;\n /**\n * Configure whether the plugin should use automatically delete failed bundles.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n autoDeleteFailed?: boolean;\n\n /**\n * Configure whether the plugin should use automatically delete previous bundles after a successful update.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n autoDeletePrevious?: boolean;\n\n /**\n * Configure whether the plugin should use Auto Update via an update server.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n autoUpdate?: boolean;\n\n /**\n * Automatically delete previous downloaded bundles when a newer native app bundle is installed to the device.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n resetWhenUpdate?: boolean;\n\n /**\n * Configure the URL / endpoint to which update checks are sent.\n *\n * Only available for Android and iOS.\n *\n * @default https://api.capgo.app/updates\n * @example https://example.com/api/auto_update\n */\n updateUrl?: string;\n\n /**\n * Configure the URL / endpoint to which update statistics are sent.\n *\n * Only available for Android and iOS. Set to \"\" to disable stats reporting.\n *\n * @default https://api.capgo.app/stats\n * @example https://example.com/api/stats\n */\n statsUrl?: string;\n /**\n * Configure the private key for end to end live update encryption.\n *\n * Only available for Android and iOS.\n *\n * @default undefined\n */\n privateKey?: string;\n\n /**\n * Configure the current version of the app. This will be used for the first update request.\n * If not set, the plugin will get the version from the native code.\n *\n * Only available for Android and iOS.\n *\n * @default undefined\n * @since 4.17.48\n */\n version?: string;\n /**\n * Make the plugin direct install the update when the app what just updated/installed. Only for autoUpdate mode.\n *\n * Only available for Android and iOS.\n *\n * @default undefined\n * @since 5.1.0\n */\n directUpdate?: boolean;\n\n /**\n * Configure the delay period for period update check. the unit is in seconds.\n *\n * Only available for Android and iOS.\n * Cannot be less than 600 seconds (10 minutes).\n *\n * @default 600 // (10 minutes)\n */\n periodCheckDelay?: number;\n\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localS3?: boolean;\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localHost?: string;\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localWebHost?: string;\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localSupa?: string;\n /**\n * Configure the CLI to use a local server for testing.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localSupaAnon?: string;\n\n /**\n * Allow the plugin to modify the updateUrl, statsUrl and channelUrl dynamically from the JavaScript side.\n *\n *\n * @default false\n * @since 5.4.0\n */\n allowModifyUrl?: boolean;\n\n /**\n * Set the default channel for the app in the config.\n *\n *\n *\n * @default undefined\n * @since 5.5.0\n */\n defaultChannel?: string;\n /**\n * Public key used for bundle signing.\n *\n *\n *\n * @default undefined\n * @since 6.1.0\n */\n signKey?: string;\n };\n }\n}\n\nexport interface CapacitorUpdaterPlugin {\n /**\n * Notify Capacitor Updater that the current bundle is working (a rollback will occur if this method is not called on every app launch)\n * By default this method should be called in the first 10 sec after app launch, otherwise a rollback will occur.\n * Change this behaviour with {@link appReadyTimeout}\n *\n * @returns {Promise<AppReadyResult>} an Promise resolved directly\n * @throws {Error}\n */\n notifyAppReady(): Promise<AppReadyResult>;\n\n /**\n * Set the updateUrl for the app, this will be used to check for updates.\n *\n * @param options contains the URL to use for checking for updates.\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 5.4.0\n */\n setUpdateUrl(options: UpdateUrl): Promise<void>;\n\n /**\n * Set the statsUrl for the app, this will be used to send statistics. Passing an empty string will disable statistics gathering.\n *\n * @param options contains the URL to use for sending statistics.\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 5.4.0\n */\n setStatsUrl(options: StatsUrl): Promise<void>;\n\n /**\n * Set the channelUrl for the app, this will be used to set the channel.\n *\n * @param options contains the URL to use for setting the channel.\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 5.4.0\n */\n setChannelUrl(options: ChannelUrl): Promise<void>;\n\n /**\n * Download a new bundle from the provided URL, it should be a zip file, with files inside or with a unique id inside with all your files\n *\n * @example const bundle = await CapacitorUpdater.download({ url: `https://example.com/versions/${version}/dist.zip`, version });\n * @returns {Promise<BundleInfo>} The {@link BundleInfo} for the specified bundle.\n * @param options The {@link DownloadOptions} for downloading a new bundle zip.\n */\n download(options: DownloadOptions): Promise<BundleInfo>;\n\n /**\n * Set the next bundle to be used when the app is reloaded.\n *\n * @param options Contains the ID of the next Bundle to set on next app launch. {@link BundleInfo.id}\n * @returns {Promise<BundleInfo>} The {@link BundleInfo} for the specified bundle id.\n * @throws {Error} When there is no index.html file inside the bundle folder.\n */\n next(options: BundleId): Promise<BundleInfo>;\n\n /**\n * Set the current bundle and immediately reloads the app.\n *\n * @param options A {@link BundleId} object containing the new bundle id to set as current.\n * @returns {Promise<void>}\n * @throws {Error} When there are is no index.html file inside the bundle folder.\n */\n set(options: BundleId): Promise<void>;\n\n /**\n * Deletes the specified bundle from the native app storage. Use with {@link list} to get the stored Bundle IDs.\n *\n * @param options A {@link BundleId} object containing the ID of a bundle to delete (note, this is the bundle id, NOT the version name)\n * @returns {Promise<void>} When the bundle is deleted\n * @throws {Error}\n */\n delete(options: BundleId): Promise<void>;\n\n /**\n * Get all locally downloaded bundles in your app\n *\n * @returns {Promise<BundleListResult>} A Promise containing the {@link BundleListResult.bundles}\n * @throws {Error}\n */\n list(): Promise<BundleListResult>;\n\n /**\n * Reset the app to the `builtin` bundle (the one sent to Apple App Store / Google Play Store ) or the last successfully loaded bundle.\n *\n * @param options Containing {@link ResetOptions.toLastSuccessful}, `true` resets to the builtin bundle and `false` will reset to the last successfully loaded bundle.\n * @returns {Promise<void>}\n * @throws {Error}\n */\n reset(options?: ResetOptions): Promise<void>;\n\n /**\n * Get the current bundle, if none are set it returns `builtin`. currentNative is the original bundle installed on the device\n *\n * @returns {Promise<CurrentBundleResult>} A Promise evaluating to the {@link CurrentBundleResult}\n * @throws {Error}\n */\n current(): Promise<CurrentBundleResult>;\n\n /**\n * Reload the view\n *\n * @returns {Promise<void>} A Promise which is resolved when the view is reloaded\n * @throws {Error}\n */\n reload(): Promise<void>;\n\n /**\n * Sets a {@link DelayCondition} array containing conditions that the Plugin will use to delay the update.\n * After all conditions are met, the update process will run start again as usual, so update will be installed after a backgrounding or killing the app.\n * For the `date` kind, the value should be an iso8601 date string.\n * For the `background` kind, the value should be a number in milliseconds.\n * For the `nativeVersion` kind, the value should be the version number.\n * For the `kill` kind, the value is not used.\n * The function has unconsistent behavior the option kill do trigger the update after the first kill and not after the next background like other options. This will be fixed in a future major release.\n *\n * @example\n * // Delay the update after the user kills the app or after a background of 300000 ms (5 minutes)\n * await CapacitorUpdater.setMultiDelay({ delayConditions: [{ kind: 'kill' }, { kind: 'background', value: '300000' }] })\n * @example\n * // Delay the update after the specific iso8601 date is expired\n * await CapacitorUpdater.setMultiDelay({ delayConditions: [{ kind: 'date', value: '2022-09-14T06:14:11.920Z' }] })\n * @example\n * // Delay the update after the first background (default behaviour without setting delay)\n * await CapacitorUpdater.setMultiDelay({ delayConditions: [{ kind: 'background' }] })\n * @param options Containing the {@link MultiDelayConditions} array of conditions to set\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 4.3.0\n */\n setMultiDelay(options: MultiDelayConditions): Promise<void>;\n\n /**\n * Cancels a {@link DelayCondition} to process an update immediately.\n *\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 4.0.0\n */\n cancelDelay(): Promise<void>;\n\n /**\n * Get Latest bundle available from update Url\n *\n * @returns {Promise<LatestVersion>} A Promise resolved when url is loaded\n * @throws {Error}\n * @since 4.0.0\n */\n getLatest(): Promise<LatestVersion>;\n\n /**\n * Sets the channel for this device. The channel has to allow for self assignment for this to work.\n * Do not use this method to set the channel at boot when `autoUpdate` is enabled in the {@link PluginsConfig}.\n * This method is to set the channel after the app is ready.\n *\n * @param options Is the {@link SetChannelOptions} channel to set\n * @returns {Promise<ChannelRes>} A Promise which is resolved when the new channel is set\n * @throws {Error}\n * @since 4.7.0\n */\n setChannel(options: SetChannelOptions): Promise<ChannelRes>;\n\n /**\n * Unset the channel for this device. The device will then return to the default channel\n *\n * @returns {Promise<ChannelRes>} A Promise resolved when channel is set\n * @throws {Error}\n * @since 4.7.0\n */\n unsetChannel(options: UnsetChannelOptions): Promise<void>;\n\n /**\n * Get the channel for this device\n *\n * @returns {Promise<ChannelRes>} A Promise that resolves with the channel info\n * @throws {Error}\n * @since 4.8.0\n */\n getChannel(): Promise<GetChannelRes>;\n\n /**\n * Set a custom ID for this device\n *\n * @param options is the {@link SetCustomIdOptions} customId to set\n * @returns {Promise<void>} an Promise resolved instantly\n * @throws {Error}\n * @since 4.9.0\n */\n setCustomId(options: SetCustomIdOptions): Promise<void>;\n\n /**\n * Get the native app version or the builtin version if set in config\n *\n * @returns {Promise<BuiltinVersion>} A Promise with version for this device\n * @since 5.2.0\n */\n getBuiltinVersion(): Promise<BuiltinVersion>;\n\n /**\n * Get unique ID used to identify device (sent to auto update server)\n *\n * @returns {Promise<DeviceId>} A Promise with id for this device\n * @throws {Error}\n */\n getDeviceId(): Promise<DeviceId>;\n\n /**\n * Get the native Capacitor Updater plugin version (sent to auto update server)\n *\n * @returns {Promise<PluginVersion>} A Promise with Plugin version\n * @throws {Error}\n */\n getPluginVersion(): Promise<PluginVersion>;\n\n /**\n * Get the state of auto update config.\n *\n * @returns {Promise<AutoUpdateEnabled>} The status for auto update. Evaluates to `false` in manual mode.\n * @throws {Error}\n */\n isAutoUpdateEnabled(): Promise<AutoUpdateEnabled>;\n\n /**\n * Remove all listeners for this plugin.\n *\n * @since 1.0.0\n */\n removeAllListeners(): Promise<void>;\n\n /**\n * Listen for bundle download event in the App. Fires once a download has started, during downloading and when finished.\n *\n * @since 2.0.11\n */\n addListener(\n eventName: \"download\",\n listenerFunc: (state: DownloadEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for no need to update event, useful when you want force check every time the app is launched\n *\n * @since 4.0.0\n */\n addListener(\n eventName: \"noNeedUpdate\",\n listenerFunc: (state: NoNeedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for available update event, useful when you want to force check every time the app is launched\n *\n * @since 4.0.0\n */\n addListener(\n eventName: \"updateAvailable\",\n listenerFunc: (state: UpdateAvailableEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for downloadComplete events.\n *\n * @since 4.0.0\n */\n addListener(\n eventName: \"downloadComplete\",\n listenerFunc: (state: DownloadCompleteEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for Major update event in the App, let you know when major update is blocked by setting disableAutoUpdateBreaking\n *\n * @since 2.3.0\n */\n addListener(\n eventName: \"majorAvailable\",\n listenerFunc: (state: MajorAvailableEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for update fail event in the App, let you know when update has fail to install at next app start\n *\n * @since 2.3.0\n */\n addListener(\n eventName: \"updateFailed\",\n listenerFunc: (state: UpdateFailedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for download fail event in the App, let you know when a bundle download has failed\n *\n * @since 4.0.0\n */\n addListener(\n eventName: \"downloadFailed\",\n listenerFunc: (state: DownloadFailedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for reload event in the App, let you know when reload has happened\n *\n * @since 4.3.0\n */\n addListener(\n eventName: \"appReloaded\",\n listenerFunc: () => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for app ready event in the App, let you know when app is ready to use\n *\n * @since 5.1.0\n */\n addListener(\n eventName: \"appReady\",\n listenerFunc: (state: AppReadyEvent) => void,\n ): Promise<PluginListenerHandle>;\n}\n\nexport type BundleStatus = \"success\" | \"error\" | \"pending\" | \"downloading\";\n\nexport type DelayUntilNext = \"background\" | \"kill\" | \"nativeVersion\" | \"date\";\n\nexport interface NoNeedEvent {\n /**\n * Current status of download, between 0 and 100.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface UpdateAvailableEvent {\n /**\n * Current status of download, between 0 and 100.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface ChannelRes {\n /**\n * Current status of set channel\n *\n * @since 4.7.0\n */\n status: string;\n error?: any;\n message?: any;\n}\n\nexport interface GetChannelRes {\n /**\n * Current status of get channel\n *\n * @since 4.8.0\n */\n channel?: string;\n error?: any;\n message?: any;\n status?: string;\n allowSet?: boolean;\n}\n\nexport interface DownloadEvent {\n /**\n * Current status of download, between 0 and 100.\n *\n * @since 4.0.0\n */\n percent: number;\n bundle: BundleInfo;\n}\n\nexport interface MajorAvailableEvent {\n /**\n * Emit when a new major bundle is available.\n *\n * @since 4.0.0\n */\n version: string;\n}\n\nexport interface DownloadFailedEvent {\n /**\n * Emit when a download fail.\n *\n * @since 4.0.0\n */\n version: string;\n}\n\nexport interface DownloadCompleteEvent {\n /**\n * Emit when a new update is available.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface UpdateFailedEvent {\n /**\n * Emit when a update failed to install.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface AppReadyEvent {\n /**\n * Emitted when the app is ready to use.\n *\n * @since 5.2.0\n */\n bundle: BundleInfo;\n status: string;\n}\n\nexport interface LatestVersion {\n /**\n * Result of getLatest method\n *\n * @since 4.0.0\n */\n version: string;\n major?: boolean;\n message?: string;\n sessionKey?: string;\n error?: string;\n old?: string;\n url?: string;\n}\n\nexport interface BundleInfo {\n id: string;\n version: string;\n downloaded: string;\n checksum: string;\n status: BundleStatus;\n}\n\nexport interface SetChannelOptions {\n channel: string;\n triggerAutoUpdate?: boolean;\n}\n\nexport interface UnsetChannelOptions {\n triggerAutoUpdate?: boolean;\n}\n\nexport interface SetCustomIdOptions {\n customId: string;\n}\n\nexport interface DelayCondition {\n /**\n * Set up delay conditions in setMultiDelay\n * @param value is useless for @param kind \"kill\", optional for \"background\" (default value: \"0\") and required for \"nativeVersion\" and \"date\"\n */\n kind: DelayUntilNext;\n value?: string;\n}\n\nexport interface AppReadyResult {\n bundle: BundleInfo;\n}\n\nexport interface UpdateUrl {\n url: string;\n}\n\nexport interface StatsUrl {\n url: string;\n}\n\nexport interface ChannelUrl {\n url: string;\n}\n\nexport interface DownloadOptions {\n /**\n * The URL of the bundle zip file (e.g: dist.zip) to be downloaded. (This can be any URL. E.g: Amazon S3, a GitHub tag, any other place you've hosted your bundle.)\n */\n url: string;\n /**\n * The version code/name of this bundle/version\n */\n version: string;\n /**\n * The session key for the update\n * @since 4.0.0\n * @default undefined\n */\n sessionKey?: string;\n /**\n * The checksum for the update\n * @since 4.0.0\n * @default undefined\n */\n checksum?: string;\n /**\n * The signature of the update. Can be generated using capgo CLI\n * @since 6.1.0\n * @default undefined\n */\n signature?: string;\n}\n\nexport interface BundleId {\n id: string;\n}\n\nexport interface BundleListResult {\n bundles: BundleInfo[];\n}\n\nexport interface ResetOptions {\n toLastSuccessful: boolean;\n}\n\nexport interface CurrentBundleResult {\n bundle: BundleInfo;\n native: string;\n}\n\nexport interface MultiDelayConditions {\n delayConditions: DelayCondition[];\n}\n\nexport interface BuiltinVersion {\n version: string;\n}\n\nexport interface DeviceId {\n deviceId: string;\n}\n\nexport interface PluginVersion {\n version: string;\n}\n\nexport interface AutoUpdateEnabled {\n enabled: boolean;\n}\n"]}
|
|
@@ -8,7 +8,13 @@ import Foundation
|
|
|
8
8
|
import SSZipArchive
|
|
9
9
|
import Alamofire
|
|
10
10
|
import zlib
|
|
11
|
+
import SwiftyRSA
|
|
11
12
|
|
|
13
|
+
extension Collection {
|
|
14
|
+
subscript(safe index: Index) -> Element? {
|
|
15
|
+
return indices.contains(index) ? self[index] : nil
|
|
16
|
+
}
|
|
17
|
+
}
|
|
12
18
|
extension URL {
|
|
13
19
|
var isDirectory: Bool {
|
|
14
20
|
(try? resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == true
|
|
@@ -91,6 +97,7 @@ struct AppVersionDec: Decodable {
|
|
|
91
97
|
let session_key: String?
|
|
92
98
|
let major: Bool?
|
|
93
99
|
let data: [String: String]?
|
|
100
|
+
let signature: String?
|
|
94
101
|
}
|
|
95
102
|
public class AppVersion: NSObject {
|
|
96
103
|
var version: String = ""
|
|
@@ -101,6 +108,7 @@ public class AppVersion: NSObject {
|
|
|
101
108
|
var sessionKey: String?
|
|
102
109
|
var major: Bool?
|
|
103
110
|
var data: [String: String]?
|
|
111
|
+
var signature: String?
|
|
104
112
|
}
|
|
105
113
|
|
|
106
114
|
extension AppVersion {
|
|
@@ -169,6 +177,8 @@ enum CustomError: Error {
|
|
|
169
177
|
case cannotUnflat
|
|
170
178
|
case cannotCreateDirectory
|
|
171
179
|
case cannotDeleteDirectory
|
|
180
|
+
case signatureNotProvided
|
|
181
|
+
case invalidSignature
|
|
172
182
|
|
|
173
183
|
// Throw in all other cases
|
|
174
184
|
case unexpected(code: Int)
|
|
@@ -177,6 +187,16 @@ enum CustomError: Error {
|
|
|
177
187
|
extension CustomError: LocalizedError {
|
|
178
188
|
public var errorDescription: String? {
|
|
179
189
|
switch self {
|
|
190
|
+
case .signatureNotProvided:
|
|
191
|
+
return NSLocalizedString(
|
|
192
|
+
"Signature was required but none was provided",
|
|
193
|
+
comment: "Signature not provided"
|
|
194
|
+
)
|
|
195
|
+
case .invalidSignature:
|
|
196
|
+
return NSLocalizedString(
|
|
197
|
+
"Signature is not valid, cannot accept update",
|
|
198
|
+
comment: "Invalid signature"
|
|
199
|
+
)
|
|
180
200
|
case .cannotUnzip:
|
|
181
201
|
return NSLocalizedString(
|
|
182
202
|
"The file cannot be unzip",
|
|
@@ -240,6 +260,7 @@ extension CustomError: LocalizedError {
|
|
|
240
260
|
public var appId: String = ""
|
|
241
261
|
public var deviceID = UIDevice.current.identifierForVendor?.uuidString ?? ""
|
|
242
262
|
public var privateKey: String = ""
|
|
263
|
+
public var signKey: PublicKey?
|
|
243
264
|
|
|
244
265
|
public var notifyDownload: (String, Int) -> Void = { _, _ in }
|
|
245
266
|
|
|
@@ -363,6 +384,32 @@ extension CustomError: LocalizedError {
|
|
|
363
384
|
return ""
|
|
364
385
|
}
|
|
365
386
|
}
|
|
387
|
+
|
|
388
|
+
private func verifyBundleSignature(version: String, filePath: URL, signature: String?) throws -> Bool {
|
|
389
|
+
if (self.signKey == nil) {
|
|
390
|
+
print("\(self.TAG) Signing not configured")
|
|
391
|
+
return true
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (self.signKey != nil && (signature == nil || signature?.isEmpty == true)) {
|
|
395
|
+
print("\(self.TAG) Signature required but none provided")
|
|
396
|
+
self.sendStats(action: "signature_not_provided", versionName: version)
|
|
397
|
+
throw CustomError.signatureNotProvided
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
do {
|
|
401
|
+
// let publicKey = try PublicKey(pemEncoded: self.signKey)
|
|
402
|
+
let signatureObj = try Signature(base64Encoded: signature!) // I THINK I can unwrap safely here (?)
|
|
403
|
+
let clear = try ClearMessage(data: Data(contentsOf: filePath))
|
|
404
|
+
|
|
405
|
+
let isSuccessful = try clear.verify(with: self.signKey!, signature: signatureObj, digestType: .sha512)
|
|
406
|
+
return isSuccessful
|
|
407
|
+
} catch {
|
|
408
|
+
print("\(self.TAG) Signature validation failed", error)
|
|
409
|
+
self.sendStats(action: "signature_validation_failed", versionName: version)
|
|
410
|
+
throw error
|
|
411
|
+
}
|
|
412
|
+
}
|
|
366
413
|
|
|
367
414
|
private func decryptFile(filePath: URL, sessionKey: String, version: String) throws {
|
|
368
415
|
if self.privateKey.isEmpty || sessionKey.isEmpty || sessionKey.components(separatedBy: ":").count != 2 {
|
|
@@ -539,6 +586,9 @@ extension CustomError: LocalizedError {
|
|
|
539
586
|
if let data = response.value?.data {
|
|
540
587
|
latest.data = data
|
|
541
588
|
}
|
|
589
|
+
if let signature = response.value?.signature {
|
|
590
|
+
latest.signature = signature
|
|
591
|
+
}
|
|
542
592
|
case let .failure(error):
|
|
543
593
|
print("\(self.TAG) Error getting Latest", response.value ?? "", error )
|
|
544
594
|
latest.message = "Error getting Latest \(String(describing: response.value))"
|
|
@@ -556,7 +606,7 @@ extension CustomError: LocalizedError {
|
|
|
556
606
|
print("\(self.TAG) Current bundle set to: \((bundle ).isEmpty ? BundleInfo.ID_BUILTIN : bundle)")
|
|
557
607
|
}
|
|
558
608
|
|
|
559
|
-
public func download(url: URL, version: String, sessionKey: String) throws -> BundleInfo {
|
|
609
|
+
public func download(url: URL, version: String, sessionKey: String, signature: String?) throws -> BundleInfo {
|
|
560
610
|
let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
|
|
561
611
|
let id: String = self.randomString(length: 10)
|
|
562
612
|
var checksum: String = ""
|
|
@@ -579,12 +629,39 @@ extension CustomError: LocalizedError {
|
|
|
579
629
|
switch response.result {
|
|
580
630
|
case .success:
|
|
581
631
|
self.notifyDownload(id, 71)
|
|
632
|
+
|
|
633
|
+
// The reason we do 2 blocks of try/catch is that in the first one we will try to cleanup afterwards. Cleanup wlll NOT happen in the second one
|
|
634
|
+
|
|
582
635
|
do {
|
|
636
|
+
let valid = try self.verifyBundleSignature(version: version, filePath: fileURL, signature: signature)
|
|
637
|
+
if (!valid) {
|
|
638
|
+
print("\(self.TAG) Invalid signature, cannot accept download")
|
|
639
|
+
self.sendStats(action: "invalid_signature", versionName: version)
|
|
640
|
+
throw CustomError.invalidSignature
|
|
641
|
+
} else {
|
|
642
|
+
print("\(self.TAG) Valid signature")
|
|
643
|
+
}
|
|
644
|
+
|
|
583
645
|
try self.decryptFile(filePath: fileURL, sessionKey: sessionKey, version: version)
|
|
584
646
|
checksum = self.calcChecksum(filePath: fileURL)
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
647
|
+
} catch {
|
|
648
|
+
print("\(self.TAG) downloaded file verification error", error)
|
|
649
|
+
mainError = error as NSError
|
|
650
|
+
|
|
651
|
+
// Cleanup
|
|
652
|
+
do {
|
|
653
|
+
try self.deleteFolder(source: fileURL)
|
|
654
|
+
} catch {
|
|
655
|
+
print("\(self.TAG) Double error, cannot cleanup", error)
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
do {
|
|
660
|
+
if (mainError == nil) {
|
|
661
|
+
try self.saveDownloaded(sourceZip: fileURL, id: id, base: self.libraryDir.appendingPathComponent(self.bundleDirectory), notify: true)
|
|
662
|
+
try self.deleteFolder(source: fileURL)
|
|
663
|
+
self.notifyDownload(id, 100)
|
|
664
|
+
}
|
|
588
665
|
} catch {
|
|
589
666
|
print("\(self.TAG) download unzip error", error)
|
|
590
667
|
mainError = error as NSError
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import Foundation
|
|
8
8
|
import Capacitor
|
|
9
9
|
import Version
|
|
10
|
+
import SwiftyRSA
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Please read the Capacitor iOS Plugin Development Guide
|
|
@@ -15,7 +16,7 @@ import Version
|
|
|
15
16
|
@objc(CapacitorUpdaterPlugin)
|
|
16
17
|
public class CapacitorUpdaterPlugin: CAPPlugin {
|
|
17
18
|
public var implementation = CapacitorUpdater()
|
|
18
|
-
private let PLUGIN_VERSION: String = "6.0.
|
|
19
|
+
private let PLUGIN_VERSION: String = "6.0.70"
|
|
19
20
|
static let updateUrlDefault = "https://api.capgo.app/updates"
|
|
20
21
|
static let statsUrlDefault = "https://api.capgo.app/stats"
|
|
21
22
|
static let channelUrlDefault = "https://api.capgo.app/channel_self"
|
|
@@ -38,6 +39,11 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
|
|
|
38
39
|
let semaphoreReady = DispatchSemaphore(value: 0)
|
|
39
40
|
|
|
40
41
|
override public func load() {
|
|
42
|
+
#if targetEnvironment(simulator)
|
|
43
|
+
print("\(self.implementation.TAG) ::::: SIMULATOR :::::")
|
|
44
|
+
print("\(self.implementation.TAG) Application directory: \(NSHomeDirectory())")
|
|
45
|
+
#endif
|
|
46
|
+
|
|
41
47
|
self.semaphoreUp()
|
|
42
48
|
print("\(self.implementation.TAG) init for device \(self.implementation.deviceID)")
|
|
43
49
|
guard let versionName = getConfig().getString("version", Bundle.main.versionName) else {
|
|
@@ -81,6 +87,15 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
|
|
|
81
87
|
implementation.statsUrl = getConfig().getString("statsUrl", CapacitorUpdaterPlugin.statsUrlDefault)!
|
|
82
88
|
implementation.channelUrl = getConfig().getString("channelUrl", CapacitorUpdaterPlugin.channelUrlDefault)!
|
|
83
89
|
implementation.defaultChannel = getConfig().getString("defaultChannel", "")!
|
|
90
|
+
do {
|
|
91
|
+
let signKeyString = getConfig().getString("signKey", "")!
|
|
92
|
+
if (!signKeyString.isEmpty) {
|
|
93
|
+
implementation.signKey = try PublicKey(base64Encoded: signKeyString)
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
print("\(self.implementation.TAG) Cannot get signKey, invalid key")
|
|
97
|
+
fatalError("Invalid signKey in capacitor config")
|
|
98
|
+
}
|
|
84
99
|
self.implementation.autoReset()
|
|
85
100
|
|
|
86
101
|
// Load the server
|
|
@@ -237,13 +252,20 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
|
|
|
237
252
|
call.reject("Download called without version")
|
|
238
253
|
return
|
|
239
254
|
}
|
|
255
|
+
let signature = call.getString("signature", "")
|
|
256
|
+
if (self.implementation.signKey != nil && signature.isEmpty) {
|
|
257
|
+
print("\(self.implementation.TAG) Signature required but none provided for download call")
|
|
258
|
+
call.reject("Signature required but none provided")
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
|
|
240
262
|
let sessionKey = call.getString("sessionKey", "")
|
|
241
263
|
let checksum = call.getString("checksum", "")
|
|
242
264
|
let url = URL(string: urlString)
|
|
243
265
|
print("\(self.implementation.TAG) Downloading \(String(describing: url))")
|
|
244
266
|
DispatchQueue.global(qos: .background).async {
|
|
245
267
|
do {
|
|
246
|
-
let next = try self.implementation.download(url: url!, version: version, sessionKey: sessionKey)
|
|
268
|
+
let next = try self.implementation.download(url: url!, version: version, sessionKey: sessionKey, signature: signature)
|
|
247
269
|
if checksum != "" && next.getChecksum() != checksum {
|
|
248
270
|
print("\(self.implementation.TAG) Error checksum", next.getChecksum(), checksum)
|
|
249
271
|
self.implementation.sendStats(action: "checksum_fail", versionName: next.getVersionName())
|
|
@@ -678,6 +700,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
|
|
|
678
700
|
return
|
|
679
701
|
}
|
|
680
702
|
let sessionKey = res.sessionKey ?? ""
|
|
703
|
+
let signature = res.signature
|
|
681
704
|
guard let downloadUrl = URL(string: res.url) else {
|
|
682
705
|
print("\(self.implementation.TAG) Error no url or wrong format")
|
|
683
706
|
self.endBackGroundTaskWithNotif(msg: "Error no url or wrong format", latestVersionName: res.version, current: current)
|
|
@@ -698,7 +721,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
|
|
|
698
721
|
print("\(self.implementation.TAG) Failed to delete failed bundle: \(nextImpl!.toString())")
|
|
699
722
|
}
|
|
700
723
|
}
|
|
701
|
-
nextImpl = try self.implementation.download(url: downloadUrl, version: latestVersionName, sessionKey: sessionKey)
|
|
724
|
+
nextImpl = try self.implementation.download(url: downloadUrl, version: latestVersionName, sessionKey: sessionKey, signature: signature)
|
|
702
725
|
}
|
|
703
726
|
guard let next = nextImpl else {
|
|
704
727
|
print("\(self.implementation.TAG) Error downloading file")
|