@capawesome/capacitor-live-update 6.2.0 → 6.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -65,15 +65,17 @@ We recommend to declare [`CA92.1`](https://developer.apple.com/documentation/bun
65
65
  <docgen-config>
66
66
  <!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
67
67
 
68
- | Prop | Type | Description | Default | Since |
69
- | ----------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----- |
70
- | **`appId`** | <code>string</code> | The app ID is used to identify the app when using [Capawesome Cloud](https://capawesome.io/cloud). | | 5.0.0 |
71
- | **`autoDeleteBundles`** | <code>boolean</code> | Whether or not to automatically delete unused bundles. When enabled, the plugin will automatically delete unused bundles after calling `ready()`. | <code>false</code> | 5.0.0 |
72
- | **`enabled`** | <code>boolean</code> | Whether or not the plugin is enabled. | <code>true</code> | 5.0.0 |
73
- | **`location`** | <code>'us' \| 'eu'</code> | The location of the server to use when using [Capawesome Cloud](https://capawesome.io/cloud). | <code>'us'</code> | 6.2.0 |
74
- | **`publicKey`** | <code>string</code> | The public key to verify the integrity of the bundle. The public key must be a PEM-encoded RSA public key. | | 6.1.0 |
75
- | **`readyTimeout`** | <code>number</code> | The timeout in milliseconds to wait for the app to be ready before resetting to the default bundle. | <code>10000</code> | 5.0.0 |
76
- | **`resetOnUpdate`** | <code>boolean</code> | Whether or not the app should be reset to the default bundle during an update. | <code>true</code> | 5.0.0 |
68
+ | Prop | Type | Description | Default | Since |
69
+ | ----------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | ----- |
70
+ | **`appId`** | <code>string</code> | The app ID is used to identify the app when using [Capawesome Cloud](https://capawesome.io/cloud). This is **NOT** the same as the app identifier (e.g. `com.example.app`). This is a unique identifier generated by Capawesome Cloud (e.g. `6e351b4f-69a7-415e-a057-4567df7ffe94`). | | 5.0.0 |
71
+ | **`autoDeleteBundles`** | <code>boolean</code> | Whether or not to automatically delete unused bundles. When enabled, the plugin will automatically delete unused bundles after calling `ready()`. | <code>false</code> | 5.0.0 |
72
+ | **`defaultChannel`** | <code>string</code> | The default channel of the app. | | 6.3.0 |
73
+ | **`enabled`** | <code>boolean</code> | Whether or not the plugin is enabled. | <code>true</code> | 5.0.0 |
74
+ | **`httpTimeout`** | <code>number</code> | The timeout in milliseconds for HTTP requests. | <code>60000</code> | 6.4.0 |
75
+ | **`location`** | <code>'us' \| 'eu'</code> | The location of the server to use when using [Capawesome Cloud](https://capawesome.io/cloud). | <code>'us'</code> | 6.2.0 |
76
+ | **`publicKey`** | <code>string</code> | The public key to verify the integrity of the bundle. The public key must be a PEM-encoded RSA public key. | | 6.1.0 |
77
+ | **`readyTimeout`** | <code>number</code> | The timeout in milliseconds to wait for the app to be ready before resetting to the default bundle. | <code>10000</code> | 5.0.0 |
78
+ | **`resetOnUpdate`** | <code>boolean</code> | Whether or not the app should be reset to the default bundle during an update. | <code>true</code> | 5.0.0 |
77
79
 
78
80
  ### Examples
79
81
 
@@ -83,9 +85,11 @@ In `capacitor.config.json`:
83
85
  {
84
86
  "plugins": {
85
87
  "LiveUpdate": {
86
- "appId": '4100e356-e851-47fe-9d3c-b411eb325a99',
88
+ "appId": '6e351b4f-69a7-415e-a057-4567df7ffe94',
87
89
  "autoDeleteBundles": undefined,
90
+ "defaultChannel": 'production',
88
91
  "enabled": undefined,
92
+ "httpTimeout": undefined,
89
93
  "location": 'eu',
90
94
  "publicKey": '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDodf1SD0OOn6hIlDuKBza0Ed0OqtwyVJwiyjmE9BJaZ7y8ZUfcF+SKmd0l2cDPM45XIg2tAFux5n29uoKyHwSt+6tCi5CJA5Z1/1eZruRRqABLonV77KS3HUtvOgqRLDnKSV89dYZkM++NwmzOPgIF422mvc+VukcVOBfc8/AHQIDAQAB-----END PUBLIC KEY-----',
91
95
  "readyTimeout": undefined,
@@ -105,9 +109,11 @@ import { CapacitorConfig } from '@capacitor/cli';
105
109
  const config: CapacitorConfig = {
106
110
  plugins: {
107
111
  LiveUpdate: {
108
- appId: '4100e356-e851-47fe-9d3c-b411eb325a99',
112
+ appId: '6e351b4f-69a7-415e-a057-4567df7ffe94',
109
113
  autoDeleteBundles: undefined,
114
+ defaultChannel: 'production',
110
115
  enabled: undefined,
116
+ httpTimeout: undefined,
111
117
  location: 'eu',
112
118
  publicKey: '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDodf1SD0OOn6hIlDuKBza0Ed0OqtwyVJwiyjmE9BJaZ7y8ZUfcF+SKmd0l2cDPM45XIg2tAFux5n29uoKyHwSt+6tCi5CJA5Z1/1eZruRRqABLonV77KS3HUtvOgqRLDnKSV89dYZkM++NwmzOPgIF422mvc+VukcVOBfc8/AHQIDAQAB-----END PUBLIC KEY-----',
113
119
  readyTimeout: undefined,
@@ -641,6 +647,20 @@ Live updates are only supported for [binary-compatible changes](https://capaweso
641
647
  If you change native code, such as adding a new plugin, you need to resubmit your app to the app stores.
642
648
  For this reason, you must be careful to [restrict live updates to compatible native versions](https://capawesome.io/blog/how-to-restrict-capacitor-live-updates-to-native-versions/) of your app.
643
649
 
650
+ ## FAQ
651
+
652
+ ### Why can't I see my changes during development?
653
+
654
+ As soon as you have installed a live update, the app will use the live update bundle and no longer the default bundle.
655
+ So if you make local changes to your app and execute `npx cap run`, for example, these changes will apply to the default bundle, which is not currently in use.
656
+ You then have three options to get back to the default bundle:
657
+
658
+ 1. **Reset**: Call the [`reset()`](#reset) method to reset the app to the default bundle.
659
+ 2. **Reinstall**: Reinstall the app to use the default bundle.
660
+ 3. **Update**: Increase the `versionCode`/`CFBundleVersion` so that the plugin automatically performs a reset (assuming the [`resetOnUpdate`](#configuration) configuration option is active).
661
+
662
+ However, this is only a problem during development. It is not a problem in production as long as you have the [`resetOnUpdate`](#configuration) configuration option enabled, as the `versionCode`/`CFBundleVersion` is always incremented during a native update and thus always resets to the default bundle.
663
+
644
664
  ## Changelog
645
665
 
646
666
  See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/live-update/CHANGELOG.md).
@@ -37,6 +37,7 @@ import java.security.Signature;
37
37
  import java.security.spec.X509EncodedKeySpec;
38
38
  import java.util.Arrays;
39
39
  import java.util.UUID;
40
+ import java.util.concurrent.TimeUnit;
40
41
  import net.lingala.zip4j.ZipFile;
41
42
  import okhttp3.Call;
42
43
  import okhttp3.Callback;
@@ -133,7 +134,7 @@ public class LiveUpdate {
133
134
  }
134
135
 
135
136
  public void getChannel(@NonNull NonEmptyCallback callback) {
136
- String channel = preferences.getChannel();
137
+ String channel = getChannel();
137
138
  GetChannelResult result = new GetChannelResult(channel);
138
139
  callback.success(result);
139
140
  }
@@ -213,6 +214,11 @@ public class LiveUpdate {
213
214
  new NonEmptyCallback<GetLatestBundleResponse>() {
214
215
  @Override
215
216
  public void error(Exception exception) {
217
+ // Throw error if the exception is a timeout
218
+ if (exception instanceof java.net.SocketTimeoutException) {
219
+ callback.error(new Exception(LiveUpdatePlugin.ERROR_HTTP_TIMEOUT));
220
+ return;
221
+ }
216
222
  // No update available
217
223
  Logger.debug(LiveUpdatePlugin.TAG, "No update available.");
218
224
  SyncResult syncResult = new SyncResult(null);
@@ -381,6 +387,11 @@ public class LiveUpdate {
381
387
  new NonEmptyCallback<File>() {
382
388
  @Override
383
389
  public void error(Exception exception) {
390
+ // Throw error if the exception is a timeout
391
+ if (exception instanceof java.net.SocketTimeoutException) {
392
+ callback.error(new Exception(LiveUpdatePlugin.ERROR_HTTP_TIMEOUT));
393
+ return;
394
+ }
384
395
  callback.error(exception);
385
396
  }
386
397
 
@@ -425,7 +436,12 @@ public class LiveUpdate {
425
436
  }
426
437
 
427
438
  private void downloadFile(@NonNull String url, @NonNull NonEmptyCallback<File> callback) {
428
- OkHttpClient okHttpClient = new OkHttpClient();
439
+ int httpTimeout = config.getHttpTimeout();
440
+ OkHttpClient okHttpClient = new OkHttpClient.Builder()
441
+ .connectTimeout(httpTimeout, TimeUnit.MILLISECONDS)
442
+ .readTimeout(httpTimeout, TimeUnit.MILLISECONDS)
443
+ .writeTimeout(httpTimeout, TimeUnit.MILLISECONDS)
444
+ .build();
429
445
  Request request = new Request.Builder().url(url).build();
430
446
  okHttpClient
431
447
  .newCall(request)
@@ -487,7 +503,7 @@ public class LiveUpdate {
487
503
  .addQueryParameter("appVersionCode", getVersionCode())
488
504
  .addQueryParameter("appVersionName", getVersionName())
489
505
  .addQueryParameter("bundleId", getCurrentBundleId())
490
- .addQueryParameter("channelName", preferences.getChannel())
506
+ .addQueryParameter("channelName", getChannel())
491
507
  .addQueryParameter("customId", preferences.getCustomId())
492
508
  .addQueryParameter("deviceId", getDeviceId())
493
509
  .addQueryParameter("osVersion", String.valueOf(Build.VERSION.SDK_INT))
@@ -495,7 +511,13 @@ public class LiveUpdate {
495
511
  .addQueryParameter("pluginVersion", LiveUpdatePlugin.VERSION)
496
512
  .build();
497
513
  Logger.debug(LiveUpdatePlugin.TAG, "Fetching latest bundle from " + url);
498
- OkHttpClient client = new OkHttpClient();
514
+ int httpTimeout = config.getHttpTimeout();
515
+ OkHttpClient client = new OkHttpClient()
516
+ .newBuilder()
517
+ .connectTimeout(httpTimeout, TimeUnit.MILLISECONDS)
518
+ .readTimeout(httpTimeout, TimeUnit.MILLISECONDS)
519
+ .writeTimeout(httpTimeout, TimeUnit.MILLISECONDS)
520
+ .build();
499
521
  okhttp3.Request request = new okhttp3.Request.Builder().url(url).build();
500
522
  client
501
523
  .newCall(request)
@@ -530,14 +552,6 @@ public class LiveUpdate {
530
552
  );
531
553
  }
532
554
 
533
- private String getVersionCode() throws PackageManager.NameNotFoundException {
534
- return String.valueOf(getPackageInfo().versionCode);
535
- }
536
-
537
- private String getVersionName() throws PackageManager.NameNotFoundException {
538
- return getPackageInfo().versionName;
539
- }
540
-
541
555
  private String[] getBundleIds() {
542
556
  File bundlesDirectory = buildBundlesDirectory();
543
557
  File[] bundles = bundlesDirectory.listFiles();
@@ -553,6 +567,18 @@ public class LiveUpdate {
553
567
  return bundleIds;
554
568
  }
555
569
 
570
+ @Nullable
571
+ private String getChannel() {
572
+ String channel = null;
573
+ if (config.getDefaultChannel() != null) {
574
+ channel = config.getDefaultChannel();
575
+ }
576
+ if (preferences.getChannel() != null) {
577
+ channel = preferences.getChannel();
578
+ }
579
+ return channel;
580
+ }
581
+
556
582
  /**
557
583
  * @return The current bundle ID (`public` for the built-in bundle).
558
584
  */
@@ -604,6 +630,14 @@ public class LiveUpdate {
604
630
  .getString(WebView.CAP_SERVER_PATH, defaultWebAssetDir);
605
631
  }
606
632
 
633
+ private String getVersionCode() throws PackageManager.NameNotFoundException {
634
+ return String.valueOf(getPackageInfo().versionCode);
635
+ }
636
+
637
+ private String getVersionName() throws PackageManager.NameNotFoundException {
638
+ return getPackageInfo().versionName;
639
+ }
640
+
607
641
  private boolean hasBundle(@NonNull String bundleId) {
608
642
  File bundleDirectory = buildBundleDirectoryFor(bundleId);
609
643
  return bundleDirectory.exists();
@@ -9,7 +9,12 @@ public class LiveUpdateConfig {
9
9
  private String appId = null;
10
10
 
11
11
  private boolean autoDeleteBundles = false;
12
+
13
+ @Nullable
14
+ private String defaultChannel = null;
15
+
12
16
  private boolean enabled = true;
17
+ private int httpTimeout = 60000;
13
18
 
14
19
  @Nullable
15
20
  private String location = null;
@@ -29,10 +34,19 @@ public class LiveUpdateConfig {
29
34
  return autoDeleteBundles;
30
35
  }
31
36
 
37
+ @Nullable
38
+ public String getDefaultChannel() {
39
+ return defaultChannel;
40
+ }
41
+
32
42
  public boolean getEnabled() {
33
43
  return enabled;
34
44
  }
35
45
 
46
+ public int getHttpTimeout() {
47
+ return httpTimeout;
48
+ }
49
+
36
50
  @Nullable
37
51
  public String getLocation() {
38
52
  return location;
@@ -59,10 +73,18 @@ public class LiveUpdateConfig {
59
73
  this.autoDeleteBundles = autoDeleteBundles;
60
74
  }
61
75
 
76
+ public void setDefaultChannel(@Nullable String defaultChannel) {
77
+ this.defaultChannel = defaultChannel;
78
+ }
79
+
62
80
  public void setEnabled(boolean enabled) {
63
81
  this.enabled = enabled;
64
82
  }
65
83
 
84
+ public void setHttpTimeout(int httpTimeout) {
85
+ this.httpTimeout = httpTimeout;
86
+ }
87
+
66
88
  public void setLocation(@Nullable String location) {
67
89
  this.location = location;
68
90
  }
@@ -19,7 +19,7 @@ import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.Result;
19
19
  public class LiveUpdatePlugin extends Plugin {
20
20
 
21
21
  public static final String TAG = "LiveUpdate";
22
- public static final String VERSION = "6.2.0";
22
+ public static final String VERSION = "6.4.0";
23
23
  public static final String SHARED_PREFERENCES_NAME = "CapawesomeLiveUpdate"; // DO NOT CHANGE
24
24
  public static final String ERROR_APP_ID_MISSING = "appId must be configured.";
25
25
  public static final String ERROR_BUNDLE_EXISTS = "bundle already exists.";
@@ -30,10 +30,14 @@ public class LiveUpdatePlugin extends Plugin {
30
30
  public static final String ERROR_CHECKSUM_MISMATCH = "Checksum mismatch.";
31
31
  public static final String ERROR_CUSTOM_ID_MISSING = "customId must be provided.";
32
32
  public static final String ERROR_DOWNLOAD_FAILED = "Bundle could not be downloaded.";
33
+ public static final String ERROR_HTTP_TIMEOUT = "Request timed out.";
33
34
  public static final String ERROR_URL_MISSING = "url must be provided.";
34
35
  public static final String ERROR_SIGNATURE_VERIFICATION_FAILED = "Signature verification failed.";
35
36
  public static final String ERROR_PUBLIC_KEY_INVALID = "Invalid public key.";
36
37
  public static final String ERROR_SIGNATURE_MISSING = "Bundle does not contain a signature.";
38
+ public static final String ERROR_SYNC_IN_PROGRESS = "Sync is already in progress.";
39
+
40
+ private boolean syncInProgress = false;
37
41
 
38
42
  @Nullable
39
43
  private LiveUpdateConfig config;
@@ -404,14 +408,23 @@ public class LiveUpdatePlugin extends Plugin {
404
408
  call.reject(ERROR_APP_ID_MISSING);
405
409
  return;
406
410
  }
411
+
412
+ if (syncInProgress) {
413
+ call.reject(ERROR_SYNC_IN_PROGRESS);
414
+ return;
415
+ }
416
+ syncInProgress = true;
417
+
407
418
  NonEmptyCallback<Result> callback = new NonEmptyCallback<>() {
408
419
  @Override
409
420
  public void success(Result result) {
421
+ syncInProgress = false;
410
422
  call.resolve(result.toJSObject());
411
423
  }
412
424
 
413
425
  @Override
414
426
  public void error(Exception exception) {
427
+ syncInProgress = false;
415
428
  Logger.error(TAG, exception.getMessage(), exception);
416
429
  call.reject(exception.getMessage());
417
430
  }
@@ -419,6 +432,7 @@ public class LiveUpdatePlugin extends Plugin {
419
432
 
420
433
  implementation.sync(callback);
421
434
  } catch (Exception exception) {
435
+ syncInProgress = false;
422
436
  Logger.error(TAG, exception.getMessage(), exception);
423
437
  call.reject(exception.getMessage());
424
438
  }
@@ -431,8 +445,12 @@ public class LiveUpdatePlugin extends Plugin {
431
445
  config.setAppId(appId);
432
446
  boolean autoDeleteBundles = getConfig().getBoolean("autoDeleteBundles", config.getAutoDeleteBundles());
433
447
  config.setAutoDeleteBundles(autoDeleteBundles);
448
+ String defaultChannel = getConfig().getString("defaultChannel", config.getDefaultChannel());
449
+ config.setDefaultChannel(defaultChannel);
434
450
  boolean enabled = getConfig().getBoolean("enabled", config.getEnabled());
435
451
  config.setEnabled(enabled);
452
+ int httpTimeout = getConfig().getInt("httpTimeout", config.getHttpTimeout());
453
+ config.setHttpTimeout(httpTimeout);
436
454
  String location = getConfig().getString("location", config.getLocation());
437
455
  config.setLocation(location);
438
456
  String publicKey = getConfig().getString("publicKey", config.getPublicKey());
package/dist/docs.json CHANGED
@@ -722,11 +722,11 @@
722
722
  "name": "since"
723
723
  },
724
724
  {
725
- "text": "'4100e356-e851-47fe-9d3c-b411eb325a99'",
725
+ "text": "'6e351b4f-69a7-415e-a057-4567df7ffe94'",
726
726
  "name": "example"
727
727
  }
728
728
  ],
729
- "docs": "The app ID is used to identify the app when using [Capawesome Cloud](https://capawesome.io/cloud).",
729
+ "docs": "The app ID is used to identify the app when using [Capawesome Cloud](https://capawesome.io/cloud).\n\nThis is **NOT** the same as the app identifier (e.g. `com.example.app`).\nThis is a unique identifier generated by Capawesome Cloud (e.g. `6e351b4f-69a7-415e-a057-4567df7ffe94`).",
730
730
  "complexTypes": [],
731
731
  "type": "string | undefined"
732
732
  },
@@ -746,6 +746,22 @@
746
746
  "complexTypes": [],
747
747
  "type": "boolean | undefined"
748
748
  },
749
+ {
750
+ "name": "defaultChannel",
751
+ "tags": [
752
+ {
753
+ "text": "6.3.0",
754
+ "name": "since"
755
+ },
756
+ {
757
+ "text": "'production'",
758
+ "name": "example"
759
+ }
760
+ ],
761
+ "docs": "The default channel of the app.",
762
+ "complexTypes": [],
763
+ "type": "string | undefined"
764
+ },
749
765
  {
750
766
  "name": "enabled",
751
767
  "tags": [
@@ -762,6 +778,22 @@
762
778
  "complexTypes": [],
763
779
  "type": "boolean | undefined"
764
780
  },
781
+ {
782
+ "name": "httpTimeout",
783
+ "tags": [
784
+ {
785
+ "text": "6.4.0",
786
+ "name": "since"
787
+ },
788
+ {
789
+ "text": "60000",
790
+ "name": "default"
791
+ }
792
+ ],
793
+ "docs": "The timeout in milliseconds for HTTP requests.",
794
+ "complexTypes": [],
795
+ "type": "number | undefined"
796
+ },
765
797
  {
766
798
  "name": "location",
767
799
  "tags": [
@@ -4,8 +4,11 @@ declare module '@capacitor/cli' {
4
4
  /**
5
5
  * The app ID is used to identify the app when using [Capawesome Cloud](https://capawesome.io/cloud).
6
6
  *
7
+ * This is **NOT** the same as the app identifier (e.g. `com.example.app`).
8
+ * This is a unique identifier generated by Capawesome Cloud (e.g. `6e351b4f-69a7-415e-a057-4567df7ffe94`).
9
+ *
7
10
  * @since 5.0.0
8
- * @example '4100e356-e851-47fe-9d3c-b411eb325a99'
11
+ * @example '6e351b4f-69a7-415e-a057-4567df7ffe94'
9
12
  */
10
13
  appId?: string;
11
14
  /**
@@ -17,6 +20,13 @@ declare module '@capacitor/cli' {
17
20
  * @default false
18
21
  */
19
22
  autoDeleteBundles?: boolean;
23
+ /**
24
+ * The default channel of the app.
25
+ *
26
+ * @since 6.3.0
27
+ * @example 'production'
28
+ */
29
+ defaultChannel?: string;
20
30
  /**
21
31
  * Whether or not the plugin is enabled.
22
32
  *
@@ -24,6 +34,13 @@ declare module '@capacitor/cli' {
24
34
  * @default true
25
35
  */
26
36
  enabled?: boolean;
37
+ /**
38
+ * The timeout in milliseconds for HTTP requests.
39
+ *
40
+ * @since 6.4.0
41
+ * @default 60000
42
+ */
43
+ httpTimeout?: number;
27
44
  /**
28
45
  * The location of the server to use when using [Capawesome Cloud](https://capawesome.io/cloud).
29
46
  *
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAAA,wCAAwC","sourcesContent":["/// <reference types=\"@capacitor/cli\" />\n\ndeclare module '@capacitor/cli' {\n export interface PluginsConfig {\n LiveUpdate?: {\n /**\n * The app ID is used to identify the app when using [Capawesome Cloud](https://capawesome.io/cloud).\n *\n * @since 5.0.0\n * @example '4100e356-e851-47fe-9d3c-b411eb325a99'\n */\n appId?: string;\n /**\n * Whether or not to automatically delete unused bundles.\n *\n * When enabled, the plugin will automatically delete unused bundles after calling `ready()`.\n *\n * @since 5.0.0\n * @default false\n */\n autoDeleteBundles?: boolean;\n /**\n * Whether or not the plugin is enabled.\n *\n * @since 5.0.0\n * @default true\n */\n enabled?: boolean;\n /**\n * The location of the server to use when using [Capawesome Cloud](https://capawesome.io/cloud).\n *\n * @since 6.2.0\n * @default 'us'\n * @example 'eu'\n * @see https://capawesome.io/cloud/live-updates/advanced/privacy/\n */\n location?: 'us' | 'eu';\n /**\n * The public key to verify the integrity of the bundle.\n *\n * The public key must be a PEM-encoded RSA public key.\n *\n * @since 6.1.0\n * @example '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDodf1SD0OOn6hIlDuKBza0Ed0OqtwyVJwiyjmE9BJaZ7y8ZUfcF+SKmd0l2cDPM45XIg2tAFux5n29uoKyHwSt+6tCi5CJA5Z1/1eZruRRqABLonV77KS3HUtvOgqRLDnKSV89dYZkM++NwmzOPgIF422mvc+VukcVOBfc8/AHQIDAQAB-----END PUBLIC KEY-----'\n */\n publicKey?: string;\n /**\n * The timeout in milliseconds to wait for the app to be ready before resetting to the default bundle.\n *\n * @since 5.0.0\n * @default 10000\n */\n readyTimeout?: number;\n /**\n * Whether or not the app should be reset to the default bundle during an update.\n *\n * @since 5.0.0\n * @default true\n */\n resetOnUpdate?: boolean;\n };\n }\n}\n\nexport interface LiveUpdatePlugin {\n /**\n * Delete a bundle from the app.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n deleteBundle(options: DeleteBundleOptions): Promise<void>;\n /**\n * Download a bundle.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n downloadBundle(options: DownloadBundleOptions): Promise<void>;\n /**\n * Get the active bundle identifier.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getBundle(): Promise<GetBundleResult>;\n /**\n * Get all available bundle identifiers.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getBundles(): Promise<GetBundlesResult>;\n /**\n * Get the channel of the app.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getChannel(): Promise<GetChannelResult>;\n /**\n * Get the custom identifier of the app.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getCustomId(): Promise<GetCustomIdResult>;\n /**\n * Get the device identifier of the app.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getDeviceId(): Promise<GetDeviceIdResult>;\n /**\n * Get the version code of the app.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getVersionCode(): Promise<GetVersionCodeResult>;\n /**\n * Get the version name of the app.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getVersionName(): Promise<GetVersionNameResult>;\n /**\n * Notify the plugin that the app is ready to use and no rollback is needed.\n *\n * **Attention**: This method should be called as soon as the app is ready to use\n * to prevent the app from being reset to the default bundle.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n ready(): Promise<void>;\n /**\n * Reload the app to apply the new bundle.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n reload(): Promise<void>;\n /**\n * Reset the app to the default bundle.\n *\n * Call `reload()` or restart the app to apply the changes.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n reset(): Promise<void>;\n /**\n * Set the next bundle to use for the app.\n *\n * Call `reload()` or restart the app to apply the changes.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n setBundle(options: SetBundleOptions): Promise<void>;\n /**\n * Set the channel of the app.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n setChannel(options: SetChannelOptions): Promise<void>;\n /**\n * Set the custom identifier of the app.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n setCustomId(options: SetCustomIdOptions): Promise<void>;\n /**\n * Automatically download and set the latest bundle for the app using the [Capawesome Cloud](https://capawesome.io/cloud/).\n *\n * Call `reload()` or restart the app to apply the changes.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n sync(): Promise<SyncResult>;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface DeleteBundleOptions {\n /**\n * The unique identifier of the bundle to delete.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string;\n}\n\nexport interface DownloadBundleOptions {\n /**\n * The unique identifier of the bundle.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string;\n /**\n * The checksum of the bundle to verify the integrity of the ZIP file.\n *\n * Must be a SHA-256 hash in hexadecimal format.\n *\n * @since 6.1.0\n * @example 'aab8e0a312e807c8eaeb923e2bdccd7731c5508869c62ad8e6e43ee881414c84'\n */\n checksum?: string;\n /**\n * The URL of the bundle to download.\n *\n * The bundle must be a ZIP file containing at least a `index.html` file.\n *\n * @since 5.0.0\n * @example 'https://example.com/bundle.zip'\n */\n url: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetBundleResult {\n /**\n * The unique identifier of the active bundle.\n *\n * If `null`, the default bundle is being used.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string | null;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetBundlesResult {\n /**\n * An array of unique identifiers of all available bundles.\n *\n * @since 5.0.0\n */\n bundleIds: string[];\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetChannelResult {\n /**\n * The channel of the app.\n *\n * If `null`, the app is using the default channel.\n *\n * @since 5.0.0\n * @example 'production'\n */\n channel: string | null;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetDeviceIdResult {\n /**\n * The unique identifier of the device.\n *\n * On iOS, [`identifierForVendor`](https://developer.apple.com/documentation/uikit/uidevice/1620059-identifierforvendor) is used.\n * The value of this property is the same for apps that come from the same vendor running on the same device.\n *\n * @since 5.0.0\n *\n * @example '50d2a548-80b7-4dad-adc7-97c0e79d8a89'\n */\n deviceId: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetVersionCodeResult {\n /**\n * The version code of the app.\n *\n * On **Android**, this is the `versionCode` from the `android/app/build.gradle` file.\n * On **iOS**, this is the `CFBundleVersion` from the `Info.plist` file.\n *\n * @since 5.0.0\n * @example \"1\"\n */\n versionCode: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetVersionNameResult {\n /**\n * The version name of the app.\n *\n * On **Android**, this is the `versionName` from the `android/app/build.gradle` file.\n * On **iOS**, this is the `CFBundleShortVersionString` from the `Info.plist` file.\n *\n * @since 5.0.0\n * @example \"1.0.0\"\n */\n versionName: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetCustomIdResult {\n /**\n * The custom identifier of the app.\n *\n * If `null`, no custom identifier is set.\n *\n * @since 5.0.0\n * @example '50d2a548-80b7-4dad-adc7-97c0e79d8a89'\n */\n customId: string | null;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SetBundleOptions {\n /**\n * The unique identifier of the bundle to use.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SetChannelOptions {\n /**\n * The channel of the app.\n *\n * Set `null` to remove the channel.\n *\n * @since 5.0.0\n */\n channel: string | null;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SetCustomIdOptions {\n /**\n * The custom identifier of the app.\n *\n * Set `null` to remove the custom identifier.\n *\n * @since 5.0.0\n */\n customId: string | null;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SyncResult {\n /**\n * The identifier of the next bundle to use.\n *\n * If `null`, the app is up-to-date and no new bundle is available.\n *\n * @since 5.0.0\n */\n nextBundleId: string | null;\n}\n"]}
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAAA,wCAAwC","sourcesContent":["/// <reference types=\"@capacitor/cli\" />\n\ndeclare module '@capacitor/cli' {\n export interface PluginsConfig {\n LiveUpdate?: {\n /**\n * The app ID is used to identify the app when using [Capawesome Cloud](https://capawesome.io/cloud).\n *\n * This is **NOT** the same as the app identifier (e.g. `com.example.app`).\n * This is a unique identifier generated by Capawesome Cloud (e.g. `6e351b4f-69a7-415e-a057-4567df7ffe94`).\n *\n * @since 5.0.0\n * @example '6e351b4f-69a7-415e-a057-4567df7ffe94'\n */\n appId?: string;\n /**\n * Whether or not to automatically delete unused bundles.\n *\n * When enabled, the plugin will automatically delete unused bundles after calling `ready()`.\n *\n * @since 5.0.0\n * @default false\n */\n autoDeleteBundles?: boolean;\n /**\n * The default channel of the app.\n *\n * @since 6.3.0\n * @example 'production'\n */\n defaultChannel?: string;\n /**\n * Whether or not the plugin is enabled.\n *\n * @since 5.0.0\n * @default true\n */\n enabled?: boolean;\n /**\n * The timeout in milliseconds for HTTP requests.\n *\n * @since 6.4.0\n * @default 60000\n */\n httpTimeout?: number;\n /**\n * The location of the server to use when using [Capawesome Cloud](https://capawesome.io/cloud).\n *\n * @since 6.2.0\n * @default 'us'\n * @example 'eu'\n * @see https://capawesome.io/cloud/live-updates/advanced/privacy/\n */\n location?: 'us' | 'eu';\n /**\n * The public key to verify the integrity of the bundle.\n *\n * The public key must be a PEM-encoded RSA public key.\n *\n * @since 6.1.0\n * @example '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDodf1SD0OOn6hIlDuKBza0Ed0OqtwyVJwiyjmE9BJaZ7y8ZUfcF+SKmd0l2cDPM45XIg2tAFux5n29uoKyHwSt+6tCi5CJA5Z1/1eZruRRqABLonV77KS3HUtvOgqRLDnKSV89dYZkM++NwmzOPgIF422mvc+VukcVOBfc8/AHQIDAQAB-----END PUBLIC KEY-----'\n */\n publicKey?: string;\n /**\n * The timeout in milliseconds to wait for the app to be ready before resetting to the default bundle.\n *\n * @since 5.0.0\n * @default 10000\n */\n readyTimeout?: number;\n /**\n * Whether or not the app should be reset to the default bundle during an update.\n *\n * @since 5.0.0\n * @default true\n */\n resetOnUpdate?: boolean;\n };\n }\n}\n\nexport interface LiveUpdatePlugin {\n /**\n * Delete a bundle from the app.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n deleteBundle(options: DeleteBundleOptions): Promise<void>;\n /**\n * Download a bundle.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n downloadBundle(options: DownloadBundleOptions): Promise<void>;\n /**\n * Get the active bundle identifier.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getBundle(): Promise<GetBundleResult>;\n /**\n * Get all available bundle identifiers.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getBundles(): Promise<GetBundlesResult>;\n /**\n * Get the channel of the app.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getChannel(): Promise<GetChannelResult>;\n /**\n * Get the custom identifier of the app.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getCustomId(): Promise<GetCustomIdResult>;\n /**\n * Get the device identifier of the app.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getDeviceId(): Promise<GetDeviceIdResult>;\n /**\n * Get the version code of the app.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getVersionCode(): Promise<GetVersionCodeResult>;\n /**\n * Get the version name of the app.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getVersionName(): Promise<GetVersionNameResult>;\n /**\n * Notify the plugin that the app is ready to use and no rollback is needed.\n *\n * **Attention**: This method should be called as soon as the app is ready to use\n * to prevent the app from being reset to the default bundle.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n ready(): Promise<void>;\n /**\n * Reload the app to apply the new bundle.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n reload(): Promise<void>;\n /**\n * Reset the app to the default bundle.\n *\n * Call `reload()` or restart the app to apply the changes.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n reset(): Promise<void>;\n /**\n * Set the next bundle to use for the app.\n *\n * Call `reload()` or restart the app to apply the changes.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n setBundle(options: SetBundleOptions): Promise<void>;\n /**\n * Set the channel of the app.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n setChannel(options: SetChannelOptions): Promise<void>;\n /**\n * Set the custom identifier of the app.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n setCustomId(options: SetCustomIdOptions): Promise<void>;\n /**\n * Automatically download and set the latest bundle for the app using the [Capawesome Cloud](https://capawesome.io/cloud/).\n *\n * Call `reload()` or restart the app to apply the changes.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n sync(): Promise<SyncResult>;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface DeleteBundleOptions {\n /**\n * The unique identifier of the bundle to delete.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string;\n}\n\nexport interface DownloadBundleOptions {\n /**\n * The unique identifier of the bundle.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string;\n /**\n * The checksum of the bundle to verify the integrity of the ZIP file.\n *\n * Must be a SHA-256 hash in hexadecimal format.\n *\n * @since 6.1.0\n * @example 'aab8e0a312e807c8eaeb923e2bdccd7731c5508869c62ad8e6e43ee881414c84'\n */\n checksum?: string;\n /**\n * The URL of the bundle to download.\n *\n * The bundle must be a ZIP file containing at least a `index.html` file.\n *\n * @since 5.0.0\n * @example 'https://example.com/bundle.zip'\n */\n url: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetBundleResult {\n /**\n * The unique identifier of the active bundle.\n *\n * If `null`, the default bundle is being used.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string | null;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetBundlesResult {\n /**\n * An array of unique identifiers of all available bundles.\n *\n * @since 5.0.0\n */\n bundleIds: string[];\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetChannelResult {\n /**\n * The channel of the app.\n *\n * If `null`, the app is using the default channel.\n *\n * @since 5.0.0\n * @example 'production'\n */\n channel: string | null;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetDeviceIdResult {\n /**\n * The unique identifier of the device.\n *\n * On iOS, [`identifierForVendor`](https://developer.apple.com/documentation/uikit/uidevice/1620059-identifierforvendor) is used.\n * The value of this property is the same for apps that come from the same vendor running on the same device.\n *\n * @since 5.0.0\n *\n * @example '50d2a548-80b7-4dad-adc7-97c0e79d8a89'\n */\n deviceId: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetVersionCodeResult {\n /**\n * The version code of the app.\n *\n * On **Android**, this is the `versionCode` from the `android/app/build.gradle` file.\n * On **iOS**, this is the `CFBundleVersion` from the `Info.plist` file.\n *\n * @since 5.0.0\n * @example \"1\"\n */\n versionCode: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetVersionNameResult {\n /**\n * The version name of the app.\n *\n * On **Android**, this is the `versionName` from the `android/app/build.gradle` file.\n * On **iOS**, this is the `CFBundleShortVersionString` from the `Info.plist` file.\n *\n * @since 5.0.0\n * @example \"1.0.0\"\n */\n versionName: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetCustomIdResult {\n /**\n * The custom identifier of the app.\n *\n * If `null`, no custom identifier is set.\n *\n * @since 5.0.0\n * @example '50d2a548-80b7-4dad-adc7-97c0e79d8a89'\n */\n customId: string | null;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SetBundleOptions {\n /**\n * The unique identifier of the bundle to use.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SetChannelOptions {\n /**\n * The channel of the app.\n *\n * Set `null` to remove the channel.\n *\n * @since 5.0.0\n */\n channel: string | null;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SetCustomIdOptions {\n /**\n * The custom identifier of the app.\n *\n * Set `null` to remove the custom identifier.\n *\n * @since 5.0.0\n */\n customId: string | null;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SyncResult {\n /**\n * The identifier of the next bundle to use.\n *\n * If `null`, the app is up-to-date and no new bundle is available.\n *\n * @since 5.0.0\n */\n nextBundleId: string | null;\n}\n"]}
@@ -11,8 +11,10 @@ public enum CustomError: Error {
11
11
  case checksumMismatch
12
12
  case customIdMissing
13
13
  case downloadFailed
14
+ case httpTimeout
14
15
  case signatureMissing
15
16
  case signatureVerificationFailed
17
+ case syncInProgress
16
18
  case urlMissing
17
19
  }
18
20
 
@@ -39,10 +41,14 @@ extension CustomError: LocalizedError {
39
41
  return NSLocalizedString("customId must be provided.", comment: "customIdMissing")
40
42
  case .downloadFailed:
41
43
  return NSLocalizedString("Bundle could not be downloaded.", comment: "downloadFailed")
44
+ case .httpTimeout:
45
+ return NSLocalizedString("Request timed out.", comment: "httpTimeout")
42
46
  case .signatureMissing:
43
47
  return NSLocalizedString("Bundle does not contain a signature.", comment: "signatureMissing")
44
48
  case .signatureVerificationFailed:
45
49
  return NSLocalizedString("Signature verification failed.", comment: "signatureVerificationFailed")
50
+ case .syncInProgress:
51
+ return NSLocalizedString("Sync is already in progress.", comment: "syncInProgress")
46
52
  case .urlMissing:
47
53
  return NSLocalizedString("url must be provided.", comment: "urlMissing")
48
54
  }
@@ -89,7 +89,7 @@ import CommonCrypto
89
89
  }
90
90
 
91
91
  @objc public func getChannel(completion: @escaping (Result?, Error?) -> Void) {
92
- let channel = preferences.getChannel()
92
+ let channel = getChannel()
93
93
  let result = GetChannelResult(channel: channel)
94
94
  completion(result, nil)
95
95
  }
@@ -356,11 +356,22 @@ import CommonCrypto
356
356
  let temporaryZipFileUrl = self.cachesDirectoryUrl.appendingPathComponent(timestamp + ".zip")
357
357
  return (temporaryZipFileUrl, [.createIntermediateDirectories])
358
358
  }
359
-
360
- AF.download(url, to: destination).validate().response { response in
359
+ var request = URLRequest(url: URL(string: url)!)
360
+ request.httpMethod = HTTPMethod.get.rawValue
361
+ request.timeoutInterval = Double(config.httpTimeout) / 1000.0
362
+ AF.download(request, to: destination).validate().response { response in
361
363
  if let error = response.error {
362
364
  CAPLog.print("[", LiveUpdatePlugin.tag, "] ", error)
363
- completion(nil, CustomError.downloadFailed)
365
+ if let urlError = error.underlyingError as? URLError {
366
+ switch urlError.code {
367
+ case .timedOut:
368
+ completion(nil, CustomError.httpTimeout)
369
+ default:
370
+ completion(nil, CustomError.downloadFailed)
371
+ }
372
+ } else {
373
+ completion(nil, CustomError.downloadFailed)
374
+ }
364
375
  return
365
376
  }
366
377
  completion(response.fileURL, nil)
@@ -372,7 +383,7 @@ import CommonCrypto
372
383
  parameters["appVersionCode"] = getVersionCode()
373
384
  parameters["appVersionName"] = getVersionName()
374
385
  parameters["bundleId"] = getCurrentBundleId()
375
- parameters["channelName"] = preferences.getChannel()
386
+ parameters["channelName"] = getChannel()
376
387
  parameters["customId"] = preferences.getCustomId()
377
388
  parameters["deviceId"] = getDeviceId()
378
389
  parameters["osVersion"] = UIDevice.current.systemVersion
@@ -384,35 +395,34 @@ import CommonCrypto
384
395
  host = "api.cloud.capawesome.eu"
385
396
  }
386
397
  }
387
- let url = URL(string: "https://\(host)/v1/apps/\(config.appId ?? "")/bundles/latest")!
388
- AF.request(url, method: .get, parameters: parameters).validate().responseDecodable(of: GetLatestBundleResponse.self) { response in
398
+ var urlComponents = URLComponents(string: "https://\(host)/v1/apps/\(config.appId ?? "")/bundles/latest")!
399
+ urlComponents.queryItems = parameters.map { URLQueryItem(name: $0.key, value: $0.value) }
400
+ var request = URLRequest(url: urlComponents.url!)
401
+ request.httpMethod = HTTPMethod.get.rawValue
402
+ request.timeoutInterval = Double(config.httpTimeout) / 1000.0
403
+ AF.request(request).validate().responseDecodable(of: GetLatestBundleResponse.self) { response in
389
404
  CAPLog.print("[", LiveUpdatePlugin.tag, "] Fetching latest bundle from ", response.request?.url?.absoluteString ?? "")
390
405
  if let error = response.error {
391
406
  CAPLog.print("[", LiveUpdatePlugin.tag, "] ", error)
392
407
  if let data = response.data {
393
408
  CAPLog.print("[", LiveUpdatePlugin.tag, "] ", String(decoding: data, as: UTF8.self))
394
409
  }
395
- completion(nil, error)
410
+ if let urlError = error.underlyingError as? URLError {
411
+ switch urlError.code {
412
+ case .timedOut:
413
+ completion(nil, CustomError.httpTimeout)
414
+ default:
415
+ completion(nil, error)
416
+ }
417
+ } else {
418
+ completion(nil, error)
419
+ }
396
420
  return
397
421
  }
398
422
  completion(response.value, nil)
399
423
  }
400
424
  }
401
425
 
402
- private func getVersionCode() -> String {
403
- guard let appVersionCode = Bundle.main.infoDictionary?["CFBundleVersion"] as? String else {
404
- return ""
405
- }
406
- return appVersionCode
407
- }
408
-
409
- private func getVersionName() -> String {
410
- guard let appVersionName = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else {
411
- return ""
412
- }
413
- return appVersionName
414
- }
415
-
416
426
  private func getBundleIds() -> [String] {
417
427
  let url = libraryDirectoryUrl.appendingPathComponent(bundlesDirectory)
418
428
  do {
@@ -427,6 +437,17 @@ import CommonCrypto
427
437
  }
428
438
  }
429
439
 
440
+ private func getChannel() -> String? {
441
+ var channel: String?
442
+ if let _ = config.defaultChannel {
443
+ channel = config.defaultChannel
444
+ }
445
+ if let _ = preferences.getChannel() {
446
+ channel = preferences.getChannel()
447
+ }
448
+ return channel
449
+ }
450
+
430
451
  /// - Returns: The current bundle ID (`public` for the built-in bundle) or `nil` if no view controller was found.
431
452
  private func getCurrentBundleId() -> String? {
432
453
  guard let path = getCurrentCapacitorServerPath() else {
@@ -463,6 +484,20 @@ import CommonCrypto
463
484
  return defaultCapacitorServerPath
464
485
  }
465
486
 
487
+ private func getVersionCode() -> String {
488
+ guard let appVersionCode = Bundle.main.infoDictionary?["CFBundleVersion"] as? String else {
489
+ return ""
490
+ }
491
+ return appVersionCode
492
+ }
493
+
494
+ private func getVersionName() -> String {
495
+ guard let appVersionName = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else {
496
+ return ""
497
+ }
498
+ return appVersionName
499
+ }
500
+
466
501
  private func hasBundle(bundleId: String) -> Bool {
467
502
  let path = buildBundlePathFor(bundleId: bundleId)
468
503
  return FileManager.default.fileExists(atPath: path)
@@ -486,11 +521,28 @@ import CommonCrypto
486
521
  }
487
522
 
488
523
  private func searchIndexHtmlFile(url: URL) -> URL? {
489
- let enumerator = FileManager.default.enumerator(atPath: url.path)
490
- while let element = enumerator?.nextObject() as? String {
491
- if element.hasSuffix("index.html") {
492
- return url.appendingPathComponent(element)
524
+ do {
525
+ let directoryContents = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: [])
526
+ if directoryContents.isEmpty {
527
+ return nil
493
528
  }
529
+ let fileNames = directoryContents.map { $0.lastPathComponent }
530
+ if fileNames.contains("index.html") {
531
+ return url.appendingPathComponent("index.html")
532
+ } else {
533
+ for fileUrl in directoryContents {
534
+ var isDirectory: ObjCBool = false
535
+ if FileManager.default.fileExists(atPath: fileUrl.path, isDirectory: &isDirectory) {
536
+ if isDirectory.boolValue {
537
+ if let indexHtmlFile = searchIndexHtmlFile(url: fileUrl) {
538
+ return indexHtmlFile
539
+ }
540
+ }
541
+ }
542
+ }
543
+ }
544
+ } catch {
545
+ CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to search index.html file: \(error.localizedDescription)")
494
546
  }
495
547
  return nil
496
548
  }
@@ -1,7 +1,9 @@
1
1
  public struct LiveUpdateConfig {
2
2
  var appId: String?
3
3
  var autoDeleteBundles = false
4
+ var defaultChannel: String?
4
5
  var enabled = true
6
+ var httpTimeout = 60000
5
7
  var location: String?
6
8
  var publicKey: String?
7
9
  var readyTimeout = 10000
@@ -8,11 +8,12 @@ import Capacitor
8
8
  @objc(LiveUpdatePlugin)
9
9
  public class LiveUpdatePlugin: CAPPlugin {
10
10
  public static let tag = "LiveUpdate"
11
- public static let version = "6.2.0"
11
+ public static let version = "6.4.0"
12
12
  public static let userDefaultsPrefix = "CapawesomeLiveUpdate" // DO NOT CHANGE
13
13
 
14
14
  private var config: LiveUpdateConfig?
15
15
  private var implementation: LiveUpdate?
16
+ private var syncInProgress = false
16
17
 
17
18
  override public func load() {
18
19
  self.config = liveUpdateConfig()
@@ -222,7 +223,15 @@ public class LiveUpdatePlugin: CAPPlugin {
222
223
  call.reject(CustomError.appIdMissing.localizedDescription)
223
224
  return
224
225
  }
226
+
227
+ guard !syncInProgress else {
228
+ call.reject(CustomError.syncInProgress.localizedDescription)
229
+ return
230
+ }
231
+ syncInProgress = true
232
+
225
233
  implementation?.sync(completion: { result, error in
234
+ self.syncInProgress = false
226
235
  if let error = error {
227
236
  CAPLog.print("[", LiveUpdatePlugin.tag, "] ", error)
228
237
  call.reject(error.localizedDescription)
@@ -239,7 +248,9 @@ public class LiveUpdatePlugin: CAPPlugin {
239
248
 
240
249
  config.appId = getConfig().getString("appId", config.appId)
241
250
  config.autoDeleteBundles = getConfig().getBoolean("autoDeleteBundles", config.autoDeleteBundles)
251
+ config.defaultChannel = getConfig().getString("defaultChannel", config.defaultChannel)
242
252
  config.enabled = getConfig().getBoolean("enabled", config.enabled)
253
+ config.httpTimeout = getConfig().getInt("httpTimeout", config.httpTimeout)
243
254
  config.location = getConfig().getString("location", config.location)
244
255
  config.publicKey = getConfig().getString("publicKey", config.publicKey)
245
256
  config.readyTimeout = getConfig().getInt("readyTimeout", config.readyTimeout)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capawesome/capacitor-live-update",
3
- "version": "6.2.0",
3
+ "version": "6.4.0",
4
4
  "description": "Capacitor plugin to update your app remotely in real-time.",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",