Skip to content

Commit

Permalink
refactor(live-update)!: remove the enabled and resetOnUpdate conf…
Browse files Browse the repository at this point in the history
…iguration options (#404)

* refactor(live-update)!: remove the `enabled` and `resetOnUpdate` configuration options

Close #335
Close #256

* wip

* style: format
  • Loading branch information
robingenz authored Jan 30, 2025
1 parent 6dacc0a commit f90c1b9
Show file tree
Hide file tree
Showing 18 changed files with 49 additions and 162 deletions.
5 changes: 5 additions & 0 deletions .changeset/thirty-weeks-pump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@capawesome/capacitor-live-update': major
---

refactor: remove the `enabled` and `resetOnUpdate` configuration options
8 changes: 8 additions & 0 deletions packages/live-update/BREAKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ The `setBundle()` method has been replaced by the `setNextBundle()` method.

The `checksum` property has been removed from the `DownloadBundleOptions` interface. The server should now return a `X-Checksum` header instead.

### `enabled` configuration option

The `enabled` configuration option has been removed. The plugin is now always enabled.

### `location` configuration option

The `location` configuration option has been replaced by the `serverDomain` configuration option.
Expand All @@ -28,3 +32,7 @@ The `location` configuration option has been replaced by the `serverDomain` conf

The default value of the `readyTimeout` configuration option has been changed from `10000` to `0` to disable the timeout by default.
However, it is strongly **recommended** to configure this option so that the plugin can roll back to the default bundle in case of problems.

### `resetOnUpdate` configuration option

The `resetOnUpdate` configuration option has been removed. Capacitor always resets the app to the default bundle during a native update.
12 changes: 3 additions & 9 deletions packages/live-update/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,9 @@ We recommend to declare [`CA92.1`](https://developer.apple.com/documentation/bun
| **`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 |
| **`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 |
| **`defaultChannel`** | <code>string</code> | The default channel of the app. | | 6.3.0 |
| **`enabled`** | <code>boolean</code> | Whether or not the plugin is enabled. | <code>true</code> | 5.0.0 |
| **`httpTimeout`** | <code>number</code> | The timeout in milliseconds for HTTP requests. | <code>60000</code> | 6.4.0 |
| **`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 |
| **`readyTimeout`** | <code>number</code> | The timeout in milliseconds to wait for the app to be ready before resetting to the default bundle. It is strongly **recommended** to configure this option so that the plugin can roll back to the default bundle in case of problems. If configured, the plugin will wait for the app to call the `ready()` method before resetting to the default bundle. Set to `0` to disable the timeout. | <code>0</code> | 5.0.0 |
| **`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 |
| **`serverDomain`** | <code>string</code> | The API domain of the [Capawesome Cloud](https://cloud.capawesome.io) server. | <code>'api.cloud.capawesome.io'</code> | 7.0.0 |

### Examples
Expand All @@ -89,11 +87,9 @@ In `capacitor.config.json`:
"appId": '6e351b4f-69a7-415e-a057-4567df7ffe94',
"autoDeleteBundles": undefined,
"defaultChannel": 'production',
"enabled": undefined,
"httpTimeout": undefined,
"publicKey": '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDodf1SD0OOn6hIlDuKBza0Ed0OqtwyVJwiyjmE9BJaZ7y8ZUfcF+SKmd0l2cDPM45XIg2tAFux5n29uoKyHwSt+6tCi5CJA5Z1/1eZruRRqABLonV77KS3HUtvOgqRLDnKSV89dYZkM++NwmzOPgIF422mvc+VukcVOBfc8/AHQIDAQAB-----END PUBLIC KEY-----',
"readyTimeout": 10000,
"resetOnUpdate": undefined,
"serverDomain": undefined
}
}
Expand All @@ -113,11 +109,9 @@ const config: CapacitorConfig = {
appId: '6e351b4f-69a7-415e-a057-4567df7ffe94',
autoDeleteBundles: undefined,
defaultChannel: 'production',
enabled: undefined,
httpTimeout: undefined,
publicKey: '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDodf1SD0OOn6hIlDuKBza0Ed0OqtwyVJwiyjmE9BJaZ7y8ZUfcF+SKmd0l2cDPM45XIg2tAFux5n29uoKyHwSt+6tCi5CJA5Z1/1eZruRRqABLonV77KS3HUtvOgqRLDnKSV89dYZkM++NwmzOPgIF422mvc+VukcVOBfc8/AHQIDAQAB-----END PUBLIC KEY-----',
readyTimeout: 10000,
resetOnUpdate: undefined,
serverDomain: undefined,
},
},
Expand Down Expand Up @@ -820,10 +814,10 @@ So if you make local changes to your app and execute `npx cap run`, for example,
You then have three options to get back to the default bundle:

