@capgo/capacitor-updater 6.14.11 → 7.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapgoCapacitorUpdater.podspec +1 -1
- package/Package.swift +4 -4
- package/README.md +7 -4
- package/android/build.gradle +9 -9
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdater.java +1 -14
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +1 -5
- package/dist/docs.json +0 -12
- package/dist/esm/definitions.d.ts +0 -8
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Plugin/CapacitorUpdater.swift +71 -22
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +3 -21
- package/ios/Plugin/CryptoCipherV2.swift +33 -22
- package/package.json +12 -11
- package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipher.java +0 -214
- package/ios/Plugin/CryptoCipher.swift +0 -288
|
@@ -11,7 +11,7 @@ Pod::Spec.new do |s|
|
|
|
11
11
|
s.author = package['author']
|
|
12
12
|
s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
|
|
13
13
|
s.source_files = 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}'
|
|
14
|
-
s.ios.deployment_target
|
|
14
|
+
s.ios.deployment_target = '14.0'
|
|
15
15
|
s.dependency 'Capacitor'
|
|
16
16
|
s.dependency 'SSZipArchive', '2.4.3'
|
|
17
17
|
s.dependency 'Alamofire'
|
package/Package.swift
CHANGED
|
@@ -3,17 +3,17 @@ import PackageDescription
|
|
|
3
3
|
|
|
4
4
|
let package = Package(
|
|
5
5
|
name: "CapgoCapacitorUpdater",
|
|
6
|
-
platforms: [.iOS(.
|
|
6
|
+
platforms: [.iOS(.v14)],
|
|
7
7
|
products: [
|
|
8
8
|
.library(
|
|
9
9
|
name: "CapgoCapacitorUpdater",
|
|
10
10
|
targets: ["CapacitorUpdaterPlugin"])
|
|
11
11
|
],
|
|
12
12
|
dependencies: [
|
|
13
|
-
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git",
|
|
13
|
+
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "7.0.0"),
|
|
14
14
|
.package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.10.2")),
|
|
15
|
-
.package(url: "https://github.com/ZipArchive/ZipArchive.git",
|
|
16
|
-
.package(url: "https://github.com/
|
|
15
|
+
.package(url: "https://github.com/ZipArchive/ZipArchive.git", .upToNextMajor(from: "2.6.0")),
|
|
16
|
+
.package(url: "https://github.com/mxcl/Version.git", .upToNextMajor(from: "2.1.0"))
|
|
17
17
|
],
|
|
18
18
|
targets: [
|
|
19
19
|
.target(
|
package/README.md
CHANGED
|
@@ -34,11 +34,17 @@ Join the [discord](https://discord.gg/VnYRvBfgA6) to get help.
|
|
|
34
34
|
## Documentation
|
|
35
35
|
I maintain a more user-friendly and complete [documentation here](https://capgo.app/docs/).
|
|
36
36
|
|
|
37
|
+
## Migration to v7
|
|
38
|
+
|
|
39
|
+
- `privateKey` is not available anymore, it was used for the old encryption method. to migrate foolow this guide : [https://capgo.app/docs/plugin/cloud-mode/getting-started/](https://capgo.app/docs/cli/migrations/encryption/)
|
|
40
|
+
- To capacitor v7 : [https://capacitorjs.com/docs/updating/7-0](https://capacitorjs.com/docs/updating/7-0)
|
|
41
|
+
|
|
37
42
|
## Compatibility
|
|
38
43
|
|
|
39
44
|
| Plugin version | Capacitor compatibility | Maintained |
|
|
40
45
|
| -------------- | ----------------------- | ----------------- |
|
|
41
|
-
|
|
|
46
|
+
| v7.\*.\* | v7.\*.\* | ✅ |
|
|
47
|
+
| v6.\*.\* | v6.\*.\* | Critical bug only |
|
|
42
48
|
| v5.\*.\* | v5.\*.\* | Critical bug only |
|
|
43
49
|
| v4.\*.\* | v4.\*.\* | ⚠️ Deprecated |
|
|
44
50
|
| v3.\*.\* | v3.\*.\* | ⚠️ Deprecated |
|
|
@@ -208,7 +214,6 @@ CapacitorUpdater can be configured with these options:
|
|
|
208
214
|
| **`updateUrl`** | <code>string</code> | Configure the URL / endpoint to which update checks are sent. Only available for Android and iOS. | <code>https://plugin.capgo.app/updates</code> | |
|
|
209
215
|
| **`channelUrl`** | <code>string</code> | Configure the URL / endpoint for channel operations. Only available for Android and iOS. | <code>https://plugin.capgo.app/channel_self</code> | |
|
|
210
216
|
| **`statsUrl`** | <code>string</code> | Configure the URL / endpoint to which update statistics are sent. Only available for Android and iOS. Set to "" to disable stats reporting. | <code>https://plugin.capgo.app/stats</code> | |
|
|
211
|
-
| **`privateKey`** | <code>string</code> | Configure the private key for end to end live update encryption. Only available for Android and iOS. Deprecated in version 6.2.0. will be removed in version 7.0.0. | <code>undefined</code> | |
|
|
212
217
|
| **`publicKey`** | <code>string</code> | Configure the public key for end to end live update encryption Version 2 Only available for Android and iOS. | <code>undefined</code> | 6.2.0 |
|
|
213
218
|
| **`version`** | <code>string</code> | Configure the current version of the app. This will be used for the first update request. If not set, the plugin will get the version from the native code. Only available for Android and iOS. | <code>undefined</code> | 4.17.48 |
|
|
214
219
|
| **`directUpdate`** | <code>boolean</code> | Make the plugin direct install the update when the app what just updated/installed. Only for autoUpdate mode. Only available for Android and iOS. | <code>undefined</code> | 5.1.0 |
|
|
@@ -242,7 +247,6 @@ In `capacitor.config.json`:
|
|
|
242
247
|
"updateUrl": https://example.com/api/auto_update,
|
|
243
248
|
"channelUrl": https://example.com/api/channel,
|
|
244
249
|
"statsUrl": https://example.com/api/stats,
|
|
245
|
-
"privateKey": undefined,
|
|
246
250
|
"publicKey": undefined,
|
|
247
251
|
"version": undefined,
|
|
248
252
|
"directUpdate": undefined,
|
|
@@ -282,7 +286,6 @@ const config: CapacitorConfig = {
|
|
|
282
286
|
updateUrl: https://example.com/api/auto_update,
|
|
283
287
|
channelUrl: https://example.com/api/channel,
|
|
284
288
|
statsUrl: https://example.com/api/stats,
|
|
285
|
-
privateKey: undefined,
|
|
286
289
|
publicKey: undefined,
|
|
287
290
|
version: undefined,
|
|
288
291
|
directUpdate: undefined,
|
package/android/build.gradle
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
ext {
|
|
2
2
|
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
|
|
3
|
-
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.
|
|
4
|
-
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.1
|
|
5
|
-
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.
|
|
3
|
+
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.0'
|
|
4
|
+
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1'
|
|
5
|
+
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1'
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
buildscript {
|
|
@@ -11,7 +11,7 @@ buildscript {
|
|
|
11
11
|
mavenCentral()
|
|
12
12
|
}
|
|
13
13
|
dependencies {
|
|
14
|
-
classpath 'com.android.tools.build:gradle:8.
|
|
14
|
+
classpath 'com.android.tools.build:gradle:8.7.2'
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -19,10 +19,10 @@ apply plugin: 'com.android.library'
|
|
|
19
19
|
|
|
20
20
|
android {
|
|
21
21
|
namespace "ee.forgr.capacitor_updater"
|
|
22
|
-
compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion :
|
|
22
|
+
compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35
|
|
23
23
|
defaultConfig {
|
|
24
|
-
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion :
|
|
25
|
-
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion :
|
|
24
|
+
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23
|
|
25
|
+
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35
|
|
26
26
|
versionCode 1
|
|
27
27
|
versionName "1.0"
|
|
28
28
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
@@ -37,8 +37,8 @@ android {
|
|
|
37
37
|
abortOnError false
|
|
38
38
|
}
|
|
39
39
|
compileOptions {
|
|
40
|
-
sourceCompatibility JavaVersion.
|
|
41
|
-
targetCompatibility JavaVersion.
|
|
40
|
+
sourceCompatibility JavaVersion.VERSION_21
|
|
41
|
+
targetCompatibility JavaVersion.VERSION_21
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -10,7 +10,6 @@ import android.app.Activity;
|
|
|
10
10
|
import android.content.Context;
|
|
11
11
|
import android.content.SharedPreferences;
|
|
12
12
|
import android.os.Build;
|
|
13
|
-
import android.util.Base64;
|
|
14
13
|
import android.util.Log;
|
|
15
14
|
import androidx.annotation.NonNull;
|
|
16
15
|
import androidx.lifecycle.LifecycleOwner;
|
|
@@ -22,27 +21,20 @@ import com.getcapacitor.plugin.WebView;
|
|
|
22
21
|
import com.google.common.util.concurrent.Futures;
|
|
23
22
|
import com.google.common.util.concurrent.ListenableFuture;
|
|
24
23
|
import java.io.BufferedInputStream;
|
|
25
|
-
import java.io.DataInputStream;
|
|
26
24
|
import java.io.File;
|
|
27
25
|
import java.io.FileInputStream;
|
|
28
26
|
import java.io.FileNotFoundException;
|
|
29
27
|
import java.io.FileOutputStream;
|
|
30
28
|
import java.io.FilenameFilter;
|
|
31
29
|
import java.io.IOException;
|
|
32
|
-
import java.security.GeneralSecurityException;
|
|
33
|
-
import java.security.MessageDigest;
|
|
34
|
-
import java.security.PrivateKey;
|
|
35
|
-
import java.security.PublicKey;
|
|
36
30
|
import java.security.SecureRandom;
|
|
37
31
|
import java.util.ArrayList;
|
|
38
32
|
import java.util.Date;
|
|
39
33
|
import java.util.Iterator;
|
|
40
34
|
import java.util.List;
|
|
41
35
|
import java.util.Objects;
|
|
42
|
-
import java.util.zip.CRC32;
|
|
43
36
|
import java.util.zip.ZipEntry;
|
|
44
37
|
import java.util.zip.ZipInputStream;
|
|
45
|
-
import javax.crypto.SecretKey;
|
|
46
38
|
import okhttp3.*;
|
|
47
39
|
import org.json.JSONArray;
|
|
48
40
|
import org.json.JSONException;
|
|
@@ -78,9 +70,7 @@ public class CapacitorUpdater {
|
|
|
78
70
|
public String channelUrl = "";
|
|
79
71
|
public String defaultChannel = "";
|
|
80
72
|
public String appId = "";
|
|
81
|
-
public String privateKey = "";
|
|
82
73
|
public String publicKey = "";
|
|
83
|
-
public boolean hasOldPrivateKeyPropertyInConfig = false;
|
|
84
74
|
public String deviceID = "";
|
|
85
75
|
public int timeout = 20000;
|
|
86
76
|
|
|
@@ -329,13 +319,10 @@ public class CapacitorUpdater {
|
|
|
329
319
|
|
|
330
320
|
if (!isManifest) {
|
|
331
321
|
String checksumDecrypted = Objects.requireNonNullElse(checksumRes, "");
|
|
332
|
-
if (!
|
|
322
|
+
if (!sessionKey.isEmpty()) {
|
|
333
323
|
CryptoCipherV2.decryptFile(downloaded, publicKey, sessionKey);
|
|
334
324
|
checksumDecrypted = CryptoCipherV2.decryptChecksum(checksumRes, publicKey);
|
|
335
325
|
checksum = CryptoCipherV2.calcChecksum(downloaded);
|
|
336
|
-
} else {
|
|
337
|
-
CryptoCipher.decryptFile(downloaded, privateKey, sessionKey, version);
|
|
338
|
-
checksum = CryptoCipher.calcChecksum(downloaded);
|
|
339
326
|
}
|
|
340
327
|
if ((!checksumDecrypted.isEmpty() || !this.publicKey.isEmpty()) && !checksumDecrypted.equals(checksum)) {
|
|
341
328
|
Log.e(CapacitorUpdater.TAG, "Error checksum '" + checksumDecrypted + "' '" + checksum + "' '");
|
|
@@ -57,7 +57,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
57
57
|
private static final String statsUrlDefault = "https://plugin.capgo.app/stats";
|
|
58
58
|
private static final String channelUrlDefault = "https://plugin.capgo.app/channel_self";
|
|
59
59
|
|
|
60
|
-
private final String PLUGIN_VERSION = "
|
|
60
|
+
private final String PLUGIN_VERSION = "7.0.2";
|
|
61
61
|
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
62
62
|
|
|
63
63
|
private SharedPreferences.Editor editor;
|
|
@@ -162,10 +162,6 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
162
162
|
}
|
|
163
163
|
Log.i(CapacitorUpdater.TAG, "appId: " + implementation.appId);
|
|
164
164
|
this.implementation.publicKey = this.getConfig().getString("publicKey", "");
|
|
165
|
-
this.implementation.privateKey = this.getConfig().getString("privateKey", "");
|
|
166
|
-
if (this.implementation.privateKey != null && !this.implementation.privateKey.isEmpty()) {
|
|
167
|
-
this.implementation.hasOldPrivateKeyPropertyInConfig = true;
|
|
168
|
-
}
|
|
169
165
|
this.implementation.statsUrl = this.getConfig().getString("statsUrl", statsUrlDefault);
|
|
170
166
|
this.implementation.channelUrl = this.getConfig().getString("channelUrl", channelUrlDefault);
|
|
171
167
|
int userValue = this.getConfig().getInt("periodCheckDelay", 0);
|
package/dist/docs.json
CHANGED
|
@@ -2082,18 +2082,6 @@
|
|
|
2082
2082
|
"complexTypes": [],
|
|
2083
2083
|
"type": "string | undefined"
|
|
2084
2084
|
},
|
|
2085
|
-
{
|
|
2086
|
-
"name": "privateKey",
|
|
2087
|
-
"tags": [
|
|
2088
|
-
{
|
|
2089
|
-
"text": "undefined",
|
|
2090
|
-
"name": "default"
|
|
2091
|
-
}
|
|
2092
|
-
],
|
|
2093
|
-
"docs": "Configure the private key for end to end live update encryption.\n\nOnly available for Android and iOS. Deprecated in version 6.2.0. will be removed in version 7.0.0.",
|
|
2094
|
-
"complexTypes": [],
|
|
2095
|
-
"type": "string | undefined"
|
|
2096
|
-
},
|
|
2097
2085
|
{
|
|
2098
2086
|
"name": "publicKey",
|
|
2099
2087
|
"tags": [
|
|
@@ -86,14 +86,6 @@ declare module '@capacitor/cli' {
|
|
|
86
86
|
* @example https://example.com/api/stats
|
|
87
87
|
*/
|
|
88
88
|
statsUrl?: string;
|
|
89
|
-
/**
|
|
90
|
-
* Configure the private key for end to end live update encryption.
|
|
91
|
-
*
|
|
92
|
-
* Only available for Android and iOS. Deprecated in version 6.2.0. will be removed in version 7.0.0.
|
|
93
|
-
*
|
|
94
|
-
* @default undefined
|
|
95
|
-
*/
|
|
96
|
-
privateKey?: string;
|
|
97
89
|
/**
|
|
98
90
|
* Configure the public key for end to end live update encryption Version 2
|
|
99
91
|
*
|
|
@@ -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://plugin.capgo.app/updates\n * @example https://example.com/api/auto_update\n */\n updateUrl?: string;\n\n /**\n * Configure the URL / endpoint for channel operations.\n *\n * Only available for Android and iOS.\n *\n * @default https://plugin.capgo.app/channel_self\n * @example https://example.com/api/channel\n */\n channelUrl?: 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://plugin.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. Deprecated in version 6.2.0. will be removed in version 7.0.0.\n *\n * @default undefined\n */\n privateKey?: string;\n /**\n * Configure the public key for end to end live update encryption Version 2\n *\n * Only available for Android and iOS.\n *\n * @default undefined\n * @since 6.2.0\n */\n publicKey?: 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 * Configure the CLI to use a local api for testing.\n *\n *\n * @default undefined\n * @since 6.3.3\n */\n localApi?: string;\n /**\n * Configure the CLI to use a local file api for testing.\n *\n *\n * @default undefined\n * @since 6.3.3\n */\n localApiFiles?: string;\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 * Configure the app id for the app in the config.\n *\n * @default undefined\n * @since 6.0.0\n */\n appId?: string;\n\n /**\n * Configure the plugin to keep the URL path after a reload.\n * WARNING: When a reload is triggered, 'window.history' will be cleared.\n *\n * @default false\n * @since 6.8.0\n */\n keepUrlPathAfterReload?: boolean;\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 * @param options The {@link ListOptions} for listing bundles\n * @throws {Error}\n */\n list(options?: ListOptions): 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(options?: GetLatestOptions): 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 * This methods send to Capgo backend a request to link the device ID to the channel. Capgo can accept or refuse depending of the setting of your channel.\n *\n *\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(eventName: 'download', listenerFunc: (state: DownloadEvent) => void): 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(eventName: 'noNeedUpdate', listenerFunc: (state: NoNeedEvent) => void): 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(eventName: 'appReloaded', listenerFunc: () => void): 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(eventName: 'appReady', listenerFunc: (state: AppReadyEvent) => void): Promise<PluginListenerHandle>;\n\n /**\n * Get if auto update is available (not disabled by serverUrl).\n *\n * @returns {Promise<AutoUpdateAvailable>} The availability status for auto update. Evaluates to `false` when serverUrl is set.\n * @throws {Error}\n */\n isAutoUpdateAvailable(): Promise<AutoUpdateAvailable>;\n\n /**\n * Get the next bundle that will be used when the app reloads.\n * Returns null if no next bundle is set.\n *\n * @returns {Promise<BundleInfo | null>} A Promise that resolves with the next bundle information or null\n * @throws {Error}\n * @since 6.8.0\n */\n getNextBundle(): Promise<BundleInfo | null>;\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?: string;\n message?: string;\n}\n\nexport interface GetChannelRes {\n /**\n * Current status of get channel\n *\n * @since 4.8.0\n */\n channel?: string;\n error?: string;\n message?: string;\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 ManifestEntry {\n file_name: string | null;\n file_hash: string | null;\n download_url: string | null;\n}\n\nexport interface LatestVersion {\n /**\n * Result of getLatest method\n *\n * @since 4.0.0\n */\n version: string;\n /**\n * @since 6\n */\n checksum?: string;\n major?: boolean;\n message?: string;\n sessionKey?: string;\n error?: string;\n old?: string;\n url?: string;\n /**\n * @since 6.1\n */\n manifest?: ManifestEntry[];\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 GetLatestOptions {\n /**\n * The channel to get the latest version for\n * The channel must allow 'self_assign' for this to work\n * @since 6.8.0\n * @default undefined\n */\n channel?: 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 ListOptions {\n /**\n * Whether to return the raw bundle list or the manifest. If true, the list will attempt to read the internal database instead of files on disk.\n * @since 6.14.0\n * @default false\n */\n raw?: 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\nexport interface AutoUpdateAvailable {\n available: 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://plugin.capgo.app/updates\n * @example https://example.com/api/auto_update\n */\n updateUrl?: string;\n\n /**\n * Configure the URL / endpoint for channel operations.\n *\n * Only available for Android and iOS.\n *\n * @default https://plugin.capgo.app/channel_self\n * @example https://example.com/api/channel\n */\n channelUrl?: 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://plugin.capgo.app/stats\n * @example https://example.com/api/stats\n */\n statsUrl?: string;\n /**\n * Configure the public key for end to end live update encryption Version 2\n *\n * Only available for Android and iOS.\n *\n * @default undefined\n * @since 6.2.0\n */\n publicKey?: 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 * Configure the CLI to use a local api for testing.\n *\n *\n * @default undefined\n * @since 6.3.3\n */\n localApi?: string;\n /**\n * Configure the CLI to use a local file api for testing.\n *\n *\n * @default undefined\n * @since 6.3.3\n */\n localApiFiles?: string;\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 * Configure the app id for the app in the config.\n *\n * @default undefined\n * @since 6.0.0\n */\n appId?: string;\n\n /**\n * Configure the plugin to keep the URL path after a reload.\n * WARNING: When a reload is triggered, 'window.history' will be cleared.\n *\n * @default false\n * @since 6.8.0\n */\n keepUrlPathAfterReload?: boolean;\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 * @param options The {@link ListOptions} for listing bundles\n * @throws {Error}\n */\n list(options?: ListOptions): 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(options?: GetLatestOptions): 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 * This methods send to Capgo backend a request to link the device ID to the channel. Capgo can accept or refuse depending of the setting of your channel.\n *\n *\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(eventName: 'download', listenerFunc: (state: DownloadEvent) => void): 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(eventName: 'noNeedUpdate', listenerFunc: (state: NoNeedEvent) => void): 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(eventName: 'appReloaded', listenerFunc: () => void): 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(eventName: 'appReady', listenerFunc: (state: AppReadyEvent) => void): Promise<PluginListenerHandle>;\n\n /**\n * Get if auto update is available (not disabled by serverUrl).\n *\n * @returns {Promise<AutoUpdateAvailable>} The availability status for auto update. Evaluates to `false` when serverUrl is set.\n * @throws {Error}\n */\n isAutoUpdateAvailable(): Promise<AutoUpdateAvailable>;\n\n /**\n * Get the next bundle that will be used when the app reloads.\n * Returns null if no next bundle is set.\n *\n * @returns {Promise<BundleInfo | null>} A Promise that resolves with the next bundle information or null\n * @throws {Error}\n * @since 6.8.0\n */\n getNextBundle(): Promise<BundleInfo | null>;\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?: string;\n message?: string;\n}\n\nexport interface GetChannelRes {\n /**\n * Current status of get channel\n *\n * @since 4.8.0\n */\n channel?: string;\n error?: string;\n message?: string;\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 ManifestEntry {\n file_name: string | null;\n file_hash: string | null;\n download_url: string | null;\n}\n\nexport interface LatestVersion {\n /**\n * Result of getLatest method\n *\n * @since 4.0.0\n */\n version: string;\n /**\n * @since 6\n */\n checksum?: string;\n major?: boolean;\n message?: string;\n sessionKey?: string;\n error?: string;\n old?: string;\n url?: string;\n /**\n * @since 6.1\n */\n manifest?: ManifestEntry[];\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 GetLatestOptions {\n /**\n * The channel to get the latest version for\n * The channel must allow 'self_assign' for this to work\n * @since 6.8.0\n * @default undefined\n */\n channel?: 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 ListOptions {\n /**\n * Whether to return the raw bundle list or the manifest. If true, the list will attempt to read the internal database instead of files on disk.\n * @since 6.14.0\n * @default false\n */\n raw?: 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\nexport interface AutoUpdateAvailable {\n available: boolean;\n}\n"]}
|
|
@@ -40,9 +40,7 @@ import UIKit
|
|
|
40
40
|
public var defaultChannel: String = ""
|
|
41
41
|
public var appId: String = ""
|
|
42
42
|
public var deviceID = ""
|
|
43
|
-
public var privateKey: String = ""
|
|
44
43
|
public var publicKey: String = ""
|
|
45
|
-
public var hasOldPrivateKeyPropertyInConfig: Bool = false
|
|
46
44
|
|
|
47
45
|
public var notifyDownloadRaw: (String, Int, Bool) -> Void = { _, _, _ in }
|
|
48
46
|
public func notifyDownload(id: String, percent: Int, ignoreMultipleOfTen: Bool = false) {
|
|
@@ -142,6 +140,50 @@ import UIKit
|
|
|
142
140
|
}
|
|
143
141
|
}
|
|
144
142
|
|
|
143
|
+
private func decryptFileV2(filePath: URL, sessionKey: String, version: String) throws {
|
|
144
|
+
if self.publicKey.isEmpty || sessionKey.isEmpty || sessionKey.components(separatedBy: ":").count != 2 {
|
|
145
|
+
print("\(CapacitorUpdater.TAG) Cannot find public key or sessionKey")
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
do {
|
|
149
|
+
guard let rsaPublicKey: RSAPublicKey = .load(rsaPublicKey: self.publicKey) else {
|
|
150
|
+
print("cannot decode publicKey", self.publicKey)
|
|
151
|
+
throw CustomError.cannotDecode
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let sessionKeyArray: [String] = sessionKey.components(separatedBy: ":")
|
|
155
|
+
guard let ivData: Data = Data(base64Encoded: sessionKeyArray[0]) else {
|
|
156
|
+
print("cannot decode sessionKey", sessionKey)
|
|
157
|
+
throw CustomError.cannotDecode
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
guard let sessionKeyDataEncrypted = Data(base64Encoded: sessionKeyArray[1]) else {
|
|
161
|
+
throw NSError(domain: "Invalid session key data", code: 1, userInfo: nil)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
guard let sessionKeyDataDecrypted = rsaPublicKey.decrypt(data: sessionKeyDataEncrypted) else {
|
|
165
|
+
throw NSError(domain: "Failed to decrypt session key data", code: 2, userInfo: nil)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let aesPrivateKey = AES128Key(iv: ivData, aes128Key: sessionKeyDataDecrypted)
|
|
169
|
+
|
|
170
|
+
guard let encryptedData = try? Data(contentsOf: filePath) else {
|
|
171
|
+
throw NSError(domain: "Failed to read encrypted data", code: 3, userInfo: nil)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
guard let decryptedData = aesPrivateKey.decrypt(data: encryptedData) else {
|
|
175
|
+
throw NSError(domain: "Failed to decrypt data", code: 4, userInfo: nil)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
try decryptedData.write(to: filePath)
|
|
179
|
+
|
|
180
|
+
} catch {
|
|
181
|
+
print("\(CapacitorUpdater.TAG) Cannot decode: \(filePath.path)", error)
|
|
182
|
+
self.sendStats(action: "decrypt_fail", versionName: version)
|
|
183
|
+
throw CustomError.cannotDecode
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
145
187
|
private func unzipProgressHandler(entry: String, zipInfo: unz_file_info, entryNumber: Int, total: Int, destUnZip: URL, id: String, unzipError: inout NSError?) {
|
|
146
188
|
if entry.contains("\\") {
|
|
147
189
|
print("\(CapacitorUpdater.TAG) unzip: Windows path is not supported, please use unix path as required by zip RFC: \(entry)")
|
|
@@ -305,12 +347,33 @@ import UIKit
|
|
|
305
347
|
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("update.dat")
|
|
306
348
|
}
|
|
307
349
|
private var tempData = Data()
|
|
308
|
-
|
|
350
|
+
|
|
309
351
|
private func verifyChecksum(file: URL, expectedHash: String) -> Bool {
|
|
310
352
|
let actualHash = CryptoCipherV2.calcChecksum(filePath: file)
|
|
311
353
|
return actualHash == expectedHash
|
|
312
354
|
}
|
|
313
355
|
|
|
356
|
+
public func decryptChecksum(checksum: String, version: String) throws -> String {
|
|
357
|
+
if self.publicKey.isEmpty {
|
|
358
|
+
return checksum
|
|
359
|
+
}
|
|
360
|
+
do {
|
|
361
|
+
let checksumBytes: Data = Data(base64Encoded: checksum)!
|
|
362
|
+
guard let rsaPublicKey: RSAPublicKey = .load(rsaPublicKey: self.publicKey) else {
|
|
363
|
+
print("cannot decode publicKey", self.publicKey)
|
|
364
|
+
throw CustomError.cannotDecode
|
|
365
|
+
}
|
|
366
|
+
guard let decryptedChecksum = rsaPublicKey.decrypt(data: checksumBytes) else {
|
|
367
|
+
throw NSError(domain: "Failed to decrypt session key data", code: 2, userInfo: nil)
|
|
368
|
+
}
|
|
369
|
+
return decryptedChecksum.base64EncodedString()
|
|
370
|
+
} catch {
|
|
371
|
+
print("\(CapacitorUpdater.TAG) Cannot decrypt checksum: \(checksum)", error)
|
|
372
|
+
self.sendStats(action: "decrypt_fail", versionName: version)
|
|
373
|
+
throw CustomError.cannotDecode
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
314
377
|
public func downloadManifest(manifest: [ManifestEntry], version: String, sessionKey: String) throws -> BundleInfo {
|
|
315
378
|
let id = self.randomString(length: 10)
|
|
316
379
|
print("\(CapacitorUpdater.TAG) downloadManifest start \(id)")
|
|
@@ -340,7 +403,7 @@ import UIKit
|
|
|
340
403
|
continue
|
|
341
404
|
}
|
|
342
405
|
|
|
343
|
-
if !self.
|
|
406
|
+
if !self.publicKey.isEmpty && !sessionKey.isEmpty {
|
|
344
407
|
do {
|
|
345
408
|
fileHash = try CryptoCipherV2.decryptChecksum(checksum: fileHash, publicKey: self.publicKey, version: version)
|
|
346
409
|
} catch {
|
|
@@ -412,7 +475,7 @@ import UIKit
|
|
|
412
475
|
finalData = decompressedData
|
|
413
476
|
|
|
414
477
|
try finalData.write(to: destFilePath)
|
|
415
|
-
if !self.
|
|
478
|
+
if !self.publicKey.isEmpty && !sessionKey.isEmpty {
|
|
416
479
|
// assume that calcChecksum != null
|
|
417
480
|
let calculatedChecksum = CryptoCipherV2.calcChecksum(filePath: destFilePath)
|
|
418
481
|
if calculatedChecksum != fileHash {
|
|
@@ -557,7 +620,7 @@ import UIKit
|
|
|
557
620
|
}
|
|
558
621
|
let session = Session(eventMonitors: [monitor])
|
|
559
622
|
|
|
560
|
-
|
|
623
|
+
let request = session.streamRequest(url, headers: requestHeaders).validate().onHTTPResponse(perform: { response in
|
|
561
624
|
if let contentLength = response.headers.value(for: "Content-Length") {
|
|
562
625
|
targetSize = (Int(contentLength) ?? -1) + Int(totalReceivedBytes)
|
|
563
626
|
}
|
|
@@ -615,17 +678,7 @@ import UIKit
|
|
|
615
678
|
|
|
616
679
|
let finalPath = tempDataPath.deletingLastPathComponent().appendingPathComponent("\(id)")
|
|
617
680
|
do {
|
|
618
|
-
|
|
619
|
-
do {
|
|
620
|
-
if !self.hasOldPrivateKeyPropertyInConfig {
|
|
621
|
-
try CryptoCipherV2.decryptFile(filePath: tempDataPath, publicKey: self.publicKey, sessionKey: sessionKey, version: version)
|
|
622
|
-
} else {
|
|
623
|
-
try CryptoCipher.decryptFile(filePath: tempDataPath, privateKey: self.privateKey, sessionKey: sessionKey, version: version)
|
|
624
|
-
}
|
|
625
|
-
} catch {
|
|
626
|
-
self.sendStats(action: "decrypt_fail", versionName: version)
|
|
627
|
-
throw error
|
|
628
|
-
}
|
|
681
|
+
try self.decryptFileV2(filePath: tempDataPath, sessionKey: sessionKey, version: version)
|
|
629
682
|
try FileManager.default.moveItem(at: tempDataPath, to: finalPath)
|
|
630
683
|
} catch {
|
|
631
684
|
print("\(CapacitorUpdater.TAG) Failed decrypt file : \(error)")
|
|
@@ -635,11 +688,7 @@ import UIKit
|
|
|
635
688
|
}
|
|
636
689
|
|
|
637
690
|
do {
|
|
638
|
-
|
|
639
|
-
checksum = CryptoCipherV2.calcChecksum(filePath: finalPath)
|
|
640
|
-
} else {
|
|
641
|
-
checksum = CryptoCipher.calcChecksum(filePath: finalPath)
|
|
642
|
-
}
|
|
691
|
+
checksum = CryptoCipherV2.calcChecksum(filePath: finalPath)
|
|
643
692
|
print("\(CapacitorUpdater.TAG) Downloading: 80% (unzipping)")
|
|
644
693
|
try self.saveDownloaded(sourceZip: finalPath, id: id, base: self.libraryDir.appendingPathComponent(self.bundleDirectory), notify: true)
|
|
645
694
|
|
|
@@ -45,7 +45,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
45
45
|
CAPPluginMethod(name: "getNextBundle", returnType: CAPPluginReturnPromise)
|
|
46
46
|
]
|
|
47
47
|
public var implementation = CapacitorUpdater()
|
|
48
|
-
private let PLUGIN_VERSION: String = "
|
|
48
|
+
private let PLUGIN_VERSION: String = "7.0.2"
|
|
49
49
|
static let updateUrlDefault = "https://plugin.capgo.app/updates"
|
|
50
50
|
static let statsUrlDefault = "https://plugin.capgo.app/stats"
|
|
51
51
|
static let channelUrlDefault = "https://plugin.capgo.app/channel_self"
|
|
@@ -106,11 +106,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
106
106
|
periodCheckDelay = periodCheckDelayValue
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
implementation.privateKey = getConfig().getString("privateKey", "")!
|
|
110
109
|
implementation.publicKey = getConfig().getString("publicKey", "")!
|
|
111
|
-
if !implementation.privateKey.isEmpty {
|
|
112
|
-
implementation.hasOldPrivateKeyPropertyInConfig = true
|
|
113
|
-
}
|
|
114
110
|
implementation.notifyDownloadRaw = notifyDownload
|
|
115
111
|
implementation.PLUGIN_VERSION = self.PLUGIN_VERSION
|
|
116
112
|
let config = (self.bridge?.viewController as? CAPBridgeViewController)?.instanceDescriptor().legacyConfig
|
|
@@ -295,14 +291,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
295
291
|
DispatchQueue.global(qos: .background).async {
|
|
296
292
|
do {
|
|
297
293
|
let next = try self.implementation.download(url: url!, version: version, sessionKey: sessionKey)
|
|
298
|
-
|
|
299
|
-
do {
|
|
300
|
-
checksum = try CryptoCipherV2.decryptChecksum(checksum: checksum, publicKey: self.implementation.publicKey, version: version)
|
|
301
|
-
} catch {
|
|
302
|
-
self.implementation.sendStats(action: "decrypt_fail", versionName: version)
|
|
303
|
-
throw error
|
|
304
|
-
}
|
|
305
|
-
}
|
|
294
|
+
checksum = try self.implementation.decryptChecksum(checksum: checksum, version: version)
|
|
306
295
|
if (checksum != "" || self.implementation.publicKey != "") && next.getChecksum() != checksum {
|
|
307
296
|
print("\(CapacitorUpdater.TAG) Error checksum", next.getChecksum(), checksum)
|
|
308
297
|
self.implementation.sendStats(action: "checksum_fail", versionName: next.getVersionName())
|
|
@@ -816,14 +805,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
816
805
|
self.endBackGroundTaskWithNotif(msg: "Latest version is in error state. Aborting update.", latestVersionName: latestVersionName, current: current)
|
|
817
806
|
return
|
|
818
807
|
}
|
|
819
|
-
|
|
820
|
-
do {
|
|
821
|
-
res.checksum = try CryptoCipherV2.decryptChecksum(checksum: res.checksum, publicKey: self.implementation.publicKey, version: latestVersionName)
|
|
822
|
-
} catch {
|
|
823
|
-
self.implementation.sendStats(action: "decrypt_fail", versionName: latestVersionName)
|
|
824
|
-
throw error
|
|
825
|
-
}
|
|
826
|
-
}
|
|
808
|
+
res.checksum = try self.implementation.decryptChecksum(checksum: res.checksum, version: latestVersionName)
|
|
827
809
|
if res.checksum != "" && next.getChecksum() != res.checksum && res.manifest == nil {
|
|
828
810
|
print("\(CapacitorUpdater.TAG) Error checksum", next.getChecksum(), res.checksum)
|
|
829
811
|
self.implementation.sendStats(action: "checksum_fail", versionName: next.getVersionName())
|
|
@@ -18,37 +18,48 @@ private enum CryptoCipherConstants {
|
|
|
18
18
|
static let rsaAlgorithm: SecKeyAlgorithm = .rsaEncryptionOAEPSHA256
|
|
19
19
|
}
|
|
20
20
|
///
|
|
21
|
-
/// The
|
|
21
|
+
/// The AES key. Contains both the initialization vector and secret key.
|
|
22
22
|
///
|
|
23
|
-
public struct
|
|
24
|
-
|
|
25
|
-
private let
|
|
26
|
-
|
|
23
|
+
public struct AES128Key {
|
|
24
|
+
/// Initialization vector
|
|
25
|
+
private let iv: Data
|
|
26
|
+
private let aes128Key: Data
|
|
27
27
|
#if DEBUG
|
|
28
|
-
public var
|
|
29
|
-
public var
|
|
28
|
+
public var __debug_iv: Data { iv }
|
|
29
|
+
public var __debug_aes128Key: Data { aes128Key }
|
|
30
30
|
#endif
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
self.
|
|
34
|
-
self.publicKey = publicKey
|
|
31
|
+
init(iv: Data, aes128Key: Data) {
|
|
32
|
+
self.iv = iv
|
|
33
|
+
self.aes128Key = aes128Key
|
|
35
34
|
}
|
|
36
|
-
|
|
37
|
-
public func extractPublicKey() -> RSAPublicKey {
|
|
38
|
-
RSAPublicKey(publicKey: publicKey)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
35
|
///
|
|
42
|
-
/// Takes the data and uses the private key to decrypt it.
|
|
36
|
+
/// Takes the data and uses the private key to decrypt it. Will call `CCCrypt` in CommonCrypto
|
|
37
|
+
/// and provide it `ivData` for the initialization vector. Will use cipher block chaining (CBC) as
|
|
38
|
+
/// the mode of operation.
|
|
39
|
+
///
|
|
43
40
|
/// Returns the decrypted data.
|
|
44
41
|
///
|
|
45
42
|
public func decrypt(data: Data) -> Data? {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
let encryptedData: UnsafePointer<UInt8> = (data as NSData).bytes.bindMemory(to: UInt8.self, capacity: data.count)
|
|
44
|
+
let encryptedDataLength: Int = data.count
|
|
45
|
+
|
|
46
|
+
if let result: NSMutableData = NSMutableData(length: encryptedDataLength) {
|
|
47
|
+
let keyData: UnsafePointer<UInt8> = (self.aes128Key as NSData).bytes.bindMemory(to: UInt8.self, capacity: self.aes128Key.count)
|
|
48
|
+
let keyLength: size_t = size_t(self.aes128Key.count)
|
|
49
|
+
let ivData: UnsafePointer<UInt8> = (iv as NSData).bytes.bindMemory(to: UInt8.self, capacity: self.iv.count)
|
|
50
|
+
|
|
51
|
+
let decryptedData: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>(result.mutableBytes.assumingMemoryBound(to: UInt8.self))
|
|
52
|
+
let decryptedDataLength: size_t = size_t(result.length)
|
|
53
|
+
|
|
54
|
+
var decryptedLength: size_t = 0
|
|
55
|
+
|
|
56
|
+
let status: CCCryptorStatus = CCCrypt(CCOperation(kCCDecrypt), CryptoCipherConstants.aesAlgorithm, CryptoCipherConstants.aesOptions, keyData, keyLength, ivData, encryptedData, encryptedDataLength, decryptedData, decryptedDataLength, &decryptedLength)
|
|
57
|
+
|
|
58
|
+
if Int32(status) == Int32(kCCSuccess) {
|
|
59
|
+
result.length = Int(decryptedLength)
|
|
60
|
+
return result as Data
|
|
50
61
|
} else {
|
|
51
|
-
return
|
|
62
|
+
return nil
|
|
52
63
|
}
|
|
53
64
|
} else {
|
|
54
65
|
return nil
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/capacitor-updater",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.2",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"description": "Live update for capacitor apps",
|
|
6
6
|
"main": "dist/plugin.cjs.js",
|
|
@@ -58,26 +58,27 @@
|
|
|
58
58
|
"prepublishOnly": "npm run build"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@capacitor/android": "^
|
|
62
|
-
"@capacitor/cli": "^
|
|
63
|
-
"@capacitor/core": "^
|
|
61
|
+
"@capacitor/android": "^7.0.0",
|
|
62
|
+
"@capacitor/cli": "^7.0.0",
|
|
63
|
+
"@capacitor/core": "^7.0.0",
|
|
64
64
|
"@capacitor/docgen": "^0.3.0",
|
|
65
|
-
"@capacitor/ios": "^
|
|
65
|
+
"@capacitor/ios": "^7.0.0",
|
|
66
66
|
"@ionic/eslint-config": "^0.4.0",
|
|
67
67
|
"@ionic/prettier-config": "^4.0.0",
|
|
68
68
|
"@ionic/swiftlint-config": "^2.0.0",
|
|
69
|
-
"@types/node": "^22.
|
|
70
|
-
"eslint": "^8.57.
|
|
69
|
+
"@types/node": "^22.13.1",
|
|
70
|
+
"eslint": "^8.57.0",
|
|
71
71
|
"eslint-plugin-import": "^2.31.0",
|
|
72
|
+
"husky": "^9.1.7",
|
|
72
73
|
"prettier": "^3.4.2",
|
|
73
|
-
"prettier-plugin-java": "^2.6.
|
|
74
|
+
"prettier-plugin-java": "^2.6.7",
|
|
74
75
|
"rimraf": "^6.0.1",
|
|
75
|
-
"rollup": "^4.
|
|
76
|
+
"rollup": "^4.34.6",
|
|
76
77
|
"swiftlint": "^2.0.0",
|
|
77
|
-
"typescript": "^5.7.
|
|
78
|
+
"typescript": "^5.7.3"
|
|
78
79
|
},
|
|
79
80
|
"peerDependencies": {
|
|
80
|
-
"@capacitor/core": "
|
|
81
|
+
"@capacitor/core": ">=7.0.0"
|
|
81
82
|
},
|
|
82
83
|
"prettier": "@ionic/prettier-config",
|
|
83
84
|
"swiftlint": "@ionic/swiftlint-config",
|
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
-
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
-
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
package ee.forgr.capacitor_updater;
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Created by Awesometic
|
|
11
|
-
* It's encrypt returns Base64 encoded, and also decrypt for Base64 encoded cipher
|
|
12
|
-
* references: http://stackoverflow.com/questions/12471999/rsa-encryption-decryption-in-android
|
|
13
|
-
*/
|
|
14
|
-
import android.util.Base64;
|
|
15
|
-
import android.util.Log;
|
|
16
|
-
import java.io.BufferedInputStream;
|
|
17
|
-
import java.io.DataInputStream;
|
|
18
|
-
import java.io.File;
|
|
19
|
-
import java.io.FileInputStream;
|
|
20
|
-
import java.io.FileOutputStream;
|
|
21
|
-
import java.io.IOException;
|
|
22
|
-
import java.security.GeneralSecurityException;
|
|
23
|
-
import java.security.InvalidAlgorithmParameterException;
|
|
24
|
-
import java.security.InvalidKeyException;
|
|
25
|
-
import java.security.KeyFactory;
|
|
26
|
-
import java.security.NoSuchAlgorithmException;
|
|
27
|
-
import java.security.PrivateKey;
|
|
28
|
-
import java.security.PublicKey;
|
|
29
|
-
import java.security.spec.InvalidKeySpecException;
|
|
30
|
-
import java.security.spec.MGF1ParameterSpec;
|
|
31
|
-
import java.security.spec.PKCS8EncodedKeySpec;
|
|
32
|
-
import java.security.spec.X509EncodedKeySpec;
|
|
33
|
-
import java.util.zip.CRC32;
|
|
34
|
-
import javax.crypto.BadPaddingException;
|
|
35
|
-
import javax.crypto.Cipher;
|
|
36
|
-
import javax.crypto.IllegalBlockSizeException;
|
|
37
|
-
import javax.crypto.NoSuchPaddingException;
|
|
38
|
-
import javax.crypto.SecretKey;
|
|
39
|
-
import javax.crypto.spec.IvParameterSpec;
|
|
40
|
-
import javax.crypto.spec.OAEPParameterSpec;
|
|
41
|
-
import javax.crypto.spec.PSource;
|
|
42
|
-
import javax.crypto.spec.SecretKeySpec;
|
|
43
|
-
|
|
44
|
-
public class CryptoCipher {
|
|
45
|
-
|
|
46
|
-
public static byte[] decryptRSA(byte[] source, PrivateKey privateKey)
|
|
47
|
-
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
|
|
48
|
-
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
|
|
49
|
-
OAEPParameterSpec oaepParams = new OAEPParameterSpec(
|
|
50
|
-
"SHA-256",
|
|
51
|
-
"MGF1",
|
|
52
|
-
new MGF1ParameterSpec("SHA-256"),
|
|
53
|
-
PSource.PSpecified.DEFAULT
|
|
54
|
-
);
|
|
55
|
-
cipher.init(Cipher.DECRYPT_MODE, privateKey, oaepParams);
|
|
56
|
-
return cipher.doFinal(source);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
public static byte[] decryptAES(byte[] cipherText, SecretKey key, byte[] iv) {
|
|
60
|
-
try {
|
|
61
|
-
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
|
|
62
|
-
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
63
|
-
SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");
|
|
64
|
-
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec);
|
|
65
|
-
return cipher.doFinal(cipherText);
|
|
66
|
-
} catch (Exception e) {
|
|
67
|
-
e.printStackTrace();
|
|
68
|
-
}
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
public static SecretKey byteToSessionKey(byte[] sessionKey) {
|
|
73
|
-
// rebuild key using SecretKeySpec
|
|
74
|
-
return new SecretKeySpec(sessionKey, 0, sessionKey.length, "AES");
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
private static PrivateKey readPkcs8PrivateKey(byte[] pkcs8Bytes) throws GeneralSecurityException {
|
|
78
|
-
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
|
79
|
-
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes);
|
|
80
|
-
try {
|
|
81
|
-
return keyFactory.generatePrivate(keySpec);
|
|
82
|
-
} catch (InvalidKeySpecException e) {
|
|
83
|
-
throw new IllegalArgumentException("Unexpected key format!", e);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
private static byte[] join(byte[] byteArray1, byte[] byteArray2) {
|
|
88
|
-
byte[] bytes = new byte[byteArray1.length + byteArray2.length];
|
|
89
|
-
System.arraycopy(byteArray1, 0, bytes, 0, byteArray1.length);
|
|
90
|
-
System.arraycopy(byteArray2, 0, bytes, byteArray1.length, byteArray2.length);
|
|
91
|
-
return bytes;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
private static PrivateKey readPkcs1PrivateKey(byte[] pkcs1Bytes) throws GeneralSecurityException {
|
|
95
|
-
// We can't use Java internal APIs to parse ASN.1 structures, so we build a PKCS#8 key Java can understand
|
|
96
|
-
int pkcs1Length = pkcs1Bytes.length;
|
|
97
|
-
int totalLength = pkcs1Length + 22;
|
|
98
|
-
byte[] pkcs8Header = new byte[] {
|
|
99
|
-
0x30,
|
|
100
|
-
(byte) 0x82,
|
|
101
|
-
(byte) ((totalLength >> 8) & 0xff),
|
|
102
|
-
(byte) (totalLength & 0xff), // Sequence + total length
|
|
103
|
-
0x2,
|
|
104
|
-
0x1,
|
|
105
|
-
0x0, // Integer (0)
|
|
106
|
-
0x30,
|
|
107
|
-
0xD,
|
|
108
|
-
0x6,
|
|
109
|
-
0x9,
|
|
110
|
-
0x2A,
|
|
111
|
-
(byte) 0x86,
|
|
112
|
-
0x48,
|
|
113
|
-
(byte) 0x86,
|
|
114
|
-
(byte) 0xF7,
|
|
115
|
-
0xD,
|
|
116
|
-
0x1,
|
|
117
|
-
0x1,
|
|
118
|
-
0x1,
|
|
119
|
-
0x5,
|
|
120
|
-
0x0, // Sequence: 1.2.840.113549.1.1.1, NULL
|
|
121
|
-
0x4,
|
|
122
|
-
(byte) 0x82,
|
|
123
|
-
(byte) ((pkcs1Length >> 8) & 0xff),
|
|
124
|
-
(byte) (pkcs1Length & 0xff) // Octet string + length
|
|
125
|
-
};
|
|
126
|
-
byte[] pkcs8bytes = join(pkcs8Header, pkcs1Bytes);
|
|
127
|
-
return readPkcs8PrivateKey(pkcs8bytes);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
public static PrivateKey stringToPrivateKey(String private_key) throws GeneralSecurityException {
|
|
131
|
-
// Base64 decode the result
|
|
132
|
-
|
|
133
|
-
String pkcs1Pem = private_key;
|
|
134
|
-
pkcs1Pem = pkcs1Pem.replace("-----BEGIN RSA PRIVATE KEY-----", "");
|
|
135
|
-
pkcs1Pem = pkcs1Pem.replace("-----END RSA PRIVATE KEY-----", "");
|
|
136
|
-
pkcs1Pem = pkcs1Pem.replace("\\n", "");
|
|
137
|
-
pkcs1Pem = pkcs1Pem.replace(" ", "");
|
|
138
|
-
|
|
139
|
-
byte[] pkcs1EncodedBytes = Base64.decode(pkcs1Pem.getBytes(), Base64.DEFAULT);
|
|
140
|
-
// extract the private key
|
|
141
|
-
return readPkcs1PrivateKey(pkcs1EncodedBytes);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
public static void decryptFile(final File file, final String privateKey, final String ivSessionKey, final String version)
|
|
145
|
-
throws IOException {
|
|
146
|
-
// (str != null && !str.isEmpty())
|
|
147
|
-
if (privateKey == null || privateKey.isEmpty()) {
|
|
148
|
-
Log.i(CapacitorUpdater.TAG, "Cannot found privateKey");
|
|
149
|
-
return;
|
|
150
|
-
} else if (ivSessionKey == null || ivSessionKey.isEmpty() || ivSessionKey.split(":").length != 2) {
|
|
151
|
-
Log.i(CapacitorUpdater.TAG, "Cannot found sessionKey");
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
try {
|
|
155
|
-
String ivB64 = ivSessionKey.split(":")[0];
|
|
156
|
-
String sessionKeyB64 = ivSessionKey.split(":")[1];
|
|
157
|
-
byte[] iv = Base64.decode(ivB64.getBytes(), Base64.DEFAULT);
|
|
158
|
-
byte[] sessionKey = Base64.decode(sessionKeyB64.getBytes(), Base64.DEFAULT);
|
|
159
|
-
PrivateKey pKey = CryptoCipher.stringToPrivateKey(privateKey);
|
|
160
|
-
byte[] decryptedSessionKey = CryptoCipher.decryptRSA(sessionKey, pKey);
|
|
161
|
-
SecretKey sKey = CryptoCipher.byteToSessionKey(decryptedSessionKey);
|
|
162
|
-
byte[] content = new byte[(int) file.length()];
|
|
163
|
-
|
|
164
|
-
try (
|
|
165
|
-
final FileInputStream fis = new FileInputStream(file);
|
|
166
|
-
final BufferedInputStream bis = new BufferedInputStream(fis);
|
|
167
|
-
final DataInputStream dis = new DataInputStream(bis)
|
|
168
|
-
) {
|
|
169
|
-
dis.readFully(content);
|
|
170
|
-
dis.close();
|
|
171
|
-
byte[] decrypted = CryptoCipher.decryptAES(content, sKey, iv);
|
|
172
|
-
// write the decrypted string to the file
|
|
173
|
-
try (final FileOutputStream fos = new FileOutputStream(file.getAbsolutePath())) {
|
|
174
|
-
fos.write(decrypted);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
} catch (GeneralSecurityException e) {
|
|
178
|
-
Log.i(CapacitorUpdater.TAG, "decryptFile fail");
|
|
179
|
-
e.printStackTrace();
|
|
180
|
-
throw new IOException("GeneralSecurityException");
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
public static String calcChecksum(File file) {
|
|
185
|
-
final int BUFFER_SIZE = 1024 * 1024 * 5; // 5 MB buffer size
|
|
186
|
-
CRC32 crc = new CRC32();
|
|
187
|
-
|
|
188
|
-
try (FileInputStream fis = new FileInputStream(file)) {
|
|
189
|
-
byte[] buffer = new byte[BUFFER_SIZE];
|
|
190
|
-
int length;
|
|
191
|
-
while ((length = fis.read(buffer)) != -1) {
|
|
192
|
-
crc.update(buffer, 0, length);
|
|
193
|
-
}
|
|
194
|
-
return String.format("%08x", crc.getValue());
|
|
195
|
-
} catch (IOException e) {
|
|
196
|
-
System.err.println(CapacitorUpdater.TAG + " Cannot calc checksum: " + file.getPath() + " " + e.getMessage());
|
|
197
|
-
return "";
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
public static PublicKey stringToPublicKey(String publicKey) {
|
|
202
|
-
byte[] encoded = Base64.decode(publicKey, Base64.DEFAULT);
|
|
203
|
-
|
|
204
|
-
KeyFactory keyFactory = null;
|
|
205
|
-
try {
|
|
206
|
-
keyFactory = KeyFactory.getInstance("RSA");
|
|
207
|
-
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
|
|
208
|
-
return keyFactory.generatePublic(keySpec);
|
|
209
|
-
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
|
210
|
-
Log.i("Capacitor-updater", "stringToPublicKey fail\nError:\n" + e.toString());
|
|
211
|
-
return null;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
-
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
-
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import Foundation
|
|
8
|
-
import CommonCrypto
|
|
9
|
-
import zlib
|
|
10
|
-
|
|
11
|
-
///
|
|
12
|
-
/// Constants
|
|
13
|
-
///
|
|
14
|
-
private enum CryptoCipherConstants {
|
|
15
|
-
static let rsaKeySizeInBits: NSNumber = 2048
|
|
16
|
-
static let aesAlgorithm: CCAlgorithm = CCAlgorithm(kCCAlgorithmAES)
|
|
17
|
-
static let aesOptions: CCOptions = CCOptions(kCCOptionPKCS7Padding)
|
|
18
|
-
static let rsaAlgorithm: SecKeyAlgorithm = .rsaEncryptionOAEPSHA256
|
|
19
|
-
}
|
|
20
|
-
///
|
|
21
|
-
/// The AES key. Contains both the initialization vector and secret key.
|
|
22
|
-
///
|
|
23
|
-
public struct AES128Key {
|
|
24
|
-
/// Initialization vector
|
|
25
|
-
private let iv: Data
|
|
26
|
-
private let aes128Key: Data
|
|
27
|
-
#if DEBUG
|
|
28
|
-
public var __debug_iv: Data { iv }
|
|
29
|
-
public var __debug_aes128Key: Data { aes128Key }
|
|
30
|
-
#endif
|
|
31
|
-
init(iv: Data, aes128Key: Data) {
|
|
32
|
-
self.iv = iv
|
|
33
|
-
self.aes128Key = aes128Key
|
|
34
|
-
}
|
|
35
|
-
///
|
|
36
|
-
/// Takes the data and uses the private key to decrypt it. Will call `CCCrypt` in CommonCrypto
|
|
37
|
-
/// and provide it `ivData` for the initialization vector. Will use cipher block chaining (CBC) as
|
|
38
|
-
/// the mode of operation.
|
|
39
|
-
///
|
|
40
|
-
/// Returns the decrypted data.
|
|
41
|
-
///
|
|
42
|
-
public func decrypt(data: Data) -> Data? {
|
|
43
|
-
let encryptedData: UnsafePointer<UInt8> = (data as NSData).bytes.bindMemory(to: UInt8.self, capacity: data.count)
|
|
44
|
-
let encryptedDataLength: Int = data.count
|
|
45
|
-
|
|
46
|
-
if let result: NSMutableData = NSMutableData(length: encryptedDataLength) {
|
|
47
|
-
let keyData: UnsafePointer<UInt8> = (self.aes128Key as NSData).bytes.bindMemory(to: UInt8.self, capacity: self.aes128Key.count)
|
|
48
|
-
let keyLength: size_t = size_t(self.aes128Key.count)
|
|
49
|
-
let ivData: UnsafePointer<UInt8> = (iv as NSData).bytes.bindMemory(to: UInt8.self, capacity: self.iv.count)
|
|
50
|
-
|
|
51
|
-
let decryptedData: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>(result.mutableBytes.assumingMemoryBound(to: UInt8.self))
|
|
52
|
-
let decryptedDataLength: size_t = size_t(result.length)
|
|
53
|
-
|
|
54
|
-
var decryptedLength: size_t = 0
|
|
55
|
-
|
|
56
|
-
let status: CCCryptorStatus = CCCrypt(CCOperation(kCCDecrypt), CryptoCipherConstants.aesAlgorithm, CryptoCipherConstants.aesOptions, keyData, keyLength, ivData, encryptedData, encryptedDataLength, decryptedData, decryptedDataLength, &decryptedLength)
|
|
57
|
-
|
|
58
|
-
if Int32(status) == Int32(kCCSuccess) {
|
|
59
|
-
result.length = Int(decryptedLength)
|
|
60
|
-
return result as Data
|
|
61
|
-
} else {
|
|
62
|
-
return nil
|
|
63
|
-
}
|
|
64
|
-
} else {
|
|
65
|
-
return nil
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
///
|
|
71
|
-
/// The RSA keypair. Includes both private and public key.
|
|
72
|
-
///
|
|
73
|
-
public struct RSAKeyPair {
|
|
74
|
-
private let privateKey: SecKey
|
|
75
|
-
private let publicKey: SecKey
|
|
76
|
-
|
|
77
|
-
#if DEBUG
|
|
78
|
-
public var __debug_privateKey: SecKey { self.privateKey }
|
|
79
|
-
public var __debug_publicKey: SecKey { self.publicKey }
|
|
80
|
-
#endif
|
|
81
|
-
|
|
82
|
-
fileprivate init(privateKey: SecKey, publicKey: SecKey) {
|
|
83
|
-
self.privateKey = privateKey
|
|
84
|
-
self.publicKey = publicKey
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
///
|
|
88
|
-
/// Takes the data and uses the private key to decrypt it.
|
|
89
|
-
/// Returns the decrypted data.
|
|
90
|
-
///
|
|
91
|
-
public func decrypt(data: Data) -> Data? {
|
|
92
|
-
var error: Unmanaged<CFError>?
|
|
93
|
-
if let decryptedData: CFData = SecKeyCreateDecryptedData(self.privateKey, CryptoCipherConstants.rsaAlgorithm, data as CFData, &error) {
|
|
94
|
-
if error != nil {
|
|
95
|
-
return nil
|
|
96
|
-
} else {
|
|
97
|
-
return decryptedData as Data
|
|
98
|
-
}
|
|
99
|
-
} else {
|
|
100
|
-
return nil
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
///
|
|
105
|
-
/// Takes the data and uses the public key to encrypt it.
|
|
106
|
-
/// Returns the encrypted data.
|
|
107
|
-
///
|
|
108
|
-
public func encrypt(data: Data) -> Data? {
|
|
109
|
-
var error: Unmanaged<CFError>?
|
|
110
|
-
if let encryptedData: CFData = SecKeyCreateEncryptedData(self.publicKey, CryptoCipherConstants.rsaAlgorithm, data as CFData, &error) {
|
|
111
|
-
if error != nil {
|
|
112
|
-
return nil
|
|
113
|
-
} else {
|
|
114
|
-
return encryptedData as Data
|
|
115
|
-
}
|
|
116
|
-
} else {
|
|
117
|
-
return nil
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
///
|
|
123
|
-
/// The RSA public key.
|
|
124
|
-
///
|
|
125
|
-
public struct RSAPrivateKey {
|
|
126
|
-
private let privateKey: SecKey
|
|
127
|
-
|
|
128
|
-
#if DEBUG
|
|
129
|
-
public var __debug_privateKey: SecKey { self.privateKey }
|
|
130
|
-
#endif
|
|
131
|
-
|
|
132
|
-
fileprivate init(privateKey: SecKey) {
|
|
133
|
-
self.privateKey = privateKey
|
|
134
|
-
}
|
|
135
|
-
///
|
|
136
|
-
/// Takes the data and uses the private key to decrypt it.
|
|
137
|
-
/// Returns the decrypted data.
|
|
138
|
-
///
|
|
139
|
-
public func decrypt(data: Data) -> Data? {
|
|
140
|
-
var error: Unmanaged<CFError>?
|
|
141
|
-
if let decryptedData: CFData = SecKeyCreateDecryptedData(self.privateKey, CryptoCipherConstants.rsaAlgorithm, data as CFData, &error) {
|
|
142
|
-
if error != nil {
|
|
143
|
-
return nil
|
|
144
|
-
} else {
|
|
145
|
-
return decryptedData as Data
|
|
146
|
-
}
|
|
147
|
-
} else {
|
|
148
|
-
return nil
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
///
|
|
153
|
-
/// Allows you to export the RSA public key to a format (so you can send over the net).
|
|
154
|
-
///
|
|
155
|
-
public func export() -> Data? {
|
|
156
|
-
return privateKey.exportToData()
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
///
|
|
160
|
-
/// Allows you to load an RSA public key (i.e. one downloaded from the net).
|
|
161
|
-
///
|
|
162
|
-
public static func load(rsaPrivateKey: String) -> RSAPrivateKey? {
|
|
163
|
-
var privKey: String = rsaPrivateKey
|
|
164
|
-
privKey = privKey.replacingOccurrences(of: "-----BEGIN RSA PRIVATE KEY-----", with: "")
|
|
165
|
-
privKey = privKey.replacingOccurrences(of: "-----END RSA PRIVATE KEY-----", with: "")
|
|
166
|
-
privKey = privKey.replacingOccurrences(of: "\\n+", with: "", options: .regularExpression)
|
|
167
|
-
privKey = privKey.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
168
|
-
do {
|
|
169
|
-
guard let rsaPrivateKeyData: Data = Data(base64Encoded: privKey) else {
|
|
170
|
-
throw CustomError.cannotDecode
|
|
171
|
-
}
|
|
172
|
-
guard let privateKey: SecKey = .loadPrivateFromData(rsaPrivateKeyData) else {
|
|
173
|
-
throw CustomError.cannotDecode
|
|
174
|
-
}
|
|
175
|
-
return RSAPrivateKey(privateKey: privateKey)
|
|
176
|
-
} catch {
|
|
177
|
-
print("Error load RSA: \(error)")
|
|
178
|
-
return nil
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
fileprivate extension SecKey {
|
|
184
|
-
func exportToData() -> Data? {
|
|
185
|
-
var error: Unmanaged<CFError>?
|
|
186
|
-
if let cfData: CFData = SecKeyCopyExternalRepresentation(self, &error) {
|
|
187
|
-
if error != nil {
|
|
188
|
-
return nil
|
|
189
|
-
} else {
|
|
190
|
-
return cfData as Data
|
|
191
|
-
}
|
|
192
|
-
} else {
|
|
193
|
-
return nil
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
static func loadPublicFromData(_ data: Data) -> SecKey? {
|
|
197
|
-
let keyDict: [NSObject: NSObject] = [
|
|
198
|
-
kSecAttrKeyType: kSecAttrKeyTypeRSA,
|
|
199
|
-
kSecAttrKeyClass: kSecAttrKeyClassPublic,
|
|
200
|
-
kSecAttrKeySizeInBits: CryptoCipherConstants.rsaKeySizeInBits
|
|
201
|
-
]
|
|
202
|
-
return SecKeyCreateWithData(data as NSData, keyDict as CFDictionary, nil)
|
|
203
|
-
}
|
|
204
|
-
static func loadPrivateFromData(_ data: Data) -> SecKey? {
|
|
205
|
-
let keyDict: [NSObject: NSObject] = [
|
|
206
|
-
kSecAttrKeyType: kSecAttrKeyTypeRSA,
|
|
207
|
-
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
|
|
208
|
-
kSecAttrKeySizeInBits: CryptoCipherConstants.rsaKeySizeInBits
|
|
209
|
-
]
|
|
210
|
-
return SecKeyCreateWithData(data as CFData, keyDict as CFDictionary, nil)
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
public struct CryptoCipher {
|
|
215
|
-
public static func calcChecksum(filePath: URL) -> String {
|
|
216
|
-
let bufferSize = 1024 * 1024 * 5 // 5 MB
|
|
217
|
-
var checksum = uLong(0)
|
|
218
|
-
|
|
219
|
-
do {
|
|
220
|
-
let fileHandle = try FileHandle(forReadingFrom: filePath)
|
|
221
|
-
defer {
|
|
222
|
-
fileHandle.closeFile()
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
while autoreleasepool(invoking: {
|
|
226
|
-
let fileData = fileHandle.readData(ofLength: bufferSize)
|
|
227
|
-
if fileData.count > 0 {
|
|
228
|
-
checksum = fileData.withUnsafeBytes {
|
|
229
|
-
crc32(checksum, $0.bindMemory(to: Bytef.self).baseAddress, uInt(fileData.count))
|
|
230
|
-
}
|
|
231
|
-
return true // Continue
|
|
232
|
-
} else {
|
|
233
|
-
return false // End of file
|
|
234
|
-
}
|
|
235
|
-
}) {}
|
|
236
|
-
|
|
237
|
-
return String(format: "%08X", checksum).lowercased()
|
|
238
|
-
} catch {
|
|
239
|
-
print("\(CapacitorUpdater.TAG) Cannot get checksum: \(filePath.path)", error)
|
|
240
|
-
return ""
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
public static func decryptFile(filePath: URL, privateKey: String, sessionKey: String, version: String) throws {
|
|
244
|
-
if privateKey.isEmpty {
|
|
245
|
-
print("\(CapacitorUpdater.TAG) Cannot found privateKey")
|
|
246
|
-
return
|
|
247
|
-
} else if sessionKey.isEmpty || sessionKey.components(separatedBy: ":").count != 2 {
|
|
248
|
-
print("\(CapacitorUpdater.TAG) Cannot found sessionKey")
|
|
249
|
-
return
|
|
250
|
-
}
|
|
251
|
-
do {
|
|
252
|
-
guard let rsaPrivateKey: RSAPrivateKey = .load(rsaPrivateKey: privateKey) else {
|
|
253
|
-
print("cannot decode privateKey", privateKey)
|
|
254
|
-
throw CustomError.cannotDecode
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
let sessionKeyArray: [String] = sessionKey.components(separatedBy: ":")
|
|
258
|
-
guard let ivData: Data = Data(base64Encoded: sessionKeyArray[0]) else {
|
|
259
|
-
print("cannot decode sessionKey", sessionKey)
|
|
260
|
-
throw CustomError.cannotDecode
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
guard let sessionKeyDataEncrypted = Data(base64Encoded: sessionKeyArray[1]) else {
|
|
264
|
-
throw NSError(domain: "Invalid session key data", code: 1, userInfo: nil)
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
guard let sessionKeyDataDecrypted = rsaPrivateKey.decrypt(data: sessionKeyDataEncrypted) else {
|
|
268
|
-
throw NSError(domain: "Failed to decrypt session key data", code: 2, userInfo: nil)
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
let aesPrivateKey = AES128Key(iv: ivData, aes128Key: sessionKeyDataDecrypted)
|
|
272
|
-
|
|
273
|
-
guard let encryptedData = try? Data(contentsOf: filePath) else {
|
|
274
|
-
throw NSError(domain: "Failed to read encrypted data", code: 3, userInfo: nil)
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
guard let decryptedData = aesPrivateKey.decrypt(data: encryptedData) else {
|
|
278
|
-
throw NSError(domain: "Failed to decrypt data", code: 4, userInfo: nil)
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
try decryptedData.write(to: filePath)
|
|
282
|
-
|
|
283
|
-
} catch {
|
|
284
|
-
print("\(CapacitorUpdater.TAG) Cannot decode: \(filePath.path)", error)
|
|
285
|
-
throw CustomError.cannotDecode
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|