native-update 1.4.9 → 3.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.
Files changed (265) hide show
  1. package/Readme.md +35 -9
  2. package/android/manifest-additions.xml +28 -0
  3. package/android/src/main/AndroidManifest.xml +3 -7
  4. package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundNotificationManager.kt +21 -13
  5. package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundUpdatePlugin.kt +15 -0
  6. package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundUpdateWorker.kt +23 -7
  7. package/android/src/main/java/com/aoneahsan/nativeupdate/LiveUpdatePlugin.kt +346 -31
  8. package/android/src/main/java/com/aoneahsan/nativeupdate/NativeUpdatePlugin.kt +32 -34
  9. package/android/src/main/java/com/aoneahsan/nativeupdate/NotificationActionReceiver.kt +10 -1
  10. package/android/src/main/java/com/aoneahsan/nativeupdate/SecurityManager.kt +18 -18
  11. package/cli/AGENTS.md +43 -0
  12. package/cli/CLAUDE.md +65 -0
  13. package/cli/commands/backend-create.js +56 -15
  14. package/cli/commands/bundle-create.js +39 -7
  15. package/cli/commands/monitor.js +72 -51
  16. package/cli/index.js +10 -24
  17. package/cli/package.json +11 -3
  18. package/dist/esm/app-review/app-review-manager.js +0 -1
  19. package/dist/esm/app-review/index.js +0 -1
  20. package/dist/esm/app-review/platform-review-handler.js +0 -1
  21. package/dist/esm/app-review/review-conditions-checker.js +0 -1
  22. package/dist/esm/app-review/review-rate-limiter.js +0 -1
  23. package/dist/esm/app-review/types.js +0 -1
  24. package/dist/esm/app-update/app-update-checker.js +0 -1
  25. package/dist/esm/app-update/app-update-installer.d.ts +1 -0
  26. package/dist/esm/app-update/app-update-installer.js +19 -8
  27. package/dist/esm/app-update/app-update-manager.js +0 -1
  28. package/dist/esm/app-update/app-update-notifier.js +0 -1
  29. package/dist/esm/app-update/index.js +0 -1
  30. package/dist/esm/app-update/platform-app-update.js +12 -3
  31. package/dist/esm/app-update/types.js +0 -1
  32. package/dist/esm/background-update/background-scheduler.js +0 -1
  33. package/dist/esm/background-update/index.js +0 -1
  34. package/dist/esm/background-update/notification-manager.js +0 -1
  35. package/dist/esm/config/support.js +0 -1
  36. package/dist/esm/core/analytics.d.ts +13 -0
  37. package/dist/esm/core/analytics.js +15 -1
  38. package/dist/esm/core/cache-manager.js +0 -1
  39. package/dist/esm/core/config.d.ts +6 -30
  40. package/dist/esm/core/config.js +1 -8
  41. package/dist/esm/core/errors.d.ts +1 -0
  42. package/dist/esm/core/errors.js +1 -1
  43. package/dist/esm/core/event-emitter.d.ts +0 -3
  44. package/dist/esm/core/event-emitter.js +8 -3
  45. package/dist/esm/core/logger.js +0 -1
  46. package/dist/esm/core/performance.js +0 -1
  47. package/dist/esm/core/plugin-manager.js +3 -1
  48. package/dist/esm/core/security.d.ts +22 -3
  49. package/dist/esm/core/security.js +46 -7
  50. package/dist/esm/definitions.d.ts +13 -40
  51. package/dist/esm/definitions.js +0 -1
  52. package/dist/esm/index.d.ts +3 -4
  53. package/dist/esm/index.js +3 -3
  54. package/dist/esm/live-update/bundle-manager.d.ts +8 -0
  55. package/dist/esm/live-update/bundle-manager.js +20 -1
  56. package/dist/esm/live-update/certificate-pinning.js +0 -1
  57. package/dist/esm/live-update/delta-processor.d.ts +1 -1
  58. package/dist/esm/live-update/delta-processor.js +3 -1
  59. package/dist/esm/live-update/download-manager.d.ts +36 -5
  60. package/dist/esm/live-update/download-manager.js +70 -24
  61. package/dist/esm/live-update/rollout-checker.d.ts +1 -1
  62. package/dist/esm/live-update/rollout-checker.js +5 -6
  63. package/dist/esm/live-update/update-manager.d.ts +12 -1
  64. package/dist/esm/live-update/update-manager.js +38 -11
  65. package/dist/esm/live-update/version-manager.d.ts +9 -12
  66. package/dist/esm/live-update/version-manager.js +40 -68
  67. package/dist/esm/plugin.js +82 -89
  68. package/dist/esm/security/crypto.js +11 -7
  69. package/dist/esm/security/validator.js +0 -1
  70. package/dist/esm/types/rollout.d.ts +85 -0
  71. package/dist/esm/types/rollout.js +25 -0
  72. package/dist/esm/web.d.ts +26 -1
  73. package/dist/esm/web.js +214 -75
  74. package/dist/plugin.cjs.js +1 -2
  75. package/dist/plugin.esm.js +1 -2
  76. package/dist/plugin.js +2 -3
  77. package/docs/AGENTS.md +38 -0
  78. package/docs/CHANGELOG.md +275 -0
  79. package/docs/CLAUDE.md +101 -0
  80. package/docs/MANUAL-TASKS.md +17 -0
  81. package/docs/MARKETING_WEBSITE_TRACKER.md +18 -2
  82. package/docs/MIGRATION.md +170 -0
  83. package/docs/PACKAGES.md +167 -0
  84. package/docs/PERFORMANCE.md +110 -0
  85. package/docs/PROJECT_COMPLETION_TRACKER.md +4 -35
  86. package/docs/README.md +12 -2
  87. package/docs/REMAINING_FEATURES.md +123 -76
  88. package/docs/ROADMAP.md +200 -120
  89. package/docs/SECURITY-IF-REPO-PUBLIC.md +232 -0
  90. package/docs/TESTING_REQUIREMENTS.md +1 -112
  91. package/docs/ai-knowledge-base/00-project-profile.md +38 -0
  92. package/docs/ai-knowledge-base/01-features.md +46 -0
  93. package/docs/ai-knowledge-base/02-routes.md +72 -0
  94. package/docs/ai-knowledge-base/03-tech-stack.md +57 -0
  95. package/docs/ai-knowledge-base/README.md +23 -0
  96. package/docs/analytics-tracking.md +37 -0
  97. package/docs/capacitor-official-plugins.md +46 -0
  98. package/docs/capawesome-plugins.md +29 -0
  99. package/docs/deployment/HOSTINGER_DEPLOY.md +370 -0
  100. package/docs/deployment/PRODUCTION-DEPLOY-CHECKLIST.md +184 -0
  101. package/docs/docs-site/plan.md +201 -0
  102. package/docs/docs-site/scope.md +127 -0
  103. package/docs/docs-site/tracker.json +235 -0
  104. package/docs/error-handling-tracking.md +37 -0
  105. package/docs/features/laravel-nova-backend/ASSESSMENT-SUMMARY.md +96 -0
  106. package/docs/features/laravel-nova-backend/IMPLEMENTATION-PLAN.md +504 -0
  107. package/docs/features/laravel-nova-backend/progress-tracker.json +184 -0
  108. package/docs/guides/testing-guide.md +15 -41
  109. package/docs/package-audit.md +41 -0
  110. package/docs/play-store/ASO-METADATA.md +5 -0
  111. package/docs/play-store/DATA-SAFETY.md +5 -0
  112. package/docs/play-store/DECLARATIONS.md +5 -0
  113. package/docs/play-store/README.md +27 -0
  114. package/docs/play-store/RELEASE-NOTES.md +99 -0
  115. package/docs/play-store/RELEASE-STEPS.md +6 -0
  116. package/docs/play-store/STORE-LISTING.md +7 -0
  117. package/docs/play-store/SUBMISSION-CHECKLIST.md +5 -0
  118. package/docs/production-readiness.md +1 -1
  119. package/docs/project-audit/2026-05-16/00-FINALIZATION-PLAN.md +123 -0
  120. package/docs/project-audit/2026-05-16/01-BATCH-PLAN.md +227 -0
  121. package/docs/project-audit/2026-05-16/02-AUDIT-FINDINGS.md +144 -0
  122. package/docs/project-audit/2026-05-16/sessions/batch-01.md +42 -0
  123. package/docs/project-audit/2026-05-16/sessions/batch-02.md +74 -0
  124. package/docs/project-audit/2026-05-16/sessions/batch-03.md +71 -0
  125. package/docs/project-audit/2026-05-16/sessions/batch-04.md +88 -0
  126. package/docs/project-audit/2026-05-16/sessions/batch-05.md +63 -0
  127. package/docs/project-audit/2026-05-16/sessions/batch-06.md +103 -0
  128. package/docs/project-audit/2026-05-16/sessions/batch-07.md +61 -0
  129. package/docs/project-audit/2026-05-16/sessions/batch-08.md +101 -0
  130. package/docs/project-audit/2026-05-16/sessions/batch-09.md +105 -0
  131. package/docs/project-audit/2026-05-16/sessions/batch-10.md +102 -0
  132. package/docs/project-audit/AUDIT-HISTORY.md +74 -0
  133. package/docs/project-audit/BATCH-PLAN.md +168 -0
  134. package/docs/project-audit/MARKETING-WEBSITE-FINDINGS-2026-05-11.md +73 -0
  135. package/docs/project-audit/PRODUCTION-READINESS-AUDIT-2026-05-08.md +275 -0
  136. package/docs/project-finalization/00-tracker.json +60 -0
  137. package/docs/project-knowledge-base/01-system-overview.md +227 -0
  138. package/docs/project-knowledge-base/02-routes-pages-forms-users.md +352 -0
  139. package/docs/project-knowledge-base/03-tech-stack-modules-services.md +379 -0
  140. package/docs/project-knowledge-base/04-data-models-integrations.md +244 -0
  141. package/docs/project-knowledge-base/05-docs-corpus-inventory.md +256 -0
  142. package/docs/project-knowledge-base/06-operations-testing-legal-content.md +208 -0
  143. package/docs/project-knowledge-base/README.md +103 -0
  144. package/docs/project-profiles/native-update-capacitor-update-platform-project-profile-last-updated-2026-05-19.md +81 -0
  145. package/docs/rules-tracker.md +102 -0
  146. package/docs/security/PRE-LAUNCH-PENTEST-CHECKLIST.md +116 -0
  147. package/docs/seo-aeo-rules.json +3043 -0
  148. package/docs/social-media-marketing/README.md +81 -0
  149. package/docs/social-media-marketing/posts-batch-01.md +613 -0
  150. package/docs/social-media-marketing/posts-batch-02.md +528 -0
  151. package/docs/social-media-marketing/whatsapp-messages-batch-01.md +458 -0
  152. package/docs/social-media-marketing/whatsapp-status-batch-01.md +355 -0
  153. package/docs/tracking/finalization-2026-05-16-tracker.json +346 -0
  154. package/docs/tracking/full-audit-tracker.json +121 -0
  155. package/docs/tracking/production-readiness-audit-tracker.json +429 -0
  156. package/docs/tracking/seo-checklist-tracker.json +333 -0
  157. package/ios/Plugin/BackgroundUpdate/BackgroundUpdatePlugin.swift +66 -9
  158. package/ios/Plugin/LiveUpdate/LiveUpdatePlugin.swift +306 -52
  159. package/ios/Plugin/NativeUpdatePlugin.swift +22 -9
  160. package/ios/Plugin/Security/SecurityManager.swift +13 -14
  161. package/package.json +37 -38
  162. package/cli/node_modules/.yarn-integrity +0 -16
  163. package/cli/node_modules/commander/LICENSE +0 -22
  164. package/cli/node_modules/commander/Readme.md +0 -1148
  165. package/cli/node_modules/commander/esm.mjs +0 -16
  166. package/cli/node_modules/commander/index.js +0 -26
  167. package/cli/node_modules/commander/lib/argument.js +0 -145
  168. package/cli/node_modules/commander/lib/command.js +0 -2179
  169. package/cli/node_modules/commander/lib/error.js +0 -43
  170. package/cli/node_modules/commander/lib/help.js +0 -462
  171. package/cli/node_modules/commander/lib/option.js +0 -329
  172. package/cli/node_modules/commander/lib/suggestSimilar.js +0 -100
  173. package/cli/node_modules/commander/package-support.json +0 -16
  174. package/cli/node_modules/commander/package.json +0 -80
  175. package/cli/node_modules/commander/typings/esm.d.mts +0 -3
  176. package/cli/node_modules/commander/typings/index.d.ts +0 -884
  177. package/cli/yarn.lock +0 -8
  178. package/dist/esm/__tests__/bundle-manager.test.d.ts +0 -1
  179. package/dist/esm/__tests__/bundle-manager.test.js +0 -151
  180. package/dist/esm/__tests__/bundle-manager.test.js.map +0 -1
  181. package/dist/esm/__tests__/config.test.d.ts +0 -1
  182. package/dist/esm/__tests__/config.test.js +0 -70
  183. package/dist/esm/__tests__/config.test.js.map +0 -1
  184. package/dist/esm/__tests__/delta-processor.test.d.ts +0 -1
  185. package/dist/esm/__tests__/delta-processor.test.js +0 -77
  186. package/dist/esm/__tests__/delta-processor.test.js.map +0 -1
  187. package/dist/esm/__tests__/firestore-schema.test.d.ts +0 -1
  188. package/dist/esm/__tests__/firestore-schema.test.js +0 -74
  189. package/dist/esm/__tests__/firestore-schema.test.js.map +0 -1
  190. package/dist/esm/__tests__/integration.test.d.ts +0 -1
  191. package/dist/esm/__tests__/integration.test.js +0 -78
  192. package/dist/esm/__tests__/integration.test.js.map +0 -1
  193. package/dist/esm/__tests__/manifest-reader.test.d.ts +0 -1
  194. package/dist/esm/__tests__/manifest-reader.test.js +0 -271
  195. package/dist/esm/__tests__/manifest-reader.test.js.map +0 -1
  196. package/dist/esm/__tests__/rollout-checker.test.d.ts +0 -1
  197. package/dist/esm/__tests__/rollout-checker.test.js +0 -210
  198. package/dist/esm/__tests__/rollout-checker.test.js.map +0 -1
  199. package/dist/esm/__tests__/security.test.d.ts +0 -1
  200. package/dist/esm/__tests__/security.test.js +0 -54
  201. package/dist/esm/__tests__/security.test.js.map +0 -1
  202. package/dist/esm/__tests__/version-manager.test.d.ts +0 -1
  203. package/dist/esm/__tests__/version-manager.test.js +0 -45
  204. package/dist/esm/__tests__/version-manager.test.js.map +0 -1
  205. package/dist/esm/app-review/app-review-manager.js.map +0 -1
  206. package/dist/esm/app-review/index.js.map +0 -1
  207. package/dist/esm/app-review/platform-review-handler.js.map +0 -1
  208. package/dist/esm/app-review/review-conditions-checker.js.map +0 -1
  209. package/dist/esm/app-review/review-rate-limiter.js.map +0 -1
  210. package/dist/esm/app-review/types.js.map +0 -1
  211. package/dist/esm/app-update/app-update-checker.js.map +0 -1
  212. package/dist/esm/app-update/app-update-installer.js.map +0 -1
  213. package/dist/esm/app-update/app-update-manager.js.map +0 -1
  214. package/dist/esm/app-update/app-update-notifier.js.map +0 -1
  215. package/dist/esm/app-update/index.js.map +0 -1
  216. package/dist/esm/app-update/platform-app-update.js.map +0 -1
  217. package/dist/esm/app-update/types.js.map +0 -1
  218. package/dist/esm/background-update/background-scheduler.js.map +0 -1
  219. package/dist/esm/background-update/index.js.map +0 -1
  220. package/dist/esm/background-update/notification-manager.js.map +0 -1
  221. package/dist/esm/config/support.js.map +0 -1
  222. package/dist/esm/core/analytics.js.map +0 -1
  223. package/dist/esm/core/cache-manager.js.map +0 -1
  224. package/dist/esm/core/config.js.map +0 -1
  225. package/dist/esm/core/errors.js.map +0 -1
  226. package/dist/esm/core/event-emitter.js.map +0 -1
  227. package/dist/esm/core/logger.js.map +0 -1
  228. package/dist/esm/core/performance.js.map +0 -1
  229. package/dist/esm/core/plugin-manager.js.map +0 -1
  230. package/dist/esm/core/security.js.map +0 -1
  231. package/dist/esm/definitions.js.map +0 -1
  232. package/dist/esm/firestore/firestore-client.d.ts +0 -109
  233. package/dist/esm/firestore/firestore-client.js +0 -264
  234. package/dist/esm/firestore/firestore-client.js.map +0 -1
  235. package/dist/esm/firestore/index.d.ts +0 -11
  236. package/dist/esm/firestore/index.js +0 -11
  237. package/dist/esm/firestore/index.js.map +0 -1
  238. package/dist/esm/firestore/manifest-reader.d.ts +0 -87
  239. package/dist/esm/firestore/manifest-reader.js +0 -294
  240. package/dist/esm/firestore/manifest-reader.js.map +0 -1
  241. package/dist/esm/firestore/schema.d.ts +0 -507
  242. package/dist/esm/firestore/schema.js +0 -73
  243. package/dist/esm/firestore/schema.js.map +0 -1
  244. package/dist/esm/index.js.map +0 -1
  245. package/dist/esm/live-update/bundle-manager.js.map +0 -1
  246. package/dist/esm/live-update/certificate-pinning.js.map +0 -1
  247. package/dist/esm/live-update/delta-processor.js.map +0 -1
  248. package/dist/esm/live-update/download-manager.js.map +0 -1
  249. package/dist/esm/live-update/rollout-checker.js.map +0 -1
  250. package/dist/esm/live-update/update-manager.js.map +0 -1
  251. package/dist/esm/live-update/version-manager.js.map +0 -1
  252. package/dist/esm/plugin.js.map +0 -1
  253. package/dist/esm/security/crypto.js.map +0 -1
  254. package/dist/esm/security/validator.js.map +0 -1
  255. package/dist/esm/web.js.map +0 -1
  256. package/dist/plugin.cjs.js.map +0 -1
  257. package/dist/plugin.esm.js.map +0 -1
  258. package/dist/plugin.js.map +0 -1
  259. package/docs/FIREBASE_INTEGRATION_TRACKER.md +0 -321
  260. package/docs/FIREBASE_QUERIES_AND_INDEXES_AUDIT.md +0 -221
  261. package/docs/examples/firebase-backend-example.md +0 -27
  262. package/docs/guides/no-cost-backend-implementation-plan.md +0 -77
  263. package/docs/guides/no-cost-firestore-google-drive-backend.md +0 -60
  264. package/docs/play-console-rejection-rules.json +0 -428
  265. /package/docs/{MARKETING_WEBSITE_PLAN.md → archive/MARKETING_WEBSITE_PLAN.md} +0 -0
