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
package/dist/esm/index.js CHANGED
@@ -1,5 +1,8 @@
1
1
  // Main plugin export
2
2
  export { NativeUpdate } from './plugin';
3
+ // Runtime enums — MUST be `export {}` not `export type {}` so they survive
4
+ // TypeScript erasure and reach the published bundle.
5
+ export { BackgroundUpdateType, NotificationPriority, UpdateStrategy, UpdateMode, InstallMode, ChecksumAlgorithm, SyncStatus, BundleStatus, InstallStatus, UpdateErrorCode, } from './definitions';
3
6
  // Error exports
4
7
  export { ErrorCode, NativeUpdateError, ConfigurationError, DownloadError, ValidationError, StorageError, UpdateError as UpdateErrorClass, } from './core/errors';
5
8
  // Core exports for advanced users
@@ -8,11 +11,8 @@ export { Logger, LogLevel } from './core/logger';
8
11
  export { SecurityValidator } from './core/security';
9
12
  export { PluginManager } from './core/plugin-manager';
10
13
  export { CacheManager } from './core/cache-manager';
11
- // Security/Crypto exports
12
- export { CryptoUtils } from './security/crypto';
13
14
  // Manager exports for advanced users
14
15
  export { BundleManager } from './live-update/bundle-manager';
15
16
  export { DownloadManager } from './live-update/download-manager';
16
17
  export { VersionManager } from './live-update/version-manager';
17
18
  export { UpdateManager } from './live-update/update-manager';
18
- //# sourceMappingURL=index.js.map
@@ -1,4 +1,5 @@
1
1
  import type { BundleInfo } from '../definitions';
2
+ import type { DownloadManager } from './download-manager';
2
3
  /**
3
4
  * Manages bundle storage and lifecycle
4
5
  */
