native-update 1.1.3 → 1.1.6
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/android/src/main/java/com/aoneahsan/nativeupdate/AppUpdatePlugin.kt +41 -0
- package/android/src/main/java/com/aoneahsan/nativeupdate/LiveUpdatePlugin.kt +12 -0
- package/android/src/main/java/com/aoneahsan/nativeupdate/NativeUpdatePlugin.kt +52 -1
- package/backend-template/README.md +56 -0
- package/backend-template/package.json +20 -0
- package/backend-template/server.js +121 -0
- package/cli/index.js +22 -14
- package/dist/esm/core/event-emitter.d.ts +33 -0
- package/dist/esm/core/event-emitter.js +75 -0
- package/dist/esm/core/event-emitter.js.map +1 -0
- package/dist/esm/core/plugin-manager.d.ts +16 -0
- package/dist/esm/core/plugin-manager.js +50 -0
- package/dist/esm/core/plugin-manager.js.map +1 -1
- package/dist/esm/definitions.d.ts +59 -0
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/plugin.js +43 -15
- package/dist/esm/plugin.js.map +1 -1
- package/dist/plugin.cjs.js +1 -1
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.esm.js +1 -1
- package/dist/plugin.esm.js.map +1 -1
- package/dist/plugin.js +2 -2
- package/dist/plugin.js.map +1 -1
- package/docs/NATIVE_UPDATES_GUIDE.md +1 -1
- package/docs/QUICK_START.md +1 -1
- package/docs/api/app-update-api.md +9 -1
- package/docs/api/background-update-api.md +157 -0
- package/docs/api/events-api.md +123 -0
- package/docs/api/live-update-api.md +16 -0
- package/docs/features/app-updates.md +4 -4
- package/docs/getting-started/quick-start.md +1 -1
- package/ios/Plugin/AppUpdate/AppUpdatePlugin.swift +15 -0
- package/ios/Plugin/LiveUpdate/LiveUpdatePlugin.swift +6 -0
- package/ios/Plugin/NativeUpdatePlugin.swift +45 -0
- package/package.json +24 -14
|
@@ -24,6 +24,7 @@ class AppUpdatePlugin(
|
|
|
24
24
|
private val activity: Activity,
|
|
25
25
|
private val context: Context
|
|
26
26
|
) {
|
|
27
|
+
private var eventListener: ((String, JSObject) -> Unit)? = null
|
|
27
28
|
private val appUpdateManager: AppUpdateManager = AppUpdateManagerFactory.create(context)
|
|
28
29
|
private var config: JSObject? = null
|
|
29
30
|
private var updateInfo: AppUpdateInfo? = null
|
|
@@ -44,6 +45,10 @@ class AppUpdatePlugin(
|
|
|
44
45
|
this.config = config
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
fun setEventListener(listener: (String, JSObject) -> Unit) {
|
|
49
|
+
this.eventListener = listener
|
|
50
|
+
}
|
|
51
|
+
|
|
47
52
|
fun getAppUpdateInfo(call: PluginCall) {
|
|
48
53
|
val appUpdateInfoTask = appUpdateManager.appUpdateInfo
|
|
49
54
|
|
|
@@ -66,6 +71,15 @@ class AppUpdatePlugin(
|
|
|
66
71
|
result.put("totalBytesToDownload", appUpdateInfo.totalBytesToDownload())
|
|
67
72
|
}
|
|
68
73
|
|
|
74
|
+
// Emit available event if update is available
|
|
75
|
+
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
|
|
76
|
+
val availableData = JSObject()
|
|
77
|
+
availableData.put("currentVersion", getCurrentAppVersion())
|
|
78
|
+
availableData.put("availableVersion", appUpdateInfo.availableVersionCode().toString())
|
|
79
|
+
availableData.put("updatePriority", appUpdateInfo.updatePriority())
|
|
80
|
+
eventListener?.invoke("appUpdateAvailable", availableData)
|
|
81
|
+
}
|
|
82
|
+
|
|
69
83
|
call.resolve(result)
|
|
70
84
|
}.addOnFailureListener { e ->
|
|
71
85
|
call.reject("UPDATE_CHECK_FAILED", e.message)
|
|
@@ -174,10 +188,33 @@ class AppUpdatePlugin(
|
|
|
174
188
|
}
|
|
175
189
|
|
|
176
190
|
private fun handleInstallState(state: InstallState) {
|
|
191
|
+
// Emit state change event
|
|
192
|
+
val eventData = JSObject()
|
|
193
|
+
eventData.put("status", getInstallStatusString(state.installStatus()))
|
|
194
|
+
if (state.installError() != 0) {
|
|
195
|
+
eventData.put("installErrorCode", state.installError())
|
|
196
|
+
}
|
|
197
|
+
eventListener?.invoke("appUpdateStateChanged", eventData)
|
|
198
|
+
|
|
199
|
+
// Emit progress event for downloads
|
|
200
|
+
if (state.installStatus() == InstallStatus.DOWNLOADING) {
|
|
201
|
+
val progressData = JSObject()
|
|
202
|
+
progressData.put("percent", if (state.totalBytesToDownload() > 0)
|
|
203
|
+
((state.bytesDownloaded() * 100) / state.totalBytesToDownload()).toInt() else 0)
|
|
204
|
+
progressData.put("bytesDownloaded", state.bytesDownloaded())
|
|
205
|
+
progressData.put("totalBytes", state.totalBytesToDownload())
|
|
206
|
+
eventListener?.invoke("appUpdateProgress", progressData)
|
|
207
|
+
}
|
|
208
|
+
|
|
177
209
|
when (state.installStatus()) {
|
|
178
210
|
InstallStatus.DOWNLOADED -> {
|
|
179
211
|
// Update has been downloaded, prompt user to restart
|
|
180
212
|
popupSnackbarForCompleteUpdate()
|
|
213
|
+
|
|
214
|
+
// Emit ready event
|
|
215
|
+
val readyData = JSObject()
|
|
216
|
+
readyData.put("message", "Update downloaded and ready to install")
|
|
217
|
+
eventListener?.invoke("appUpdateReady", readyData)
|
|
181
218
|
}
|
|
182
219
|
InstallStatus.INSTALLED -> {
|
|
183
220
|
// Update installed, cleanup
|
|
@@ -185,6 +222,10 @@ class AppUpdatePlugin(
|
|
|
185
222
|
}
|
|
186
223
|
InstallStatus.FAILED -> {
|
|
187
224
|
// Update failed
|
|
225
|
+
val failedData = JSObject()
|
|
226
|
+
failedData.put("error", "Update installation failed")
|
|
227
|
+
failedData.put("code", "INSTALL_FAILED")
|
|
228
|
+
eventListener?.invoke("appUpdateFailed", failedData)
|
|
188
229
|
}
|
|
189
230
|
else -> {
|
|
190
231
|
// Handle other states
|
|
@@ -23,10 +23,13 @@ class LiveUpdatePlugin(
|
|
|
23
23
|
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
|
24
24
|
private lateinit var okHttpClient: OkHttpClient
|
|
25
25
|
private val securityManager = SecurityManager(context)
|
|
26
|
+
private val activeDownloads = mutableMapOf<String, Long>()
|
|
27
|
+
private var downloadManager: android.app.DownloadManager? = null
|
|
26
28
|
|
|
27
29
|
init {
|
|
28
30
|
// Initialize OkHttp with default settings
|
|
29
31
|
okHttpClient = createOkHttpClient()
|
|
32
|
+
downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as? android.app.DownloadManager
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
private fun createOkHttpClient(): OkHttpClient {
|
|
@@ -495,6 +498,15 @@ class LiveUpdatePlugin(
|
|
|
495
498
|
}
|
|
496
499
|
}
|
|
497
500
|
|
|
501
|
+
fun cleanup() {
|
|
502
|
+
// Clean up any resources
|
|
503
|
+
// Cancel any ongoing downloads
|
|
504
|
+
activeDownloads.values.forEach { downloadId ->
|
|
505
|
+
downloadManager?.remove(downloadId)
|
|
506
|
+
}
|
|
507
|
+
activeDownloads.clear()
|
|
508
|
+
}
|
|
509
|
+
|
|
498
510
|
private fun markBundleAsVerified() {
|
|
499
511
|
val prefs = context.getSharedPreferences("native_update", Context.MODE_PRIVATE)
|
|
500
512
|
prefs.edit().putBoolean("current_bundle_verified", true).apply()
|
|
@@ -23,6 +23,7 @@ class NativeUpdatePlugin : Plugin() {
|
|
|
23
23
|
private lateinit var appReviewPlugin: AppReviewPlugin
|
|
24
24
|
private lateinit var backgroundUpdatePlugin: BackgroundUpdatePlugin
|
|
25
25
|
private lateinit var securityManager: SecurityManager
|
|
26
|
+
private var isInitialized = false
|
|
26
27
|
|
|
27
28
|
override fun load() {
|
|
28
29
|
super.load()
|
|
@@ -34,6 +35,11 @@ class NativeUpdatePlugin : Plugin() {
|
|
|
34
35
|
backgroundUpdatePlugin = BackgroundUpdatePlugin()
|
|
35
36
|
securityManager = SecurityManager(context)
|
|
36
37
|
|
|
38
|
+
// Set up app update event listener
|
|
39
|
+
appUpdatePlugin.setEventListener { eventName, data ->
|
|
40
|
+
notifyListeners(eventName, data)
|
|
41
|
+
}
|
|
42
|
+
|
|
37
43
|
// Register plugins with manager for background access
|
|
38
44
|
BackgroundUpdateManager.registerLiveUpdatePlugin(liveUpdatePlugin)
|
|
39
45
|
BackgroundUpdateManager.registerAppUpdatePlugin(appUpdatePlugin)
|
|
@@ -48,6 +54,45 @@ class NativeUpdatePlugin : Plugin() {
|
|
|
48
54
|
}
|
|
49
55
|
}
|
|
50
56
|
|
|
57
|
+
@PluginMethod
|
|
58
|
+
fun initialize(call: PluginCall) {
|
|
59
|
+
val config = call.getObject("config")
|
|
60
|
+
if (config == null) {
|
|
61
|
+
call.reject("Configuration object is required")
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
// Initialize with filesystem and preferences if provided
|
|
67
|
+
// In Android, we typically don't need these as we use native APIs
|
|
68
|
+
|
|
69
|
+
// Apply configuration
|
|
70
|
+
configure(call)
|
|
71
|
+
|
|
72
|
+
isInitialized = true
|
|
73
|
+
call.resolve()
|
|
74
|
+
} catch (e: Exception) {
|
|
75
|
+
call.reject("Initialization failed", e)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@PluginMethod
|
|
80
|
+
fun isInitialized(call: PluginCall) {
|
|
81
|
+
call.resolve(JSObject().put("initialized", isInitialized))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@PluginMethod
|
|
85
|
+
fun cleanup(call: PluginCall) {
|
|
86
|
+
try {
|
|
87
|
+
// Clean up any resources
|
|
88
|
+
liveUpdatePlugin.cleanup()
|
|
89
|
+
backgroundUpdatePlugin.disableBackgroundUpdates()
|
|
90
|
+
call.resolve()
|
|
91
|
+
} catch (e: Exception) {
|
|
92
|
+
call.reject("Cleanup failed", e)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
51
96
|
@PluginMethod
|
|
52
97
|
fun configure(call: PluginCall) {
|
|
53
98
|
val config = call.getObject("config")
|
|
@@ -118,7 +163,13 @@ class NativeUpdatePlugin : Plugin() {
|
|
|
118
163
|
|
|
119
164
|
@PluginMethod
|
|
120
165
|
fun reset(call: PluginCall) {
|
|
121
|
-
|
|
166
|
+
try {
|
|
167
|
+
// Reset plugin state
|
|
168
|
+
liveUpdatePlugin.reset(call)
|
|
169
|
+
isInitialized = false
|
|
170
|
+
} catch (e: Exception) {
|
|
171
|
+
call.reject("Reset failed", e)
|
|
172
|
+
}
|
|
122
173
|
}
|
|
123
174
|
|
|
124
175
|
@PluginMethod
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Minimal Backend Server Template
|
|
2
|
+
|
|
3
|
+
This is a minimal backend server for testing Capacitor Native Update plugin.
|
|
4
|
+
|
|
5
|
+
⚠️ **NOT FOR PRODUCTION USE** - This is only for development and testing!
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
1. Install dependencies:
|
|
10
|
+
```bash
|
|
11
|
+
npm install
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
2. Start server:
|
|
15
|
+
```bash
|
|
16
|
+
npm start
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Server runs on http://localhost:3000
|
|
20
|
+
|
|
21
|
+
## API Endpoints
|
|
22
|
+
|
|
23
|
+
### Check for Updates
|
|
24
|
+
```
|
|
25
|
+
GET /api/v1/check?version=1.0.0&channel=production&appId=com.example.app
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Upload Bundle
|
|
29
|
+
```
|
|
30
|
+
POST /api/v1/bundles
|
|
31
|
+
Content-Type: multipart/form-data
|
|
32
|
+
Fields:
|
|
33
|
+
- bundle: ZIP file
|
|
34
|
+
- version: 1.0.1
|
|
35
|
+
- appId: com.example.app
|
|
36
|
+
- channel: production
|
|
37
|
+
- mandatory: false
|
|
38
|
+
- releaseNotes: Bug fixes
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Download Bundle
|
|
42
|
+
```
|
|
43
|
+
GET /api/v1/bundles/bundle-1.0.1.zip
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Production Requirements
|
|
47
|
+
|
|
48
|
+
For production, you need:
|
|
49
|
+
- Real database (PostgreSQL, MongoDB, etc.)
|
|
50
|
+
- Authentication & authorization
|
|
51
|
+
- CDN for bundle distribution
|
|
52
|
+
- Rate limiting
|
|
53
|
+
- Monitoring & logging
|
|
54
|
+
- SSL/TLS certificates
|
|
55
|
+
- Load balancing
|
|
56
|
+
- Backup strategy
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "capacitor-update-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Minimal backend server for Capacitor Native Update",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "server.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "node server.js",
|
|
9
|
+
"dev": "node --watch server.js"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"express": "^4.18.2",
|
|
13
|
+
"cors": "^2.8.5",
|
|
14
|
+
"multer": "^1.4.5-lts.1",
|
|
15
|
+
"crypto": "^1.0.1"
|
|
16
|
+
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=18.0.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import multer from 'multer';
|
|
6
|
+
import crypto from 'crypto';
|
|
7
|
+
|
|
8
|
+
const app = express();
|
|
9
|
+
const PORT = process.env.PORT || 3000;
|
|
10
|
+
|
|
11
|
+
// Ensure directories exist
|
|
12
|
+
if (!existsSync('./bundles')) mkdirSync('./bundles');
|
|
13
|
+
if (!existsSync('./data')) mkdirSync('./data');
|
|
14
|
+
|
|
15
|
+
// Middleware
|
|
16
|
+
app.use(cors());
|
|
17
|
+
app.use(express.json());
|
|
18
|
+
|
|
19
|
+
// Storage for bundles
|
|
20
|
+
const storage = multer.diskStorage({
|
|
21
|
+
destination: './bundles',
|
|
22
|
+
filename: (req, file, cb) => {
|
|
23
|
+
const version = req.body.version || Date.now();
|
|
24
|
+
cb(null, `bundle-${version}.zip`);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const upload = multer({ storage });
|
|
29
|
+
|
|
30
|
+
// In-memory database (use real DB in production)
|
|
31
|
+
let versions = {};
|
|
32
|
+
const versionsFile = './data/versions.json';
|
|
33
|
+
|
|
34
|
+
// Load existing versions
|
|
35
|
+
if (existsSync(versionsFile)) {
|
|
36
|
+
versions = JSON.parse(readFileSync(versionsFile, 'utf8'));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Save versions
|
|
40
|
+
function saveVersions() {
|
|
41
|
+
writeFileSync(versionsFile, JSON.stringify(versions, null, 2));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Routes
|
|
45
|
+
app.get('/api/v1/check', (req, res) => {
|
|
46
|
+
const { version, channel = 'production', appId } = req.query;
|
|
47
|
+
|
|
48
|
+
const key = `${appId}-${channel}`;
|
|
49
|
+
const latest = versions[key];
|
|
50
|
+
|
|
51
|
+
if (!latest) {
|
|
52
|
+
return res.json({ updateAvailable: false });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
res.json({
|
|
56
|
+
updateAvailable: latest.version !== version,
|
|
57
|
+
version: latest.version,
|
|
58
|
+
downloadUrl: `${req.protocol}://${req.get('host')}/api/v1/bundles/${latest.bundleId}`,
|
|
59
|
+
checksum: latest.checksum,
|
|
60
|
+
size: latest.size,
|
|
61
|
+
mandatory: latest.mandatory || false,
|
|
62
|
+
releaseNotes: latest.releaseNotes || 'Bug fixes and improvements'
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
app.post('/api/v1/bundles', upload.single('bundle'), (req, res) => {
|
|
67
|
+
const { version, channel = 'production', appId, mandatory, releaseNotes } = req.body;
|
|
68
|
+
|
|
69
|
+
if (!req.file) {
|
|
70
|
+
return res.status(400).json({ error: 'No bundle file provided' });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Calculate checksum
|
|
74
|
+
const bundleData = readFileSync(req.file.path);
|
|
75
|
+
const checksum = crypto.createHash('sha256').update(bundleData).digest('hex');
|
|
76
|
+
|
|
77
|
+
const bundleId = req.file.filename;
|
|
78
|
+
const key = `${appId}-${channel}`;
|
|
79
|
+
|
|
80
|
+
versions[key] = {
|
|
81
|
+
version,
|
|
82
|
+
bundleId,
|
|
83
|
+
checksum,
|
|
84
|
+
size: req.file.size,
|
|
85
|
+
mandatory: mandatory === 'true',
|
|
86
|
+
releaseNotes,
|
|
87
|
+
uploadedAt: new Date().toISOString()
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
saveVersions();
|
|
91
|
+
|
|
92
|
+
res.json({
|
|
93
|
+
success: true,
|
|
94
|
+
version,
|
|
95
|
+
bundleId,
|
|
96
|
+
checksum
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
app.get('/api/v1/bundles/:bundleId', (req, res) => {
|
|
101
|
+
const bundlePath = join('./bundles', req.params.bundleId);
|
|
102
|
+
|
|
103
|
+
if (!existsSync(bundlePath)) {
|
|
104
|
+
return res.status(404).json({ error: 'Bundle not found' });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
res.sendFile(bundlePath, { root: '.' });
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
app.get('/health', (req, res) => {
|
|
111
|
+
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
app.listen(PORT, () => {
|
|
115
|
+
console.log(`Update server running on http://localhost:${PORT}`);
|
|
116
|
+
console.log('\nEndpoints:');
|
|
117
|
+
console.log(`- GET /api/v1/check`);
|
|
118
|
+
console.log(`- POST /api/v1/bundles`);
|
|
119
|
+
console.log(`- GET /api/v1/bundles/:bundleId`);
|
|
120
|
+
console.log(`- GET /health`);
|
|
121
|
+
});
|
package/cli/index.js
CHANGED
|
@@ -38,9 +38,11 @@ ${chalk.bold('Documentation:')}
|
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
// Bundle Management Commands
|
|
41
|
-
program
|
|
41
|
+
const bundleCmd = program
|
|
42
42
|
.command('bundle')
|
|
43
|
-
.description('Bundle management commands')
|
|
43
|
+
.description('Bundle management commands');
|
|
44
|
+
|
|
45
|
+
bundleCmd
|
|
44
46
|
.command('create <webDir>')
|
|
45
47
|
.alias('create-bundle')
|
|
46
48
|
.description(`Create an update bundle from your web directory
|
|
@@ -63,8 +65,7 @@ ${chalk.bold('Examples:')}
|
|
|
63
65
|
await createBundle(webDir, options);
|
|
64
66
|
});
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
.command('bundle')
|
|
68
|
+
bundleCmd
|
|
68
69
|
.command('sign <bundlePath>')
|
|
69
70
|
.alias('sign-bundle')
|
|
70
71
|
.description(`Sign an update bundle with your private key
|
|
@@ -82,8 +83,7 @@ ${chalk.bold('Examples:')}
|
|
|
82
83
|
await signBundle(bundlePath, options);
|
|
83
84
|
});
|
|
84
85
|
|
|
85
|
-
|
|
86
|
-
.command('bundle')
|
|
86
|
+
bundleCmd
|
|
87
87
|
.command('verify <bundlePath>')
|
|
88
88
|
.alias('verify-bundle')
|
|
89
89
|
.description(`Verify a signed bundle with public key
|
|
@@ -97,9 +97,11 @@ ${chalk.bold('Example:')}
|
|
|
97
97
|
});
|
|
98
98
|
|
|
99
99
|
// Key Management Commands
|
|
100
|
-
program
|
|
100
|
+
const keysCmd = program
|
|
101
101
|
.command('keys')
|
|
102
|
-
.description('Key management commands')
|
|
102
|
+
.description('Key management commands');
|
|
103
|
+
|
|
104
|
+
keysCmd
|
|
103
105
|
.command('generate')
|
|
104
106
|
.alias('generate-keys')
|
|
105
107
|
.description(`Generate a new key pair for bundle signing
|
|
@@ -122,9 +124,11 @@ ${chalk.bold('Examples:')}
|
|
|
122
124
|
});
|
|
123
125
|
|
|
124
126
|
// Server Commands
|
|
125
|
-
program
|
|
127
|
+
const serverCmd = program
|
|
126
128
|
.command('server')
|
|
127
|
-
.description('Development server commands')
|
|
129
|
+
.description('Development server commands');
|
|
130
|
+
|
|
131
|
+
serverCmd
|
|
128
132
|
.command('start')
|
|
129
133
|
.alias('start-server')
|
|
130
134
|
.description('Start a local update server for testing')
|
|
@@ -158,9 +162,11 @@ ${chalk.bold('Examples:')}
|
|
|
158
162
|
});
|
|
159
163
|
|
|
160
164
|
// Backend Commands
|
|
161
|
-
program
|
|
165
|
+
const backendCmd = program
|
|
162
166
|
.command('backend')
|
|
163
|
-
.description('Backend template commands')
|
|
167
|
+
.description('Backend template commands');
|
|
168
|
+
|
|
169
|
+
backendCmd
|
|
164
170
|
.command('create <type>')
|
|
165
171
|
.alias('create-backend')
|
|
166
172
|
.description(`Create a backend template (express, firebase, vercel)
|
|
@@ -183,9 +189,11 @@ ${chalk.bold('Examples:')}
|
|
|
183
189
|
});
|
|
184
190
|
|
|
185
191
|
// Config Commands
|
|
186
|
-
program
|
|
192
|
+
const configCmd = program
|
|
187
193
|
.command('config')
|
|
188
|
-
.description('Configuration commands')
|
|
194
|
+
.description('Configuration commands');
|
|
195
|
+
|
|
196
|
+
configCmd
|
|
189
197
|
.command('check')
|
|
190
198
|
.description('Validate your native-update configuration')
|
|
191
199
|
.action(async () => {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized event emitter for the Native Update plugin
|
|
3
|
+
*/
|
|
4
|
+
export declare class EventEmitter {
|
|
5
|
+
private static instance;
|
|
6
|
+
private listeners;
|
|
7
|
+
private constructor();
|
|
8
|
+
static getInstance(): EventEmitter;
|
|
9
|
+
/**
|
|
10
|
+
* Add a listener for an event
|
|
11
|
+
*/
|
|
12
|
+
addListener(eventName: string, listener: (data: unknown) => void): () => void;
|
|
13
|
+
/**
|
|
14
|
+
* Emit an event to all listeners
|
|
15
|
+
*/
|
|
16
|
+
emit(eventName: string, data: unknown): void;
|
|
17
|
+
/**
|
|
18
|
+
* Remove all listeners for a specific event
|
|
19
|
+
*/
|
|
20
|
+
removeListeners(eventName: string): void;
|
|
21
|
+
/**
|
|
22
|
+
* Remove all listeners
|
|
23
|
+
*/
|
|
24
|
+
removeAllListeners(): void;
|
|
25
|
+
/**
|
|
26
|
+
* Get the number of listeners for an event
|
|
27
|
+
*/
|
|
28
|
+
listenerCount(eventName: string): number;
|
|
29
|
+
/**
|
|
30
|
+
* Get all event names that have listeners
|
|
31
|
+
*/
|
|
32
|
+
eventNames(): string[];
|
|
33
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized event emitter for the Native Update plugin
|
|
3
|
+
*/
|
|
4
|
+
export class EventEmitter {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.listeners = new Map();
|
|
7
|
+
}
|
|
8
|
+
static getInstance() {
|
|
9
|
+
if (!EventEmitter.instance) {
|
|
10
|
+
EventEmitter.instance = new EventEmitter();
|
|
11
|
+
}
|
|
12
|
+
return EventEmitter.instance;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Add a listener for an event
|
|
16
|
+
*/
|
|
17
|
+
addListener(eventName, listener) {
|
|
18
|
+
if (!this.listeners.has(eventName)) {
|
|
19
|
+
this.listeners.set(eventName, new Set());
|
|
20
|
+
}
|
|
21
|
+
this.listeners.get(eventName).add(listener);
|
|
22
|
+
// Return remove function
|
|
23
|
+
return () => {
|
|
24
|
+
const listeners = this.listeners.get(eventName);
|
|
25
|
+
if (listeners) {
|
|
26
|
+
listeners.delete(listener);
|
|
27
|
+
if (listeners.size === 0) {
|
|
28
|
+
this.listeners.delete(eventName);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Emit an event to all listeners
|
|
35
|
+
*/
|
|
36
|
+
emit(eventName, data) {
|
|
37
|
+
const listeners = this.listeners.get(eventName);
|
|
38
|
+
if (listeners) {
|
|
39
|
+
listeners.forEach(listener => {
|
|
40
|
+
try {
|
|
41
|
+
listener(data);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
console.error(`Error in event listener for ${eventName}:`, error);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Remove all listeners for a specific event
|
|
51
|
+
*/
|
|
52
|
+
removeListeners(eventName) {
|
|
53
|
+
this.listeners.delete(eventName);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Remove all listeners
|
|
57
|
+
*/
|
|
58
|
+
removeAllListeners() {
|
|
59
|
+
this.listeners.clear();
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get the number of listeners for an event
|
|
63
|
+
*/
|
|
64
|
+
listenerCount(eventName) {
|
|
65
|
+
var _a;
|
|
66
|
+
return ((_a = this.listeners.get(eventName)) === null || _a === void 0 ? void 0 : _a.size) || 0;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get all event names that have listeners
|
|
70
|
+
*/
|
|
71
|
+
eventNames() {
|
|
72
|
+
return Array.from(this.listeners.keys());
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=event-emitter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-emitter.js","sourceRoot":"","sources":["../../../src/core/event-emitter.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,OAAO,YAAY;IAIvB;QAFQ,cAAS,GAA8C,IAAI,GAAG,EAAE,CAAC;IAElD,CAAC;IAExB,MAAM,CAAC,WAAW;QAChB,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,CAAC;YAC3B,YAAY,CAAC,QAAQ,GAAG,IAAI,YAAY,EAAE,CAAC;QAC7C,CAAC;QACD,OAAO,YAAY,CAAC,QAAQ,CAAC;IAC/B,CAAC;IAED;;OAEG;IACH,WAAW,CACT,SAAiB,EACjB,QAAiC;QAEjC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YACnC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;QAC3C,CAAC;QAED,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAE7C,yBAAyB;QACzB,OAAO,GAAG,EAAE;YACV,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YAChD,IAAI,SAAS,EAAE,CAAC;gBACd,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBAC3B,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;oBACzB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;gBACnC,CAAC;YACH,CAAC;QACH,CAAC,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,IAAI,CAAC,SAAiB,EAAE,IAAa;QACnC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAChD,IAAI,SAAS,EAAE,CAAC;YACd,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAC3B,IAAI,CAAC;oBACH,QAAQ,CAAC,IAAI,CAAC,CAAC;gBACjB,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,OAAO,CAAC,KAAK,CAAC,+BAA+B,SAAS,GAAG,EAAE,KAAK,CAAC,CAAC;gBACpE,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED;;OAEG;IACH,eAAe,CAAC,SAAiB;QAC/B,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACnC,CAAC;IAED;;OAEG;IACH,kBAAkB;QAChB,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;IACzB,CAAC;IAED;;OAEG;IACH,aAAa,CAAC,SAAiB;;QAC7B,OAAO,CAAA,MAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,0CAAE,IAAI,KAAI,CAAC,CAAC;IAClD,CAAC;IAED;;OAEG;IACH,UAAU;QACR,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC;CACF"}
|
|
@@ -4,6 +4,8 @@ import { SecurityValidator } from './security';
|
|
|
4
4
|
import { BundleManager } from '../live-update/bundle-manager';
|
|
5
5
|
import { DownloadManager } from '../live-update/download-manager';
|
|
6
6
|
import { VersionManager } from '../live-update/version-manager';
|
|
7
|
+
import { AppUpdateManager } from '../app-update/app-update-manager';
|
|
8
|
+
import { EventEmitter } from './event-emitter';
|
|
7
9
|
import { PluginInitConfig } from '../definitions';
|
|
8
10
|
/**
|
|
9
11
|
* Central manager for all plugin components
|
|
@@ -13,9 +15,11 @@ export declare class PluginManager {
|
|
|
13
15
|
private readonly configManager;
|
|
14
16
|
private readonly logger;
|
|
15
17
|
private readonly securityValidator;
|
|
18
|
+
private readonly eventEmitter;
|
|
16
19
|
private bundleManager;
|
|
17
20
|
private downloadManager;
|
|
18
21
|
private versionManager;
|
|
22
|
+
private appUpdateManager;
|
|
19
23
|
private initialized;
|
|
20
24
|
private constructor();
|
|
21
25
|
static getInstance(): PluginManager;
|
|
@@ -55,6 +59,14 @@ export declare class PluginManager {
|
|
|
55
59
|
* Get security validator
|
|
56
60
|
*/
|
|
57
61
|
getSecurityValidator(): SecurityValidator;
|
|
62
|
+
/**
|
|
63
|
+
* Get app update manager
|
|
64
|
+
*/
|
|
65
|
+
getAppUpdateManager(): AppUpdateManager;
|
|
66
|
+
/**
|
|
67
|
+
* Get event emitter
|
|
68
|
+
*/
|
|
69
|
+
getEventEmitter(): EventEmitter;
|
|
58
70
|
/**
|
|
59
71
|
* Reset plugin state
|
|
60
72
|
*/
|
|
@@ -63,4 +75,8 @@ export declare class PluginManager {
|
|
|
63
75
|
* Clean up resources
|
|
64
76
|
*/
|
|
65
77
|
cleanup(): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Setup event bridge between AppUpdateManager and central EventEmitter
|
|
80
|
+
*/
|
|
81
|
+
private setupAppUpdateEventBridge;
|
|
66
82
|
}
|