@@ -2,8 +2,6 @@ import { ConfigManager } from '../core/config';
2
2
  import { Logger } from '../core/logger';
3
3
  import { SecurityValidator } from '../core/security';
4
4
  import { ValidationError, ErrorCode } from '../core/errors';
5
- import { FirestoreClient } from '../firestore/firestore-client';
6
- import { ManifestReader } from '../firestore/manifest-reader';
7
5
  /**
8
6
  * Manages version checking and comparison
9
7
  */
@@ -13,8 +11,6 @@ export class VersionManager {
13
11
  this.CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
14
12
  this.preferences = null;
15
13
  this.memoryCache = new Map();
16
- this.firestoreClient = null;
17
- this.manifestReader = null;
18
14
  this.logger = Logger.getInstance();
19
15
  this.configManager = ConfigManager.getInstance();
20
16
  this.securityValidator = SecurityValidator.getInstance();
@@ -78,69 +74,6 @@ export class VersionManager {
78
74
  if (!this.preferences) {
79
75
  throw new ValidationError(ErrorCode.MISSING_DEPENDENCY, 'Preferences not configured. Please configure the plugin first.');
80
76
  }
81
- // Initialize Firestore client if configured
82
- const backendType = this.configManager.get('backendType');
83
- if (backendType === 'firestore') {
84
- const firestoreConfig = this.configManager.get('firestore');
85
- if (!firestoreConfig) {
86
- throw new ValidationError(ErrorCode.INVALID_CONFIG, 'Firestore configuration required when using firestore backend');
87
- }
88
- this.firestoreClient = new FirestoreClient(firestoreConfig);
89
- this.manifestReader = new ManifestReader(this.firestoreClient);
90
- this.logger.debug('Firestore backend initialized');
91
- }
92
- }
93
- /**
94
- * Check for updates from Firestore backend
95
- * Uses ManifestReader for efficient single-document reads
96
- */
97
- async checkForUpdatesFromFirestore(currentVersion, deviceInfo) {
98
- if (!this.manifestReader) {
99
- throw new ValidationError(ErrorCode.INVALID_CONFIG, 'Firestore backend not initialized. Call initialize() first or configure firestore backend.');
100
- }
101
- // Validate current version
102
- this.securityValidator.validateVersion(currentVersion);
103
- const options = {
104
- currentVersion,
105
- deviceInfo,
106
- checkDeltas: this.configManager.get('enableDeltaUpdates'),
107
- };
108
- try {
109
- const response = await this.manifestReader.checkForUpdates(options);
110
- if (response) {
111
- this.logger.info('Firestore update check completed', {
112
- currentVersion,
113
- latestVersion: response.version,
114
- updateAvailable: response.updateAvailable,
115
- eligible: response.rolloutEligible,
116
- });
117
- }
118
- return response;
119
- }
120
- catch (error) {
121
- this.logger.error('Failed to check for updates from Firestore', error);
122
- return null;
123
- }
124
- }
125
- /**
126
- * Get the appropriate update checker based on backend type
127
- */
128
- async checkForUpdatesAuto(currentVersion, deviceInfo) {
129
- const backendType = this.configManager.get('backendType') || 'http';
130
- if (backendType === 'firestore') {
131
- if (!deviceInfo) {
132
- throw new ValidationError(ErrorCode.INVALID_CONFIG, 'Device info is required for Firestore backend');
133
- }
134
- return this.checkForUpdatesFromFirestore(currentVersion, deviceInfo);
135
- }
136
- // Fall back to HTTP-based update check
137
- const serverUrl = this.configManager.get('serverUrl');
138
- const channel = this.configManager.get('channel') || 'production';
139
- const appId = this.configManager.get('appId');
140
- if (!serverUrl || !appId) {
141
- throw new ValidationError(ErrorCode.INVALID_CONFIG, 'Server URL and App ID are required for HTTP backend');
142
- }
143
- return this.checkForUpdates(serverUrl, channel, currentVersion, appId);
144
77
  }
145
78
  /**
146
79
  * Check for latest version from server
@@ -398,5 +331,44 @@ export class VersionManager {
398
331
  return true;
399
332
  }
400
333
  }
334
+ /**
335
+ * Persisted high-water-mark version per channel. Survives reinstalls that
336
+ * leave Preferences intact; protects against an attacker feeding an older
337
+ * (and potentially vulnerable) bundle even after a full cache wipe of
338
+ * downloaded bundles. Paired with `rememberAppliedVersion`.
339
+ */
340
+ maxVersionKey(channel) {
341
+ return `nu_max_version_${channel}`;
342
+ }
343
+ async getMaxSeenVersion(channel) {
344
+ if (!this.preferences)
345
+ return null;
346
+ try {
347
+ const { value } = await this.preferences.get({
348
+ key: this.maxVersionKey(channel),
349
+ });
350
+ return value !== null && value !== void 0 ? value : null;
351
+ }
352
+ catch (error) {
353
+ this.logger.warn('Failed to read max-seen version', error);
354
+ return null;
355
+ }
356
+ }
357
+ async rememberAppliedVersion(channel, version) {
358
+ if (!this.preferences)
359
+ return;
360
+ const current = await this.getMaxSeenVersion(channel);
361
+ if (current && VersionManager.compareVersions(current, version) >= 0) {
362
+ return;
363
+ }
364
+ try {
365
+ await this.preferences.set({
366
+ key: this.maxVersionKey(channel),
367
+ value: version,
368
+ });
369
+ }
370
+ catch (error) {
371
+ this.logger.warn('Failed to persist max-seen version', error);
372
+ }
373
+ }
401
374
  }