@@ -10,7 +11,14 @@ export declare class BundleManager {
10
11
  private readonly configManager;
11
12
  private cache;
12
13
  private cacheExpiry;
14
+ private downloadManager;
13
15
  constructor();
16
+ /**
17
+ * Wire the DownloadManager so deleteBundle can also remove the
18
+ * persisted bundle file. Without this, deleted bundles leave their
19
+ * blob on disk forever.
20
+ */
21
+ setDownloadManager(downloadManager: DownloadManager): void;
14
22
  /**
15
23
  * Initialize the bundle manager with preferences
16
24
  */
@@ -11,9 +11,18 @@ export class BundleManager {
11
11
  this.preferences = null;
12
12
  this.cache = new Map();
13
13
  this.cacheExpiry = 0;
14
+ this.downloadManager = null;
14
15
  this.logger = Logger.getInstance();
15
16
  this.configManager = ConfigManager.getInstance();
16
17
  }
18
+ /**
19
+ * Wire the DownloadManager so deleteBundle can also remove the
20
+ * persisted bundle file. Without this, deleted bundles leave their
21
+ * blob on disk forever.
22
+ */
23
+ setDownloadManager(downloadManager) {
24
+ this.downloadManager = downloadManager;
25
+ }
17
26
  /**
18
27
  * Initialize the bundle manager with preferences
19
28
  */
@@ -127,6 +136,17 @@ export class BundleManager {
127
136
  if (activeBundleId === bundleId) {
128
137
  await this.clearActiveBundle();
129
138
  }
139
+ // Remove the persisted bundle file from filesystem. Failure to delete
140
+ // metadata earlier was a storage leak: the on-disk zip stayed forever
141
+ // because deleteBundle only cleaned the metadata cache.
142
+ if (this.downloadManager) {
143
+ try {
144
+ await this.downloadManager.deleteBlob(bundleId);
145
+ }
146
+ catch (error) {
147
+ this.logger.warn('Failed to delete bundle file', { bundleId, error });
148
+ }
149
+ }
130
150
  this.logger.info('Bundle deleted', { bundleId });
131
151
  }
132
152
  /**
@@ -322,4 +342,3 @@ export class BundleManager {
322
342
  }
323
343
  }
324
344
  }
325
- //# sourceMappingURL=bundle-manager.js.map
@@ -75,4 +75,3 @@ export class CertificatePinning {
75
75
  };
76
76
  }
77
77
  }
78
- //# sourceMappingURL=certificate-pinning.js.map
@@ -7,7 +7,7 @@
7
7
  * For production, a WASM-based bsdiff/bspatch implementation can be added
8
8
  * for better performance on binary files.
9
9
  */
10
- import type { UpdateCheckResponse } from '../firestore/schema';
10
+ import type { UpdateCheckResponse } from '../types/rollout';
11
11
  /**
12
12
  * Delta patch information
13
13
  */
@@ -10,6 +10,7 @@
10
10
  import { Logger } from '../core/logger';
11
11
  import { ConfigManager } from '../core/config';
12
12
  import { DownloadError, ErrorCode, ValidationError } from '../core/errors';
13
+ import { SecurityValidator } from '../core/security';
13
14
  /**
14
15
  * Delta processor for applying patch updates
15
16
  */
@@ -92,6 +93,7 @@ export class DeltaProcessor {
92
93
  * Download a delta patch file
93
94
  */
94
95
  async downloadPatch(url) {
96
+ SecurityValidator.validateDownloadUrl(url);
95
97
  const response = await fetch(url);
96
98
  if (!response.ok) {
97
99
  throw new DownloadError(ErrorCode.DOWNLOAD_FAILED, `Failed to download delta patch: ${response.status}`);
@@ -102,6 +104,7 @@ export class DeltaProcessor {
102
104
  * Download full bundle
103
105
  */
104
106
  async downloadFull(url, onProgress) {
107
+ SecurityValidator.validateDownloadUrl(url);
105
108
  const response = await fetch(url);
106
109
  if (!response.ok) {
107
110
  throw new DownloadError(ErrorCode.DOWNLOAD_FAILED, `Failed to download bundle: ${response.status}`);
@@ -296,4 +299,3 @@ export class DeltaProcessor {
296
299
  export function createDeltaProcessor() {
297
300
  return new DeltaProcessor();
298
301
  }
299
- //# sourceMappingURL=delta-processor.js.map
@@ -1,9 +1,26 @@
1
1
  import type { DownloadProgressEvent } from '../definitions';
2
+ export interface DownloadOptions {
3
+ onProgress?: (event: DownloadProgressEvent) => void;
4
+ /**
5
+ * Byte offset to resume from. When set, the request carries a
6
+ * `Range: bytes=<N>-` header. If the server responds with 200 instead
7
+ * of 206, the plugin discards any partial data the caller held and
8
+ * treats the response as a full download (see resumeDownload).
9
+ */
10
+ resumePosition?: number;
11
+ }
2
12
  /**
3
13
  * Manages file downloads with progress tracking and resume capability
4
14
  */
5
15
  export declare class DownloadManager {
6
16
  private activeDownloads;
17
+ /**
18
+ * Persisted-past-request metadata for the most recent `download()`
19
+ * call per bundleId. Today we only stash whether the server honoured
20
+ * a Range request (HTTP 206 vs 200). resumeDownload() consults this
21
+ * to decide whether to concatenate or discard its partial prefix.
22
+ */
23
+ private lastResponseMeta;
7
24
  private readonly logger;
8
25
  private readonly configManager;
9
26
  private filesystem;
@@ -17,11 +34,25 @@ export declare class DownloadManager {
17
34
  */
18
35
  private validateUrl;
19
36
  /**
20
- * Download a file with progress tracking
21
- */
22
- download(url: string, bundleId: string, onProgress?: (event: DownloadProgressEvent) => void): Promise<Blob>;
23
- /**
24
- * Resume a previously interrupted download
37
+ * Download a file with progress tracking.
38
+ *
39
+ * Accepts either a bare `onProgress` callback (back-compat) or a full
40
+ * DownloadOptions object. When `resumePosition` is set, the request
41
+ * carries a Range header; the resulting download state records
42
+ * whether the server actually honoured it (`serverReturnedPartial`).
43
+ */
44
+ download(url: string, bundleId: string, onProgressOrOptions?: ((event: DownloadProgressEvent) => void) | DownloadOptions): Promise<Blob>;
45
+ /**
46
+ * Resume a previously interrupted download.
47
+ *
48
+ * Emits a Range-request download starting at the partial-data offset.
49
+ * If the server honours the range (HTTP 206), the partial + remaining
50
+ * bytes are concatenated. If the server returns a full response
51
+ * (HTTP 200 — common when the upstream URL's ETag changed, or when a
52
+ * CDN doesn't honour ranges) the partial data is discarded and the
53
+ * full response is used. Previously this path silently double-wrote
54
+ * bytes when the server fell back to 200; this was a latent
55
+ * bundle-corruption bug.
25
56
  */
26
57
  resumeDownload(url: string, bundleId: string, partialData: Blob, onProgress?: (event: DownloadProgressEvent) => void): Promise<Blob>;
27
58
  /**
@@ -8,6 +8,13 @@ import { Directory } from '@capacitor/filesystem';
8
8
  export class DownloadManager {
9
9
  constructor() {
10
10
  this.activeDownloads = new Map();
11
+ /**
12
+ * Persisted-past-request metadata for the most recent `download()`
13
+ * call per bundleId. Today we only stash whether the server honoured
14
+ * a Range request (HTTP 206 vs 200). resumeDownload() consults this
15
+ * to decide whether to concatenate or discard its partial prefix.
16
+ */
17
+ this.lastResponseMeta = new Map();
11
18
  this.filesystem = null;
12
19
  this.logger = Logger.getInstance();
13
20
  this.configManager = ConfigManager.getInstance();
@@ -45,9 +52,20 @@ export class DownloadManager {
45
52
  }
46
53
  }
47
54
  /**
48
- * Download a file with progress tracking
55
+ * Download a file with progress tracking.
56
+ *
57
+ * Accepts either a bare `onProgress` callback (back-compat) or a full
58
+ * DownloadOptions object. When `resumePosition` is set, the request
59
+ * carries a Range header; the resulting download state records
60
+ * whether the server actually honoured it (`serverReturnedPartial`).
49
61
  */
50
- async download(url, bundleId, onProgress) {
62
+ async download(url, bundleId, onProgressOrOptions) {
63
+ var _a;
64
+ const options = typeof onProgressOrOptions === 'function'
65
+ ? { onProgress: onProgressOrOptions }
66
+ : onProgressOrOptions !== null && onProgressOrOptions !== void 0 ? onProgressOrOptions : {};
67
+ const onProgress = options.onProgress;
68
+ const resumePosition = (_a = options.resumePosition) !== null && _a !== void 0 ? _a : 0;
51
69
  // Validate inputs
52
70
  this.validateUrl(url);
53
71
  if (!bundleId) {
@@ -62,6 +80,7 @@ export class DownloadManager {
62
80
  const downloadState = {
63
81
  controller: abortController,
64
82
  startTime: Date.now(),
83
+ resumePosition: resumePosition > 0 ? resumePosition : undefined,
65
84
  };
66
85
  this.activeDownloads.set(bundleId, downloadState);
67
86
  try {
@@ -73,8 +92,8 @@ export class DownloadManager {
73
92
  Accept: 'application/octet-stream, application/zip',
74
93
  };
75
94
  // Add Range header if resuming from a previous position
76
- if (downloadState.resumePosition && downloadState.resumePosition > 0) {
77
- headers['Range'] = `bytes=${downloadState.resumePosition}-`;
95
+ if (resumePosition > 0) {
96
+ headers['Range'] = `bytes=${resumePosition}-`;
78
97
  }
79
98
  const response = await fetch(url, {
80
99
  signal: abortController.signal,
@@ -84,6 +103,13 @@ export class DownloadManager {
84
103
  if (!response.ok) {
85
104
  throw new DownloadError(ErrorCode.DOWNLOAD_FAILED, `Download failed: ${response.status} ${response.statusText}`, { status: response.status, statusText: response.statusText });
86
105
  }
106
+ // Record whether the server actually honoured the Range request.
107
+ // Stored on both the live state (for introspection during the
108
+ // download) and the lastResponseMeta map (so resumeDownload can
109
+ // read it after download() cleans up the live state).
110
+ const serverReturnedPartial = resumePosition > 0 && response.status === 206;
111
+ downloadState.serverReturnedPartial = serverReturnedPartial;
112
+ this.lastResponseMeta.set(bundleId, { partial: serverReturnedPartial });
87
113
  // Validate content type
88
114
  const contentType = response.headers.get('content-type');
89
115
  if (contentType && !this.isValidContentType(contentType)) {
@@ -155,25 +181,31 @@ export class DownloadManager {
155
181
  }
156
182
  }
157
183
  /**
158
- * Resume a previously interrupted download
184
+ * Resume a previously interrupted download.
185
+ *
186
+ * Emits a Range-request download starting at the partial-data offset.
187
+ * If the server honours the range (HTTP 206), the partial + remaining
188
+ * bytes are concatenated. If the server returns a full response
189
+ * (HTTP 200 — common when the upstream URL's ETag changed, or when a
190
+ * CDN doesn't honour ranges) the partial data is discarded and the
191
+ * full response is used. Previously this path silently double-wrote
192
+ * bytes when the server fell back to 200; this was a latent
193
+ * bundle-corruption bug.
159
194
  */
160
195
  async resumeDownload(url, bundleId, partialData, onProgress) {
161
- // Update download state with resume position
162
- const downloadState = this.activeDownloads.get(bundleId);
163
- if (downloadState) {
164
- downloadState.resumePosition = partialData.size;
165
- }
166
- try {
167
- // Download remaining bytes
168
- const remainingData = await this.download(url, bundleId, onProgress);
169
- // Combine partial and remaining data
170
- return new Blob([partialData, remainingData]);
171
- }
172
- catch (error) {
173
- // Resume failed, clean up
174
- this.activeDownloads.delete(bundleId);
175
- throw error;
196
+ const responseBlob = await this.download(url, bundleId, {
197
+ onProgress,
198
+ resumePosition: partialData.size,
199
+ });
200
+ // Read the flag download() set in lastResponseMeta. If the server
201
+ // returned 206 we concatenate; if 200 we discard the partial.
202
+ const meta = this.lastResponseMeta.get(bundleId);
203
+ this.lastResponseMeta.delete(bundleId);
204
+ if (meta === null || meta === void 0 ? void 0 : meta.partial) {
205
+ return new Blob([partialData, responseBlob]);
176
206
  }
207
+ this.logger.info('Resume: server returned full response; discarding partial data', { bundleId, partialSize: partialData.size, responseSize: responseBlob.size });
208
+ return responseBlob;
177
209
  }
178
210
  /**
179
211
  * Check if a download can be resumed
@@ -259,9 +291,16 @@ export class DownloadManager {
259
291
  let lastError = null;
260
292
  for (let attempt = 0; attempt < maxRetries; attempt++) {
261
293
  try {
262
- // Add delay between retries (exponential backoff)
294
+ // Exponential backoff with ±25% jitter, capped at 30s. Jitter
295
+ // avoids a thundering-herd when many clients retry in lockstep
296
+ // after a common failure (CDN blip, server restart) — without
297
+ // it every client would wake up at exactly the same moment and
298
+ // DDoS the recovering server. 25% is enough desync without
299
+ // making the retry interval feel unpredictable.
263
300
  if (attempt > 0) {
264
- const delay = Math.min(retryDelay * Math.pow(2, attempt - 1), 30000);
301
+ const base = retryDelay * Math.pow(2, attempt - 1);
302
+ const jitter = (Math.random() - 0.5) * 0.5 * base;
303
+ const delay = Math.min(Math.max(base + jitter, 100), 30000);
265
304
  await new Promise((resolve) => setTimeout(resolve, delay));
266
305
  this.logger.debug('Retrying download', { bundleId, attempt, delay });
267
306
  }
@@ -296,7 +335,15 @@ export class DownloadManager {
296
335
  throw new DownloadError(ErrorCode.MISSING_DEPENDENCY, 'Filesystem not initialized');
297
336
  }
298
337
  const arrayBuffer = await this.blobToArrayBuffer(blob);
299
- const base64 = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));
338
+ // Spreading a 100MB Uint8Array into String.fromCharCode is a stack
339
+ // overflow on most engines. Chunk the conversion in 32KB blocks.
340
+ const bytes = new Uint8Array(arrayBuffer);
341
+ const chunkSize = 0x8000;
342
+ let binary = '';
343
+ for (let i = 0; i < bytes.length; i += chunkSize) {
344
+ binary += String.fromCharCode.apply(null, Array.from(bytes.subarray(i, i + chunkSize)));
345
+ }
346
+ const base64 = btoa(binary);
300
347
  const path = `bundles/${bundleId}/bundle.zip`;
301
348
  await this.filesystem.writeFile({
302
349
  path,
@@ -363,4 +410,3 @@ export class DownloadManager {
363
410
  }
364
411
  }
365
412
  }
366
- //# sourceMappingURL=download-manager.js.map
@@ -4,7 +4,7 @@
4
4
  * Client-side rollout eligibility checking for staged deployments.
5
5
  * Uses deterministic hashing for consistent rollout decisions.
6
6
  */
7
- import type { RolloutConfig, DeviceInfo } from '../firestore/schema';
7
+ import type { RolloutConfig, DeviceInfo } from '../types/rollout';
8
8
  /**
9
9
  * Rollout eligibility result
10
10
  */
@@ -4,7 +4,7 @@
4
4
  * Client-side rollout eligibility checking for staged deployments.
5
5
  * Uses deterministic hashing for consistent rollout decisions.
6
6
  */
7
- import { timestampToDate } from '../firestore/schema';
7
+ import { toEpochMs } from '../types/rollout';
8
8
  import { Logger } from '../core/logger';
9
9
  import { ConfigManager } from '../core/config';
10
10
  /**
@@ -77,7 +77,7 @@ export class RolloutChecker {
77
77
  */
78
78
  checkTimeWindow(rollout) {
79
79
  const now = Date.now();
80
- const startTime = timestampToDate(rollout.startTime).getTime();
80
+ const startTime = toEpochMs(rollout.startTime);
81
81
  if (now < startTime) {
82
82
  return {
83
83
  eligible: false,
@@ -85,7 +85,7 @@ export class RolloutChecker {
85
85
  };
86
86
  }
87
87
  if (rollout.endTime) {
88
- const endTime = timestampToDate(rollout.endTime).getTime();
88
+ const endTime = toEpochMs(rollout.endTime);
89
89
  if (now > endTime) {
90
90
  return {
91
91
  eligible: false,
@@ -100,7 +100,7 @@ export class RolloutChecker {
100
100
  */
101
101
  checkScheduled(scheduledTime) {
102
102
  const now = Date.now();
103
- const scheduled = timestampToDate(scheduledTime).getTime();
103
+ const scheduled = toEpochMs(scheduledTime);
104
104
  if (now < scheduled) {
105
105
  return {
106
106
  eligible: false,
@@ -170,7 +170,7 @@ export class RolloutChecker {
170
170
  if (!gradualSteps || !gradualInterval) {
171
171
  return rollout.percentage;
172
172
  }
173
- const startTime = timestampToDate(rollout.startTime).getTime();
173
+ const startTime = toEpochMs(rollout.startTime);
174
174
  const elapsedHours = (Date.now() - startTime) / (1000 * 60 * 60);
175
175
  const stepIndex = Math.floor(elapsedHours / gradualInterval);
176
176
  if (stepIndex >= gradualSteps.length) {
@@ -302,4 +302,3 @@ export async function collectDeviceInfo() {
302
302
  region: undefined, // Could be determined via geolocation or timezone
303
303
  };
304
304
  }
305
- //# sourceMappingURL=rollout-checker.js.map
@@ -18,7 +18,18 @@ export declare class UpdateManager {
18
18
  */
19
19
  applyUpdate(bundleId: string, options?: UpdateOptions): Promise<void>;
20
20
  /**
21
- * Validate update before applying
21
+ * Validate update before applying.
22
+ *
23
+ * Version-safety checks here are the last line of defense before a bundle
24
+ * is activated. We enforce two invariants:
25
+ * 1. Active-bundle monotonicity — new bundle must be strictly newer than
26
+ * whatever is currently active on this device.
27
+ * 2. Persisted high-water-mark — new bundle must be strictly newer than
28
+ * the highest version this device has ever applied in this channel.
29
+ * Catches an attacker who wipes the download cache (or serves an
30
+ * older manifest) to force a downgrade to a vulnerable build.
31
+ * The old `allowDowngrade` escape hatch was removed in v2 — downgrades are
32
+ * a debug/recovery flow and belong in a dedicated API, not an option flag.
22
33
  */
23
34
  private validateUpdate;
24
35
  /**
@@ -26,6 +26,7 @@ export class UpdateManager {
26
26
  * Apply bundle update atomically
27
27
  */
28
28
  async applyUpdate(bundleId, options) {
29
+ var _a;
29
30
  if (this.updateInProgress) {
30
31
  throw new UpdateError(ErrorCode.UPDATE_FAILED, 'Another update is already in progress');
31
32
  }
@@ -64,9 +65,20 @@ export class UpdateManager {
64
65
  await this.verifyUpdate(newBundle);
65
66
  // Mark as active
66
67
  await bundleManager.setActiveBundle(bundleId);
67
- // Clean up old bundles if configured
68
- if (options === null || options === void 0 ? void 0 : options.cleanupOldBundles) {
69
- await bundleManager.cleanupOldBundles(options.keepBundleCount || 3);
68
+ // Advance the monotonic high-water mark so future update checks and
69
+ // reinstalls cannot be tricked into applying an older version.
70
+ const versionManager = this.pluginManager.getVersionManager();
71
+ const configManager = this.pluginManager.getConfigManager();
72
+ await versionManager.rememberAppliedVersion(configManager.get('channel'), newBundle.version);
73
+ // Auto-cleanup by default. The old opt-in flag left most
74
+ // integrations accumulating bundles forever, which is a disk
75
+ // exhaustion path on devices with limited storage. Host apps
76
+ // that genuinely want every historical bundle can pass
77
+ // cleanupOldBundles: false explicitly.
78
+ const shouldCleanup = (options === null || options === void 0 ? void 0 : options.cleanupOldBundles) !== false;
79
+ if (shouldCleanup) {
80
+ const keep = (_a = options === null || options === void 0 ? void 0 : options.keepBundleCount) !== null && _a !== void 0 ? _a : 3;
81
+ await bundleManager.cleanupOldBundles(keep);
70
82
  }
71
83
  logger.info('Bundle update completed successfully', {
72
84
  bundleId,
@@ -89,16 +101,32 @@ export class UpdateManager {
89
101
  }
90
102
  }
91
103
  /**
92
- * Validate update before applying
104
+ * Validate update before applying.
105
+ *
106
+ * Version-safety checks here are the last line of defense before a bundle
107
+ * is activated. We enforce two invariants:
108
+ * 1. Active-bundle monotonicity — new bundle must be strictly newer than
109
+ * whatever is currently active on this device.
110
+ * 2. Persisted high-water-mark — new bundle must be strictly newer than
111
+ * the highest version this device has ever applied in this channel.
112
+ * Catches an attacker who wipes the download cache (or serves an
113
+ * older manifest) to force a downgrade to a vulnerable build.
114
+ * The old `allowDowngrade` escape hatch was removed in v2 — downgrades are
115
+ * a debug/recovery flow and belong in a dedicated API, not an option flag.
93
116
  */
94
- async validateUpdate(currentBundle, newBundle, options) {
117
+ async validateUpdate(currentBundle, newBundle, _options) {
95
118
  const logger = this.pluginManager.getLogger();
96
119
  const versionManager = this.pluginManager.getVersionManager();
97
- // Check if downgrade
98
- if (currentBundle && !(options === null || options === void 0 ? void 0 : options.allowDowngrade)) {
99
- if (versionManager.shouldBlockDowngrade(currentBundle.version, newBundle.version)) {
100
- throw new ValidationError(ErrorCode.VERSION_DOWNGRADE, `Cannot downgrade from ${currentBundle.version} to ${newBundle.version}`);
101
- }
120
+ const configManager = this.pluginManager.getConfigManager();
121
+ if (currentBundle &&
122
+ versionManager.shouldBlockDowngrade(currentBundle.version, newBundle.version)) {
123
+ throw new ValidationError(ErrorCode.VERSION_DOWNGRADE, `Cannot downgrade from ${currentBundle.version} to ${newBundle.version}`);
124
+ }
125
+ const channel = configManager.get('channel');
126
+ const maxSeen = await versionManager.getMaxSeenVersion(channel);
127
+ if (maxSeen &&
128
+ versionManager.shouldBlockDowngrade(maxSeen, newBundle.version)) {
129
+ throw new ValidationError(ErrorCode.VERSION_DOWNGRADE, `Cannot apply ${newBundle.version} - device has previously applied ${maxSeen} on channel ${channel}`);
102
130
  }
103
131
  // Verify bundle integrity
104
132
  if (!newBundle.verified) {
@@ -291,4 +319,3 @@ export class UpdateManager {
291
319
  this.currentState = null;
292
320
  }
293
321
  }
294
- //# sourceMappingURL=update-manager.js.map
@@ -1,5 +1,4 @@
1
1
  import type { LatestVersion } from '../definitions';
2
- import type { DeviceInfo, UpdateCheckResponse } from '../firestore/schema';
3
2
  /**
4
3
  * Manages version checking and comparison
5
4
  */
@@ -11,8 +10,6 @@ export declare class VersionManager {
11
10
  private readonly securityValidator;
12
11
  private preferences;
13
12
  private memoryCache;
14
- private firestoreClient;
15
- private manifestReader;
16
13
  constructor();
17
14
  /**
18
15
  * Compare two semantic versions
@@ -30,15 +27,6 @@ export declare class VersionManager {
30
27
  * Initialize the version manager
31
28
  */
32
29
  initialize(): Promise<void>;
33
- /**
34
- * Check for updates from Firestore backend
35
- * Uses ManifestReader for efficient single-document reads
36
- */
37
- checkForUpdatesFromFirestore(currentVersion: string, deviceInfo: DeviceInfo): Promise<UpdateCheckResponse | null>;
38
- /**
39
- * Get the appropriate update checker based on backend type
40
- */
41
- checkForUpdatesAuto(currentVersion: string, deviceInfo?: DeviceInfo): Promise<LatestVersion | UpdateCheckResponse | null>;
42
30
  /**
43
31
  * Check for latest version from server
44
32
  */
@@ -93,4 +81,13 @@ export declare class VersionManager {
93
81
  * Check if downgrade protection should block update
94
82
  */
95
83
  shouldBlockDowngrade(currentVersion: string, newVersion: string): boolean;
84
+ /**
85
+ * Persisted high-water-mark version per channel. Survives reinstalls that
86
+ * leave Preferences intact; protects against an attacker feeding an older
87
+ * (and potentially vulnerable) bundle even after a full cache wipe of
88
+ * downloaded bundles. Paired with `rememberAppliedVersion`.
89
+ */
90
+ private maxVersionKey;
91
+ getMaxSeenVersion(channel: string): Promise<string | null>;
92
+ rememberAppliedVersion(channel: string, version: string): Promise<void>;
96
93
  }