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.
Files changed (59) hide show
  1. package/AI-INTEGRATION-GUIDE.md +4 -4
  2. package/Readme.md +33 -33
  3. package/android/src/main/AndroidManifest.xml +2 -3
  4. package/dist/esm/__tests__/bundle-manager.test.js +49 -22
  5. package/dist/esm/__tests__/bundle-manager.test.js.map +1 -1
  6. package/dist/esm/app-update/app-update-checker.d.ts +27 -1
  7. package/dist/esm/app-update/app-update-checker.js +109 -4
  8. package/dist/esm/app-update/app-update-checker.js.map +1 -1
  9. package/dist/esm/background-update/background-scheduler.d.ts +25 -0
  10. package/dist/esm/background-update/background-scheduler.js +176 -61
  11. package/dist/esm/background-update/background-scheduler.js.map +1 -1
  12. package/dist/esm/core/config.d.ts +15 -0
  13. package/dist/esm/core/config.js +8 -0
  14. package/dist/esm/core/config.js.map +1 -1
  15. package/dist/esm/core/errors.d.ts +4 -0
  16. package/dist/esm/core/errors.js +5 -0
  17. package/dist/esm/core/errors.js.map +1 -1
  18. package/dist/esm/core/plugin-manager.d.ts +6 -0
  19. package/dist/esm/core/plugin-manager.js +17 -0
  20. package/dist/esm/core/plugin-manager.js.map +1 -1
  21. package/dist/esm/definitions.d.ts +82 -0
  22. package/dist/esm/definitions.js +1 -0
  23. package/dist/esm/definitions.js.map +1 -1
  24. package/dist/esm/firestore/schema.d.ts +1 -0
  25. package/dist/esm/firestore/schema.js +5 -1
  26. package/dist/esm/firestore/schema.js.map +1 -1
  27. package/dist/esm/index.d.ts +3 -1
  28. package/dist/esm/index.js +2 -0
  29. package/dist/esm/index.js.map +1 -1
  30. package/dist/esm/live-update/delta-processor.d.ts +7 -3
  31. package/dist/esm/live-update/delta-processor.js +100 -13
  32. package/dist/esm/live-update/delta-processor.js.map +1 -1
  33. package/dist/esm/plugin.js +384 -21
  34. package/dist/esm/plugin.js.map +1 -1
  35. package/dist/esm/security/crypto.d.ts +64 -1
  36. package/dist/esm/security/crypto.js +158 -1
  37. package/dist/esm/security/crypto.js.map +1 -1
  38. package/dist/esm/web.d.ts +40 -1
  39. package/dist/esm/web.js +317 -23
  40. package/dist/esm/web.js.map +1 -1
  41. package/dist/plugin.cjs.js +1 -1
  42. package/dist/plugin.cjs.js.map +1 -1
  43. package/dist/plugin.esm.js +1 -1
  44. package/dist/plugin.esm.js.map +1 -1
  45. package/dist/plugin.js +2 -2
  46. package/dist/plugin.js.map +1 -1
  47. package/docs/CHANGELOG.md +13 -0
  48. package/docs/KNOWN_LIMITATIONS.md +54 -69
  49. package/docs/REMAINING_FEATURES.md +14 -13
  50. package/docs/features/live-updates.md +7 -7
  51. package/docs/production-readiness.md +20 -23
  52. package/docs/reports/CLAUDE-CODE-COMPLETION-PROMPT.md +403 -0
  53. package/docs/reports/CLAUDE_CODE_PROMPT.md +29 -0
  54. package/docs/reports/CODEBASE_STATUS_REPORT.md +272 -0
  55. package/docs/reports/COMPREHENSIVE-PROJECT-AUDIT-2026-02-24.md +747 -0
  56. package/docs/reports/claude-completion.json +241 -0
  57. package/docs/tracking/capacitor-rollout-note-2026-03-01.md +21 -0
  58. package/docs/tracking/completion-tracker-2026-02-24.json +174 -0
  59. 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":"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"}
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
- // Store the plugin config
26
- // In a real implementation, this would configure the plugin properly
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 = true; // In real implementation, would verify checksum/signature
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
- // Fallback to website or app landing page
260
- const fallbackUrl = ((_b = (_a = this.config.appUpdate) === null || _a === void 0 ? void 0 : _a.storeUrl) === null || _b === void 0 ? void 0 : _b.android) ||
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
- 'https://example.com/download';
263
- window.open(fallbackUrl, '_blank');
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
- const reviewUrl = 'https://example.com/review';
282
- const shouldRedirect = confirm('Would you like to leave a review for our app?');
283
- if (shouldRedirect) {
284
- window.open(reviewUrl, '_blank');
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: true,
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
- async validateChecksum(_data, _expectedChecksum) {
533
- // In a real implementation, would calculate actual checksum
534
- // console.log('Web: Validating checksum...', expectedChecksum);
535
- return true;
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
- async validateSignature(_data, _signature) {
538
- // In a real implementation, would verify signature
539
- // console.log('Web: Validating signature...', signature);
540
- return true;
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