402
- //# sourceMappingURL=version-manager.js.map
@@ -2,7 +2,6 @@ import { registerPlugin } from '@capacitor/core';
2
2
  import { SyncStatus, BundleStatus, UpdateErrorCode } from './definitions';
3
3
  import { PluginManager } from './core/plugin-manager';
4
4
  import { NativeUpdateError, ErrorCode } from './core/errors';
5
- import { CryptoUtils } from './security/crypto';
6
5
  /**
7
6
  * Web implementation of the Native Update Plugin
8
7
  */
@@ -53,8 +52,6 @@ class NativeUpdatePluginWeb {
53
52
  requireSignature: liveUpdate === null || liveUpdate === void 0 ? void 0 : liveUpdate.requireSignature,
54
53
  checksumAlgorithm: liveUpdate === null || liveUpdate === void 0 ? void 0 : liveUpdate.checksumAlgorithm,
55
54
  checkInterval: liveUpdate === null || liveUpdate === void 0 ? void 0 : liveUpdate.checkInterval,
56
- backendType: liveUpdate === null || liveUpdate === void 0 ? void 0 : liveUpdate.backendType,
57
- firestore: liveUpdate === null || liveUpdate === void 0 ? void 0 : liveUpdate.firestore,
58
55
  security: config.security,
59
56
  appStoreId: appUpdate === null || appUpdate === void 0 ? void 0 : appUpdate.appStoreId,
60
57
  packageName: appUpdate === null || appUpdate === void 0 ? void 0 : appUpdate.packageName,
@@ -128,23 +125,33 @@ class NativeUpdatePluginWeb {
128
125
  const downloadManager = this.pluginManager.getDownloadManager();
129
126
  const bundleManager = this.pluginManager.getBundleManager();
130
127
  const blob = await downloadManager.downloadWithRetry(options.url, options.version);
131
- const path = await downloadManager.saveBlob(options.version, blob);
128
+ // Verify checksum/signature against ACTUAL downloaded bytes before
129
+ // persisting. Without this, an attacker who swaps the URL or proxies
130
+ // a tampered bundle can plant arbitrary code and the next set() will
131
+ // activate it. validateBundleData throws on mismatch.
132
+ const bundleData = await blob.arrayBuffer();
133
+ await this.validateBundleData(bundleData, options.checksum, options.signature);
134
+ const validatedBlob = new Blob([bundleData], { type: blob.type });
135
+ const path = await downloadManager.saveBlob(options.version, validatedBlob);
132
136
  const bundleInfo = {
133
137
  bundleId: options.version,
134
138
  version: options.version,
135
139
  path,
136
140
  downloadTime: Date.now(),
137
- size: blob.size,
141
+ size: validatedBlob.size,
138
142
  status: BundleStatus.READY,
139
143
  checksum: options.checksum,
140
144
  signature: options.signature,
141
- verified: false,
145
+ verified: true,
142
146
  };
143
147
  await bundleManager.saveBundleInfo(bundleInfo);
144
148
  return bundleInfo;
145
149
  }
146
150
  async set(bundle) {
147
151
  const bundleManager = this.pluginManager.getBundleManager();
152
+ if (!bundle.verified) {
153
+ throw new NativeUpdateError(ErrorCode.SIGNATURE_INVALID, `Refusing to activate unverified bundle ${bundle.bundleId}`);
154
+ }
148
155
  await bundleManager.setActiveBundle(bundle.bundleId);
149
156
  }
150
157
  async reload() {
@@ -206,22 +213,50 @@ class NativeUpdatePluginWeb {
206
213
  });
207
214
  }
