@simplysm/capacitor-plugin-auto-update 13.0.69 → 13.0.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -196
- package/dist/ApkInstaller.d.ts +8 -8
- package/dist/ApkInstaller.js +5 -5
- package/dist/AutoUpdate.js +17 -17
- package/dist/AutoUpdate.js.map +1 -1
- package/dist/web/ApkInstallerWeb.js +1 -1
- package/dist/web/ApkInstallerWeb.js.map +1 -1
- package/package.json +9 -8
- package/src/ApkInstaller.ts +54 -0
- package/src/AutoUpdate.ts +236 -0
- package/src/IApkInstallerPlugin.ts +12 -0
- package/src/index.ts +6 -0
- package/src/web/ApkInstallerWeb.ts +31 -0
package/README.md
CHANGED
|
@@ -1,211 +1,28 @@
|
|
|
1
1
|
# @simplysm/capacitor-plugin-auto-update
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Supported Platforms
|
|
6
|
-
|
|
7
|
-
| Platform | Supported | Notes |
|
|
8
|
-
|--------|-----------|------|
|
|
9
|
-
| Android | Yes | API 23+ (minSdk 23), compileSdk 35 |
|
|
10
|
-
| Web | Partial | Fallback implementation provided (alert notification, permission checks skipped) |
|
|
11
|
-
| iOS | No | Not supported |
|
|
3
|
+
Simplysm Package - Capacitor Auto Update Plugin
|
|
12
4
|
|
|
13
5
|
## Installation
|
|
14
6
|
|
|
15
|
-
```bash
|
|
16
7
|
pnpm add @simplysm/capacitor-plugin-auto-update
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Android Configuration
|
|
20
|
-
|
|
21
|
-
### AndroidManifest.xml
|
|
22
|
-
|
|
23
|
-
The plugin requires `REQUEST_INSTALL_PACKAGES` permission for APK installation. This is already declared in the plugin's manifest, so you don't need to add it separately in your app.
|
|
24
|
-
|
|
25
|
-
```xml
|
|
26
|
-
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
### Capacitor Plugin Registration
|
|
30
|
-
|
|
31
|
-
The plugin is automatically registered in `capacitor.config.ts` or in the Android project. The Android source path is specified in the `capacitor` field of `package.json`.
|
|
32
|
-
|
|
33
|
-
## Main Modules
|
|
34
|
-
|
|
35
|
-
This package exports two main classes.
|
|
36
|
-
|
|
37
|
-
### ApkInstaller
|
|
38
|
-
|
|
39
|
-
A low-level API responsible for APK installation and permission management. All methods are `static`.
|
|
40
|
-
|
|
41
|
-
| Method | Return Type | Description |
|
|
42
|
-
|--------|-----------|------|
|
|
43
|
-
| `hasPermissionManifest()` | `Promise<boolean>` | Check if `REQUEST_INSTALL_PACKAGES` permission is declared in AndroidManifest.xml |
|
|
44
|
-
| `hasPermission()` | `Promise<boolean>` | Check if `REQUEST_INSTALL_PACKAGES` permission is currently granted |
|
|
45
|
-
| `requestPermission()` | `Promise<void>` | Request installation permission (navigates to Android settings screen) |
|
|
46
|
-
| `install(apkUri)` | `Promise<void>` | Execute APK installation. `apkUri` is a `content://` URI (FileProvider URI) |
|
|
47
|
-
| `getVersionInfo()` | `Promise<IVersionInfo>` | Retrieve current app version information |
|
|
48
|
-
|
|
49
|
-
### IVersionInfo
|
|
50
|
-
|
|
51
|
-
An interface representing app version information.
|
|
52
|
-
|
|
53
|
-
| Property | Type | Description |
|
|
54
|
-
|------|------|------|
|
|
55
|
-
| `versionName` | `string` | App version name (e.g., `"1.2.3"`) |
|
|
56
|
-
| `versionCode` | `string` | App version code (integer represented as string) |
|
|
57
|
-
|
|
58
|
-
### IApkInstallerPlugin
|
|
59
|
-
|
|
60
|
-
Capacitor native plugin interface. Generally used indirectly through the `ApkInstaller` class.
|
|
61
|
-
|
|
62
|
-
| Method | Return Type | Description |
|
|
63
|
-
|--------|-----------|------|
|
|
64
|
-
| `install(options)` | `Promise<void>` | Install APK specified in `options.uri` |
|
|
65
|
-
| `hasPermission()` | `Promise<{ granted: boolean }>` | Check installation permission |
|
|
66
|
-
| `requestPermission()` | `Promise<void>` | Request installation permission |
|
|
67
|
-
| `hasPermissionManifest()` | `Promise<{ declared: boolean }>` | Check manifest permission declaration |
|
|
68
|
-
| `getVersionInfo()` | `Promise<IVersionInfo>` | Retrieve version information |
|
|
69
|
-
|
|
70
|
-
### AutoUpdate
|
|
71
|
-
|
|
72
|
-
A high-level API that manages the complete automatic update flow. All methods are `static`.
|
|
73
|
-
|
|
74
|
-
| Method | Return Type | Description |
|
|
75
|
-
|--------|-----------|------|
|
|
76
|
-
| `run(opt)` | `Promise<void>` | Execute server-based automatic update |
|
|
77
|
-
| `runByExternalStorage(opt)` | `Promise<void>` | Execute external storage-based automatic update |
|
|
78
|
-
|
|
79
|
-
## Usage Examples
|
|
80
|
-
|
|
81
|
-
### Server-Based Automatic Update
|
|
82
|
-
|
|
83
|
-
The most common usage method for checking and updating to the latest APK deployed on the server. It connects to the server's `AutoUpdateService` through `ServiceClient` to retrieve the latest version information.
|
|
84
|
-
|
|
85
|
-
```typescript
|
|
86
|
-
import { AutoUpdate } from "@simplysm/capacitor-plugin-auto-update";
|
|
87
|
-
import { createServiceClient } from "@simplysm/service-client";
|
|
88
|
-
|
|
89
|
-
const serviceClient = createServiceClient("my-app", {
|
|
90
|
-
host: "your-server.example.com",
|
|
91
|
-
port: 443,
|
|
92
|
-
ssl: true,
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
await AutoUpdate.run({
|
|
96
|
-
log: (messageHtml: string) => {
|
|
97
|
-
// Display update progress in UI
|
|
98
|
-
document.getElementById("update-status")!.innerHTML = messageHtml;
|
|
99
|
-
},
|
|
100
|
-
serviceClient,
|
|
101
|
-
});
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
Internal flow of the `run` method:
|
|
105
|
-
|
|
106
|
-
1. Call server's `AutoUpdateService.getLastVersion("android")` to check latest version
|
|
107
|
-
2. Check and request `REQUEST_INSTALL_PACKAGES` permission (wait up to 5 minutes)
|
|
108
|
-
3. Compare current app version with server version using semver
|
|
109
|
-
4. Download APK if server version is higher (provides progress callback)
|
|
110
|
-
5. Save APK file to app cache and execute installation intent
|
|
111
|
-
6. Freeze app after installation (encourage user to restart)
|
|
112
|
-
|
|
113
|
-
### External Storage-Based Update
|
|
114
|
-
|
|
115
|
-
A method for updating using an APK file pre-placed on external storage (e.g., USB, SD card) without network connectivity. The APK filename must be in semver format (e.g., `1.2.3.apk`).
|
|
116
|
-
|
|
117
|
-
```typescript
|
|
118
|
-
import { AutoUpdate } from "@simplysm/capacitor-plugin-auto-update";
|
|
119
|
-
|
|
120
|
-
await AutoUpdate.runByExternalStorage({
|
|
121
|
-
log: (messageHtml: string) => {
|
|
122
|
-
document.getElementById("update-status")!.innerHTML = messageHtml;
|
|
123
|
-
},
|
|
124
|
-
dirPath: "MyApp/updates",
|
|
125
|
-
});
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
Internal flow of the `runByExternalStorage` method:
|
|
129
|
-
|
|
130
|
-
1. Check and request `REQUEST_INSTALL_PACKAGES` permission
|
|
131
|
-
2. Retrieve `.apk` file list from specified directory in external storage
|
|
132
|
-
3. Extract version from filenames and determine latest version using semver
|
|
133
|
-
4. Compare with current app version and execute installation if latest version
|
|
134
|
-
|
|
135
|
-
### Direct Use of ApkInstaller
|
|
136
|
-
|
|
137
|
-
Use `ApkInstaller` when you need to directly control the update flow.
|
|
138
|
-
|
|
139
|
-
```typescript
|
|
140
|
-
import { ApkInstaller } from "@simplysm/capacitor-plugin-auto-update";
|
|
141
|
-
|
|
142
|
-
// Check current app version
|
|
143
|
-
const versionInfo = await ApkInstaller.getVersionInfo();
|
|
144
|
-
console.log(`Current version: ${versionInfo.versionName} (${versionInfo.versionCode})`);
|
|
145
|
-
|
|
146
|
-
// Check and request permission
|
|
147
|
-
const hasManifest = await ApkInstaller.hasPermissionManifest();
|
|
148
|
-
if (!hasManifest) {
|
|
149
|
-
throw new Error("REQUEST_INSTALL_PACKAGES permission is not declared in AndroidManifest.xml.");
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const hasPermission = await ApkInstaller.hasPermission();
|
|
153
|
-
if (!hasPermission) {
|
|
154
|
-
await ApkInstaller.requestPermission();
|
|
155
|
-
// Need to wait until user grants permission in settings screen
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Install APK (requires content:// URI)
|
|
159
|
-
await ApkInstaller.install("content://com.example.fileprovider/apk/update.apk");
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
## Web Environment Behavior
|
|
163
|
-
|
|
164
|
-
When running in a web browser, the `ApkInstallerWeb` fallback is automatically used.
|
|
165
|
-
|
|
166
|
-
| Method | Web Behavior |
|
|
167
|
-
|--------|---------|
|
|
168
|
-
| `install()` | Show alert about unsupported feature then return normally |
|
|
169
|
-
| `hasPermission()` | Always return `{ granted: true }` |
|
|
170
|
-
| `requestPermission()` | No operation (no-op) |
|
|
171
|
-
| `hasPermissionManifest()` | Always return `{ declared: true }` |
|
|
172
|
-
| `getVersionInfo()` | Return `import.meta.env.__VER__` value as `versionName` (or `"0.0.0"` if not available) |
|
|
173
|
-
|
|
174
|
-
## Server-Side Requirements
|
|
175
|
-
|
|
176
|
-
To use the `AutoUpdate.run()` method, the server must implement the `AutoUpdateService` interface.
|
|
177
|
-
|
|
178
|
-
```typescript
|
|
179
|
-
interface AutoUpdateService {
|
|
180
|
-
getLastVersion(platform: string): Promise<
|
|
181
|
-
| { version: string; downloadPath: string }
|
|
182
|
-
| undefined
|
|
183
|
-
>;
|
|
184
|
-
}
|
|
185
|
-
```
|
|
186
8
|
|
|
187
|
-
|
|
188
|
-
- `version`: Version string in semver format (e.g., `"1.2.3"`)
|
|
189
|
-
- `downloadPath`: Download path for the APK file (combined with server host URL)
|
|
9
|
+
**Peer Dependencies:** `@capacitor/core ^7.4.4`
|
|
190
10
|
|
|
191
|
-
##
|
|
11
|
+
## Source Index
|
|
192
12
|
|
|
193
|
-
###
|
|
13
|
+
### APK Installer
|
|
194
14
|
|
|
195
|
-
|
|
|
196
|
-
|
|
197
|
-
|
|
|
15
|
+
| Source | Exports | Description | Test |
|
|
16
|
+
|--------|---------|-------------|------|
|
|
17
|
+
| `src/ApkInstaller.ts` | `ApkInstaller` | Static class to check/request install permission and trigger APK installation | - |
|
|
18
|
+
| `src/IApkInstallerPlugin.ts` | `IVersionInfo`, `IApkInstallerPlugin` | Interfaces for app version info and the native APK installer plugin contract | - |
|
|
198
19
|
|
|
199
|
-
###
|
|
20
|
+
### Auto Update
|
|
200
21
|
|
|
201
|
-
|
|
|
202
|
-
|
|
203
|
-
|
|
|
204
|
-
| `@simplysm/core-browser` | `fetchUrlBytes` (file download) |
|
|
205
|
-
| `@simplysm/capacitor-plugin-file-system` | File read/write, URI conversion, storage paths |
|
|
206
|
-
| `@simplysm/service-client` | `ServiceClient` (server communication) |
|
|
207
|
-
| `@simplysm/service-common` | `AutoUpdateService` interface definition |
|
|
22
|
+
| Source | Exports | Description | Test |
|
|
23
|
+
|--------|---------|-------------|------|
|
|
24
|
+
| `src/AutoUpdate.ts` | `AutoUpdate` | Downloads and installs the latest APK from a server or external storage | - |
|
|
208
25
|
|
|
209
26
|
## License
|
|
210
27
|
|
|
211
|
-
|
|
28
|
+
Apache-2.0
|
package/dist/ApkInstaller.d.ts
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
import type { IVersionInfo } from "./IApkInstallerPlugin";
|
|
2
2
|
/**
|
|
3
|
-
* APK
|
|
4
|
-
* - Android: APK
|
|
5
|
-
* - Browser: alert
|
|
3
|
+
* APK installation plugin
|
|
4
|
+
* - Android: Executes APK install intent, manages REQUEST_INSTALL_PACKAGES permission
|
|
5
|
+
* - Browser: Shows alert message and returns normally
|
|
6
6
|
*/
|
|
7
7
|
export declare abstract class ApkInstaller {
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Check if REQUEST_INSTALL_PACKAGES permission is declared in the manifest
|
|
10
10
|
*/
|
|
11
11
|
static hasPermissionManifest(): Promise<boolean>;
|
|
12
12
|
/**
|
|
13
|
-
* REQUEST_INSTALL_PACKAGES
|
|
13
|
+
* Check if REQUEST_INSTALL_PACKAGES permission is granted
|
|
14
14
|
*/
|
|
15
15
|
static hasPermission(): Promise<boolean>;
|
|
16
16
|
/**
|
|
17
|
-
* REQUEST_INSTALL_PACKAGES
|
|
17
|
+
* Request REQUEST_INSTALL_PACKAGES permission (navigates to settings)
|
|
18
18
|
*/
|
|
19
19
|
static requestPermission(): Promise<void>;
|
|
20
20
|
/**
|
|
21
|
-
* APK
|
|
21
|
+
* Install APK
|
|
22
22
|
* @param apkUri content:// URI (FileProvider URI)
|
|
23
23
|
*/
|
|
24
24
|
static install(apkUri: string): Promise<void>;
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
26
|
+
* Get app version info
|
|
27
27
|
*/
|
|
28
28
|
static getVersionInfo(): Promise<IVersionInfo>;
|
|
29
29
|
}
|
package/dist/ApkInstaller.js
CHANGED
|
@@ -7,34 +7,34 @@ const ApkInstallerPlugin = registerPlugin("ApkInstaller", {
|
|
|
7
7
|
});
|
|
8
8
|
class ApkInstaller {
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Check if REQUEST_INSTALL_PACKAGES permission is declared in the manifest
|
|
11
11
|
*/
|
|
12
12
|
static async hasPermissionManifest() {
|
|
13
13
|
const result = await ApkInstallerPlugin.hasPermissionManifest();
|
|
14
14
|
return result.declared;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
* REQUEST_INSTALL_PACKAGES
|
|
17
|
+
* Check if REQUEST_INSTALL_PACKAGES permission is granted
|
|
18
18
|
*/
|
|
19
19
|
static async hasPermission() {
|
|
20
20
|
const result = await ApkInstallerPlugin.hasPermission();
|
|
21
21
|
return result.granted;
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
* REQUEST_INSTALL_PACKAGES
|
|
24
|
+
* Request REQUEST_INSTALL_PACKAGES permission (navigates to settings)
|
|
25
25
|
*/
|
|
26
26
|
static async requestPermission() {
|
|
27
27
|
await ApkInstallerPlugin.requestPermission();
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
|
-
* APK
|
|
30
|
+
* Install APK
|
|
31
31
|
* @param apkUri content:// URI (FileProvider URI)
|
|
32
32
|
*/
|
|
33
33
|
static async install(apkUri) {
|
|
34
34
|
await ApkInstallerPlugin.install({ uri: apkUri });
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
|
-
*
|
|
37
|
+
* Get app version info
|
|
38
38
|
*/
|
|
39
39
|
static async getVersionInfo() {
|
|
40
40
|
return ApkInstallerPlugin.getVersionInfo();
|
package/dist/AutoUpdate.js
CHANGED
|
@@ -29,34 +29,34 @@ class AutoUpdate {
|
|
|
29
29
|
class="_button"
|
|
30
30
|
href="intent://${targetHref.replace(/^https?:\/\//, "")}#Intent;scheme=http;end"
|
|
31
31
|
>
|
|
32
|
-
|
|
32
|
+
Download
|
|
33
33
|
</a>
|
|
34
34
|
` : "";
|
|
35
35
|
throw new Error(html`
|
|
36
|
-
|
|
36
|
+
You need to re-download and install the APK file(${code}). ${downloadHtml}
|
|
37
37
|
`);
|
|
38
38
|
}
|
|
39
39
|
static async _checkPermission(log, targetHref) {
|
|
40
40
|
if (!navigator.userAgent.toLowerCase().includes("android")) {
|
|
41
|
-
throw new Error("
|
|
41
|
+
throw new Error("Only Android is supported.");
|
|
42
42
|
}
|
|
43
43
|
try {
|
|
44
44
|
if (!await ApkInstaller.hasPermissionManifest()) {
|
|
45
45
|
this._throwAboutReinstall(1, targetHref);
|
|
46
46
|
}
|
|
47
47
|
} catch (err) {
|
|
48
|
-
console.error("[AutoUpdate] hasPermissionManifest
|
|
48
|
+
console.error("[AutoUpdate] hasPermissionManifest check failed:", err);
|
|
49
49
|
this._throwAboutReinstall(2, targetHref);
|
|
50
50
|
}
|
|
51
51
|
const hasPerm = await ApkInstaller.hasPermission();
|
|
52
52
|
if (!hasPerm) {
|
|
53
53
|
log(html`
|
|
54
|
-
|
|
54
|
+
Installation permission must be enabled.
|
|
55
55
|
<style>
|
|
56
56
|
button { ${this._BUTTON_CSS} }
|
|
57
57
|
button:active { ${this._BUTTON_ACTIVE_CSS} }
|
|
58
58
|
</style>
|
|
59
|
-
<button onclick="location.reload()"
|
|
59
|
+
<button onclick="location.reload()">Retry</button>
|
|
60
60
|
`);
|
|
61
61
|
await ApkInstaller.requestPermission();
|
|
62
62
|
await waitUntil(
|
|
@@ -70,19 +70,19 @@ class AutoUpdate {
|
|
|
70
70
|
}
|
|
71
71
|
static async _installApk(log, apkFilePath) {
|
|
72
72
|
log(html`
|
|
73
|
-
|
|
73
|
+
Please install the latest version and restart.
|
|
74
74
|
<style>
|
|
75
75
|
button { ${this._BUTTON_CSS} }
|
|
76
76
|
button:active { ${this._BUTTON_ACTIVE_CSS} }
|
|
77
77
|
</style>
|
|
78
|
-
<button onclick="location.reload()"
|
|
78
|
+
<button onclick="location.reload()">Retry</button>
|
|
79
79
|
`);
|
|
80
80
|
const apkFileUri = await FileSystem.getFileUri(apkFilePath);
|
|
81
81
|
await ApkInstaller.install(apkFileUri);
|
|
82
82
|
}
|
|
83
83
|
static _getErrorMessage(err) {
|
|
84
84
|
return html`
|
|
85
|
-
|
|
85
|
+
Error occurred during update:
|
|
86
86
|
<br />
|
|
87
87
|
${err instanceof Error ? err.message : String(err)}
|
|
88
88
|
`;
|
|
@@ -93,14 +93,14 @@ class AutoUpdate {
|
|
|
93
93
|
}
|
|
94
94
|
static async run(opt) {
|
|
95
95
|
try {
|
|
96
|
-
opt.log(
|
|
96
|
+
opt.log(`Checking latest version...`);
|
|
97
97
|
const autoUpdateServiceClient = opt.serviceClient.getService("AutoUpdateService");
|
|
98
98
|
const serverVersionInfo = await autoUpdateServiceClient.getLastVersion("android");
|
|
99
99
|
if (!serverVersionInfo) {
|
|
100
|
-
console.log("
|
|
100
|
+
console.log("Failed to get latest version information from server.");
|
|
101
101
|
return;
|
|
102
102
|
}
|
|
103
|
-
opt.log(
|
|
103
|
+
opt.log(`Checking permission...`);
|
|
104
104
|
await this._checkPermission(
|
|
105
105
|
opt.log,
|
|
106
106
|
opt.serviceClient.hostUrl + serverVersionInfo.downloadPath
|
|
@@ -113,7 +113,7 @@ class AutoUpdate {
|
|
|
113
113
|
if (!semver.gt(serverVersionInfo.version, currentVersionInfo.versionName)) {
|
|
114
114
|
return;
|
|
115
115
|
}
|
|
116
|
-
opt.log(
|
|
116
|
+
opt.log(`Downloading latest version file...`);
|
|
117
117
|
const buffer = await fetchUrlBytes(
|
|
118
118
|
opt.serviceClient.hostUrl + serverVersionInfo.downloadPath,
|
|
119
119
|
{
|
|
@@ -121,7 +121,7 @@ class AutoUpdate {
|
|
|
121
121
|
const progressText = (progress.receivedLength * 100 / progress.contentLength).toFixed(
|
|
122
122
|
2
|
|
123
123
|
);
|
|
124
|
-
opt.log(
|
|
124
|
+
opt.log(`Downloading latest version file...(${progressText}%)`);
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
);
|
|
@@ -137,9 +137,9 @@ class AutoUpdate {
|
|
|
137
137
|
}
|
|
138
138
|
static async runByExternalStorage(opt) {
|
|
139
139
|
try {
|
|
140
|
-
opt.log(
|
|
140
|
+
opt.log(`Checking permission...`);
|
|
141
141
|
await this._checkPermission(opt.log);
|
|
142
|
-
opt.log(
|
|
142
|
+
opt.log(`Checking latest version...`);
|
|
143
143
|
const externalPath = await FileSystem.getStoragePath("external");
|
|
144
144
|
const fileInfos = await FileSystem.readdir(pathJoin(externalPath, opt.dirPath));
|
|
145
145
|
const versions = fileInfos.filter((fileInfo) => !fileInfo.isDirectory).map((fileInfo) => ({
|
|
@@ -155,7 +155,7 @@ class AutoUpdate {
|
|
|
155
155
|
"*"
|
|
156
156
|
);
|
|
157
157
|
if (latestVersion == null) {
|
|
158
|
-
console.log("
|
|
158
|
+
console.log("No valid semver version files found.");
|
|
159
159
|
return;
|
|
160
160
|
}
|
|
161
161
|
const currentVersionInfo = await ApkInstaller.getVersionInfo();
|
package/dist/AutoUpdate.js.map
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/AutoUpdate.ts"],
|
|
4
|
-
"mappings": "AAAA,SAAS,kBAAkB;AAC3B,SAAS,MAAM,WAAW,UAAU,cAAc,mBAAmB;AACrE,SAAS,qBAAqB;AAG9B,OAAO,YAAY;AACnB,SAAS,oBAAoB;AAEtB,MAAe,WAAW;AAAA,EAC/B,OAAwB,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAatC,OAAwB,qBAAqB;AAAA;AAAA;AAAA,EAI7C,OAAe,qBAAqB,MAAc,YAAqB;AACrE,UAAM,eACJ,cAAc,OACV;AAAA;AAAA,2BAEiB,KAAK,WAAW;AAAA,kCACT,KAAK,kBAAkB;AAAA;AAAA;AAAA;AAAA,+BAI1B,WAAW,QAAQ,gBAAgB,EAAE,CAAC;AAAA;AAAA;AAAA;AAAA,cAK3D;AAEN,UAAM,IAAI,MAAM;AAAA,
|
|
4
|
+
"mappings": "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",
|
|
5
5
|
"names": []
|
|
6
6
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { WebPlugin } from "@capacitor/core";
|
|
2
2
|
class ApkInstallerWeb extends WebPlugin {
|
|
3
3
|
install(_options) {
|
|
4
|
-
alert("[ApkInstaller]
|
|
4
|
+
alert("[ApkInstaller] APK installation is not supported in web environment.");
|
|
5
5
|
return Promise.resolve();
|
|
6
6
|
}
|
|
7
7
|
hasPermission() {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/web/ApkInstallerWeb.ts"],
|
|
4
|
-
"mappings": "AAAA,SAAS,iBAAiB;AAGnB,MAAM,wBAAwB,UAAyC;AAAA,EAC5E,QAAQ,UAA0C;AAChD,UAAM,
|
|
4
|
+
"mappings": "AAAA,SAAS,iBAAiB;AAGnB,MAAM,wBAAwB,UAAyC;AAAA,EAC5E,QAAQ,UAA0C;AAChD,UAAM,sEAAsE;AAC5E,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAAA,EAEA,gBAA+C;AAE7C,WAAO,QAAQ,QAAQ,EAAE,SAAS,KAAK,CAAC;AAAA,EAC1C;AAAA,EAEA,MAAM,oBAAmC;AAAA,EAEzC;AAAA,EAEA,wBAAwD;AAEtD,WAAO,QAAQ,QAAQ,EAAE,UAAU,KAAK,CAAC;AAAA,EAC3C;AAAA,EAEA,iBAAwC;AAvB1C;AAwBI,WAAO,QAAQ,QAAQ;AAAA,MACrB,eACG,iBAA4D,QAA5D,mBAAkE,eAAc;AAAA,MACnF,aAAa;AAAA,IACf,CAAC;AAAA,EACH;AACF;",
|
|
5
5
|
"names": []
|
|
6
6
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/capacitor-plugin-auto-update",
|
|
3
|
-
"version": "13.0.
|
|
4
|
-
"description": "
|
|
5
|
-
"author": "
|
|
3
|
+
"version": "13.0.70",
|
|
4
|
+
"description": "Simplysm Package - Capacitor Auto Update Plugin",
|
|
5
|
+
"author": "simplysm",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -14,15 +14,16 @@
|
|
|
14
14
|
"types": "./dist/index.d.ts",
|
|
15
15
|
"files": [
|
|
16
16
|
"dist",
|
|
17
|
+
"src",
|
|
17
18
|
"android"
|
|
18
19
|
],
|
|
19
20
|
"dependencies": {
|
|
20
21
|
"semver": "^7.7.4",
|
|
21
|
-
"@simplysm/capacitor-plugin-file-system": "13.0.
|
|
22
|
-
"@simplysm/core-browser": "13.0.
|
|
23
|
-
"@simplysm/core-common": "13.0.
|
|
24
|
-
"@simplysm/service-client": "13.0.
|
|
25
|
-
"@simplysm/service-common": "13.0.
|
|
22
|
+
"@simplysm/capacitor-plugin-file-system": "13.0.70",
|
|
23
|
+
"@simplysm/core-browser": "13.0.70",
|
|
24
|
+
"@simplysm/core-common": "13.0.70",
|
|
25
|
+
"@simplysm/service-client": "13.0.70",
|
|
26
|
+
"@simplysm/service-common": "13.0.70"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
29
|
"@capacitor/core": "^7.5.0",
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { registerPlugin } from "@capacitor/core";
|
|
2
|
+
import type { IApkInstallerPlugin, IVersionInfo } from "./IApkInstallerPlugin";
|
|
3
|
+
|
|
4
|
+
const ApkInstallerPlugin = registerPlugin<IApkInstallerPlugin>("ApkInstaller", {
|
|
5
|
+
web: async () => {
|
|
6
|
+
const { ApkInstallerWeb } = await import("./web/ApkInstallerWeb");
|
|
7
|
+
return new ApkInstallerWeb();
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* APK installation plugin
|
|
13
|
+
* - Android: Executes APK install intent, manages REQUEST_INSTALL_PACKAGES permission
|
|
14
|
+
* - Browser: Shows alert message and returns normally
|
|
15
|
+
*/
|
|
16
|
+
export abstract class ApkInstaller {
|
|
17
|
+
/**
|
|
18
|
+
* Check if REQUEST_INSTALL_PACKAGES permission is declared in the manifest
|
|
19
|
+
*/
|
|
20
|
+
static async hasPermissionManifest(): Promise<boolean> {
|
|
21
|
+
const result = await ApkInstallerPlugin.hasPermissionManifest();
|
|
22
|
+
return result.declared;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Check if REQUEST_INSTALL_PACKAGES permission is granted
|
|
27
|
+
*/
|
|
28
|
+
static async hasPermission(): Promise<boolean> {
|
|
29
|
+
const result = await ApkInstallerPlugin.hasPermission();
|
|
30
|
+
return result.granted;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Request REQUEST_INSTALL_PACKAGES permission (navigates to settings)
|
|
35
|
+
*/
|
|
36
|
+
static async requestPermission(): Promise<void> {
|
|
37
|
+
await ApkInstallerPlugin.requestPermission();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Install APK
|
|
42
|
+
* @param apkUri content:// URI (FileProvider URI)
|
|
43
|
+
*/
|
|
44
|
+
static async install(apkUri: string): Promise<void> {
|
|
45
|
+
await ApkInstallerPlugin.install({ uri: apkUri });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get app version info
|
|
50
|
+
*/
|
|
51
|
+
static async getVersionInfo(): Promise<IVersionInfo> {
|
|
52
|
+
return ApkInstallerPlugin.getVersionInfo();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { FileSystem } from "@simplysm/capacitor-plugin-file-system";
|
|
2
|
+
import { html, waitUntil, pathJoin, pathBasename, pathExtname } from "@simplysm/core-common";
|
|
3
|
+
import { fetchUrlBytes } from "@simplysm/core-browser";
|
|
4
|
+
import type { ServiceClient } from "@simplysm/service-client";
|
|
5
|
+
import type { AutoUpdateService } from "@simplysm/service-common";
|
|
6
|
+
import semver from "semver";
|
|
7
|
+
import { ApkInstaller } from "./ApkInstaller";
|
|
8
|
+
|
|
9
|
+
export abstract class AutoUpdate {
|
|
10
|
+
private static readonly _BUTTON_CSS = `
|
|
11
|
+
all: unset;
|
|
12
|
+
color: blue;
|
|
13
|
+
width: 100%;
|
|
14
|
+
padding: 10px;
|
|
15
|
+
line-height: 1.5em;
|
|
16
|
+
font-size: 20px;
|
|
17
|
+
position: fixed;
|
|
18
|
+
bottom: 0;
|
|
19
|
+
left: 0;
|
|
20
|
+
border-top: 1px solid lightgrey;
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
private static readonly _BUTTON_ACTIVE_CSS = `
|
|
24
|
+
background: lightgrey;
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
private static _throwAboutReinstall(code: number, targetHref?: string) {
|
|
28
|
+
const downloadHtml =
|
|
29
|
+
targetHref != null
|
|
30
|
+
? html`
|
|
31
|
+
<style>
|
|
32
|
+
._button { ${this._BUTTON_CSS} }
|
|
33
|
+
._button:active { ${this._BUTTON_ACTIVE_CSS} }
|
|
34
|
+
</style>
|
|
35
|
+
<a
|
|
36
|
+
class="_button"
|
|
37
|
+
href="intent://${targetHref.replace(/^https?:\/\//, "")}#Intent;scheme=http;end"
|
|
38
|
+
>
|
|
39
|
+
Download
|
|
40
|
+
</a>
|
|
41
|
+
`
|
|
42
|
+
: "";
|
|
43
|
+
|
|
44
|
+
throw new Error(html`
|
|
45
|
+
You need to re-download and install the APK file(${code}). ${downloadHtml}
|
|
46
|
+
`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private static async _checkPermission(log: (messageHtml: string) => void, targetHref?: string) {
|
|
50
|
+
if (!navigator.userAgent.toLowerCase().includes("android")) {
|
|
51
|
+
throw new Error("Only Android is supported.");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
if (!(await ApkInstaller.hasPermissionManifest())) {
|
|
56
|
+
this._throwAboutReinstall(1, targetHref);
|
|
57
|
+
}
|
|
58
|
+
} catch (err) {
|
|
59
|
+
// eslint-disable-next-line no-console
|
|
60
|
+
console.error("[AutoUpdate] hasPermissionManifest check failed:", err);
|
|
61
|
+
this._throwAboutReinstall(2, targetHref);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const hasPerm = await ApkInstaller.hasPermission();
|
|
65
|
+
if (!hasPerm) {
|
|
66
|
+
log(html`
|
|
67
|
+
Installation permission must be enabled.
|
|
68
|
+
<style>
|
|
69
|
+
button { ${this._BUTTON_CSS} }
|
|
70
|
+
button:active { ${this._BUTTON_ACTIVE_CSS} }
|
|
71
|
+
</style>
|
|
72
|
+
<button onclick="location.reload()">Retry</button>
|
|
73
|
+
`);
|
|
74
|
+
await ApkInstaller.requestPermission();
|
|
75
|
+
// Wait up to 5 minutes (300 seconds) - time for user to grant permission in settings
|
|
76
|
+
await waitUntil(
|
|
77
|
+
async () => {
|
|
78
|
+
return ApkInstaller.hasPermission();
|
|
79
|
+
},
|
|
80
|
+
1000,
|
|
81
|
+
300,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private static async _installApk(
|
|
87
|
+
log: (messageHtml: string) => void,
|
|
88
|
+
apkFilePath: string,
|
|
89
|
+
): Promise<void> {
|
|
90
|
+
log(html`
|
|
91
|
+
Please install the latest version and restart.
|
|
92
|
+
<style>
|
|
93
|
+
button { ${this._BUTTON_CSS} }
|
|
94
|
+
button:active { ${this._BUTTON_ACTIVE_CSS} }
|
|
95
|
+
</style>
|
|
96
|
+
<button onclick="location.reload()">Retry</button>
|
|
97
|
+
`);
|
|
98
|
+
const apkFileUri = await FileSystem.getFileUri(apkFilePath);
|
|
99
|
+
await ApkInstaller.install(apkFileUri);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private static _getErrorMessage(err: unknown) {
|
|
103
|
+
return html`
|
|
104
|
+
Error occurred during update:
|
|
105
|
+
<br />
|
|
106
|
+
${err instanceof Error ? err.message : String(err)}
|
|
107
|
+
`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private static async _freezeApp() {
|
|
111
|
+
await new Promise(() => {}); // Wait indefinitely
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static async run(opt: { log: (messageHtml: string) => void; serviceClient: ServiceClient }) {
|
|
115
|
+
try {
|
|
116
|
+
opt.log(`Checking latest version...`);
|
|
117
|
+
|
|
118
|
+
// Get version and download link from server
|
|
119
|
+
const autoUpdateServiceClient =
|
|
120
|
+
opt.serviceClient.getService<AutoUpdateService>("AutoUpdateService");
|
|
121
|
+
|
|
122
|
+
const serverVersionInfo = await autoUpdateServiceClient.getLastVersion("android");
|
|
123
|
+
if (!serverVersionInfo) {
|
|
124
|
+
// eslint-disable-next-line no-console
|
|
125
|
+
console.log("Failed to get latest version information from server.");
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
opt.log(`Checking permission...`);
|
|
130
|
+
await this._checkPermission(
|
|
131
|
+
opt.log,
|
|
132
|
+
opt.serviceClient.hostUrl + serverVersionInfo.downloadPath,
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// Get current app version
|
|
136
|
+
const currentVersionInfo = await ApkInstaller.getVersionInfo();
|
|
137
|
+
|
|
138
|
+
// Return if already latest or server version is lower
|
|
139
|
+
if (
|
|
140
|
+
semver.valid(currentVersionInfo.versionName) === null ||
|
|
141
|
+
semver.valid(serverVersionInfo.version) === null
|
|
142
|
+
) {
|
|
143
|
+
// eslint-disable-next-line no-console
|
|
144
|
+
console.log("Invalid semver version, skipping update check");
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (!semver.gt(serverVersionInfo.version, currentVersionInfo.versionName)) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
opt.log(`Downloading latest version file...`);
|
|
152
|
+
const buffer = await fetchUrlBytes(
|
|
153
|
+
opt.serviceClient.hostUrl + serverVersionInfo.downloadPath,
|
|
154
|
+
{
|
|
155
|
+
onProgress: (progress) => {
|
|
156
|
+
const progressText = ((progress.receivedLength * 100) / progress.contentLength).toFixed(
|
|
157
|
+
2,
|
|
158
|
+
);
|
|
159
|
+
opt.log(`Downloading latest version file...(${progressText}%)`);
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
);
|
|
163
|
+
const storagePath = await FileSystem.getStoragePath("appCache");
|
|
164
|
+
const apkFilePath = pathJoin(storagePath, `latest.apk`);
|
|
165
|
+
await FileSystem.writeFile(apkFilePath, buffer);
|
|
166
|
+
|
|
167
|
+
await this._installApk(opt.log, apkFilePath);
|
|
168
|
+
await this._freezeApp();
|
|
169
|
+
} catch (err) {
|
|
170
|
+
opt.log(this._getErrorMessage(err));
|
|
171
|
+
await this._freezeApp();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
static async runByExternalStorage(opt: { log: (messageHtml: string) => void; dirPath: string }) {
|
|
176
|
+
try {
|
|
177
|
+
opt.log(`Checking permission...`);
|
|
178
|
+
await this._checkPermission(opt.log);
|
|
179
|
+
|
|
180
|
+
opt.log(`Checking latest version...`);
|
|
181
|
+
|
|
182
|
+
// Get versions
|
|
183
|
+
const externalPath = await FileSystem.getStoragePath("external");
|
|
184
|
+
const fileInfos = await FileSystem.readdir(pathJoin(externalPath, opt.dirPath));
|
|
185
|
+
|
|
186
|
+
const versions = fileInfos
|
|
187
|
+
.filter((fileInfo) => !fileInfo.isDirectory)
|
|
188
|
+
.map((fileInfo) => ({
|
|
189
|
+
fileName: fileInfo.name,
|
|
190
|
+
version: pathBasename(fileInfo.name, pathExtname(fileInfo.name)),
|
|
191
|
+
extName: pathExtname(fileInfo.name),
|
|
192
|
+
}))
|
|
193
|
+
.filter((item) => {
|
|
194
|
+
return item.extName === ".apk" && /^[0-9.]*$/.test(item.version);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Return if no version files are saved
|
|
198
|
+
if (versions.length === 0) return;
|
|
199
|
+
|
|
200
|
+
const latestVersion = semver.maxSatisfying(
|
|
201
|
+
versions.map((item) => item.version),
|
|
202
|
+
"*",
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Return if no valid semver versions
|
|
206
|
+
if (latestVersion == null) {
|
|
207
|
+
// eslint-disable-next-line no-console
|
|
208
|
+
console.log("No valid semver version files found.");
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Get current app version
|
|
213
|
+
const currentVersionInfo = await ApkInstaller.getVersionInfo();
|
|
214
|
+
|
|
215
|
+
// Return if already latest or external storage version is lower
|
|
216
|
+
if (
|
|
217
|
+
semver.valid(currentVersionInfo.versionName) === null ||
|
|
218
|
+
semver.valid(latestVersion) === null
|
|
219
|
+
) {
|
|
220
|
+
// eslint-disable-next-line no-console
|
|
221
|
+
console.log("Invalid semver version, skipping update check");
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (!semver.gt(latestVersion, currentVersionInfo.versionName)) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const apkFilePath = pathJoin(externalPath, opt.dirPath, latestVersion + ".apk");
|
|
229
|
+
await this._installApk(opt.log, apkFilePath);
|
|
230
|
+
await this._freezeApp();
|
|
231
|
+
} catch (err) {
|
|
232
|
+
opt.log(this._getErrorMessage(err));
|
|
233
|
+
await this._freezeApp();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface IVersionInfo {
|
|
2
|
+
versionName: string;
|
|
3
|
+
versionCode: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface IApkInstallerPlugin {
|
|
7
|
+
install(options: { uri: string }): Promise<void>;
|
|
8
|
+
hasPermission(): Promise<{ granted: boolean }>;
|
|
9
|
+
requestPermission(): Promise<void>;
|
|
10
|
+
hasPermissionManifest(): Promise<{ declared: boolean }>;
|
|
11
|
+
getVersionInfo(): Promise<IVersionInfo>;
|
|
12
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { WebPlugin } from "@capacitor/core";
|
|
2
|
+
import type { IApkInstallerPlugin, IVersionInfo } from "../IApkInstallerPlugin";
|
|
3
|
+
|
|
4
|
+
export class ApkInstallerWeb extends WebPlugin implements IApkInstallerPlugin {
|
|
5
|
+
install(_options: { uri: string }): Promise<void> {
|
|
6
|
+
alert("[ApkInstaller] APK installation is not supported in web environment.");
|
|
7
|
+
return Promise.resolve();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
hasPermission(): Promise<{ granted: boolean }> {
|
|
11
|
+
// Skip permission check on web
|
|
12
|
+
return Promise.resolve({ granted: true });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async requestPermission(): Promise<void> {
|
|
16
|
+
// No-op on web
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
hasPermissionManifest(): Promise<{ declared: boolean }> {
|
|
20
|
+
// Skip manifest check on web
|
|
21
|
+
return Promise.resolve({ declared: true });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getVersionInfo(): Promise<IVersionInfo> {
|
|
25
|
+
return Promise.resolve({
|
|
26
|
+
versionName:
|
|
27
|
+
(import.meta as unknown as { env?: Record<string, string> }).env?.["__VER__"] ?? "0.0.0",
|
|
28
|
+
versionCode: "0",
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|