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.
- package/Readme.md +35 -9
- package/android/manifest-additions.xml +28 -0
- package/android/src/main/AndroidManifest.xml +3 -7
- package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundNotificationManager.kt +21 -13
- package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundUpdatePlugin.kt +15 -0
- package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundUpdateWorker.kt +23 -7
- package/android/src/main/java/com/aoneahsan/nativeupdate/LiveUpdatePlugin.kt +346 -31
- package/android/src/main/java/com/aoneahsan/nativeupdate/NativeUpdatePlugin.kt +32 -34
- package/android/src/main/java/com/aoneahsan/nativeupdate/NotificationActionReceiver.kt +10 -1
- package/android/src/main/java/com/aoneahsan/nativeupdate/SecurityManager.kt +18 -18
- package/cli/AGENTS.md +43 -0
- package/cli/CLAUDE.md +65 -0
- package/cli/commands/backend-create.js +56 -15
- package/cli/commands/bundle-create.js +39 -7
- package/cli/commands/monitor.js +72 -51
- package/cli/index.js +10 -24
- package/cli/package.json +11 -3
- package/dist/esm/app-review/app-review-manager.js +0 -1
- package/dist/esm/app-review/index.js +0 -1
- package/dist/esm/app-review/platform-review-handler.js +0 -1
- package/dist/esm/app-review/review-conditions-checker.js +0 -1
- package/dist/esm/app-review/review-rate-limiter.js +0 -1
- package/dist/esm/app-review/types.js +0 -1
- package/dist/esm/app-update/app-update-checker.js +0 -1
- package/dist/esm/app-update/app-update-installer.d.ts +1 -0
- package/dist/esm/app-update/app-update-installer.js +19 -8
- package/dist/esm/app-update/app-update-manager.js +0 -1
- package/dist/esm/app-update/app-update-notifier.js +0 -1
- package/dist/esm/app-update/index.js +0 -1
- package/dist/esm/app-update/platform-app-update.js +12 -3
- package/dist/esm/app-update/types.js +0 -1
- package/dist/esm/background-update/background-scheduler.js +0 -1
- package/dist/esm/background-update/index.js +0 -1
- package/dist/esm/background-update/notification-manager.js +0 -1
- package/dist/esm/config/support.js +0 -1
- package/dist/esm/core/analytics.d.ts +13 -0
- package/dist/esm/core/analytics.js +15 -1
- package/dist/esm/core/cache-manager.js +0 -1
- package/dist/esm/core/config.d.ts +6 -30
- package/dist/esm/core/config.js +1 -8
- package/dist/esm/core/errors.d.ts +1 -0
- package/dist/esm/core/errors.js +1 -1
- package/dist/esm/core/event-emitter.d.ts +0 -3
- package/dist/esm/core/event-emitter.js +8 -3
- package/dist/esm/core/logger.js +0 -1
- package/dist/esm/core/performance.js +0 -1
- package/dist/esm/core/plugin-manager.js +3 -1
- package/dist/esm/core/security.d.ts +22 -3
- package/dist/esm/core/security.js +46 -7
- package/dist/esm/definitions.d.ts +13 -40
- package/dist/esm/definitions.js +0 -1
- package/dist/esm/index.d.ts +3 -4
- package/dist/esm/index.js +3 -3
- package/dist/esm/live-update/bundle-manager.d.ts +8 -0
- package/dist/esm/live-update/bundle-manager.js +20 -1
- package/dist/esm/live-update/certificate-pinning.js +0 -1
- package/dist/esm/live-update/delta-processor.d.ts +1 -1
- package/dist/esm/live-update/delta-processor.js +3 -1
- package/dist/esm/live-update/download-manager.d.ts +36 -5
- package/dist/esm/live-update/download-manager.js +70 -24
- package/dist/esm/live-update/rollout-checker.d.ts +1 -1
- package/dist/esm/live-update/rollout-checker.js +5 -6
- package/dist/esm/live-update/update-manager.d.ts +12 -1
- package/dist/esm/live-update/update-manager.js +38 -11
- package/dist/esm/live-update/version-manager.d.ts +9 -12
- package/dist/esm/live-update/version-manager.js +40 -68
- package/dist/esm/plugin.js +82 -89
- package/dist/esm/security/crypto.js +11 -7
- package/dist/esm/security/validator.js +0 -1
- package/dist/esm/types/rollout.d.ts +85 -0
- package/dist/esm/types/rollout.js +25 -0
- package/dist/esm/web.d.ts +26 -1
- package/dist/esm/web.js +214 -75
- package/dist/plugin.cjs.js +1 -2
- package/dist/plugin.esm.js +1 -2
- package/dist/plugin.js +2 -3
- package/docs/AGENTS.md +38 -0
- package/docs/CHANGELOG.md +275 -0
- package/docs/CLAUDE.md +101 -0
- package/docs/MANUAL-TASKS.md +17 -0
- package/docs/MARKETING_WEBSITE_TRACKER.md +18 -2
- package/docs/MIGRATION.md +170 -0
- package/docs/PACKAGES.md +167 -0
- package/docs/PERFORMANCE.md +110 -0
- package/docs/PROJECT_COMPLETION_TRACKER.md +4 -35
- package/docs/README.md +12 -2
- package/docs/REMAINING_FEATURES.md +123 -76
- package/docs/ROADMAP.md +200 -120
- package/docs/SECURITY-IF-REPO-PUBLIC.md +232 -0
- package/docs/TESTING_REQUIREMENTS.md +1 -112
- package/docs/ai-knowledge-base/00-project-profile.md +38 -0
- package/docs/ai-knowledge-base/01-features.md +46 -0
- package/docs/ai-knowledge-base/02-routes.md +72 -0
- package/docs/ai-knowledge-base/03-tech-stack.md +57 -0
- package/docs/ai-knowledge-base/README.md +23 -0
- package/docs/analytics-tracking.md +37 -0
- package/docs/capacitor-official-plugins.md +46 -0
- package/docs/capawesome-plugins.md +29 -0
- package/docs/deployment/HOSTINGER_DEPLOY.md +370 -0
- package/docs/deployment/PRODUCTION-DEPLOY-CHECKLIST.md +184 -0
- package/docs/docs-site/plan.md +201 -0
- package/docs/docs-site/scope.md +127 -0
- package/docs/docs-site/tracker.json +235 -0
- package/docs/error-handling-tracking.md +37 -0
- package/docs/features/laravel-nova-backend/ASSESSMENT-SUMMARY.md +96 -0
- package/docs/features/laravel-nova-backend/IMPLEMENTATION-PLAN.md +504 -0
- package/docs/features/laravel-nova-backend/progress-tracker.json +184 -0
- package/docs/guides/testing-guide.md +15 -41
- package/docs/package-audit.md +41 -0
- package/docs/play-store/ASO-METADATA.md +5 -0
- package/docs/play-store/DATA-SAFETY.md +5 -0
- package/docs/play-store/DECLARATIONS.md +5 -0
- package/docs/play-store/README.md +27 -0
- package/docs/play-store/RELEASE-NOTES.md +99 -0
- package/docs/play-store/RELEASE-STEPS.md +6 -0
- package/docs/play-store/STORE-LISTING.md +7 -0
- package/docs/play-store/SUBMISSION-CHECKLIST.md +5 -0
- package/docs/production-readiness.md +1 -1
- package/docs/project-audit/2026-05-16/00-FINALIZATION-PLAN.md +123 -0
- package/docs/project-audit/2026-05-16/01-BATCH-PLAN.md +227 -0
- package/docs/project-audit/2026-05-16/02-AUDIT-FINDINGS.md +144 -0
- package/docs/project-audit/2026-05-16/sessions/batch-01.md +42 -0
- package/docs/project-audit/2026-05-16/sessions/batch-02.md +74 -0
- package/docs/project-audit/2026-05-16/sessions/batch-03.md +71 -0
- package/docs/project-audit/2026-05-16/sessions/batch-04.md +88 -0
- package/docs/project-audit/2026-05-16/sessions/batch-05.md +63 -0
- package/docs/project-audit/2026-05-16/sessions/batch-06.md +103 -0
- package/docs/project-audit/2026-05-16/sessions/batch-07.md +61 -0
- package/docs/project-audit/2026-05-16/sessions/batch-08.md +101 -0
- package/docs/project-audit/2026-05-16/sessions/batch-09.md +105 -0
- package/docs/project-audit/2026-05-16/sessions/batch-10.md +102 -0
- package/docs/project-audit/AUDIT-HISTORY.md +74 -0
- package/docs/project-audit/BATCH-PLAN.md +168 -0
- package/docs/project-audit/MARKETING-WEBSITE-FINDINGS-2026-05-11.md +73 -0
- package/docs/project-audit/PRODUCTION-READINESS-AUDIT-2026-05-08.md +275 -0
- package/docs/project-finalization/00-tracker.json +60 -0
- package/docs/project-knowledge-base/01-system-overview.md +227 -0
- package/docs/project-knowledge-base/02-routes-pages-forms-users.md +352 -0
- package/docs/project-knowledge-base/03-tech-stack-modules-services.md +379 -0
- package/docs/project-knowledge-base/04-data-models-integrations.md +244 -0
- package/docs/project-knowledge-base/05-docs-corpus-inventory.md +256 -0
- package/docs/project-knowledge-base/06-operations-testing-legal-content.md +208 -0
- package/docs/project-knowledge-base/README.md +103 -0
- package/docs/project-profiles/native-update-capacitor-update-platform-project-profile-last-updated-2026-05-19.md +81 -0
- package/docs/rules-tracker.md +102 -0
- package/docs/security/PRE-LAUNCH-PENTEST-CHECKLIST.md +116 -0
- package/docs/seo-aeo-rules.json +3043 -0
- package/docs/social-media-marketing/README.md +81 -0
- package/docs/social-media-marketing/posts-batch-01.md +613 -0
- package/docs/social-media-marketing/posts-batch-02.md +528 -0
- package/docs/social-media-marketing/whatsapp-messages-batch-01.md +458 -0
- package/docs/social-media-marketing/whatsapp-status-batch-01.md +355 -0
- package/docs/tracking/finalization-2026-05-16-tracker.json +346 -0
- package/docs/tracking/full-audit-tracker.json +121 -0
- package/docs/tracking/production-readiness-audit-tracker.json +429 -0
- package/docs/tracking/seo-checklist-tracker.json +333 -0
- package/ios/Plugin/BackgroundUpdate/BackgroundUpdatePlugin.swift +66 -9
- package/ios/Plugin/LiveUpdate/LiveUpdatePlugin.swift +306 -52
- package/ios/Plugin/NativeUpdatePlugin.swift +22 -9
- package/ios/Plugin/Security/SecurityManager.swift +13 -14
- package/package.json +37 -38
- package/cli/node_modules/.yarn-integrity +0 -16
- package/cli/node_modules/commander/LICENSE +0 -22
- package/cli/node_modules/commander/Readme.md +0 -1148
- package/cli/node_modules/commander/esm.mjs +0 -16
- package/cli/node_modules/commander/index.js +0 -26
- package/cli/node_modules/commander/lib/argument.js +0 -145
- package/cli/node_modules/commander/lib/command.js +0 -2179
- package/cli/node_modules/commander/lib/error.js +0 -43
- package/cli/node_modules/commander/lib/help.js +0 -462
- package/cli/node_modules/commander/lib/option.js +0 -329
- package/cli/node_modules/commander/lib/suggestSimilar.js +0 -100
- package/cli/node_modules/commander/package-support.json +0 -16
- package/cli/node_modules/commander/package.json +0 -80
- package/cli/node_modules/commander/typings/esm.d.mts +0 -3
- package/cli/node_modules/commander/typings/index.d.ts +0 -884
- package/cli/yarn.lock +0 -8
- package/dist/esm/__tests__/bundle-manager.test.d.ts +0 -1
- package/dist/esm/__tests__/bundle-manager.test.js +0 -151
- package/dist/esm/__tests__/bundle-manager.test.js.map +0 -1
- package/dist/esm/__tests__/config.test.d.ts +0 -1
- package/dist/esm/__tests__/config.test.js +0 -70
- package/dist/esm/__tests__/config.test.js.map +0 -1
- package/dist/esm/__tests__/delta-processor.test.d.ts +0 -1
- package/dist/esm/__tests__/delta-processor.test.js +0 -77
- package/dist/esm/__tests__/delta-processor.test.js.map +0 -1
- package/dist/esm/__tests__/firestore-schema.test.d.ts +0 -1
- package/dist/esm/__tests__/firestore-schema.test.js +0 -74
- package/dist/esm/__tests__/firestore-schema.test.js.map +0 -1
- package/dist/esm/__tests__/integration.test.d.ts +0 -1
- package/dist/esm/__tests__/integration.test.js +0 -78
- package/dist/esm/__tests__/integration.test.js.map +0 -1
- package/dist/esm/__tests__/manifest-reader.test.d.ts +0 -1
- package/dist/esm/__tests__/manifest-reader.test.js +0 -271
- package/dist/esm/__tests__/manifest-reader.test.js.map +0 -1
- package/dist/esm/__tests__/rollout-checker.test.d.ts +0 -1
- package/dist/esm/__tests__/rollout-checker.test.js +0 -210
- package/dist/esm/__tests__/rollout-checker.test.js.map +0 -1
- package/dist/esm/__tests__/security.test.d.ts +0 -1
- package/dist/esm/__tests__/security.test.js +0 -54
- package/dist/esm/__tests__/security.test.js.map +0 -1
- package/dist/esm/__tests__/version-manager.test.d.ts +0 -1
- package/dist/esm/__tests__/version-manager.test.js +0 -45
- package/dist/esm/__tests__/version-manager.test.js.map +0 -1
- package/dist/esm/app-review/app-review-manager.js.map +0 -1
- package/dist/esm/app-review/index.js.map +0 -1
- package/dist/esm/app-review/platform-review-handler.js.map +0 -1
- package/dist/esm/app-review/review-conditions-checker.js.map +0 -1
- package/dist/esm/app-review/review-rate-limiter.js.map +0 -1
- package/dist/esm/app-review/types.js.map +0 -1
- package/dist/esm/app-update/app-update-checker.js.map +0 -1
- package/dist/esm/app-update/app-update-installer.js.map +0 -1
- package/dist/esm/app-update/app-update-manager.js.map +0 -1
- package/dist/esm/app-update/app-update-notifier.js.map +0 -1
- package/dist/esm/app-update/index.js.map +0 -1
- package/dist/esm/app-update/platform-app-update.js.map +0 -1
- package/dist/esm/app-update/types.js.map +0 -1
- package/dist/esm/background-update/background-scheduler.js.map +0 -1
- package/dist/esm/background-update/index.js.map +0 -1
- package/dist/esm/background-update/notification-manager.js.map +0 -1
- package/dist/esm/config/support.js.map +0 -1
- package/dist/esm/core/analytics.js.map +0 -1
- package/dist/esm/core/cache-manager.js.map +0 -1
- package/dist/esm/core/config.js.map +0 -1
- package/dist/esm/core/errors.js.map +0 -1
- package/dist/esm/core/event-emitter.js.map +0 -1
- package/dist/esm/core/logger.js.map +0 -1
- package/dist/esm/core/performance.js.map +0 -1
- package/dist/esm/core/plugin-manager.js.map +0 -1
- package/dist/esm/core/security.js.map +0 -1
- package/dist/esm/definitions.js.map +0 -1
- package/dist/esm/firestore/firestore-client.d.ts +0 -109
- package/dist/esm/firestore/firestore-client.js +0 -264
- package/dist/esm/firestore/firestore-client.js.map +0 -1
- package/dist/esm/firestore/index.d.ts +0 -11
- package/dist/esm/firestore/index.js +0 -11
- package/dist/esm/firestore/index.js.map +0 -1
- package/dist/esm/firestore/manifest-reader.d.ts +0 -87
- package/dist/esm/firestore/manifest-reader.js +0 -294
- package/dist/esm/firestore/manifest-reader.js.map +0 -1
- package/dist/esm/firestore/schema.d.ts +0 -507
- package/dist/esm/firestore/schema.js +0 -73
- package/dist/esm/firestore/schema.js.map +0 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/live-update/bundle-manager.js.map +0 -1
- package/dist/esm/live-update/certificate-pinning.js.map +0 -1
- package/dist/esm/live-update/delta-processor.js.map +0 -1
- package/dist/esm/live-update/download-manager.js.map +0 -1
- package/dist/esm/live-update/rollout-checker.js.map +0 -1
- package/dist/esm/live-update/update-manager.js.map +0 -1
- package/dist/esm/live-update/version-manager.js.map +0 -1
- package/dist/esm/plugin.js.map +0 -1
- package/dist/esm/security/crypto.js.map +0 -1
- package/dist/esm/security/validator.js.map +0 -1
- package/dist/esm/web.js.map +0 -1
- package/dist/plugin.cjs.js.map +0 -1
- package/dist/plugin.esm.js.map +0 -1
- package/dist/plugin.js.map +0 -1
- package/docs/FIREBASE_INTEGRATION_TRACKER.md +0 -321
- package/docs/FIREBASE_QUERIES_AND_INDEXES_AUDIT.md +0 -221
- package/docs/examples/firebase-backend-example.md +0 -27
- package/docs/guides/no-cost-backend-implementation-plan.md +0 -77
- package/docs/guides/no-cost-firestore-google-drive-backend.md +0 -60
- package/docs/play-console-rejection-rules.json +0 -428
- /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
|
package/dist/esm/plugin.js
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
141
|
+
size: validatedBlob.size,
|
|
138
142
|
status: BundleStatus.READY,
|
|
139
143
|
checksum: options.checksum,
|
|
140
144
|
signature: options.signature,
|
|
141
|
-
verified:
|
|
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
|
-
|
|
211
|
-
|
|
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
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
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
|
|
223
|
-
signatureValid
|
|
224
|
-
sizeValid
|
|
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
|
-
|
|
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
|
-
|
|
292
|
-
|
|
319
|
+
if (!data.available) {
|
|
320
|
+
return {
|
|
321
|
+
available: false,
|
|
322
|
+
currentVersion,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
293
325
|
return {
|
|
294
|
-
available:
|
|
326
|
+
available: true,
|
|
295
327
|
currentVersion,
|
|
296
|
-
latestVersion:
|
|
297
|
-
url:
|
|
328
|
+
latestVersion: data.version,
|
|
329
|
+
url: data.downloadUrl,
|
|
298
330
|
mandatory: data.mandatory,
|
|
299
|
-
notes: data.
|
|
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
|
|
341
|
-
|
|
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
|
-
//
|
|
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(
|
|
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
|
-
|
|
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
|
|
@@ -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;
|