208
215
  }
216
+ /**
217
+ * @deprecated since v2. The update server URL is fixed at `initialize()`
218
+ * time and cannot be mutated at runtime. Runtime mutation is an XSS
219
+ * attack vector — a compromised WebView could repoint updates to a
220
+ * malicious host. Call `initialize()` again with the new config if you
221
+ * legitimately need to switch servers.
222
+ */
209
223
  async setUpdateUrl(url) {
210
- const configManager = this.pluginManager.getConfigManager();
211
- configManager.configure({ baseUrl: url });
224
+ void url;
225
+ const logger = this.pluginManager.getLogger();
226
+ logger.warn('setUpdateUrl() is deprecated and ignored. Update URL is locked after initialize(). ' +
227
+ 'Call initialize() again with an updated serverUrl to switch servers.');
212
228
  }
213
229
  async validateUpdate(options) {
214
230
  const securityValidator = this.pluginManager.getSecurityValidator();
231
+ const downloadManager = this.pluginManager.getDownloadManager();
215
232
  try {
216
- // Validate checksum
217
- const isValid = await securityValidator.validateChecksum(new ArrayBuffer(0), // Placeholder for bundle data
218
- options.checksum);
233
+ // bundlePath may be either a stored path (`bundles/<id>/bundle.zip`)
234
+ // or a bare bundleId. Normalize to bundleId so we can read the
235
+ // persisted blob.
236
+ const pathMatch = options.bundlePath.match(/bundles\/([^/]+)\/bundle\.zip$/);
237
+ const bundleId = pathMatch ? pathMatch[1] : options.bundlePath;
238
+ const blob = await downloadManager.loadBlob(bundleId);
239
+ if (!blob) {
240
+ return {
241
+ isValid: false,
242
+ error: `Bundle not found at ${options.bundlePath}`,
243
+ };
244
+ }
245
+ const bundleData = await blob.arrayBuffer();
246
+ const checksumValid = await securityValidator.verifyChecksum(bundleData, options.checksum);
247
+ let signatureValid = true;
248
+ if (options.signature) {
249
+ signatureValid = await securityValidator.verifySignature(bundleData, options.signature);
250
+ }
251
+ const sizeValid = options.maxSize
252
+ ? bundleData.byteLength <= options.maxSize
253
+ : true;
219
254
  return {
220
- isValid,
255
+ isValid: checksumValid && signatureValid && sizeValid,
221
256
  details: {
222
- checksumValid: isValid,
223
- signatureValid: true,
224
- sizeValid: true,
257
+ checksumValid,
258
+ signatureValid,
259
+ sizeValid,
225
260
  versionValid: true,
226
261
  },
227
262
  };
@@ -242,31 +277,9 @@ class NativeUpdatePluginWeb {
242
277
  async checkForUpdate() {
243
278
  const bundleManager = this.pluginManager.getBundleManager();
244
279
  const configManager = this.pluginManager.getConfigManager();
245
- const versionManager = this.pluginManager.getVersionManager();
246
280
  try {
247
281
  const currentBundle = await bundleManager.getActiveBundle();
248
282
  const currentVersion = (currentBundle === null || currentBundle === void 0 ? void 0 : currentBundle.version) || '1.0.0';
249
- const backendType = configManager.get('backendType');
250
- if (backendType === 'firestore') {
251
- const result = (await versionManager.checkForUpdatesAuto(currentVersion, this.getDeviceInfo(currentVersion)));
252
- if (!(result === null || result === void 0 ? void 0 : result.updateAvailable)) {
253
- return {
254
- available: false,
255
- currentVersion,
256
- };
257
- }
258
- return {
259
- available: true,
260
- currentVersion,
261
- latestVersion: result.version,
262
- url: result.bundleUrl,
263
- mandatory: result.mandatory,
264
- notes: result.releaseNotes,
265
- size: result.size,
266
- checksum: result.checksum,
267
- signature: result.signature,
268
- };
269
- }
270
283
  const baseUrl = configManager.get('baseUrl');
271
284
  if (!baseUrl) {
272
285
  return {
@@ -274,11 +287,26 @@ class NativeUpdatePluginWeb {
274
287
  currentVersion,
275
288
  };
276
289
  }
277
- const response = await fetch(`${baseUrl}/updates/latest`, {
290
+ // Laravel backend contract: GET /v1/updates/check with X-API-Key,
291
+ // X-Device-ID, X-Current-Version, X-Platform headers. The
292
+ // backend returns either {available: false} or {available: true,
293
+ // version, downloadUrl, checksum, signature, size, ...}.
294
+ const apiKey = configManager.get('apiKey');
295
+ if (!apiKey) {
296
+ throw new NativeUpdateError(ErrorCode.INVALID_CONFIG, 'apiKey is required when using Laravel backend');
297
+ }
298
+ const channel = configManager.get('channel') || 'production';
299
+ const deviceInfo = this.getDeviceInfo(currentVersion);
300
+ const url = `${baseUrl.replace(/\/$/, '')}/v1/updates/check?channel=${encodeURIComponent(channel)}`;
301
+ const response = await fetch(url, {
278
302
  method: 'GET',
279
303
  headers: {
280
304
  'Content-Type': 'application/json',
305
+ 'X-API-Key': apiKey,
306
+ 'X-Device-ID': deviceInfo.deviceId,
281
307
  'X-Current-Version': currentVersion,
308
+ 'X-Platform': deviceInfo.platform,
309
+ 'X-App-Version': deviceInfo.appVersion,
282
310
  },
283
311
  });
284
312
  if (!response.ok) {
@@ -288,15 +316,19 @@ class NativeUpdatePluginWeb {
288
316
  };
289
317
  }
290
318
  const data = await response.json();
291
- const latestVersion = data.version || data.latestVersion;
292
- const isNewer = this.compareVersions(currentVersion, latestVersion) < 0;
319
+ if (!data.available) {
320
+ return {
321
+ available: false,
322
+ currentVersion,
323
+ };
324
+ }
293
325
  return {
294
- available: isNewer,
326
+ available: true,
295
327
  currentVersion,
296
- latestVersion: isNewer ? latestVersion : undefined,
297
- url: isNewer ? data.url || data.downloadUrl : undefined,
328
+ latestVersion: data.version,
329
+ url: data.downloadUrl,
298
330
  mandatory: data.mandatory,
299
- notes: data.notes || data.releaseNotes,
331
+ notes: data.releaseNotes,
300
332
  size: data.size,
301
333
  checksum: data.checksum,
302
334
  signature: data.signature,
@@ -337,10 +369,8 @@ class NativeUpdatePluginWeb {
337
369
  };
338
370
  // Download the bundle
339
371
  const blob = await downloadManager.downloadWithRetry(url, version, onProgress);
340
- // Get ArrayBuffer for decryption and validation
341
- let bundleData = await blob.arrayBuffer();
342
- // Decrypt if encryption is enabled
343
- bundleData = await this.decryptBundleIfNeeded(bundleData);
372
+ // Get ArrayBuffer for validation
373
+ const bundleData = await blob.arrayBuffer();
344
374
  // Validate checksum and signature before saving
345
375
  await this.validateBundleData(bundleData, checksum, signature || undefined);
346
376
  // Convert back to Blob for storage
@@ -416,9 +446,12 @@ class NativeUpdatePluginWeb {
416
446
  return downloadManager.getActiveDownloadCount();
417
447
  }
418
448
  /**
419
- * Compare two semantic versions
449
+ * Compare two semantic versions. Currently unused — the Laravel
450
+ * backend decides update availability server-side. Retained as a
451
+ * helper for advanced consumers that need client-side comparison.
420
452
  * Returns: -1 if v1 < v2, 0 if equal, 1 if v1 > v2
421
453
  */
454
+ // @ts-expect-error retained as a helper for advanced consumers
422
455
  compareVersions(v1, v2) {
423
456
  const parts1 = v1.split('.').map(Number);
424
457
  const parts2 = v2.split('.').map(Number);
@@ -442,45 +475,6 @@ class NativeUpdatePluginWeb {
442
475
  locale: typeof navigator !== 'undefined' ? navigator.language : 'en-US',
443
476
  };
444
477
  }
445
- /**
446
- * Decrypt bundle data if encryption is enabled
447
- * Returns the decrypted data or the original data if encryption is not enabled
448
- */
449
- async decryptBundleIfNeeded(bundleData) {
450
- const configManager = this.pluginManager.getConfigManager();
451
- const enableEncryption = configManager.get('enableEncryption');
452
- const encryptionKey = configManager.get('encryptionKey');
453
- const encryptionSalt = configManager.get('encryptionSalt');
454
- if (!enableEncryption) {
455
- return bundleData;
456
- }
457
- // Validate encryption configuration
458
- if (!encryptionKey) {
459
- throw new NativeUpdateError(ErrorCode.INVALID_CONFIG, 'Encryption is enabled but encryptionKey is not configured');
460
- }
461
- if (!encryptionSalt) {
462
- throw new NativeUpdateError(ErrorCode.INVALID_CONFIG, 'Encryption is enabled but encryptionSalt is not configured');
463
- }
464
- try {
465
- // Try to parse as encrypted bundle JSON
466
- const decoder = new TextDecoder();
467
- const jsonString = decoder.decode(bundleData);
468
- const encryptedBundle = JSON.parse(jsonString);
469
- // Verify it's an encrypted bundle
470
- if (!CryptoUtils.isEncryptedBundle(encryptedBundle)) {
471
- throw new NativeUpdateError(ErrorCode.VALIDATION_ERROR, 'Encryption is enabled but bundle is not encrypted');
472
- }
473
- // Decrypt the bundle
474
- const decryptedData = await CryptoUtils.decryptBundle(encryptedBundle, encryptionKey, encryptionSalt);
475
- return decryptedData;
476
- }
477
- catch (error) {
478
- if (error instanceof NativeUpdateError) {
479
- throw error;
480
- }
481
- throw new NativeUpdateError(ErrorCode.VALIDATION_ERROR, `Failed to decrypt bundle: ${error instanceof Error ? error.message : 'Unknown error'}`);
482
- }
483
- }
484
478
  /**
485
479
  * Validate bundle data using SecurityValidator
486
480
  * Returns true if valid, throws error if invalid
@@ -656,4 +650,3 @@ const NativeUpdate = registerPlugin('NativeUpdate', {
656
650
  web: () => new NativeUpdatePluginWeb(),
657
651
  });
658
652
  export { NativeUpdate };
659
- //# sourceMappingURL=plugin.js.map
@@ -26,25 +26,30 @@ export class CryptoUtils {
26
26
  */
27
27
  static async verifySignature(data, signature, publicKey) {
28
28
  try {
29
- // Convert base64 strings to ArrayBuffer
29
+ // Accept either PEM (`-----BEGIN PUBLIC KEY-----...`) or raw base64
30
+ // SPKI. Two callers existed historically and they passed different
31
+ // formats; standardize on PEM-aware parsing here so both work.
32
+ const pemBody = publicKey
33
+ .replace(/-----BEGIN PUBLIC KEY-----/g, '')
34
+ .replace(/-----END PUBLIC KEY-----/g, '')
35
+ .replace(/\s+/g, '');
30
36
  const signatureBuffer = this.base64ToArrayBuffer(signature);
31
- const publicKeyBuffer = this.base64ToArrayBuffer(publicKey);
32
- // Import public key
37
+ const publicKeyBuffer = this.base64ToArrayBuffer(pemBody);
33
38
  const key = await crypto.subtle.importKey('spki', publicKeyBuffer, {
34
39
  name: 'RSA-PSS',
35
40
  hash: 'SHA-256',
36
41
  }, false, ['verify']);
37
- // Prepare data
38
42
  const encoder = new TextEncoder();
39
43
  const dataBuffer = typeof data === 'string' ? encoder.encode(data) : data;
40
- // Verify signature
41
44
  return await crypto.subtle.verify({
42
45
  name: 'RSA-PSS',
43
46
  saltLength: 32,
44
47
  }, key, signatureBuffer, dataBuffer);
45
48
  }
46
49
  catch (error) {
47
- console.error('Signature verification failed:', error);
50
+ // Verification failure must NOT throw — we want a clean false so
51
+ // upstream code can refuse the bundle without crashing.
52
+ void error;
48
53
  return false;
49
54
  }
50
55
  }
@@ -224,4 +229,3 @@ export class CryptoUtils {
224
229
  return btoa(binary);
225
230
  }
226
231
  }
227
- //# sourceMappingURL=crypto.js.map
@@ -140,4 +140,3 @@ export class Validator {
140
140
  return { valid: true };
141
141
  }
142
142
  }
143
- //# sourceMappingURL=validator.js.map
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Rollout + update-check types.
3
+ *
4
+ * These types describe staged-rollout configuration and the response
5
+ * shape the Laravel backend returns from `GET /v1/updates/check`.
6
+ * They were previously co-located with the Firestore schema; v3 of the
7
+ * plugin dropped Firestore as a backend, so the types live here in a
8
+ * backend-neutral location.
9
+ */
10
+ /**
11
+ * A point in time in a rollout config. Accepts ISO-8601 strings (what
12
+ * the Laravel backend returns), epoch milliseconds, or a `Date` for
13
+ * convenience when constructing config in code.
14
+ */
15
+ export type RolloutTimestamp = number | string | Date;
16
+ /**
17
+ * Convert a `RolloutTimestamp` to epoch milliseconds. Throws on a
18
+ * value that doesn't parse — callers are expected to validate before
19
+ * persisting rollout config.
20
+ */
21
+ export declare function toEpochMs(value: RolloutTimestamp): number;
22
+ /**
23
+ * Staged rollout configuration returned by the backend on update checks.
24
+ */
25
+ export interface RolloutConfig {
26
+ enabled: boolean;
27
+ percentage: number;
28
+ startTime: RolloutTimestamp;
29
+ endTime: RolloutTimestamp | null;
30
+ targetSegments?: RolloutTargetSegments;
31
+ schedule?: RolloutSchedule;
32
+ }
33
+ export interface RolloutTargetSegments {
34
+ platforms?: ('ios' | 'android' | 'web')[];
35
+ minAppVersion?: string;
36
+ maxAppVersion?: string;
37
+ deviceIds?: string[];
38
+ regions?: string[];
39
+ }
40
+ export interface RolloutSchedule {
41
+ type: 'immediate' | 'gradual' | 'scheduled';
42
+ gradualSteps?: number[];
43
+ gradualInterval?: number;
44
+ scheduledTime?: RolloutTimestamp;
45
+ }
46
+ /**
47
+ * Device information used for rollout eligibility checks. Sent to the
48
+ * backend on `/v1/updates/check` via `X-Device-ID`, `X-Platform`,
49
+ * `X-App-Version` headers and used locally by `RolloutChecker` for
50
+ * deterministic percentile bucketing.
51
+ */
52
+ export interface DeviceInfo {
53
+ deviceId: string;
54
+ platform: 'ios' | 'android' | 'web';
55
+ appVersion: string;
56
+ bundleVersion: string;
57
+ osVersion: string;
58
+ region?: string;
59
+ locale: string;
60
+ }
61
+ /**
62
+ * Normalized response from `GET /v1/updates/check`. The Laravel
63
+ * backend returns this exact shape (snake_case fields are mapped on
64
+ * the wire — this interface is the camelCase local form).
65
+ */
66
+ export interface UpdateCheckResponse {
67
+ updateAvailable: boolean;
68
+ version?: string;
69
+ bundleUrl?: string;
70
+ minNativeVersion?: string;
71
+ releaseNotes?: string;
72
+ signature?: string;
73
+ checksum?: string;
74
+ mandatory?: boolean;
75
+ size?: number;
76
+ delta?: {
77
+ available: boolean;
78
+ patchUrl?: string;
79
+ patchSize?: number;
80
+ patchChecksum?: string;
81
+ targetChecksum?: string;
82
+ };
83
+ rolloutEligible?: boolean;
84
+ rolloutReason?: string;
85
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Rollout + update-check types.
3
+ *
4
+ * These types describe staged-rollout configuration and the response
5
+ * shape the Laravel backend returns from `GET /v1/updates/check`.
6
+ * They were previously co-located with the Firestore schema; v3 of the
7
+ * plugin dropped Firestore as a backend, so the types live here in a
8
+ * backend-neutral location.
9
+ */
10
+ /**
11
+ * Convert a `RolloutTimestamp` to epoch milliseconds. Throws on a
12
+ * value that doesn't parse — callers are expected to validate before
13
+ * persisting rollout config.
14
+ */
15
+ export function toEpochMs(value) {
16
+ if (typeof value === 'number')
17
+ return value;
18
+ if (value instanceof Date)
19
+ return value.getTime();
20
+ const parsed = new Date(value).getTime();
21
+ if (Number.isNaN(parsed)) {
22
+ throw new Error(`Invalid rollout timestamp: ${String(value)}`);
23
+ }
24
+ return parsed;
25
+ }
package/dist/esm/web.d.ts CHANGED
@@ -11,6 +11,7 @@ export declare class NativeUpdateWeb extends WebPlugin implements NativeUpdateCo
11
11
  private backgroundCheckInterval;
12
12
  private configManager;
13
13
  private isConfigured;
14
+ private downloadedBundleBytes;
14
15
  constructor();
15
16
  /**
16
17
  * Configuration and Core Methods
@@ -33,6 +34,10 @@ export declare class NativeUpdateWeb extends WebPlugin implements NativeUpdateCo
33
34
  notifyAppReady(): Promise<void>;
34
35
  getLatest(): Promise<LatestVersion>;
35
36
  setChannel(channel: string): Promise<void>;
37
+ /**
38
+ * @deprecated since v2. See NativeUpdatePluginWeb.setUpdateUrl in plugin.ts.
39
+ * Update URL is locked after initialize(); this method is a no-op.
40
+ */
36
41
  setUpdateUrl(url: string): Promise<void>;
37
42
  validateUpdate(options: ValidateOptions): Promise<ValidationResult>;
38
43
  /**
@@ -82,7 +87,11 @@ export declare class NativeUpdateWeb extends WebPlugin implements NativeUpdateCo
82
87
  */
83
88
  private validateChecksum;
84
89
  /**
85
- * Validate digital signature using RSA-PSS or ECDSA
90
+ * Validate digital signature using RSA-PSS or ECDSA.
91
+ *
92
+ * Fails closed: if the host app configured a public key or
93
+ * requireSignature, a missing signature throws. Silent-pass was a P0 gap.
94
+ *
86
95
  * @param data - The data that was signed (as string path or ArrayBuffer)
87
96
  * @param signature - The signature to verify (base64 encoded)
88
97
  */
@@ -99,8 +108,24 @@ export declare class NativeUpdateWeb extends WebPlugin implements NativeUpdateCo
99
108
  * Import ECDSA public key from PEM format
100
109
  */
101
110
  private importPublicKeyECDSA;
111
+ /**
112
+ * Safe localStorage accessor. Returns `null` in environments where
113
+ * `localStorage` isn't available — SSR, Node-only test runners that
114
+ * stub `window` without storage, or sandboxed iframes. All callers
115
+ * MUST tolerate a `null` return and skip persistence.
116
+ */
117
+ private getLocalStorage;
102
118
  private loadStoredData;
103
119
  private saveStoredData;
120
+ /**
121
+ * Strip secrets before persisting config to localStorage. A browser's
122
+ * localStorage is accessible to any script running in the page (XSS,
123
+ * compromised deps) — an `apiKey` or `publicKey` sitting there is a
124
+ * ready-to-use credential. Host apps must pass these on every
125
+ * `initialize()` call; they are never cached cross-session.
126
+ */
127
+ private static readonly SENSITIVE_CONFIG_PATHS;
128
+ private sanitizeConfigForPersistence;
104
129
  private saveConfiguration;
105
130
  private getInstallDate;
106
131
  private incrementLaunchCount;