1. **Reset**: Call the [`reset()`](#reset) method to reset the app to the default bundle.
2. **Reinstall**: Reinstall the app to use the default bundle.
3. **Update**: Increase the `versionCode`/`CFBundleVersion` so that the plugin automatically performs a reset (assuming the [`resetOnUpdate`](#configuration) configuration option is active).
2. **Reinstall**: Reinstall the app to remove the live update bundle.
3. **Update**: Increase the native version code of your app so that Capacitor automatically resets to the default bundle.

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.
However, this is only a problem during development. It is not a problem in production, as Capacitor automatically resets to the default bundle after a native update.

## Changelog

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,9 @@ public LiveUpdate(@NonNull LiveUpdateConfig config, @NonNull LiveUpdatePlugin pl
this.preferences = new LiveUpdatePreferences(plugin.getContext());
this.webViewSettingsEditor = plugin.getContext().getSharedPreferences(WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE).edit();

if (config.getEnabled()) {
if (wasUpdated() && config.getResetOnUpdate()) {
Logger.debug(LiveUpdatePlugin.TAG, "App was updated. Resetting to default bundle.");
reset();
} else {
startRollbackTimer();
}
saveCurrentVersionCode();
}
// Start the rollback timer to rollback to the default bundle
// if the app is not ready after a certain time
startRollbackTimer();
}

public void deleteBundle(@NonNull DeleteBundleOptions options, @NonNull EmptyCallback callback) {
Expand Down Expand Up @@ -820,11 +814,6 @@ private void rollback() {
}
}

private void saveCurrentVersionCode() throws PackageManager.NameNotFoundException {
int currentVersionCode = getPackageInfo().versionCode;
preferences.setLastVersionCode(currentVersionCode);
}

@Nullable
private File searchIndexHtmlFile(@NonNull File directory) {
File[] files = directory.listFiles();
Expand Down Expand Up @@ -965,12 +954,6 @@ private boolean verifySignatureForFile(@NonNull File file, @NonNull String signa
}
}

private boolean wasUpdated() throws PackageManager.NameNotFoundException {
int lastVersionCode = preferences.getLastVersionCode();
int currentVersionCode = getPackageInfo().versionCode;
return lastVersionCode != currentVersionCode;
}

private PackageInfo getPackageInfo() throws PackageManager.NameNotFoundException {
String packageName = this.plugin.getContext().getPackageName();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,12 @@ public class LiveUpdateConfig {
@Nullable
private String defaultChannel = null;

private boolean enabled = true;
private int httpTimeout = 60000;

@Nullable
private String publicKey = null;

private int readyTimeout = 0;
private boolean resetOnUpdate = true;
private String serverDomain = "api.cloud.capawesome.io";

@Nullable
Expand All @@ -37,10 +35,6 @@ public String getDefaultChannel() {
return defaultChannel;
}

public boolean getEnabled() {
return enabled;
}

public int getHttpTimeout() {
return httpTimeout;
}
Expand All @@ -54,10 +48,6 @@ public int getReadyTimeout() {
return readyTimeout;
}

public boolean getResetOnUpdate() {
return resetOnUpdate;
}

public String getServerDomain() {
return serverDomain;
}
Expand All @@ -74,10 +64,6 @@ public void setDefaultChannel(@Nullable String defaultChannel) {
this.defaultChannel = defaultChannel;
}

public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public void setHttpTimeout(int httpTimeout) {
this.httpTimeout = httpTimeout;
}
Expand All @@ -90,10 +76,6 @@ public void setReadyTimeout(int readyTimeout) {
this.readyTimeout = readyTimeout;
}

public void setResetOnUpdate(boolean resetOnUpdate) {
this.resetOnUpdate = resetOnUpdate;
}

public void setServerDomain(String serverDomain) {
this.serverDomain = serverDomain;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -482,16 +482,12 @@ private LiveUpdateConfig getLiveUpdateConfig() {
config.setAutoDeleteBundles(autoDeleteBundles);
String defaultChannel = getConfig().getString("defaultChannel", config.getDefaultChannel());
config.setDefaultChannel(defaultChannel);
boolean enabled = getConfig().getBoolean("enabled", config.getEnabled());
config.setEnabled(enabled);
int httpTimeout = getConfig().getInt("httpTimeout", config.getHttpTimeout());
config.setHttpTimeout(httpTimeout);
String publicKey = getConfig().getString("publicKey", config.getPublicKey());
config.setPublicKey(publicKey);
int readyTimeout = getConfig().getInt("readyTimeout", config.getReadyTimeout());
config.setReadyTimeout(readyTimeout);
boolean resetOnUpdate = getConfig().getBoolean("resetOnUpdate", config.getResetOnUpdate());
config.setResetOnUpdate(resetOnUpdate);
String serverDomain = getConfig().getString("serverDomain", config.getServerDomain());
config.setServerDomain(serverDomain);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ public class LiveUpdatePreferences {
private final String channelKey = "channel"; // DO NOT CHANGE
private final String deviceIdKey = "deviceId"; // DO NOT CHANGE
private final String customIdKey = "customId"; // DO NOT CHANGE
private final String lastVersionCodeKey = "lastVersionCode"; // DO NOT CHANGE
private final String previousBundleIdKey = "previousBundleId"; // DO NOT CHANGE

public LiveUpdatePreferences(@NonNull Context context) {
Expand Down Expand Up @@ -49,10 +48,6 @@ public String getDeviceIdForApp(@Nullable String appId) {
}
}

public int getLastVersionCode() {
return context.getSharedPreferences(LiveUpdatePlugin.SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).getInt(lastVersionCodeKey, 0);
}

@Nullable
public String getPreviousBundleId() {
return context
Expand Down Expand Up @@ -87,11 +82,6 @@ public void setDeviceIdForApp(@Nullable String appId, @NonNull String deviceId)
settingsEditor.apply();
}

public void setLastVersionCode(int lastVersionCode) {
settingsEditor.putInt(lastVersionCodeKey, lastVersionCode);
settingsEditor.apply();
}

public void setPreviousBundleId(@Nullable String bundleId) {
if (bundleId == null) {
settingsEditor.remove(previousBundleIdKey);
Expand Down
1 change: 0 additions & 1 deletion packages/live-update/docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

The following test cases should be covered when testing the plugin:

- [ ] Verify that the app is reset to the default bundle during a native update (see `resetOnUpdate` configuration option).
- [ ] Verify that the live update fails when the wrong public key is used.
- [ ] Verify that the live update fails when the checksum of the downloaded file does not match the expected checksum.
- [ ] Verify that a rollback is performed when the `ready()` method is not called within the `readyTimeout` period.
Expand Down
3 changes: 3 additions & 0 deletions packages/live-update/example/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ node_modules/
.DS_Store
.sourcemaps
dist/

*.pem
*.crt
11 changes: 0 additions & 11 deletions packages/live-update/example/capacitor.config.json

This file was deleted.

18 changes: 18 additions & 0 deletions packages/live-update/example/capacitor.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// <reference types="@capawesome/capacitor-live-update" />
import type { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
appId: 'com.example.plugin',
appName: 'example',
webDir: 'dist',
plugins: {
LiveUpdate: {
appId: '46d641f5-2703-4e99-b498-006192c70484',
// publicKey:
// '-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoMb8yazAm5rY2ys6nQDgsKdGlQiLStdd7VBYjKKaRT2xwKXCPtsmeBtjvVUH//ae3IHbz7w79CoawKPDqKAPtqILWc3VyAR2hQ6ftoGVxYq5NyuV4UdZozEn9v45Se91gvi7Jc+NakZaPYBAG695X1d9iCdktWINqexQjZWZUEnQjdLoKSdlbtyU0GYiDTkPDUruVBx1YF7W7qOIEr5uhbPF2HKtU6VvsoKOHePfIZQa12rn0Q5s69jb0++ro1zHMZSV6eJox6RJg/7uHOKo5Ri8FRhHHZQxxbP/Pp4FTHvpfQyav2yOuq0l9mAAgzi9txWMfB1AXwZbbUceuE7UjwIDAQAB-----END PUBLIC KEY-----',
readyTimeout: 10000,
},
},
};

export default config;
10 changes: 5 additions & 5 deletions packages/live-update/example/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/live-update/example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
},
"devDependencies": {
"@capacitor/cli": "latest",
"@capawesome/cli": "^1.3.0",
"@capawesome/cli": "^1.4.0",
"vite": "^2.9.13"
}
}
40 changes: 3 additions & 37 deletions packages/live-update/ios/Plugin/LiveUpdate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,9 @@ import CommonCrypto
self.preferences = LiveUpdatePreferences()
super.init()

if config.enabled {
if wasUpdated() && config.resetOnUpdate {
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "App was updated. Resetting to default bundle.")
reset()
} else {
startRollbackTimer()
}
saveCurrentBundleShortVersionStringAndBundleVersion()
}
// Start the rollback timer to rollback to the default bundle
// if the app is not ready after a certain time
startRollbackTimer()
}

@objc public func deleteBundle(_ options: DeleteBundleOptions, completion: @escaping (Error?) -> Void) {
Expand Down Expand Up @@ -647,13 +641,6 @@ import CommonCrypto
}
}

private func saveCurrentBundleShortVersionStringAndBundleVersion() {
let currentBundleShortVersionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
preferences.setLastBundleShortVersionString(currentBundleShortVersionString)
let currentBundleVersion = Bundle.main.infoDictionary?["CFBundleVersion"] as? String
preferences.setLastBundleVersion(currentBundleVersion)
}

private func searchIndexHtmlFile(url: URL) -> URL? {
do {
let directoryContents = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: [])
Expand Down Expand Up @@ -825,27 +812,6 @@ import CommonCrypto
}
return verificationResult
}

private func wasUpdated() -> Bool {
guard let lastBundleShortVersionString = preferences.getLastBundleShortVersionString() else {
// If the last bundle short version is not set, the app may have been updated
return true
}
guard let lastBundleVersionString = preferences.getLastBundleVersion() else {
// If the last bundle version is not set, the app may have been updated
return true
}
guard let currentBundleShortVersionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else {
// If the current bundle short version is not set, return true for safety reasons
return true
}
guard let currentBundleVersionString = Bundle.main.infoDictionary?["CFBundleVersion"] as? String else {
// If the current bundle version is not set, return true for safety reasons
return true
}
// `CFBundleVersion` is not unique across different versions of the app
return lastBundleShortVersionString + "_" + lastBundleVersionString != currentBundleShortVersionString + "_" + currentBundleVersionString
}
}

extension DispatchQueue {
Expand Down
2 changes: 0 additions & 2 deletions packages/live-update/ios/Plugin/LiveUpdateConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ public struct LiveUpdateConfig {
var appId: String?
var autoDeleteBundles = false
var defaultChannel: String?
var enabled = true
var httpTimeout = 60000
var publicKey: String?
var readyTimeout = 0
var resetOnUpdate = true
var serverDomain = "api.cloud.capawesome.io"
}
Loading

0 comments on commit f90c1b9

Please sign in to comment.