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
package/dist/esm/web.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { WebPlugin } from '@capacitor/core';
|
|
2
2
|
import { SyncStatus, BundleStatus, UpdateErrorCode } from './definitions';
|
|
3
3
|
import { ConfigManager } from './core/config';
|
|
4
|
+
import { Logger } from './core/logger';
|
|
5
|
+
// Module-level logger so we never leak sensitive headers / tokens
|
|
6
|
+
// through raw console.* calls. The Logger sanitises payloads (paths,
|
|
7
|
+
// keys, secrets) and respects the runtime log-level switch.
|
|
8
|
+
const logger = new Logger('NativeUpdateWeb');
|
|
4
9
|
export class NativeUpdateWeb extends WebPlugin {
|
|
5
10
|
constructor() {
|
|
6
11
|
super();
|
|
@@ -17,6 +22,10 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
17
22
|
};
|
|
18
23
|
this.backgroundCheckInterval = null;
|
|
19
24
|
this.isConfigured = false;
|
|
25
|
+
// Single-slot cache of the most recently downloaded bundle's bytes so a
|
|
26
|
+
// follow-up validateUpdate() on the same URL doesn't re-download the
|
|
27
|
+
// whole bundle just to hash it. Overwritten on each download().
|
|
28
|
+
this.downloadedBundleBytes = null;
|
|
20
29
|
this.configManager = ConfigManager.getInstance();
|
|
21
30
|
this.loadStoredData();
|
|
22
31
|
this.incrementLaunchCount();
|
|
@@ -84,8 +93,7 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
84
93
|
}
|
|
85
94
|
}
|
|
86
95
|
async download(options) {
|
|
87
|
-
var _a;
|
|
88
|
-
// Validate URL
|
|
96
|
+
var _a, _b;
|
|
89
97
|
if (!options.url.startsWith('https://') &&
|
|
90
98
|
((_a = this.config.security) === null || _a === void 0 ? void 0 : _a.enforceHttps) !== false) {
|
|
91
99
|
throw this.createError(UpdateErrorCode.INSECURE_URL, 'Download URL must use HTTPS');
|
|
@@ -96,44 +104,82 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
96
104
|
version: options.version,
|
|
97
105
|
path: options.url,
|
|
98
106
|
downloadTime: Date.now(),
|
|
99
|
-
size: 0,
|
|
107
|
+
size: 0,
|
|
100
108
|
status: BundleStatus.DOWNLOADING,
|
|
101
109
|
checksum: options.checksum,
|
|
102
110
|
signature: options.signature,
|
|
103
111
|
verified: false,
|
|
104
112
|
};
|
|
105
113
|
this.bundles.set(bundleId, bundle);
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
// Actually fetch the bundle so we hash REAL bytes, not the bundleId
|
|
115
|
+
// string. This was a P0: validateChecksum used to receive `bundleId`
|
|
116
|
+
// and silently fail (or pass) regardless of the bundle contents.
|
|
117
|
+
let bundleBytes;
|
|
118
|
+
try {
|
|
119
|
+
const response = await fetch(options.url);
|
|
120
|
+
if (!response.ok) {
|
|
121
|
+
throw new Error(`HTTP ${response.status}`);
|
|
122
|
+
}
|
|
123
|
+
const reader = (_b = response.body) === null || _b === void 0 ? void 0 : _b.getReader();
|
|
124
|
+
const contentLength = Number(response.headers.get('content-length') || 0);
|
|
125
|
+
if (reader) {
|
|
126
|
+
const chunks = [];
|
|
127
|
+
let received = 0;
|
|
128
|
+
while (true) {
|
|
129
|
+
const { done, value } = await reader.read();
|
|
130
|
+
if (done)
|
|
131
|
+
break;
|
|
132
|
+
chunks.push(value);
|
|
133
|
+
received += value.length;
|
|
134
|
+
if (contentLength > 0) {
|
|
135
|
+
await this.notifyListeners('downloadProgress', {
|
|
136
|
+
percent: Math.round((received / contentLength) * 100),
|
|
137
|
+
bytesDownloaded: received,
|
|
138
|
+
totalBytes: contentLength,
|
|
139
|
+
bundleId,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const combined = new Uint8Array(received);
|
|
144
|
+
let offset = 0;
|
|
145
|
+
for (const chunk of chunks) {
|
|
146
|
+
combined.set(chunk, offset);
|
|
147
|
+
offset += chunk.length;
|
|
148
|
+
}
|
|
149
|
+
bundleBytes = combined.buffer;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
bundleBytes = await response.arrayBuffer();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
bundle.status = BundleStatus.FAILED;
|
|
157
|
+
this.bundles.delete(bundleId);
|
|
158
|
+
throw this.createError(UpdateErrorCode.NETWORK_ERROR, `Failed to download bundle: ${error.message}`);
|
|
115
159
|
}
|
|
116
|
-
|
|
117
|
-
let verified =
|
|
160
|
+
bundle.size = bundleBytes.byteLength;
|
|
161
|
+
let verified = false;
|
|
118
162
|
if (options.checksum) {
|
|
119
|
-
const checksumValid = await this.validateChecksum(
|
|
163
|
+
const checksumValid = await this.validateChecksum(bundleBytes, options.checksum);
|
|
120
164
|
if (!checksumValid) {
|
|
121
165
|
bundle.status = BundleStatus.FAILED;
|
|
122
166
|
this.bundles.delete(bundleId);
|
|
123
167
|
throw this.createError(UpdateErrorCode.CHECKSUM_ERROR, 'Bundle checksum verification failed');
|
|
124
168
|
}
|
|
169
|
+
verified = true;
|
|
125
170
|
}
|
|
126
171
|
if (options.signature) {
|
|
127
|
-
const signatureValid = await this.validateSignature(
|
|
172
|
+
const signatureValid = await this.validateSignature(bundleBytes, options.signature);
|
|
128
173
|
if (!signatureValid) {
|
|
129
174
|
bundle.status = BundleStatus.FAILED;
|
|
130
175
|
this.bundles.delete(bundleId);
|
|
131
176
|
throw this.createError(UpdateErrorCode.SIGNATURE_ERROR, 'Bundle signature verification failed');
|
|
132
177
|
}
|
|
178
|
+
verified = true;
|
|
133
179
|
}
|
|
134
|
-
// Update bundle status
|
|
135
180
|
bundle.status = BundleStatus.READY;
|
|
136
181
|
bundle.verified = verified;
|
|
182
|
+
this.downloadedBundleBytes = { path: options.url, bytes: bundleBytes };
|
|
137
183
|
this.saveStoredData();
|
|
138
184
|
await this.notifyListeners('updateStateChanged', {
|
|
139
185
|
status: bundle.status,
|
|
@@ -146,24 +192,25 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
146
192
|
if (!this.bundles.has(bundle.bundleId)) {
|
|
147
193
|
throw this.createError(UpdateErrorCode.UNKNOWN_ERROR, 'Bundle not found');
|
|
148
194
|
}
|
|
195
|
+
const stored = this.bundles.get(bundle.bundleId);
|
|
196
|
+
if (!stored.verified) {
|
|
197
|
+
throw this.createError(UpdateErrorCode.SIGNATURE_ERROR, `Refusing to activate unverified bundle ${bundle.bundleId}`);
|
|
198
|
+
}
|
|
149
199
|
const previousBundle = this.currentBundle;
|
|
150
|
-
this.currentBundle =
|
|
200
|
+
this.currentBundle = stored;
|
|
151
201
|
this.currentBundle.status = BundleStatus.ACTIVE;
|
|
152
|
-
// Update previous bundle status
|
|
153
202
|
if (previousBundle) {
|
|
154
203
|
previousBundle.status = BundleStatus.READY;
|
|
155
204
|
}
|
|
156
205
|
this.saveStoredData();
|
|
157
206
|
}
|
|
158
207
|
async reload() {
|
|
159
|
-
// console.log('Web: Reloading application...');
|
|
160
208
|
// In web, we can reload the page
|
|
161
209
|
if (typeof window !== 'undefined') {
|
|
162
210
|
window.location.reload();
|
|
163
211
|
}
|
|
164
212
|
}
|
|
165
213
|
async reset() {
|
|
166
|
-
// console.log('Web: Resetting to original bundle...');
|
|
167
214
|
this.currentBundle = null;
|
|
168
215
|
this.bundles.clear();
|
|
169
216
|
this.saveStoredData();
|
|
@@ -197,7 +244,6 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
197
244
|
this.saveStoredData();
|
|
198
245
|
}
|
|
199
246
|
async notifyAppReady() {
|
|
200
|
-
// console.log('Web: App ready notification received');
|
|
201
247
|
if (this.currentBundle) {
|
|
202
248
|
this.currentBundle.verified = true;
|
|
203
249
|
this.saveStoredData();
|
|
@@ -226,25 +272,49 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
226
272
|
this.config.liveUpdate.channel = channel;
|
|
227
273
|
this.saveConfiguration();
|
|
228
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* @deprecated since v2. See NativeUpdatePluginWeb.setUpdateUrl in plugin.ts.
|
|
277
|
+
* Update URL is locked after initialize(); this method is a no-op.
|
|
278
|
+
*/
|
|
229
279
|
async setUpdateUrl(url) {
|
|
280
|
+
void url;
|
|
281
|
+
logger.warn('setUpdateUrl() is deprecated and ignored. Update URL is locked after initialize().');
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
async validateUpdate(options) {
|
|
230
285
|
var _a;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
286
|
+
// bundlePath on web is the bundle URL (we don't persist bytes locally
|
|
287
|
+
// here). Reuse the bytes from the last download() when the URL matches;
|
|
288
|
+
// only re-fetch when validating a bundle we never downloaded.
|
|
289
|
+
let bundleBytes;
|
|
290
|
+
if (((_a = this.downloadedBundleBytes) === null || _a === void 0 ? void 0 : _a.path) === options.bundlePath) {
|
|
291
|
+
bundleBytes = this.downloadedBundleBytes.bytes;
|
|
234
292
|
}
|
|
235
|
-
|
|
236
|
-
|
|
293
|
+
else {
|
|
294
|
+
try {
|
|
295
|
+
const response = await fetch(options.bundlePath);
|
|
296
|
+
if (!response.ok) {
|
|
297
|
+
return {
|
|
298
|
+
isValid: false,
|
|
299
|
+
error: `Failed to fetch bundle for validation: HTTP ${response.status}`,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
bundleBytes = await response.arrayBuffer();
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
return {
|
|
306
|
+
isValid: false,
|
|
307
|
+
error: `Failed to fetch bundle for validation: ${error.message}`,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
237
310
|
}
|
|
238
|
-
this.
|
|
239
|
-
this.saveConfiguration();
|
|
240
|
-
}
|
|
241
|
-
async validateUpdate(options) {
|
|
242
|
-
// Simulate validation
|
|
243
|
-
const checksumValid = await this.validateChecksum(options.bundlePath, options.checksum);
|
|
311
|
+
const checksumValid = await this.validateChecksum(bundleBytes, options.checksum);
|
|
244
312
|
const signatureValid = options.signature
|
|
245
|
-
? await this.validateSignature(
|
|
313
|
+
? await this.validateSignature(bundleBytes, options.signature)
|
|
314
|
+
: true;
|
|
315
|
+
const sizeValid = options.maxSize
|
|
316
|
+
? bundleBytes.byteLength <= options.maxSize
|
|
246
317
|
: true;
|
|
247
|
-
const sizeValid = options.maxSize ? true : true; // Would check actual size
|
|
248
318
|
const isValid = checksumValid && signatureValid && sizeValid;
|
|
249
319
|
return {
|
|
250
320
|
isValid,
|
|
@@ -349,7 +419,6 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
349
419
|
* App Update Methods
|
|
350
420
|
*/
|
|
351
421
|
async getAppUpdateInfo() {
|
|
352
|
-
// console.log('Web: App updates not supported on web platform');
|
|
353
422
|
return {
|
|
354
423
|
updateAvailable: false,
|
|
355
424
|
currentVersion: '1.0.0',
|
|
@@ -381,6 +450,7 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
381
450
|
* App Review Methods
|
|
382
451
|
*/
|
|
383
452
|
async requestReview() {
|
|
453
|
+
var _a;
|
|
384
454
|
const canRequest = await this.canRequestReview();
|
|
385
455
|
if (!canRequest.canRequest) {
|
|
386
456
|
return {
|
|
@@ -388,10 +458,9 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
388
458
|
error: canRequest.reason,
|
|
389
459
|
};
|
|
390
460
|
}
|
|
391
|
-
// console.log('Web: Showing review request fallback');
|
|
392
461
|
// Update last request time
|
|
393
462
|
this.lastReviewRequest = Date.now();
|
|
394
|
-
|
|
463
|
+
(_a = this.getLocalStorage()) === null || _a === void 0 ? void 0 : _a.setItem('native-update-last-review', this.lastReviewRequest.toString());
|
|
395
464
|
// In web, we could show a custom modal or redirect to a review page
|
|
396
465
|
// Note: Web platform cannot display native in-app review dialogs
|
|
397
466
|
// We can offer a fallback redirect, but `displayed` reflects native capability
|
|
@@ -447,7 +516,6 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
447
516
|
* Background Update Methods
|
|
448
517
|
*/
|
|
449
518
|
async enableBackgroundUpdates(config) {
|
|
450
|
-
// console.log('Web: Enabling background updates', config);
|
|
451
519
|
if (!this.config.backgroundUpdate) {
|
|
452
520
|
this.config.backgroundUpdate = config;
|
|
453
521
|
}
|
|
@@ -464,7 +532,6 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
464
532
|
this.saveConfiguration();
|
|
465
533
|
}
|
|
466
534
|
async disableBackgroundUpdates() {
|
|
467
|
-
// console.log('Web: Disabling background updates');
|
|
468
535
|
if (this.backgroundCheckInterval) {
|
|
469
536
|
clearInterval(this.backgroundCheckInterval);
|
|
470
537
|
this.backgroundCheckInterval = null;
|
|
@@ -481,7 +548,6 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
481
548
|
return Object.assign({}, this.backgroundUpdateStatus);
|
|
482
549
|
}
|
|
483
550
|
async scheduleBackgroundCheck(interval) {
|
|
484
|
-
// console.log('Web: Scheduling background check with interval', interval);
|
|
485
551
|
if (this.backgroundCheckInterval) {
|
|
486
552
|
clearInterval(this.backgroundCheckInterval);
|
|
487
553
|
}
|
|
@@ -494,7 +560,6 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
494
560
|
this.backgroundUpdateStatus.nextCheckTime = Date.now() + interval;
|
|
495
561
|
}
|
|
496
562
|
async triggerBackgroundCheck() {
|
|
497
|
-
// console.log('Web: Triggering background check');
|
|
498
563
|
if (!this.backgroundUpdateStatus.enabled) {
|
|
499
564
|
return {
|
|
500
565
|
success: false,
|
|
@@ -546,7 +611,6 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
546
611
|
}
|
|
547
612
|
}
|
|
548
613
|
async setNotificationPreferences(preferences) {
|
|
549
|
-
// console.log('Web: Setting notification preferences', preferences);
|
|
550
614
|
if (!this.config.backgroundUpdate) {
|
|
551
615
|
this.config.backgroundUpdate = {};
|
|
552
616
|
}
|
|
@@ -625,7 +689,7 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
625
689
|
return true;
|
|
626
690
|
}
|
|
627
691
|
catch (error) {
|
|
628
|
-
|
|
692
|
+
logger.error('Failed to send notification', error);
|
|
629
693
|
return false;
|
|
630
694
|
}
|
|
631
695
|
}
|
|
@@ -657,8 +721,7 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
657
721
|
*/
|
|
658
722
|
async validateChecksum(data, expectedChecksum) {
|
|
659
723
|
if (!expectedChecksum) {
|
|
660
|
-
|
|
661
|
-
return true;
|
|
724
|
+
throw new Error('Checksum is required but was not provided');
|
|
662
725
|
}
|
|
663
726
|
try {
|
|
664
727
|
let dataBuffer;
|
|
@@ -677,7 +740,7 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
677
740
|
// Compare checksums (case-insensitive)
|
|
678
741
|
const isValid = hashHex.toLowerCase() === expectedChecksum.toLowerCase();
|
|
679
742
|
if (!isValid) {
|
|
680
|
-
|
|
743
|
+
logger.warn('Checksum validation failed', {
|
|
681
744
|
expected: expectedChecksum.toLowerCase(),
|
|
682
745
|
actual: hashHex.toLowerCase(),
|
|
683
746
|
});
|
|
@@ -685,28 +748,34 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
685
748
|
return isValid;
|
|
686
749
|
}
|
|
687
750
|
catch (error) {
|
|
688
|
-
|
|
751
|
+
logger.error('Checksum validation error', error);
|
|
689
752
|
return false;
|
|
690
753
|
}
|
|
691
754
|
}
|
|
692
755
|
/**
|
|
693
|
-
* Validate digital signature using RSA-PSS or ECDSA
|
|
756
|
+
* Validate digital signature using RSA-PSS or ECDSA.
|
|
757
|
+
*
|
|
758
|
+
* Fails closed: if the host app configured a public key or
|
|
759
|
+
* requireSignature, a missing signature throws. Silent-pass was a P0 gap.
|
|
760
|
+
*
|
|
694
761
|
* @param data - The data that was signed (as string path or ArrayBuffer)
|
|
695
762
|
* @param signature - The signature to verify (base64 encoded)
|
|
696
763
|
*/
|
|
697
764
|
async validateSignature(data, signature) {
|
|
698
|
-
var _a;
|
|
765
|
+
var _a, _b;
|
|
766
|
+
const publicKeyPem = ((_a = this.config.liveUpdate) === null || _a === void 0 ? void 0 : _a.publicKey) ||
|
|
767
|
+
this.config.publicKey;
|
|
768
|
+
const requireSignature = ((_b = this.config.liveUpdate) === null || _b === void 0 ? void 0 : _b.requireSignature) ||
|
|
769
|
+
this.config.requireSignature === true;
|
|
770
|
+
const signatureRequired = !!publicKeyPem || requireSignature;
|
|
699
771
|
if (!signature) {
|
|
700
|
-
|
|
772
|
+
if (signatureRequired) {
|
|
773
|
+
throw new Error('Signature is required but was not provided');
|
|
774
|
+
}
|
|
701
775
|
return true;
|
|
702
776
|
}
|
|
703
|
-
// Get public key from config (can be in liveUpdate.publicKey or top-level config)
|
|
704
|
-
const publicKeyPem = ((_a = this.config.liveUpdate) === null || _a === void 0 ? void 0 : _a.publicKey) ||
|
|
705
|
-
this.config.publicKey;
|
|
706
777
|
if (!publicKeyPem) {
|
|
707
|
-
|
|
708
|
-
console.warn('No public key configured for signature validation');
|
|
709
|
-
return true;
|
|
778
|
+
throw new Error('Public key not configured for signature validation');
|
|
710
779
|
}
|
|
711
780
|
try {
|
|
712
781
|
let dataBuffer;
|
|
@@ -726,12 +795,12 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
726
795
|
saltLength: 32,
|
|
727
796
|
}, publicKey, signatureBuffer, dataBuffer);
|
|
728
797
|
if (!isValid) {
|
|
729
|
-
|
|
798
|
+
logger.warn('Signature validation failed');
|
|
730
799
|
}
|
|
731
800
|
return isValid;
|
|
732
801
|
}
|
|
733
802
|
catch (error) {
|
|
734
|
-
|
|
803
|
+
logger.error('Signature validation error', error);
|
|
735
804
|
// Try ECDSA as fallback
|
|
736
805
|
return this.validateSignatureECDSA(data, signature);
|
|
737
806
|
}
|
|
@@ -744,7 +813,10 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
744
813
|
const publicKeyPem = ((_a = this.config.liveUpdate) === null || _a === void 0 ? void 0 : _a.publicKey) ||
|
|
745
814
|
this.config.publicKey;
|
|
746
815
|
if (!publicKeyPem) {
|
|
747
|
-
|
|
816
|
+
// ECDSA is only reached as a fallback from validateSignature, which
|
|
817
|
+
// already confirmed a key is configured. Reaching here without one
|
|
818
|
+
// is a logic error — fail closed rather than silently passing.
|
|
819
|
+
return false;
|
|
748
820
|
}
|
|
749
821
|
try {
|
|
750
822
|
let dataBuffer;
|
|
@@ -763,7 +835,7 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
763
835
|
return isValid;
|
|
764
836
|
}
|
|
765
837
|
catch (error) {
|
|
766
|
-
|
|
838
|
+
logger.error('ECDSA signature validation error', error);
|
|
767
839
|
return false;
|
|
768
840
|
}
|
|
769
841
|
}
|
|
@@ -796,55 +868,109 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
796
868
|
namedCurve: 'P-256',
|
|
797
869
|
}, false, ['verify']);
|
|
798
870
|
}
|
|
871
|
+
/**
|
|
872
|
+
* Safe localStorage accessor. Returns `null` in environments where
|
|
873
|
+
* `localStorage` isn't available — SSR, Node-only test runners that
|
|
874
|
+
* stub `window` without storage, or sandboxed iframes. All callers
|
|
875
|
+
* MUST tolerate a `null` return and skip persistence.
|
|
876
|
+
*/
|
|
877
|
+
getLocalStorage() {
|
|
878
|
+
try {
|
|
879
|
+
if (typeof window === 'undefined')
|
|
880
|
+
return null;
|
|
881
|
+
if (typeof window.localStorage === 'undefined')
|
|
882
|
+
return null;
|
|
883
|
+
return window.localStorage;
|
|
884
|
+
}
|
|
885
|
+
catch (_a) {
|
|
886
|
+
// Some browsers throw on localStorage access in private mode.
|
|
887
|
+
return null;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
799
890
|
loadStoredData() {
|
|
800
|
-
|
|
801
|
-
|
|
891
|
+
const storage = this.getLocalStorage();
|
|
892
|
+
if (!storage)
|
|
893
|
+
return;
|
|
894
|
+
// Load configuration — note that any previously-persisted copy was
|
|
895
|
+
// pre-v2 and may contain an apiKey/publicKey in plaintext. We still
|
|
896
|
+
// read it to preserve non-sensitive state (channel, etc.) but secrets
|
|
897
|
+
// are stripped on save; an old cache wipes itself on first save.
|
|
898
|
+
const storedConfig = storage.getItem('native-update-config');
|
|
802
899
|
if (storedConfig) {
|
|
803
900
|
this.config = JSON.parse(storedConfig);
|
|
804
901
|
}
|
|
805
902
|
// Load bundles
|
|
806
|
-
const storedBundles =
|
|
903
|
+
const storedBundles = storage.getItem('native-update-bundles');
|
|
807
904
|
if (storedBundles) {
|
|
808
905
|
const bundlesArray = JSON.parse(storedBundles);
|
|
809
906
|
bundlesArray.forEach((bundle) => this.bundles.set(bundle.bundleId, bundle));
|
|
810
907
|
}
|
|
811
908
|
// Load current bundle
|
|
812
|
-
const storedCurrent =
|
|
909
|
+
const storedCurrent = storage.getItem('native-update-current');
|
|
813
910
|
if (storedCurrent) {
|
|
814
911
|
this.currentBundle = JSON.parse(storedCurrent);
|
|
815
912
|
}
|
|
816
913
|
// Load last review request time
|
|
817
|
-
const storedLastReview =
|
|
914
|
+
const storedLastReview = storage.getItem('native-update-last-review');
|
|
818
915
|
if (storedLastReview) {
|
|
819
916
|
this.lastReviewRequest = parseInt(storedLastReview, 10);
|
|
820
917
|
}
|
|
821
918
|
// Load launch count
|
|
822
|
-
const storedLaunchCount =
|
|
919
|
+
const storedLaunchCount = storage.getItem('native-update-launch-count');
|
|
823
920
|
if (storedLaunchCount) {
|
|
824
921
|
this.launchCount = parseInt(storedLaunchCount, 10);
|
|
825
922
|
}
|
|
826
923
|
}
|
|
827
924
|
saveStoredData() {
|
|
828
|
-
|
|
925
|
+
const storage = this.getLocalStorage();
|
|
926
|
+
if (!storage)
|
|
927
|
+
return;
|
|
928
|
+
storage.setItem('native-update-bundles', JSON.stringify(Array.from(this.bundles.values())));
|
|
829
929
|
if (this.currentBundle) {
|
|
830
|
-
|
|
930
|
+
storage.setItem('native-update-current', JSON.stringify(this.currentBundle));
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
sanitizeConfigForPersistence(raw) {
|
|
934
|
+
if (!raw || typeof raw !== 'object')
|
|
935
|
+
return raw;
|
|
936
|
+
const clone = JSON.parse(JSON.stringify(raw));
|
|
937
|
+
for (const path of NativeUpdateWeb.SENSITIVE_CONFIG_PATHS) {
|
|
938
|
+
const segments = path.split('.');
|
|
939
|
+
let cursor = clone;
|
|
940
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
941
|
+
const next = cursor === null || cursor === void 0 ? void 0 : cursor[segments[i]];
|
|
942
|
+
if (next && typeof next === 'object') {
|
|
943
|
+
cursor = next;
|
|
944
|
+
}
|
|
945
|
+
else {
|
|
946
|
+
cursor = undefined;
|
|
947
|
+
break;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
if (cursor) {
|
|
951
|
+
delete cursor[segments[segments.length - 1]];
|
|
952
|
+
}
|
|
831
953
|
}
|
|
954
|
+
return clone;
|
|
832
955
|
}
|
|
833
956
|
saveConfiguration() {
|
|
834
|
-
|
|
957
|
+
var _a;
|
|
958
|
+
(_a = this.getLocalStorage()) === null || _a === void 0 ? void 0 : _a.setItem('native-update-config', JSON.stringify(this.sanitizeConfigForPersistence(this.config)));
|
|
835
959
|
}
|
|
836
960
|
getInstallDate() {
|
|
837
|
-
const
|
|
961
|
+
const storage = this.getLocalStorage();
|
|
962
|
+
const stored = storage === null || storage === void 0 ? void 0 : storage.getItem('native-update-install-date');
|
|
838
963
|
if (stored) {
|
|
839
964
|
return parseInt(stored, 10);
|
|
840
965
|
}
|
|
841
966
|
const now = Date.now();
|
|
842
|
-
|
|
967
|
+
storage === null || storage === void 0 ? void 0 : storage.setItem('native-update-install-date', now.toString());
|
|
843
968
|
return now;
|
|
844
969
|
}
|
|
845
970
|
incrementLaunchCount() {
|
|
971
|
+
var _a;
|
|
846
972
|
this.launchCount++;
|
|
847
|
-
|
|
973
|
+
(_a = this.getLocalStorage()) === null || _a === void 0 ? void 0 : _a.setItem('native-update-launch-count', this.launchCount.toString());
|
|
848
974
|
}
|
|
849
975
|
/**
|
|
850
976
|
* Get configured review URL from plugin config
|
|
@@ -879,11 +1005,24 @@ export class NativeUpdateWeb extends WebPlugin {
|
|
|
879
1005
|
return `https://play.google.com/store/apps/details?id=${packageName}&showAllReviews=true`;
|
|
880
1006
|
}
|
|
881
1007
|
// No review URL configured - return null to indicate review not available
|
|
882
|
-
|
|
1008
|
+
logger.warn('No review URL configured. Set appUpdate.storeUrl.ios, appUpdate.storeUrl.android, ' +
|
|
883
1009
|
'appReview.webReviewUrl, appStoreId, or packageName in plugin config.');
|
|
884
1010
|
return null;
|
|
885
1011
|
}
|
|
886
1012
|
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Strip secrets before persisting config to localStorage. A browser's
|
|
1015
|
+
* localStorage is accessible to any script running in the page (XSS,
|
|
1016
|
+
* compromised deps) — an `apiKey` or `publicKey` sitting there is a
|
|
1017
|
+
* ready-to-use credential. Host apps must pass these on every
|
|
1018
|
+
* `initialize()` call; they are never cached cross-session.
|
|
1019
|
+
*/
|
|
1020
|
+
NativeUpdateWeb.SENSITIVE_CONFIG_PATHS = [
|
|
1021
|
+
'apiKey',
|
|
1022
|
+
'publicKey',
|
|
1023
|
+
'liveUpdate.publicKey',
|
|
1024
|
+
'liveUpdate.apiKey',
|
|
1025
|
+
'security.publicKey',
|
|
1026
|
+
];
|
|
887
1027
|
// Alias for backward compatibility with tests
|
|
888
1028
|
// Already exported above
|
|
889
|
-
//# sourceMappingURL=web.js.map
|