@simplysm/capacitor-plugin-auto-update 13.0.68 → 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 CHANGED
@@ -1,211 +1,28 @@
1
1
  # @simplysm/capacitor-plugin-auto-update
2
2
 
3
- A Capacitor plugin that supports automatic updates for Android apps. It provides the complete update flow including checking the latest APK version from the server, downloading, and installing it. It also supports updates through APK files on external storage.
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
- - `platform`: The string `"android"` is passed
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
- ## Dependencies
11
+ ## Source Index
192
12
 
193
- ### Peer Dependencies
13
+ ### APK Installer
194
14
 
195
- | Package | Version |
196
- |--------|------|
197
- | `@capacitor/core` | `^7.4.4` |
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
- ### Internal Dependencies
20
+ ### Auto Update
200
21
 
201
- | Package | Purpose |
202
- |--------|------|
203
- | `@simplysm/core-common` | Path utilities, HTML templates, `waitUntil`, etc. |
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
- MIT
28
+ Apache-2.0
@@ -1,29 +1,29 @@
1
1
  import type { IVersionInfo } from "./IApkInstallerPlugin";
2
2
  /**
3
- * APK 설치 플러그인
4
- * - Android: APK 설치 인텐트 실행, REQUEST_INSTALL_PACKAGES 권한 관리
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
- * Manifest에 REQUEST_INSTALL_PACKAGES 권한이 선언되어 있는지 확인
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
  }
@@ -7,34 +7,34 @@ const ApkInstallerPlugin = registerPlugin("ApkInstaller", {
7
7
  });
8
8
  class ApkInstaller {
9
9
  /**
10
- * Manifest에 REQUEST_INSTALL_PACKAGES 권한이 선언되어 있는지 확인
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();
@@ -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
- APK파일을 다시 다운로드 받아, 설치해야 합니다(${code}). ${downloadHtml}
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("\uC548\uB4DC\uB85C\uC774\uB4DC\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4.");
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 \uCCB4\uD06C \uC2E4\uD328:", err);
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()">재시도</button>
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()">재시도</button>
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(`\uCD5C\uC2E0\uBC84\uC804 \uD655\uC778 \uC911...`);
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("\uC11C\uBC84\uC5D0\uC11C \uCD5C\uC2E0\uBC84\uC804 \uC815\uBCF4\uB97C \uAC00\uC838\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
100
+ console.log("Failed to get latest version information from server.");
101
101
  return;
102
102
  }
103
- opt.log(`\uAD8C\uD55C \uD655\uC778 \uC911...`);
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(`\uCD5C\uC2E0\uBC84\uC804 \uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC\uC911...`);
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(`\uCD5C\uC2E0\uBC84\uC804 \uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC\uC911...(${progressText}%)`);
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(`\uAD8C\uD55C \uD655\uC778 \uC911...`);
140
+ opt.log(`Checking permission...`);
141
141
  await this._checkPermission(opt.log);
142
- opt.log(`\uCD5C\uC2E0\uBC84\uC804 \uD655\uC778 \uC911...`);
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("\uC720\uD6A8\uD55C semver \uBC84\uC804 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
158
+ console.log("No valid semver version files found.");
159
159
  return;
160
160
  }
161
161
  const currentVersionInfo = await ApkInstaller.getVersionInfo();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/AutoUpdate.ts"],
4
- "mappings": "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",
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] \uC6F9 \uD658\uACBD\uC5D0\uC11C\uB294 APK \uC124\uCE58\uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
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,gIAA2C;AACjD,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;",
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.68",
4
- "description": "심플리즘 패키지 - Capacitor Auto Update Plugin",
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.68",
22
- "@simplysm/core-browser": "13.0.68",
23
- "@simplysm/core-common": "13.0.68",
24
- "@simplysm/service-common": "13.0.68",
25
- "@simplysm/service-client": "13.0.68"
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,6 @@
1
+ // APK Installer
2
+ export * from "./ApkInstaller";
3
+ export * from "./IApkInstallerPlugin";
4
+
5
+ // Auto Update
6
+ export * from "./AutoUpdate";
@@ -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
+ }