native-update 1.4.4 → 1.4.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/AI-INTEGRATION-GUIDE.md +4 -4
- package/Readme.md +33 -33
- package/android/src/main/AndroidManifest.xml +2 -3
- package/dist/esm/__tests__/bundle-manager.test.js +49 -22
- package/dist/esm/__tests__/bundle-manager.test.js.map +1 -1
- package/dist/esm/app-update/app-update-checker.d.ts +27 -1
- package/dist/esm/app-update/app-update-checker.js +109 -4
- package/dist/esm/app-update/app-update-checker.js.map +1 -1
- package/dist/esm/background-update/background-scheduler.d.ts +25 -0
- package/dist/esm/background-update/background-scheduler.js +176 -61
- package/dist/esm/background-update/background-scheduler.js.map +1 -1
- package/dist/esm/core/config.d.ts +15 -0
- package/dist/esm/core/config.js +8 -0
- package/dist/esm/core/config.js.map +1 -1
- package/dist/esm/core/errors.d.ts +4 -0
- package/dist/esm/core/errors.js +5 -0
- package/dist/esm/core/errors.js.map +1 -1
- package/dist/esm/core/plugin-manager.d.ts +6 -0
- package/dist/esm/core/plugin-manager.js +17 -0
- package/dist/esm/core/plugin-manager.js.map +1 -1
- package/dist/esm/definitions.d.ts +82 -0
- package/dist/esm/definitions.js +1 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/firestore/schema.d.ts +1 -0
- package/dist/esm/firestore/schema.js +5 -1
- package/dist/esm/firestore/schema.js.map +1 -1
- package/dist/esm/index.d.ts +3 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/live-update/delta-processor.d.ts +7 -3
- package/dist/esm/live-update/delta-processor.js +100 -13
- package/dist/esm/live-update/delta-processor.js.map +1 -1
- package/dist/esm/plugin.js +384 -21
- package/dist/esm/plugin.js.map +1 -1
- package/dist/esm/security/crypto.d.ts +64 -1
- package/dist/esm/security/crypto.js +158 -1
- package/dist/esm/security/crypto.js.map +1 -1
- package/dist/esm/web.d.ts +40 -1
- package/dist/esm/web.js +317 -23
- package/dist/esm/web.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/CHANGELOG.md +13 -0
- package/docs/KNOWN_LIMITATIONS.md +54 -69
- package/docs/REMAINING_FEATURES.md +14 -13
- package/docs/features/live-updates.md +7 -7
- package/docs/production-readiness.md +20 -23
- package/docs/reports/CLAUDE-CODE-COMPLETION-PROMPT.md +403 -0
- package/docs/reports/CLAUDE_CODE_PROMPT.md +29 -0
- package/docs/reports/CODEBASE_STATUS_REPORT.md +272 -0
- package/docs/reports/COMPREHENSIVE-PROJECT-AUDIT-2026-02-24.md +747 -0
- package/docs/reports/claude-completion.json +241 -0
- package/docs/tracking/capacitor-rollout-note-2026-03-01.md +21 -0
- package/docs/tracking/completion-tracker-2026-02-24.json +174 -0
- package/package.json +10 -10
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cryptographic utilities for bundle verification
|
|
2
|
+
* Cryptographic utilities for bundle verification and encryption
|
|
3
3
|
*/
|
|
4
|
+
/**
|
|
5
|
+
* Default encryption configuration
|
|
6
|
+
*/
|
|
7
|
+
const DEFAULT_ENCRYPTION_CONFIG = {
|
|
8
|
+
algorithm: 'AES-GCM',
|
|
9
|
+
keyLength: 256,
|
|
10
|
+
ivLength: 12, // Recommended for GCM
|
|
11
|
+
tagLength: 128, // 128-bit authentication tag
|
|
12
|
+
};
|
|
4
13
|
export class CryptoUtils {
|
|
5
14
|
/**
|
|
6
15
|
* Calculate SHA-256 checksum of data
|
|
@@ -66,5 +75,153 @@ export class CryptoUtils {
|
|
|
66
75
|
const hexPattern = /^[a-f0-9]+$/i;
|
|
67
76
|
return checksum.length === expectedLength && hexPattern.test(checksum);
|
|
68
77
|
}
|
|
78
|
+
// ============================================
|
|
79
|
+
// AES-256-GCM Encryption/Decryption
|
|
80
|
+
// ============================================
|
|
81
|
+
/**
|
|
82
|
+
* Derive an encryption key from a password/secret
|
|
83
|
+
* Uses PBKDF2 for secure key derivation
|
|
84
|
+
*/
|
|
85
|
+
static async deriveKey(password, salt, keyLength = 256) {
|
|
86
|
+
const encoder = new TextEncoder();
|
|
87
|
+
const passwordBuffer = encoder.encode(password);
|
|
88
|
+
// Import password as raw key material
|
|
89
|
+
const passwordKey = await crypto.subtle.importKey('raw', passwordBuffer, 'PBKDF2', false, ['deriveKey']);
|
|
90
|
+
// Derive AES key using PBKDF2
|
|
91
|
+
return crypto.subtle.deriveKey({
|
|
92
|
+
name: 'PBKDF2',
|
|
93
|
+
salt,
|
|
94
|
+
iterations: 100000, // High iteration count for security
|
|
95
|
+
hash: 'SHA-256',
|
|
96
|
+
}, passwordKey, {
|
|
97
|
+
name: 'AES-GCM',
|
|
98
|
+
length: keyLength,
|
|
99
|
+
}, false, // Not extractable
|
|
100
|
+
['encrypt', 'decrypt']);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Import a raw encryption key
|
|
104
|
+
*/
|
|
105
|
+
static async importKey(keyData, keyLength = 256) {
|
|
106
|
+
const keyBuffer = typeof keyData === 'string'
|
|
107
|
+
? this.base64ToArrayBuffer(keyData)
|
|
108
|
+
: keyData;
|
|
109
|
+
return crypto.subtle.importKey('raw', keyBuffer, {
|
|
110
|
+
name: 'AES-GCM',
|
|
111
|
+
length: keyLength,
|
|
112
|
+
}, false, ['encrypt', 'decrypt']);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Encrypt data using AES-256-GCM
|
|
116
|
+
*/
|
|
117
|
+
static async encrypt(data, key, config = {}) {
|
|
118
|
+
const finalConfig = Object.assign(Object.assign({}, DEFAULT_ENCRYPTION_CONFIG), config);
|
|
119
|
+
// Generate random IV
|
|
120
|
+
const iv = crypto.getRandomValues(new Uint8Array(finalConfig.ivLength));
|
|
121
|
+
// Convert to Uint8Array view for consistent handling
|
|
122
|
+
let dataArray;
|
|
123
|
+
if (data instanceof Uint8Array) {
|
|
124
|
+
dataArray = data;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
dataArray = new Uint8Array(data);
|
|
128
|
+
}
|
|
129
|
+
// Encrypt the data (cast to BufferSource to satisfy TypeScript)
|
|
130
|
+
const ciphertext = await crypto.subtle.encrypt({
|
|
131
|
+
name: 'AES-GCM',
|
|
132
|
+
iv,
|
|
133
|
+
tagLength: finalConfig.tagLength,
|
|
134
|
+
}, key, dataArray);
|
|
135
|
+
// Convert IV buffer to proper ArrayBuffer
|
|
136
|
+
const ivArray = new Uint8Array(iv);
|
|
137
|
+
const ivBuffer = new ArrayBuffer(ivArray.length);
|
|
138
|
+
new Uint8Array(ivBuffer).set(ivArray);
|
|
139
|
+
return {
|
|
140
|
+
iv: this.arrayBufferToBase64(ivBuffer),
|
|
141
|
+
data: this.arrayBufferToBase64(ciphertext),
|
|
142
|
+
algorithm: `AES-${finalConfig.keyLength}-GCM`,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Decrypt data using AES-256-GCM
|
|
147
|
+
*/
|
|
148
|
+
static async decrypt(encryptedBundle, key, config = {}) {
|
|
149
|
+
const finalConfig = Object.assign(Object.assign({}, DEFAULT_ENCRYPTION_CONFIG), config);
|
|
150
|
+
// Decode IV and ciphertext
|
|
151
|
+
const iv = new Uint8Array(this.base64ToArrayBuffer(encryptedBundle.iv));
|
|
152
|
+
const ciphertext = this.base64ToArrayBuffer(encryptedBundle.data);
|
|
153
|
+
// Decrypt the data
|
|
154
|
+
return crypto.subtle.decrypt({
|
|
155
|
+
name: 'AES-GCM',
|
|
156
|
+
iv,
|
|
157
|
+
tagLength: finalConfig.tagLength,
|
|
158
|
+
}, key, ciphertext);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Encrypt a bundle file (convenience method for typical use case)
|
|
162
|
+
*/
|
|
163
|
+
static async encryptBundle(bundleData, encryptionKey, salt) {
|
|
164
|
+
// Generate salt if not provided
|
|
165
|
+
const saltArray = salt ? new Uint8Array(salt) : crypto.getRandomValues(new Uint8Array(16));
|
|
166
|
+
const actualSalt = saltArray.buffer.slice(saltArray.byteOffset, saltArray.byteOffset + saltArray.byteLength);
|
|
167
|
+
// Derive key from password
|
|
168
|
+
const key = await this.deriveKey(encryptionKey, actualSalt);
|
|
169
|
+
// Encrypt the bundle
|
|
170
|
+
const encrypted = await this.encrypt(bundleData, key);
|
|
171
|
+
return {
|
|
172
|
+
encrypted,
|
|
173
|
+
salt: this.arrayBufferToBase64(actualSalt),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Decrypt a bundle file (convenience method for typical use case)
|
|
178
|
+
*/
|
|
179
|
+
static async decryptBundle(encryptedBundle, encryptionKey, salt) {
|
|
180
|
+
// Decode salt
|
|
181
|
+
const saltBuffer = this.base64ToArrayBuffer(salt);
|
|
182
|
+
// Derive key from password
|
|
183
|
+
const key = await this.deriveKey(encryptionKey, saltBuffer);
|
|
184
|
+
// Decrypt the bundle
|
|
185
|
+
return this.decrypt(encryptedBundle, key);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Check if a bundle is encrypted
|
|
189
|
+
*/
|
|
190
|
+
static isEncryptedBundle(data) {
|
|
191
|
+
return (typeof data === 'object' &&
|
|
192
|
+
data !== null &&
|
|
193
|
+
'iv' in data &&
|
|
194
|
+
'data' in data &&
|
|
195
|
+
'algorithm' in data &&
|
|
196
|
+
typeof data.iv === 'string' &&
|
|
197
|
+
typeof data.data === 'string' &&
|
|
198
|
+
typeof data.algorithm === 'string');
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Generate a random encryption key
|
|
202
|
+
*/
|
|
203
|
+
static generateEncryptionKey(length = 256) {
|
|
204
|
+
const keyBytes = length / 8;
|
|
205
|
+
const key = crypto.getRandomValues(new Uint8Array(keyBytes));
|
|
206
|
+
return this.arrayBufferToBase64(key.buffer);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Generate a random salt
|
|
210
|
+
*/
|
|
211
|
+
static generateSalt(length = 16) {
|
|
212
|
+
const salt = crypto.getRandomValues(new Uint8Array(length));
|
|
213
|
+
return this.arrayBufferToBase64(salt.buffer);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Convert ArrayBuffer to base64 string
|
|
217
|
+
*/
|
|
218
|
+
static arrayBufferToBase64(buffer) {
|
|
219
|
+
const bytes = new Uint8Array(buffer);
|
|
220
|
+
let binary = '';
|
|
221
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
222
|
+
binary += String.fromCharCode(bytes[i]);
|
|
223
|
+
}
|
|
224
|
+
return btoa(binary);
|
|
225
|
+
}
|
|
69
226
|
}
|
|
70
227
|
//# sourceMappingURL=crypto.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../../src/security/crypto.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../../src/security/crypto.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/web.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { WebPlugin } from '@capacitor/core';
|
|
2
|
-
import type { NativeUpdateCombinedPlugin, SecurityInfo, SyncOptions, SyncResult, DownloadOptions, BundleInfo, DeleteOptions, LatestVersion, ValidateOptions, ValidationResult, AppUpdateInfo, OpenAppStoreOptions, ReviewResult, CanRequestReviewResult, BackgroundUpdateConfig, BackgroundUpdateStatus, BackgroundCheckResult, NotificationPreferences, NotificationPermissionStatus } from './definitions';
|
|
2
|
+
import type { NativeUpdateCombinedPlugin, SecurityInfo, SyncOptions, SyncResult, DownloadOptions, BundleInfo, DeleteOptions, LatestVersion, ValidateOptions, ValidationResult, AppUpdateInfo, OpenAppStoreOptions, ReviewResult, CanRequestReviewResult, BackgroundUpdateConfig, BackgroundUpdateStatus, BackgroundCheckResult, NotificationPreferences, NotificationPermissionStatus, CheckForUpdateResult, DownloadUpdateOptions } from './definitions';
|
|
3
3
|
import type { PluginInitConfig } from './definitions';
|
|
4
4
|
export declare class NativeUpdateWeb extends WebPlugin implements NativeUpdateCombinedPlugin {
|
|
5
5
|
private config;
|
|
@@ -9,6 +9,8 @@ export declare class NativeUpdateWeb extends WebPlugin implements NativeUpdateCo
|
|
|
9
9
|
private launchCount;
|
|
10
10
|
private backgroundUpdateStatus;
|
|
11
11
|
private backgroundCheckInterval;
|
|
12
|
+
private configManager;
|
|
13
|
+
private isConfigured;
|
|
12
14
|
constructor();
|
|
13
15
|
/**
|
|
14
16
|
* Configuration and Core Methods
|
|
@@ -33,6 +35,16 @@ export declare class NativeUpdateWeb extends WebPlugin implements NativeUpdateCo
|
|
|
33
35
|
setChannel(channel: string): Promise<void>;
|
|
34
36
|
setUpdateUrl(url: string): Promise<void>;
|
|
35
37
|
validateUpdate(options: ValidateOptions): Promise<ValidationResult>;
|
|
38
|
+
/**
|
|
39
|
+
* Convenience Methods
|
|
40
|
+
*/
|
|
41
|
+
checkForUpdate(): Promise<CheckForUpdateResult>;
|
|
42
|
+
downloadUpdate(options?: DownloadUpdateOptions): Promise<BundleInfo>;
|
|
43
|
+
applyUpdate(bundleId?: string): Promise<void>;
|
|
44
|
+
cancelDownload(bundleId: string): Promise<void>;
|
|
45
|
+
cancelAllDownloads(): Promise<void>;
|
|
46
|
+
isDownloading(bundleId: string): Promise<boolean>;
|
|
47
|
+
getActiveDownloadCount(): Promise<number>;
|
|
36
48
|
/**
|
|
37
49
|
* App Update Methods
|
|
38
50
|
*/
|
|
@@ -63,11 +75,38 @@ export declare class NativeUpdateWeb extends WebPlugin implements NativeUpdateCo
|
|
|
63
75
|
*/
|
|
64
76
|
private createError;
|
|
65
77
|
private createDefaultBundle;
|
|
78
|
+
/**
|
|
79
|
+
* Validate checksum using SHA-256
|
|
80
|
+
* @param data - The data to validate (as string path or ArrayBuffer)
|
|
81
|
+
* @param expectedChecksum - Expected SHA-256 hash (hex string)
|
|
82
|
+
*/
|
|
66
83
|
private validateChecksum;
|
|
84
|
+
/**
|
|
85
|
+
* Validate digital signature using RSA-PSS or ECDSA
|
|
86
|
+
* @param data - The data that was signed (as string path or ArrayBuffer)
|
|
87
|
+
* @param signature - The signature to verify (base64 encoded)
|
|
88
|
+
*/
|
|
67
89
|
private validateSignature;
|
|
90
|
+
/**
|
|
91
|
+
* Fallback signature validation using ECDSA
|
|
92
|
+
*/
|
|
93
|
+
private validateSignatureECDSA;
|
|
94
|
+
/**
|
|
95
|
+
* Import RSA public key from PEM format
|
|
96
|
+
*/
|
|
97
|
+
private importPublicKey;
|
|
98
|
+
/**
|
|
99
|
+
* Import ECDSA public key from PEM format
|
|
100
|
+
*/
|
|
101
|
+
private importPublicKeyECDSA;
|
|
68
102
|
private loadStoredData;
|
|
69
103
|
private saveStoredData;
|
|
70
104
|
private saveConfiguration;
|
|
71
105
|
private getInstallDate;
|
|
72
106
|
private incrementLaunchCount;
|
|
107
|
+
/**
|
|
108
|
+
* Get configured review URL from plugin config
|
|
109
|
+
* Returns Play Store, App Store, or custom web review URL based on platform/config
|
|
110
|
+
*/
|
|
111
|
+
private getConfiguredReviewUrl;
|
|
73
112
|
}
|
package/dist/esm/web.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { WebPlugin } from '@capacitor/core';
|
|
2
2
|
import { SyncStatus, BundleStatus, UpdateErrorCode } from './definitions';
|
|
3
|
+
import { ConfigManager } from './core/config';
|
|
3
4
|
export class NativeUpdateWeb extends WebPlugin {
|
|
4
5
|
constructor() {
|
|
5
6
|
super();
|
|
@@ -15,6 +16,8 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
15
16
|
failureCount: 0,
|
|
16
17
|
};
|
|
17
18
|
this.backgroundCheckInterval = null;
|
|
19
|
+
this.isConfigured = false;
|
|
20
|
+
this.configManager = ConfigManager.getInstance();
|
|
18
21
|
this.loadStoredData();
|
|
19
22
|
this.incrementLaunchCount();
|
|
20
23
|
}
|
|
@@ -22,12 +25,13 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
22
25
|
* Configuration and Core Methods
|
|
23
26
|
*/
|
|
24
27
|
async configure(options) {
|
|
25
|
-
//
|
|
26
|
-
//
|
|
28
|
+
// Validate and store the plugin config using ConfigManager
|
|
29
|
+
// This will throw if baseUrl is not HTTPS
|
|
27
30
|
if (options.config) {
|
|
31
|
+
this.configManager.configure(options.config);
|
|
32
|
+
this.isConfigured = true;
|
|
28
33
|
this.saveConfiguration();
|
|
29
34
|
}
|
|
30
|
-
// console.log('NativeUpdate configured:', options.config);
|
|
31
35
|
}
|
|
32
36
|
async getSecurityInfo() {
|
|
33
37
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
@@ -45,8 +49,11 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
45
49
|
* Live Update Methods
|
|
46
50
|
*/
|
|
47
51
|
async sync(_options) {
|
|
48
|
-
// console.log('Web: Checking for updates...', options);
|
|
49
52
|
var _a;
|
|
53
|
+
// Require configuration before sync
|
|
54
|
+
if (!this.isConfigured) {
|
|
55
|
+
throw this.createError(UpdateErrorCode.NOT_CONFIGURED, 'Plugin not configured. Call configure() first.');
|
|
56
|
+
}
|
|
50
57
|
try {
|
|
51
58
|
// In web, we can check for service worker updates
|
|
52
59
|
if ('serviceWorker' in navigator) {
|
|
@@ -106,9 +113,27 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
106
113
|
});
|
|
107
114
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
108
115
|
}
|
|
116
|
+
// Verify bundle integrity if checksum/signature provided
|
|
117
|
+
let verified = true;
|
|
118
|
+
if (options.checksum) {
|
|
119
|
+
const checksumValid = await this.validateChecksum(bundleId, options.checksum);
|
|
120
|
+
if (!checksumValid) {
|
|
121
|
+
bundle.status = BundleStatus.FAILED;
|
|
122
|
+
this.bundles.delete(bundleId);
|
|
123
|
+
throw this.createError(UpdateErrorCode.CHECKSUM_ERROR, 'Bundle checksum verification failed');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (options.signature) {
|
|
127
|
+
const signatureValid = await this.validateSignature(bundleId, options.signature);
|
|
128
|
+
if (!signatureValid) {
|
|
129
|
+
bundle.status = BundleStatus.FAILED;
|
|
130
|
+
this.bundles.delete(bundleId);
|
|
131
|
+
throw this.createError(UpdateErrorCode.SIGNATURE_ERROR, 'Bundle signature verification failed');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
109
134
|
// Update bundle status
|
|
110
135
|
bundle.status = BundleStatus.READY;
|
|
111
|
-
bundle.verified =
|
|
136
|
+
bundle.verified = verified;
|
|
112
137
|
this.saveStoredData();
|
|
113
138
|
await this.notifyListeners('updateStateChanged', {
|
|
114
139
|
status: bundle.status,
|
|
@@ -232,6 +257,94 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
232
257
|
},
|
|
233
258
|
};
|
|
234
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* Convenience Methods
|
|
262
|
+
*/
|
|
263
|
+
async checkForUpdate() {
|
|
264
|
+
var _a;
|
|
265
|
+
const currentVersion = ((_a = this.currentBundle) === null || _a === void 0 ? void 0 : _a.version) || '1.0.0';
|
|
266
|
+
// Check for service worker updates or server updates
|
|
267
|
+
const latest = await this.getLatest();
|
|
268
|
+
return {
|
|
269
|
+
available: latest.available,
|
|
270
|
+
currentVersion,
|
|
271
|
+
latestVersion: latest.version,
|
|
272
|
+
url: latest.url,
|
|
273
|
+
mandatory: latest.mandatory,
|
|
274
|
+
notes: latest.notes,
|
|
275
|
+
size: latest.size,
|
|
276
|
+
checksum: latest.checksum,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
async downloadUpdate(options) {
|
|
280
|
+
// Check for update first if no options provided
|
|
281
|
+
let url = options === null || options === void 0 ? void 0 : options.url;
|
|
282
|
+
let version = options === null || options === void 0 ? void 0 : options.version;
|
|
283
|
+
let checksum = (options === null || options === void 0 ? void 0 : options.checksum) || '';
|
|
284
|
+
if (!url || !version) {
|
|
285
|
+
const updateInfo = await this.checkForUpdate();
|
|
286
|
+
if (!updateInfo.available || !updateInfo.url) {
|
|
287
|
+
throw this.createError(UpdateErrorCode.UPDATE_NOT_AVAILABLE, 'No update available to download');
|
|
288
|
+
}
|
|
289
|
+
url = url || updateInfo.url;
|
|
290
|
+
version = version || updateInfo.latestVersion || 'latest';
|
|
291
|
+
checksum = checksum || updateInfo.checksum || '';
|
|
292
|
+
}
|
|
293
|
+
// Download using the standard download method
|
|
294
|
+
return this.download({
|
|
295
|
+
url,
|
|
296
|
+
version,
|
|
297
|
+
checksum,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
async applyUpdate(bundleId) {
|
|
301
|
+
// Find the bundle to apply
|
|
302
|
+
let bundle;
|
|
303
|
+
if (bundleId) {
|
|
304
|
+
bundle = this.bundles.get(bundleId);
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
// Find the most recent ready bundle
|
|
308
|
+
const readyBundles = Array.from(this.bundles.values())
|
|
309
|
+
.filter(b => b.status === BundleStatus.READY)
|
|
310
|
+
.sort((a, b) => b.downloadTime - a.downloadTime);
|
|
311
|
+
bundle = readyBundles[0];
|
|
312
|
+
}
|
|
313
|
+
if (!bundle) {
|
|
314
|
+
throw this.createError(UpdateErrorCode.UNKNOWN_ERROR, 'No ready bundle found to apply');
|
|
315
|
+
}
|
|
316
|
+
// Set the bundle as active and reload
|
|
317
|
+
await this.set(bundle);
|
|
318
|
+
await this.reload();
|
|
319
|
+
}
|
|
320
|
+
async cancelDownload(bundleId) {
|
|
321
|
+
// In web implementation, we can't truly cancel an ongoing fetch
|
|
322
|
+
// But we can remove the bundle from tracking
|
|
323
|
+
const bundle = this.bundles.get(bundleId);
|
|
324
|
+
if (bundle && bundle.status === BundleStatus.DOWNLOADING) {
|
|
325
|
+
bundle.status = BundleStatus.FAILED;
|
|
326
|
+
this.bundles.delete(bundleId);
|
|
327
|
+
this.saveStoredData();
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
async cancelAllDownloads() {
|
|
331
|
+
const downloadingBundles = Array.from(this.bundles.values())
|
|
332
|
+
.filter(b => b.status === BundleStatus.DOWNLOADING);
|
|
333
|
+
for (const bundle of downloadingBundles) {
|
|
334
|
+
bundle.status = BundleStatus.FAILED;
|
|
335
|
+
this.bundles.delete(bundle.bundleId);
|
|
336
|
+
}
|
|
337
|
+
this.saveStoredData();
|
|
338
|
+
}
|
|
339
|
+
async isDownloading(bundleId) {
|
|
340
|
+
const bundle = this.bundles.get(bundleId);
|
|
341
|
+
return (bundle === null || bundle === void 0 ? void 0 : bundle.status) === BundleStatus.DOWNLOADING;
|
|
342
|
+
}
|
|
343
|
+
async getActiveDownloadCount() {
|
|
344
|
+
return Array.from(this.bundles.values())
|
|
345
|
+
.filter(b => b.status === BundleStatus.DOWNLOADING)
|
|
346
|
+
.length;
|
|
347
|
+
}
|
|
235
348
|
/**
|
|
236
349
|
* App Update Methods
|
|
237
350
|
*/
|
|
@@ -254,13 +367,15 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
254
367
|
throw this.createError(UpdateErrorCode.PLATFORM_NOT_SUPPORTED, 'App updates are not supported on web platform');
|
|
255
368
|
}
|
|
256
369
|
async openAppStore(_options) {
|
|
257
|
-
// console.log('Web: Opening app store fallback URL', options);
|
|
258
370
|
var _a, _b, _c, _d;
|
|
259
|
-
//
|
|
260
|
-
const
|
|
371
|
+
// Get configured store URL
|
|
372
|
+
const storeUrl = ((_b = (_a = this.config.appUpdate) === null || _a === void 0 ? void 0 : _a.storeUrl) === null || _b === void 0 ? void 0 : _b.android) ||
|
|
261
373
|
((_d = (_c = this.config.appUpdate) === null || _c === void 0 ? void 0 : _c.storeUrl) === null || _d === void 0 ? void 0 : _d.ios) ||
|
|
262
|
-
|
|
263
|
-
|
|
374
|
+
this.getConfiguredReviewUrl();
|
|
375
|
+
if (!storeUrl) {
|
|
376
|
+
throw this.createError(UpdateErrorCode.INVALID_CONFIG, 'No app store URL configured. Set appUpdate.storeUrl.ios or appUpdate.storeUrl.android in plugin config.');
|
|
377
|
+
}
|
|
378
|
+
window.open(storeUrl, '_blank');
|
|
264
379
|
}
|
|
265
380
|
/**
|
|
266
381
|
* App Review Methods
|
|
@@ -278,13 +393,19 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
278
393
|
this.lastReviewRequest = Date.now();
|
|
279
394
|
localStorage.setItem('native-update-last-review', this.lastReviewRequest.toString());
|
|
280
395
|
// In web, we could show a custom modal or redirect to a review page
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
396
|
+
// Note: Web platform cannot display native in-app review dialogs
|
|
397
|
+
// We can offer a fallback redirect, but `displayed` reflects native capability
|
|
398
|
+
const reviewUrl = this.getConfiguredReviewUrl();
|
|
399
|
+
// Guard against environments where confirm is not available (e.g., test environments)
|
|
400
|
+
if (typeof confirm === 'function' && reviewUrl) {
|
|
401
|
+
const shouldRedirect = confirm('Would you like to leave a review for our app?');
|
|
402
|
+
if (shouldRedirect && typeof window !== 'undefined' && window.open) {
|
|
403
|
+
window.open(reviewUrl, '_blank');
|
|
404
|
+
}
|
|
285
405
|
}
|
|
406
|
+
// Web platform returns displayed: false because we cannot show native review dialog
|
|
286
407
|
return {
|
|
287
|
-
displayed:
|
|
408
|
+
displayed: false,
|
|
288
409
|
};
|
|
289
410
|
}
|
|
290
411
|
async canRequestReview() {
|
|
@@ -529,15 +650,151 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
529
650
|
verified: true,
|
|
530
651
|
};
|
|
531
652
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
653
|
+
/**
|
|
654
|
+
* Validate checksum using SHA-256
|
|
655
|
+
* @param data - The data to validate (as string path or ArrayBuffer)
|
|
656
|
+
* @param expectedChecksum - Expected SHA-256 hash (hex string)
|
|
657
|
+
*/
|
|
658
|
+
async validateChecksum(data, expectedChecksum) {
|
|
659
|
+
if (!expectedChecksum) {
|
|
660
|
+
// If no checksum provided, skip validation
|
|
661
|
+
return true;
|
|
662
|
+
}
|
|
663
|
+
try {
|
|
664
|
+
let dataBuffer;
|
|
665
|
+
if (typeof data === 'string') {
|
|
666
|
+
// If data is a path/URL, we need to fetch it
|
|
667
|
+
// For local validation, convert string to buffer
|
|
668
|
+
dataBuffer = new TextEncoder().encode(data).buffer;
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
dataBuffer = data;
|
|
672
|
+
}
|
|
673
|
+
// Calculate SHA-256 hash using Web Crypto API
|
|
674
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', dataBuffer);
|
|
675
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
676
|
+
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
677
|
+
// Compare checksums (case-insensitive)
|
|
678
|
+
const isValid = hashHex.toLowerCase() === expectedChecksum.toLowerCase();
|
|
679
|
+
if (!isValid) {
|
|
680
|
+
console.warn('Checksum validation failed', {
|
|
681
|
+
expected: expectedChecksum.toLowerCase(),
|
|
682
|
+
actual: hashHex.toLowerCase(),
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
return isValid;
|
|
686
|
+
}
|
|
687
|
+
catch (error) {
|
|
688
|
+
console.error('Checksum validation error:', error);
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Validate digital signature using RSA-PSS or ECDSA
|
|
694
|
+
* @param data - The data that was signed (as string path or ArrayBuffer)
|
|
695
|
+
* @param signature - The signature to verify (base64 encoded)
|
|
696
|
+
*/
|
|
697
|
+
async validateSignature(data, signature) {
|
|
698
|
+
var _a;
|
|
699
|
+
if (!signature) {
|
|
700
|
+
// If no signature provided, skip validation
|
|
701
|
+
return true;
|
|
702
|
+
}
|
|
703
|
+
// Get public key from config (can be in liveUpdate.publicKey or top-level config)
|
|
704
|
+
const publicKeyPem = ((_a = this.config.liveUpdate) === null || _a === void 0 ? void 0 : _a.publicKey) ||
|
|
705
|
+
this.config.publicKey;
|
|
706
|
+
if (!publicKeyPem) {
|
|
707
|
+
// If no public key configured, skip signature validation
|
|
708
|
+
console.warn('No public key configured for signature validation');
|
|
709
|
+
return true;
|
|
710
|
+
}
|
|
711
|
+
try {
|
|
712
|
+
let dataBuffer;
|
|
713
|
+
if (typeof data === 'string') {
|
|
714
|
+
dataBuffer = new TextEncoder().encode(data).buffer;
|
|
715
|
+
}
|
|
716
|
+
else {
|
|
717
|
+
dataBuffer = data;
|
|
718
|
+
}
|
|
719
|
+
// Decode base64 signature
|
|
720
|
+
const signatureBuffer = Uint8Array.from(atob(signature), c => c.charCodeAt(0));
|
|
721
|
+
// Import public key
|
|
722
|
+
const publicKey = await this.importPublicKey(publicKeyPem);
|
|
723
|
+
// Verify signature using RSA-PSS
|
|
724
|
+
const isValid = await crypto.subtle.verify({
|
|
725
|
+
name: 'RSA-PSS',
|
|
726
|
+
saltLength: 32,
|
|
727
|
+
}, publicKey, signatureBuffer, dataBuffer);
|
|
728
|
+
if (!isValid) {
|
|
729
|
+
console.warn('Signature validation failed');
|
|
730
|
+
}
|
|
731
|
+
return isValid;
|
|
732
|
+
}
|
|
733
|
+
catch (error) {
|
|
734
|
+
console.error('Signature validation error:', error);
|
|
735
|
+
// Try ECDSA as fallback
|
|
736
|
+
return this.validateSignatureECDSA(data, signature);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Fallback signature validation using ECDSA
|
|
741
|
+
*/
|
|
742
|
+
async validateSignatureECDSA(data, signature) {
|
|
743
|
+
var _a;
|
|
744
|
+
const publicKeyPem = ((_a = this.config.liveUpdate) === null || _a === void 0 ? void 0 : _a.publicKey) ||
|
|
745
|
+
this.config.publicKey;
|
|
746
|
+
if (!publicKeyPem) {
|
|
747
|
+
return true;
|
|
748
|
+
}
|
|
749
|
+
try {
|
|
750
|
+
let dataBuffer;
|
|
751
|
+
if (typeof data === 'string') {
|
|
752
|
+
dataBuffer = new TextEncoder().encode(data).buffer;
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
dataBuffer = data;
|
|
756
|
+
}
|
|
757
|
+
const signatureBuffer = Uint8Array.from(atob(signature), c => c.charCodeAt(0));
|
|
758
|
+
const publicKey = await this.importPublicKeyECDSA(publicKeyPem);
|
|
759
|
+
const isValid = await crypto.subtle.verify({
|
|
760
|
+
name: 'ECDSA',
|
|
761
|
+
hash: 'SHA-256',
|
|
762
|
+
}, publicKey, signatureBuffer, dataBuffer);
|
|
763
|
+
return isValid;
|
|
764
|
+
}
|
|
765
|
+
catch (error) {
|
|
766
|
+
console.error('ECDSA signature validation error:', error);
|
|
767
|
+
return false;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Import RSA public key from PEM format
|
|
772
|
+
*/
|
|
773
|
+
async importPublicKey(pem) {
|
|
774
|
+
// Remove PEM headers and decode
|
|
775
|
+
const pemContents = pem
|
|
776
|
+
.replace(/-----BEGIN PUBLIC KEY-----/, '')
|
|
777
|
+
.replace(/-----END PUBLIC KEY-----/, '')
|
|
778
|
+
.replace(/\s/g, '');
|
|
779
|
+
const binaryDer = Uint8Array.from(atob(pemContents), c => c.charCodeAt(0));
|
|
780
|
+
return crypto.subtle.importKey('spki', binaryDer, {
|
|
781
|
+
name: 'RSA-PSS',
|
|
782
|
+
hash: 'SHA-256',
|
|
783
|
+
}, false, ['verify']);
|
|
536
784
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
785
|
+
/**
|
|
786
|
+
* Import ECDSA public key from PEM format
|
|
787
|
+
*/
|
|
788
|
+
async importPublicKeyECDSA(pem) {
|
|
789
|
+
const pemContents = pem
|
|
790
|
+
.replace(/-----BEGIN PUBLIC KEY-----/, '')
|
|
791
|
+
.replace(/-----END PUBLIC KEY-----/, '')
|
|
792
|
+
.replace(/\s/g, '');
|
|
793
|
+
const binaryDer = Uint8Array.from(atob(pemContents), c => c.charCodeAt(0));
|
|
794
|
+
return crypto.subtle.importKey('spki', binaryDer, {
|
|
795
|
+
name: 'ECDSA',
|
|
796
|
+
namedCurve: 'P-256',
|
|
797
|
+
}, false, ['verify']);
|
|
541
798
|
}
|
|
542
799
|
loadStoredData() {
|
|
543
800
|
// Load configuration
|
|
@@ -589,6 +846,43 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
589
846
|
this.launchCount++;
|
|
590
847
|
localStorage.setItem('native-update-launch-count', this.launchCount.toString());
|
|
591
848
|
}
|
|
849
|
+
/**
|
|
850
|
+
* Get configured review URL from plugin config
|
|
851
|
+
* Returns Play Store, App Store, or custom web review URL based on platform/config
|
|
852
|
+
*/
|
|
853
|
+
getConfiguredReviewUrl() {
|
|
854
|
+
var _a;
|
|
855
|
+
// Check for platform-specific store URLs from config
|
|
856
|
+
const storeUrls = (_a = this.config.appUpdate) === null || _a === void 0 ? void 0 : _a.storeUrl;
|
|
857
|
+
// Try to get iOS App Store URL
|
|
858
|
+
if (storeUrls === null || storeUrls === void 0 ? void 0 : storeUrls.ios) {
|
|
859
|
+
return storeUrls.ios;
|
|
860
|
+
}
|
|
861
|
+
// Try to get Android Play Store URL
|
|
862
|
+
if (storeUrls === null || storeUrls === void 0 ? void 0 : storeUrls.android) {
|
|
863
|
+
return storeUrls.android;
|
|
864
|
+
}
|
|
865
|
+
// Try custom web review URL from config (may be in extended config)
|
|
866
|
+
const extendedConfig = this.config;
|
|
867
|
+
const appReviewConfig = this.config.appReview;
|
|
868
|
+
if (appReviewConfig === null || appReviewConfig === void 0 ? void 0 : appReviewConfig.webReviewUrl) {
|
|
869
|
+
return appReviewConfig.webReviewUrl;
|
|
870
|
+
}
|
|
871
|
+
// Try to construct URL from app store IDs
|
|
872
|
+
const appStoreId = extendedConfig.appStoreId;
|
|
873
|
+
if (appStoreId) {
|
|
874
|
+
return `https://apps.apple.com/app/id${appStoreId}?action=write-review`;
|
|
875
|
+
}
|
|
876
|
+
// Check for package name for Play Store
|
|
877
|
+
const packageName = extendedConfig.packageName;
|
|
878
|
+
if (packageName) {
|
|
879
|
+
return `https://play.google.com/store/apps/details?id=${packageName}&showAllReviews=true`;
|
|
880
|
+
}
|
|
881
|
+
// No review URL configured - return null to indicate review not available
|
|
882
|
+
console.warn('No review URL configured. Set appUpdate.storeUrl.ios, appUpdate.storeUrl.android, ' +
|
|
883
|
+
'appReview.webReviewUrl, appStoreId, or packageName in plugin config.');
|
|
884
|
+
return null;
|
|
885
|
+
}
|
|
592
886
|
}
|
|
593
887
|
// Alias for backward compatibility with tests
|
|
594
888
|
// Already exported above
|