@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.
@@ -16,5 +16,6 @@ Pod::Spec.new do |s|
16
16
  s.dependency 'SSZipArchive'
17
17
  s.dependency 'Alamofire'
18
18
  s.dependency 'Version'
19
+ s.dependency 'SwiftyRSA'
19
20
  s.swift_version = '5.1'
20
21
  end
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
- final File downloaded = new File(this.documentsDir, dest);
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
- this.notifyDownload(id, 71);
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
- if (
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(id, dest, version, sessionKey, checksum, false);
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) throws IOException {
663
- return this.delete(id, true);
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
- try {
729
- final Boolean res = this.delete(fallback.getId());
730
- if (res) {
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
- "Failed to delete previous bundle: " + fallback.getVersionName(),
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.69";
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.scheduleAtFixedRate(
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(dest, id, version, checksum, sessionKey, "");
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("", id, version, checksum, sessionKey, "low_mem_fail");
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
- try self.saveDownloaded(sourceZip: fileURL, id: id, base: self.libraryDir.appendingPathComponent(self.bundleDirectory), notify: true)
586
- try self.deleteFolder(source: fileURL)
587
- self.notifyDownload(id, 100)
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.69"
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")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-updater",
3
- "version": "6.0.69",
3
+ "version": "6.0.70",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Live update for capacitor apps",
6
6
  "main": "dist/plugin.cjs.js",