native-update 1.0.8 → 1.1.0
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 +35 -17
- package/android/src/main/AndroidManifest.xml +0 -16
- package/cli/cap-update.js +45 -0
- package/cli/commands/backend-create.js +582 -0
- package/cli/commands/bundle-create.js +113 -0
- package/cli/commands/bundle-sign.js +58 -0
- package/cli/commands/bundle-verify.js +55 -0
- package/cli/commands/init.js +146 -0
- package/cli/commands/keys-generate.js +92 -0
- package/cli/commands/monitor.js +68 -0
- package/cli/commands/server-start.js +96 -0
- package/cli/index.js +269 -0
- package/cli/package.json +12 -0
- package/docs/BUNDLE_SIGNING.md +16 -9
- package/docs/LIVE_UPDATES_GUIDE.md +1 -1
- package/docs/README.md +1 -0
- package/docs/cli-reference.md +321 -0
- package/docs/examples/android-manifest-example.xml +77 -0
- package/docs/getting-started/configuration.md +3 -2
- package/docs/getting-started/installation.md +101 -2
- package/docs/getting-started/quick-start.md +53 -1
- package/docs/guides/deployment-guide.md +9 -7
- package/docs/guides/key-management.md +284 -0
- package/docs/guides/migration-from-codepush.md +9 -5
- package/docs/guides/testing-guide.md +4 -4
- package/package.json +15 -2
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!--
|
|
3
|
+
EXAMPLE: Complete Android Manifest for apps using native-update plugin
|
|
4
|
+
Location: android/app/src/main/AndroidManifest.xml (in YOUR app, not the plugin)
|
|
5
|
+
|
|
6
|
+
This example shows the correct placement of all required elements for the
|
|
7
|
+
native-update plugin with background update support.
|
|
8
|
+
-->
|
|
9
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
10
|
+
xmlns:tools="http://schemas.android.com/tools"
|
|
11
|
+
package="com.yourcompany.yourapp">
|
|
12
|
+
|
|
13
|
+
<!-- Permissions are added automatically by the plugin, but listed here for reference -->
|
|
14
|
+
<!-- These permissions allow the plugin to function properly -->
|
|
15
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
16
|
+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
17
|
+
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
18
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
19
|
+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
20
|
+
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
|
21
|
+
|
|
22
|
+
<application
|
|
23
|
+
android:allowBackup="true"
|
|
24
|
+
android:icon="@mipmap/ic_launcher"
|
|
25
|
+
android:label="@string/app_name"
|
|
26
|
+
android:roundIcon="@mipmap/ic_launcher_round"
|
|
27
|
+
android:supportsRtl="true"
|
|
28
|
+
android:theme="@style/AppTheme">
|
|
29
|
+
|
|
30
|
+
<!-- Your main activity and other components -->
|
|
31
|
+
<activity
|
|
32
|
+
android:name=".MainActivity"
|
|
33
|
+
android:label="@string/app_name"
|
|
34
|
+
android:theme="@style/AppTheme.NoActionBar"
|
|
35
|
+
android:launchMode="singleTask"
|
|
36
|
+
android:exported="true">
|
|
37
|
+
<intent-filter>
|
|
38
|
+
<action android:name="android.intent.action.MAIN" />
|
|
39
|
+
<category android:name="android.intent.category.LAUNCHER" />
|
|
40
|
+
</intent-filter>
|
|
41
|
+
</activity>
|
|
42
|
+
|
|
43
|
+
<!-- ============================================= -->
|
|
44
|
+
<!-- REQUIRED FOR NATIVE-UPDATE BACKGROUND UPDATES -->
|
|
45
|
+
<!-- ============================================= -->
|
|
46
|
+
|
|
47
|
+
<!-- WorkManager Foreground Service -->
|
|
48
|
+
<!-- This service is REQUIRED for background downloads to work -->
|
|
49
|
+
<!-- Without it: Downloads will fail with ServiceNotFoundException -->
|
|
50
|
+
<!-- Purpose: Keeps downloads running when app is in background -->
|
|
51
|
+
<service
|
|
52
|
+
android:name="androidx.work.impl.foreground.SystemForegroundService"
|
|
53
|
+
android:foregroundServiceType="dataSync"
|
|
54
|
+
tools:node="merge" />
|
|
55
|
+
|
|
56
|
+
<!-- Notification Action Receiver -->
|
|
57
|
+
<!-- This receiver is REQUIRED for notification buttons to work -->
|
|
58
|
+
<!-- Without it: Update notifications will show but buttons won't work -->
|
|
59
|
+
<!-- Purpose: Handles "Update Now", "Update Later", and "Dismiss" actions -->
|
|
60
|
+
<receiver
|
|
61
|
+
android:name="com.aoneahsan.nativeupdate.NotificationActionReceiver"
|
|
62
|
+
android:exported="false">
|
|
63
|
+
<intent-filter>
|
|
64
|
+
<!-- Action triggered when user taps "Update Now" -->
|
|
65
|
+
<action android:name="com.aoneahsan.nativeupdate.UPDATE_NOW" />
|
|
66
|
+
<!-- Action triggered when user taps "Update Later" -->
|
|
67
|
+
<action android:name="com.aoneahsan.nativeupdate.UPDATE_LATER" />
|
|
68
|
+
<!-- Action triggered when user dismisses the notification -->
|
|
69
|
+
<action android:name="com.aoneahsan.nativeupdate.DISMISS" />
|
|
70
|
+
</intent-filter>
|
|
71
|
+
</receiver>
|
|
72
|
+
|
|
73
|
+
<!-- Other app components like providers, services, etc. -->
|
|
74
|
+
|
|
75
|
+
</application>
|
|
76
|
+
|
|
77
|
+
</manifest>
|
|
@@ -66,8 +66,9 @@ liveUpdate: {
|
|
|
66
66
|
serverUrl: 'https://updates.yourserver.com',
|
|
67
67
|
channel: 'production',
|
|
68
68
|
|
|
69
|
-
// Security
|
|
70
|
-
|
|
69
|
+
// Security (see Key Management Guide for generating keys)
|
|
70
|
+
// Generate keys: npx native-update keys generate --type rsa --size 4096
|
|
71
|
+
publicKey: 'YOUR_RSA_PUBLIC_KEY', // Base64 encoded public key
|
|
71
72
|
requireSignature: true,
|
|
72
73
|
checksumAlgorithm: 'SHA-256', // or 'SHA-512'
|
|
73
74
|
|
|
@@ -98,8 +98,73 @@ This command will:
|
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
3. The plugin automatically adds required permissions to the manifest:
|
|
101
|
-
-
|
|
102
|
-
-
|
|
101
|
+
- **`INTERNET`** - Required for downloading update bundles from your server
|
|
102
|
+
- **`ACCESS_NETWORK_STATE`** - Required to check network availability before downloading
|
|
103
|
+
- **`WAKE_LOCK`** - Required to keep the device awake during background downloads
|
|
104
|
+
- **`FOREGROUND_SERVICE`** - Required for showing download progress notifications
|
|
105
|
+
- **`POST_NOTIFICATIONS`** - Required for update notifications on Android 13+ ([API 33](https://developer.android.com/develop/ui/views/notifications/notification-permission))
|
|
106
|
+
- **`RECEIVE_BOOT_COMPLETED`** - Required to resume interrupted downloads after device restart
|
|
107
|
+
|
|
108
|
+
4. **CRITICAL: Android Manifest Configuration for Background Updates**
|
|
109
|
+
|
|
110
|
+
If you're using background updates, you **MUST** add these elements to your app's `AndroidManifest.xml` inside the `<application>` tag. Without these, background updates will fail silently:
|
|
111
|
+
|
|
112
|
+
```xml
|
|
113
|
+
<application>
|
|
114
|
+
<!-- Your existing application configuration -->
|
|
115
|
+
|
|
116
|
+
<!-- WorkManager Foreground Service (REQUIRED for background downloads) -->
|
|
117
|
+
<!-- Without this: Background downloads will be killed by the system after ~10 minutes -->
|
|
118
|
+
<!-- Documentation: https://developer.android.com/topic/libraries/architecture/workmanager/advanced/long-running -->
|
|
119
|
+
<service
|
|
120
|
+
android:name="androidx.work.impl.foreground.SystemForegroundService"
|
|
121
|
+
android:foregroundServiceType="dataSync"
|
|
122
|
+
tools:node="merge" />
|
|
123
|
+
|
|
124
|
+
<!-- Notification Action Receiver (REQUIRED for update notifications) -->
|
|
125
|
+
<!-- Without this: "Update Now" and "Dismiss" buttons in notifications won't work -->
|
|
126
|
+
<!-- The receiver handles user actions from update notifications -->
|
|
127
|
+
<receiver
|
|
128
|
+
android:name="com.aoneahsan.nativeupdate.NotificationActionReceiver"
|
|
129
|
+
android:exported="false">
|
|
130
|
+
<intent-filter>
|
|
131
|
+
<action android:name="com.aoneahsan.nativeupdate.UPDATE_NOW" />
|
|
132
|
+
<action android:name="com.aoneahsan.nativeupdate.UPDATE_LATER" />
|
|
133
|
+
<action android:name="com.aoneahsan.nativeupdate.DISMISS" />
|
|
134
|
+
</intent-filter>
|
|
135
|
+
</receiver>
|
|
136
|
+
</application>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**What happens if you skip this step:**
|
|
140
|
+
- ❌ Background downloads will be terminated by Android after ~10 minutes
|
|
141
|
+
- ❌ Update notifications won't display progress
|
|
142
|
+
- ❌ Notification action buttons won't respond to taps
|
|
143
|
+
- ❌ Downloads won't resume after app restart
|
|
144
|
+
- ❌ Users will experience failed or incomplete updates
|
|
145
|
+
|
|
146
|
+
**Detailed Explanation of Each Component:**
|
|
147
|
+
|
|
148
|
+
- **SystemForegroundService**: Android's WorkManager service that handles long-running background tasks
|
|
149
|
+
- **Purpose**: Keeps downloads alive when app is backgrounded or device is locked
|
|
150
|
+
- **`foregroundServiceType="dataSync"`**: Tells Android this service downloads/syncs data
|
|
151
|
+
- **What breaks without it**: Background downloads crash with `ServiceNotFoundException`
|
|
152
|
+
- **Reference**: [WorkManager Long-running Workers](https://developer.android.com/topic/libraries/architecture/workmanager/advanced/long-running)
|
|
153
|
+
|
|
154
|
+
- **NotificationActionReceiver**: Handles user interactions with update notifications
|
|
155
|
+
- **`UPDATE_NOW`**: Triggered when user taps "Update Now" - starts immediate installation
|
|
156
|
+
- **`UPDATE_LATER`**: Triggered when user taps "Update Later" - schedules for later
|
|
157
|
+
- **`DISMISS`**: Triggered when user dismisses notification - cancels pending update
|
|
158
|
+
- **What breaks without it**: Buttons appear in notifications but don't do anything when tapped
|
|
159
|
+
- **Reference**: [Notification Actions](https://developer.android.com/develop/ui/views/notifications/notification-actions)
|
|
160
|
+
|
|
161
|
+
- **Security Attributes Explained**:
|
|
162
|
+
- **`android:exported="false"`**: Prevents other apps from sending intents to your receiver (security best practice)
|
|
163
|
+
- **`tools:node="merge"`**: Handles conflicts if multiple libraries define the same service
|
|
164
|
+
|
|
165
|
+
**Note:** If you're only using immediate (foreground) updates, these additions are optional.
|
|
166
|
+
|
|
167
|
+
📋 **[View Complete Android Manifest Example](../examples/android-manifest-example.xml)** - Shows the correct placement of all elements
|
|
103
168
|
|
|
104
169
|
#### Web Setup
|
|
105
170
|
|
|
@@ -165,6 +230,40 @@ import type {
|
|
|
165
230
|
|
|
166
231
|
## Troubleshooting Installation
|
|
167
232
|
|
|
233
|
+
### Android Manifest Errors
|
|
234
|
+
|
|
235
|
+
1. **"AAPT: error: unexpected element <service> found in <manifest>"**
|
|
236
|
+
- **Cause**: Service/receiver elements placed outside `<application>` tag or in wrong manifest
|
|
237
|
+
- **Fix**: Add to your **app's** manifest at `android/app/src/main/AndroidManifest.xml`, NOT the plugin's manifest
|
|
238
|
+
- **Example of CORRECT placement**:
|
|
239
|
+
```xml
|
|
240
|
+
<manifest>
|
|
241
|
+
<uses-permission ... />
|
|
242
|
+
<application>
|
|
243
|
+
<!-- HERE is where service and receiver go -->
|
|
244
|
+
<service ... />
|
|
245
|
+
<receiver ... />
|
|
246
|
+
</application>
|
|
247
|
+
</manifest>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
2. **"ServiceNotFoundException" when downloading updates**
|
|
251
|
+
- **Cause**: Missing WorkManager service declaration
|
|
252
|
+
- **Symptoms**: App crashes when starting background download
|
|
253
|
+
- **Fix**: Add the SystemForegroundService to your app's manifest as shown above
|
|
254
|
+
- **Log message**: `java.lang.IllegalArgumentException: Service not registered: androidx.work.impl.foreground.SystemForegroundService`
|
|
255
|
+
|
|
256
|
+
3. **Notification buttons don't work**
|
|
257
|
+
- **Cause**: Missing NotificationActionReceiver declaration
|
|
258
|
+
- **Symptoms**: Update notification appears but buttons don't respond
|
|
259
|
+
- **Fix**: Add the receiver with all three action filters to your app's manifest
|
|
260
|
+
- **Test**: Send a test notification and verify buttons trigger actions
|
|
261
|
+
|
|
262
|
+
4. **"Permission denied" errors on Android 13+**
|
|
263
|
+
- **Cause**: Missing runtime permission request for notifications
|
|
264
|
+
- **Fix**: Request `POST_NOTIFICATIONS` permission at runtime
|
|
265
|
+
- **Reference**: [Android 13 Notification Permission](https://developer.android.com/develop/ui/views/notifications/notification-permission)
|
|
266
|
+
|
|
168
267
|
### Common Issues
|
|
169
268
|
|
|
170
269
|
1. **"Module not found" error**
|
|
@@ -345,14 +345,66 @@ async function safeUpdateCheck() {
|
|
|
345
345
|
});
|
|
346
346
|
```
|
|
347
347
|
|
|
348
|
+
## Setting Up Backend & Creating Updates
|
|
349
|
+
|
|
350
|
+
### Quick Backend Setup
|
|
351
|
+
|
|
352
|
+
Use our CLI to quickly set up a backend:
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
# Create an Express.js backend with admin dashboard
|
|
356
|
+
npx native-update backend create express --with-admin
|
|
357
|
+
|
|
358
|
+
# Or create a Firebase Functions backend
|
|
359
|
+
npx native-update backend create firebase --with-monitoring
|
|
360
|
+
|
|
361
|
+
# Start the development server
|
|
362
|
+
cd native-update-backend
|
|
363
|
+
npm install
|
|
364
|
+
npm run dev
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Creating and Deploying Updates
|
|
368
|
+
|
|
369
|
+
1. **Generate signing keys** (see [Key Management Guide](../guides/key-management.md) for details):
|
|
370
|
+
```bash
|
|
371
|
+
npx native-update keys generate --type rsa --size 4096
|
|
372
|
+
```
|
|
373
|
+
This creates a private key (keep secret) and public key (add to app config)
|
|
374
|
+
|
|
375
|
+
2. **Build and create update bundle**:
|
|
376
|
+
```bash
|
|
377
|
+
npm run build
|
|
378
|
+
npx native-update bundle create ./www --version 1.0.1
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
3. **Sign the bundle**:
|
|
382
|
+
```bash
|
|
383
|
+
npx native-update bundle sign ./update-bundles/bundle-*.zip --key ./keys/private-*.pem
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
4. **Upload via admin dashboard or API**
|
|
387
|
+
|
|
388
|
+
### Development Server
|
|
389
|
+
|
|
390
|
+
For local testing:
|
|
391
|
+
```bash
|
|
392
|
+
# Start a local update server
|
|
393
|
+
npx native-update server start --port 3000
|
|
394
|
+
|
|
395
|
+
# Monitor updates in real-time
|
|
396
|
+
npx native-update monitor --server http://localhost:3000
|
|
397
|
+
```
|
|
398
|
+
|
|
348
399
|
## Next Steps
|
|
349
400
|
|
|
350
401
|
Now that you have the basics working:
|
|
351
402
|
|
|
352
|
-
1.
|
|
403
|
+
1. Deploy your backend to production
|
|
353
404
|
2. Implement [Security Best Practices](../guides/security-best-practices.md)
|
|
354
405
|
3. Configure [Advanced Options](./configuration.md)
|
|
355
406
|
4. Explore [API Reference](../api/live-update-api.md) for all available methods
|
|
407
|
+
5. See [CLI Reference](../cli-reference.md) for all available commands
|
|
356
408
|
|
|
357
409
|
## Quick Reference
|
|
358
410
|
|
|
@@ -90,11 +90,13 @@ pm2 startup
|
|
|
90
90
|
|
|
91
91
|
### 1. Production Keys
|
|
92
92
|
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
93
|
+
```bash
|
|
94
|
+
# Generate RSA keys for production
|
|
95
|
+
npx native-update keys generate --type rsa --size 4096
|
|
96
|
+
|
|
97
|
+
# This creates:
|
|
98
|
+
# - private-[timestamp].pem (keep secure on CI/CD server)
|
|
99
|
+
# - public-[timestamp].pem (embed in app)
|
|
98
100
|
```
|
|
99
101
|
|
|
100
102
|
### 2. Configure Plugin
|
|
@@ -181,10 +183,10 @@ ServerDown:
|
|
|
181
183
|
npm run build
|
|
182
184
|
|
|
183
185
|
# Create bundle
|
|
184
|
-
|
|
186
|
+
npx native-update bundle create ./www
|
|
185
187
|
|
|
186
188
|
# Sign bundle
|
|
187
|
-
|
|
189
|
+
npx native-update bundle sign bundle.zip --key private-key.pem
|
|
188
190
|
```
|
|
189
191
|
|
|
190
192
|
### 2. Upload to Server
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# Key Management Guide
|
|
2
|
+
|
|
3
|
+
This guide explains how to generate, manage, and use cryptographic keys for bundle signing in the Native Update plugin.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Native Update plugin uses public key cryptography to ensure the security and integrity of update bundles. This system prevents tampering and ensures updates come from trusted sources.
|
|
8
|
+
|
|
9
|
+
### Why Use Cryptographic Signing?
|
|
10
|
+
|
|
11
|
+
1. **Integrity**: Ensures bundles haven't been modified during transmission
|
|
12
|
+
2. **Authenticity**: Verifies bundles come from your trusted server
|
|
13
|
+
3. **Non-repudiation**: Proves the origin of updates
|
|
14
|
+
4. **Security**: Prevents man-in-the-middle attacks and bundle injection
|
|
15
|
+
|
|
16
|
+
## Key Generation
|
|
17
|
+
|
|
18
|
+
### Using the CLI Tool (Recommended)
|
|
19
|
+
|
|
20
|
+
The easiest way to generate keys is using our CLI tool:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Generate RSA 4096-bit keys (recommended for production)
|
|
24
|
+
npx native-update keys generate --type rsa --size 4096
|
|
25
|
+
|
|
26
|
+
# Generate RSA 2048-bit keys (faster, still secure)
|
|
27
|
+
npx native-update keys generate --type rsa --size 2048
|
|
28
|
+
|
|
29
|
+
# Generate EC keys (smaller signatures, modern)
|
|
30
|
+
npx native-update keys generate --type ec --size 256
|
|
31
|
+
|
|
32
|
+
# Specify output directory
|
|
33
|
+
npx native-update keys generate --output ./my-keys
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This will create:
|
|
37
|
+
- `private-{timestamp}.pem` - Keep this SECRET on your server
|
|
38
|
+
- `public-{timestamp}.pem` - Include this in your app
|
|
39
|
+
|
|
40
|
+
### Manual Key Generation
|
|
41
|
+
|
|
42
|
+
If you prefer to generate keys manually, you can use OpenSSL:
|
|
43
|
+
|
|
44
|
+
#### RSA Keys
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Generate 4096-bit RSA private key
|
|
48
|
+
openssl genrsa -out private.pem 4096
|
|
49
|
+
|
|
50
|
+
# Extract public key from private key
|
|
51
|
+
openssl rsa -in private.pem -pubout -out public.pem
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
#### Elliptic Curve Keys
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Generate EC private key (P-256 curve)
|
|
58
|
+
openssl ecparam -genkey -name prime256v1 -out private-ec.pem
|
|
59
|
+
|
|
60
|
+
# Extract public key
|
|
61
|
+
openssl ec -in private-ec.pem -pubout -out public-ec.pem
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Key Usage
|
|
65
|
+
|
|
66
|
+
### 1. Store Private Key Securely
|
|
67
|
+
|
|
68
|
+
The private key must be kept secure on your server:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Set restrictive permissions
|
|
72
|
+
chmod 600 private-*.pem
|
|
73
|
+
|
|
74
|
+
# Store in secure location
|
|
75
|
+
sudo mv private-*.pem /secure/keys/
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Security Tips:**
|
|
79
|
+
- Never commit private keys to version control
|
|
80
|
+
- Use environment variables or key management services
|
|
81
|
+
- Rotate keys periodically
|
|
82
|
+
- Keep backup copies in secure storage
|
|
83
|
+
|
|
84
|
+
### 2. Configure Public Key in App
|
|
85
|
+
|
|
86
|
+
Add the public key to your Capacitor configuration:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// capacitor.config.ts
|
|
90
|
+
import { CapacitorConfig } from '@capacitor/cli';
|
|
91
|
+
|
|
92
|
+
const config: CapacitorConfig = {
|
|
93
|
+
plugins: {
|
|
94
|
+
NativeUpdate: {
|
|
95
|
+
publicKey: 'YOUR_BASE64_PUBLIC_KEY_HERE',
|
|
96
|
+
// ... other config
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export default config;
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
To get the base64 version of your public key:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# Convert public key to base64
|
|
108
|
+
cat public-*.pem | base64 -w 0 > public-key.b64
|
|
109
|
+
|
|
110
|
+
# On macOS
|
|
111
|
+
cat public-*.pem | base64 > public-key.b64
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 3. Sign Bundles
|
|
115
|
+
|
|
116
|
+
Use the private key to sign update bundles:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Sign a bundle
|
|
120
|
+
npx native-update bundle sign ./bundle.zip --key /secure/keys/private.pem
|
|
121
|
+
|
|
122
|
+
# This creates a signature file alongside the bundle
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 4. Verify Signatures
|
|
126
|
+
|
|
127
|
+
The plugin automatically verifies signatures using the configured public key. You can manually verify:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# Verify a signed bundle
|
|
131
|
+
npx native-update bundle verify ./bundle.zip --key ./public.pem
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Security Best Practices
|
|
135
|
+
|
|
136
|
+
### Key Storage
|
|
137
|
+
|
|
138
|
+
1. **Server Side (Private Key)**:
|
|
139
|
+
- Store in hardware security module (HSM) if possible
|
|
140
|
+
- Use encrypted storage
|
|
141
|
+
- Implement access controls
|
|
142
|
+
- Never expose via API or logs
|
|
143
|
+
|
|
144
|
+
2. **Client Side (Public Key)**:
|
|
145
|
+
- Embed in app configuration
|
|
146
|
+
- Can be safely distributed
|
|
147
|
+
- Consider certificate pinning for extra security
|
|
148
|
+
|
|
149
|
+
### Key Rotation
|
|
150
|
+
|
|
151
|
+
Implement a key rotation strategy:
|
|
152
|
+
|
|
153
|
+
1. Generate new key pair
|
|
154
|
+
2. Start signing new bundles with new key
|
|
155
|
+
3. Update app with both old and new public keys
|
|
156
|
+
4. Phase out old key after all users update
|
|
157
|
+
5. Remove old public key in future release
|
|
158
|
+
|
|
159
|
+
### Environment-Specific Keys
|
|
160
|
+
|
|
161
|
+
Use different keys for different environments:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
const config: CapacitorConfig = {
|
|
165
|
+
plugins: {
|
|
166
|
+
NativeUpdate: {
|
|
167
|
+
publicKey: process.env.NODE_ENV === 'production'
|
|
168
|
+
? 'PRODUCTION_PUBLIC_KEY_BASE64'
|
|
169
|
+
: 'STAGING_PUBLIC_KEY_BASE64',
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Implementation Details
|
|
176
|
+
|
|
177
|
+
### How Signing Works
|
|
178
|
+
|
|
179
|
+
1. **Bundle Creation**: Your web assets are packaged into a ZIP file
|
|
180
|
+
2. **Hash Generation**: SHA-256 hash of the bundle is calculated
|
|
181
|
+
3. **Signing**: Hash is signed with your private key using RSA-SHA256
|
|
182
|
+
4. **Distribution**: Bundle + signature are uploaded to your server
|
|
183
|
+
|
|
184
|
+
### How Verification Works
|
|
185
|
+
|
|
186
|
+
1. **Download**: App downloads bundle and signature
|
|
187
|
+
2. **Hash Calculation**: App calculates SHA-256 hash of downloaded bundle
|
|
188
|
+
3. **Signature Verification**: App uses public key to verify signature matches hash
|
|
189
|
+
4. **Installation**: Bundle is only installed if verification succeeds
|
|
190
|
+
|
|
191
|
+
### Signature Format
|
|
192
|
+
|
|
193
|
+
Signatures are base64-encoded for transport:
|
|
194
|
+
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"bundle": "bundle-1.0.1.zip",
|
|
198
|
+
"signature": "MEUCIQDp3...base64...==",
|
|
199
|
+
"algorithm": "RSA-SHA256",
|
|
200
|
+
"timestamp": "2024-01-20T10:30:00Z"
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Troubleshooting
|
|
205
|
+
|
|
206
|
+
### Common Issues
|
|
207
|
+
|
|
208
|
+
1. **"Signature verification failed"**
|
|
209
|
+
- Ensure public key in app matches private key used for signing
|
|
210
|
+
- Check bundle hasn't been modified after signing
|
|
211
|
+
- Verify base64 encoding is correct
|
|
212
|
+
|
|
213
|
+
2. **"Invalid key format"**
|
|
214
|
+
- Keys must be in PEM format
|
|
215
|
+
- Check for proper headers/footers in key files
|
|
216
|
+
- Ensure no extra whitespace or characters
|
|
217
|
+
|
|
218
|
+
3. **"Permission denied" when accessing keys**
|
|
219
|
+
- Set proper file permissions: `chmod 600 private-*.pem`
|
|
220
|
+
- Store in accessible location for your server process
|
|
221
|
+
|
|
222
|
+
### Testing Key Pairs
|
|
223
|
+
|
|
224
|
+
Test your keys are properly paired:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
# Create test message
|
|
228
|
+
echo "test message" > test.txt
|
|
229
|
+
|
|
230
|
+
# Sign with private key
|
|
231
|
+
openssl dgst -sha256 -sign private.pem -out test.sig test.txt
|
|
232
|
+
|
|
233
|
+
# Verify with public key
|
|
234
|
+
openssl dgst -sha256 -verify public.pem -signature test.sig test.txt
|
|
235
|
+
# Should output: "Verified OK"
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Advanced Topics
|
|
239
|
+
|
|
240
|
+
### Hardware Security Modules (HSM)
|
|
241
|
+
|
|
242
|
+
For maximum security, use HSM for key storage:
|
|
243
|
+
|
|
244
|
+
```javascript
|
|
245
|
+
// Example using AWS KMS
|
|
246
|
+
const AWS = require('aws-sdk');
|
|
247
|
+
const kms = new AWS.KMS();
|
|
248
|
+
|
|
249
|
+
async function signWithHSM(data) {
|
|
250
|
+
const params = {
|
|
251
|
+
KeyId: 'arn:aws:kms:...',
|
|
252
|
+
Message: data,
|
|
253
|
+
SigningAlgorithm: 'RSASSA_PKCS1_V1_5_SHA_256'
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const result = await kms.sign(params).promise();
|
|
257
|
+
return result.Signature;
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Certificate Chains
|
|
262
|
+
|
|
263
|
+
For enterprise deployments, use certificate chains:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
# Create certificate chain
|
|
267
|
+
cat intermediate.crt root.crt > chain.pem
|
|
268
|
+
|
|
269
|
+
# Configure in app
|
|
270
|
+
{
|
|
271
|
+
"certificateChain": "base64_encoded_chain",
|
|
272
|
+
"publicKey": "base64_encoded_public_key"
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Next Steps
|
|
277
|
+
|
|
278
|
+
- Review [Security Best Practices](./security-best-practices.md)
|
|
279
|
+
- Implement [Bundle Signing](../BUNDLE_SIGNING.md)
|
|
280
|
+
- Set up [CI/CD Integration](../ci-cd-integration.md)
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
Made with ❤️ by Ahsan Mahmood
|
|
@@ -23,10 +23,14 @@ This guide helps you migrate from Microsoft CodePush to Capacitor Native Update.
|
|
|
23
23
|
First, you need to set up your own update server:
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
#
|
|
27
|
-
|
|
26
|
+
# Create a backend using our templates
|
|
27
|
+
npx native-update backend create express --with-admin
|
|
28
|
+
# or for Firebase
|
|
29
|
+
npx native-update backend create firebase --with-monitoring
|
|
30
|
+
|
|
31
|
+
cd native-update-backend
|
|
28
32
|
npm install
|
|
29
|
-
npm
|
|
33
|
+
npm run dev
|
|
30
34
|
```
|
|
31
35
|
|
|
32
36
|
### 2. Update Your App Code
|
|
@@ -88,8 +92,8 @@ Replace release process:
|
|
|
88
92
|
code-push release-react MyApp ios -d Production
|
|
89
93
|
|
|
90
94
|
# New (Capacitor Native Update)
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
npx native-update bundle create ./www
|
|
96
|
+
npx native-update bundle sign bundle.zip --key private-key.pem
|
|
93
97
|
# Upload to your server
|
|
94
98
|
```
|
|
95
99
|
|
|
@@ -120,7 +120,7 @@ try {
|
|
|
120
120
|
#### Signature Verification
|
|
121
121
|
```bash
|
|
122
122
|
# Create signed bundle
|
|
123
|
-
|
|
123
|
+
npx native-update bundle sign test-bundle.zip --key private-key.pem
|
|
124
124
|
|
|
125
125
|
# Verify in app
|
|
126
126
|
const isValid = await NativeUpdate.validateUpdate({
|
|
@@ -153,10 +153,10 @@ NativeUpdate.addListener('downloadProgress', (progress) => {
|
|
|
153
153
|
1. **Prepare Test Bundle**
|
|
154
154
|
```bash
|
|
155
155
|
# Create bundle
|
|
156
|
-
|
|
156
|
+
npx native-update bundle create ./www
|
|
157
157
|
|
|
158
158
|
# Sign bundle
|
|
159
|
-
|
|
159
|
+
npx native-update bundle sign bundle.zip --key private-key.pem
|
|
160
160
|
|
|
161
161
|
# Upload to server
|
|
162
162
|
curl -X POST http://localhost:3000/api/updates/upload/update-id \
|
|
@@ -299,7 +299,7 @@ jobs:
|
|
|
299
299
|
### Bundle Testing
|
|
300
300
|
```bash
|
|
301
301
|
# Create test bundle
|
|
302
|
-
|
|
302
|
+
npx native-update bundle create ./test-www
|
|
303
303
|
|
|
304
304
|
# Verify bundle
|
|
305
305
|
unzip -t test-bundle.zip
|