Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[4.1.0] - 2026-09-05
Fixed
- A locally-signed bundle can now actually be uploaded (ISSUE-08).
builds.signatureexisted, the model exposed it and/v1/updates/checkalready served it — but no upload validation accepted a signature and neither store path set one from input. Sonative-update bundle signproduced a detached.sigthat nothing could carry to the server, every build landedsignature: null, and an app configuredrequireSignature: truerejected the download on device, indistinguishable from "no update arrived".POST /apps/{app}/buildsnow accepts an optionalsignatureon both the dashboard and public planes, stores it, and serves it on the update-check.
Added
native-update deploy --signature <file>, and automatic pick-up of the sibling.sigthatbundle signwrites — the documented local-signing flow now works end to end without a new flag to learn.bundle signrecordsbundleSha256in its.sig, anddeployrefuses to upload a signature whose checksum does not cover the bundle being sent. The server cannot detect that mismatch (it holds no public key to check against), so without this guard a stale.sigwould publish a release every device rejects.
Notes
- A caller-supplied signature wins over server-side signing and is never silently replaced — signing a consumer's bundle with the server key would produce a release that fails on device while every server-side check looked healthy.
- The server deliberately does not verify a caller-supplied signature: it never holds that public key. Verification happens on the device, against the key compiled into the binary, where it fails closed.
[4.0.2] - 2026-08-06
Fixed
- Prevent update checks from offering an active bundle whose semantic version is equal to or older than the device's current bundle, with the public TypeScript helper enforcing the same rule as a defense-in-depth check.
- Report the Android binary
versionNamefromcurrent()while the default bundled web assets are active, so a newly installed store build cannot be mistaken for version1.0.0and downgraded.
Backend deploy required
- Deploy the update-check downgrade guard and verify
/api/versionreturns2026.08.06.2.
[4.0.1] - 2026-08-05
Fixed
- Excluded nested Yarn install state from the published tarball and made the normalized
binand repository metadata explicit. Runtime code is unchanged from 4.0.0.
[4.0.0] - 2026-08-05
Added
- Real delta delivery. The canonical backend generates deterministic
NUDELTA/1copy/literal patches from the last three compatible active builds, stores only patches smaller than 85% of the full target archive, and advertises them on the existing update-check response. Web, Android, and iOS validate the patch checksum, source checksum, operation bounds, target size, and final target checksum; every failure falls back to the signed full bundle. - Active GitHub Actions gates for the plugin, website, Laravel backend, Android, iOS, CLI, examples, dependency audits, publish-artifact inspection, and the no-source-map policy.
- Focused TypeScript delta tests and Laravel reconstruction/signed-download tests; the backend suite now contains 426 passing tests (1,253 assertions), alongside 83 Android plugin tests.
Changed
- Requires Capacitor 8, Node.js 24.16+, Java 21, Android API 26+, Android compile/target SDK 36, Gradle 8.14.5, and current supported dependencies across every monorepo unit.
- Standardizes the wire and all verifiers on
RSA-SHA256(RSASSA-PKCS1-v1_5 with SHA-256), matching the backend signer. Removed false RSA-PSS/ECDSA fallback claims. - Configuration is immutable after initialization.
setUpdateUrl()remains a deprecated no-op; changing locked settings requiresreset()followed by a newinitialize()/configure()call. - Android sensitive configuration now uses a non-exportable Android Keystore AES-256-GCM key rather than deprecated EncryptedSharedPreferences. iOS Keychain entries are device-only.
- Reveal endpoints for retrievable API/access-token secrets are POST-only, rate-limited, audited, and return no-store headers. Keystore replacement validates ZIP structure and uploads the new private object before deleting the old one.
Fixed
- Native bridge return/argument shape drift, omitted iOS bridge methods, double-resolved initialization calls, nonfunctional background helper instances, fake worker responses, download cancellation tracking, boot-time signed-ZIP deletion, and inconsistent HTTPS/host/path checks.
- PayPal subscription plan changes now call the vendor-supported revise operation.
- The published examples and marketing snippets now use the real package name, v4 configuration shape, signed manifest fields, delta-aware download helper, and Capacitor 8 APIs.
Backend deploy required
- Apply
2026_08_05_000001_create_delta_patches_table.php, rebuild Laravel caches, restart PHP-FPM when needed for OPcache, and verify/api/versionreturns2026.08.05.1.
[3.4.2] - 2026-07-25
Fixed
- 🔴 Android release builds failed for every consumer.
android/build.gradlecalledgetDefaultProguardFile('proguard-android.txt'), which the current Android Gradle Plugin hard-rejects because that file bundles-dontoptimize. The call is evaluated at configuration time, so a consuming app's./gradlew :app:processReleaseManifestandbundleReleasefailed during evaluation of:native-update— even withminifyEnabled false. Now usesproguard-android-optimize.txt, which is what every other Capacitor plugin already uses. Consumers carrying a local yarnpatch:for this can drop it.
Changed
- README rewritten to the canonical package pattern and renamed
Readme.md→README.md. The previous file shipped with no heading anchors across 24 sections, no table of contents, an internal## Current Stateaudit block, and relative links (./LICENSE,./CONTRIBUTING.md,./docs/deployment/…) that are dead wherever npm renders the README outside the repository. Limitations, Changelog and Keywords sections added; every link is now absolute and probed. fundingadded;descriptionshortened to match the README's one-line promise..npmignoredeleted. It coexisted with thefilesallowlist, which silently wins — and it listedCHANGELOG.md, whichfilesexplicitly ships. Proven inert before removal: packing with and without it produced identical 136-file lists.
[3.4.1] - 2026-07-25
Fixed
- 🔴
require('native-update')returned an empty object. Every CommonJS consumer got{}— no error, just nothing — while ESM consumers got all 28 exports.package.jsondeclares"type": "module", which makes Node treat every.jsfile as ESM, and the CommonJS artifact was emitted asdist/plugin.cjs.js. That.jsending meant genuinely-CommonJS output (exports.X = …) was loaded as an ES module, exposing no named exports. The artifact is nowdist/plugin.cjs, whose extension forces CommonJS regardless oftype;mainandexports["."].requireupdated to match. Proven with identical bytes under two extensions in one directory:.cjs→ 28 exports,.cjs.js→ 0.dist/plugin.esm.jsis unaffected and stays ESM. repositorypointed at the documentation site, not a git remote, sonpm repoand the registry's Repository link did not lead to source. Nowgit+https://github.com/aoneahsan/native-update.gitwith an explicit"type": "git".cli/CLAUDE.mdandcli/AGENTS.mdwere shipping inside the tarball. Those are internal agent-instruction files and have no business in a published package; thefilesentry"cli/"swept them in. Excluded with!**/CLAUDE.mdand!**/AGENTS.md. Tarball goes 138 → 136 files, with every real artifact retained.
No runtime behaviour changed for ESM consumers.
[3.4.0] - 2026-07-23
Added
- Full app lifecycle on the Public Management API. Beyond builds, the
/api/public/v1plane now creates, edits, and deletes apps; manages an app's API keys (list, create, rotate, reveal, restrict, revoke, deprecate, delete — the secret is only ever shown on create/rotate/reveal); manages its signing key (show public key + fingerprint, create, rotate — the private key never leaves the server); and stores a private keystore backup (upload and download a.zipup to 10 MB, held with private visibility and only ever streamed back through the API, never a public URL). CI or an agent can now provision and run an app end to end without the dashboard. Machine-readable spec: https://nativeupdate-docs.aoneahsan.com/openapi/public-api.json (now v1.1.0), with a front-matter-stripped Markdown mirror at/raw/*.mdindexed by/raw/manifest.json. all_appstoken scope. An access token can now cover every app you own — the ones you have and any created later — instead of a fixed ticked list. Creating an app with a specifically-scoped token auto-attaches the new app to it.GET /tokenreportsall_apps.- Two more opt-in delete permissions. Alongside
builds.delete, a token can be grantedapps.deleteandkeys.delete(both off by default). Deleting an app or a key without the matching permission answers403 TOKEN_PERMISSION_DENIED. Access tokens themselves stay dashboard-only — nothing on the API can create, rotate, or delete one. - Build controls: a
mandatoryflag on upload and edit; aBUILD_NOT_READYguard so a build cannot be forcedactivebefore its upload job completes; and rollout pause/resume viaenabled, alongside the percentage (0halts). - New CLI commands for the whole surface:
apps create|show|update|delete,apps keys list|create|show|rotate|reveal|revoke|deprecate|restrict|delete,apps signing show|create|rotate, andapps keystore upload|info|download|delete. Auth viaNATIVE_UPDATE_TOKEN, as withdeploy.
Backend
- Requires a deploy: new public-plane controllers and routes for app CRUD,
API-key management, signing keys, and the keystore backup; migrations for the
keystore backup and the
all_appstoken flag; and a permissions seeder re-run for the newapps.deleteandkeys.deletepermissions. 423 PHPUnit tests pass.
[3.3.1] - 2026-07-16
Changed
- README now documents the public management API and the
deploy/ release-control CLI commands added in 3.3.0. npm is where CI and agent users land, so the package's own front page needed to say the feature exists.
Fixed
- Dashboard: the access-token form rendered a
<Badge>(a block element) inside a<p>, so browsers auto-closed the paragraph and produced malformed DOM. Caught by running the page — type-check, lint, and build all passed it.
[3.3.0] - 2026-07-16
Added
- Public Management API — manage your apps and releases over HTTP. Everything
the dashboard does (list apps, upload a bundle, publish, promote between
channels, adjust a rollout, delete) is now an API at
https://nativeupdatebe.aoneahsan.com/api/public/v1, for CI, scripts, and agents. Machine-readable spec: https://nativeupdate-docs.aoneahsan.com/openapi/public-api.json - Access tokens — a new user-level credential (
nu_pat_…), created in the dashboard under Access Tokens:- App-scoped. Tick the apps a token may manage; it reaches nothing else. Anything out of scope answers 404, never 403, so the API cannot be used to discover other people's apps.
- Permission split. Every token has an implied
manage(read, upload, publish, promote, rollout).builds.deleteis opt-in and off by default, so a leaked CI token can ship a release but never erase your history. - Retrievable. Copy a token from the dashboard anytime, or rotate it — the old secret stops working immediately. Same posture as app API keys.
- Distinct from app API keys (
nu_app_…), which authenticate the plugin inside your app. The two are not interchangeable, and the public plane deliberately sends no CORS headers: an access token has no origin restrictions and belongs in a CI secret, never a browser.
- Queued uploads with a job to poll.
POST /apps/{app}/buildsanswers 202 with{job_id, status_url}— a 202 means accepted, not live. Devices only ever receiveactivebuilds, so a release is invisible untilGET /jobs/{jobId}reportscompleted. On failure the job reportserror, the build never goes live, and the owner is emailed. - New CLI commands (no new dependencies):
native-update deploy <webDirOrZip> --app <app> [--wait]— zips a directory if needed, uploads it, and with--waitblocks until the release is live, exiting non-zero if it fails, so a broken release fails your pipeline.native-update token info·apps list·builds list|promote|rollout|status|delete·jobs status.- Auth via
NATIVE_UPDATE_TOKEN(preferred — a--tokenflag leaks into shell history and CI logs);NATIVE_UPDATE_SERVERfor self-hosted backends.
Changed
- Bundle-upload logic (duplicate-version and monotonic-version guards, sync and
queued storage paths, zero-copy promotion) moved into a shared
BuildUploadService, so the dashboard and the public API cannot drift apart. No behaviour change to the dashboard.
Backend
- Requires a deploy: two migrations (
access_tokens+access_token_app,api_jobs) and aRolesPermissionsSeederre-run for the newaccess-tokens.managepermission.
[3.2.0] - 2026-07-14
Added
-
API-key client restrictions — ship
nu_app_…keys directly in your app, no self-hosted proxy needed. In the dashboard (Apps → API Keys → Restrictions) you can now lock each key to its clients:- Web — allowed origins (exact
https://app.example.comor ahttps://*.example.comwildcard), enforced against the browser'sOriginheader. The public update API (/v1/updates/check, analytics, signed download) is now callable straight from the browser (permissive CORS on that plane; the real gating is the per-key check, not CORS). - Android — allowed apps by package name + signing-cert SHA-256/SHA-1.
- iOS — allowed apps by bundle identifier.
A key with no restrictions keeps working from anywhere (unchanged, back-compatible). A restricted key that a client does not satisfy gets a
403 API_KEY_RESTRICTED(the response never leaks the allowlist). - Web — allowed origins (exact
-
LiveUpdate.getAppIdentity()returns this app's client identity (Android: applicationId + signing-cert SHA-256/SHA-1; iOS: bundle id; web:{ platform: 'web' }). Use it when you perform your OWN update-check fetch, then attach theX-Android-*/X-Ios-*headers. The plugin's native update-check attaches them automatically.
Notes
- Android/iOS restrictions require the app to be built with native-update ≥ 3.2.0 (older builds send no identity headers and will be blocked by an Android/iOS restriction). Web restrictions work with any version.
- The native identity headers are client-attested (parity with Google API-key app restrictions): they stop cross-site/casual key reuse and quota abuse, not a determined attacker replaying headers by hand. Only the web Origin check is browser-enforced.
[3.1.5] - 2026-07-14
Changed
- npm
repositorylink now points to the public documentation site (https://nativeupdate-docs.aoneahsan.com) instead of the private source repository (which returned 404 for the public on the npm package page). Metadata-only release — no plugin code, API, or runtime changes.
[3.1.4] - 2026-07-14
Fixed
- CLI
bundle create --version <v>now works. The long-form--versionflag was swallowed by the root-V, --versionoption (commander's default parsing matches program options anywhere on the line), sonative-update bundle create ./dist --version 1.2.0printed the CLI version and exited instead of building the bundle. The program and every parent command now useenablePositionalOptions(), so flags after a subcommand reach the leaf command. Short-vand the rootnative-update --versionare unchanged. - CLI
bundle createno longer crashes onarchiverimport.archiverv8 is ESM with named-only exports (no default export), so the previousimport archiver from 'archiver'threw at load. Migrated to the v8 API (import { ZipArchive }+new ZipArchive({ zlib: { level: 9 } })); the.pipe()/.directory()/.finalize()flow is unchanged.
[3.1.3] - 2026-07-14
Fixed
- [CRITICAL] Native Android/iOS update checks now speak the real wire
contract. Both native layers previously called the dead
{serverUrl}/checkpath with NO auth headers, so on-devicesync()/getLatest()/ background checks silently reported "up to date" forever against the hosted backend. They now issueGET {serverUrl}/v1/updates/check?channel=…withX-API-Key,X-Device-ID(stable per-install UUID, newly minted/persisted),X-Current-Version(active bundle version — Android previously hardcoded "1.0.0"),X-Platform, andX-App-Version(binary version), and parse the v3 response fields (version/downloadUrl/checksum/signature/size/mandatory/releaseNotes). - Flat
initialize(PluginInitConfig)now configures the NATIVE layers too. Nativeconfigure/initializeonly accepted the wrapped{ config: {...} }shape and rejected the flat form outright, so serverUrl/apiKey never reached Android/iOS. Both shapes (plus nestedUpdateConfig) are now accepted and normalized. configure({ liveUpdate })can finally carry the API key — newLiveUpdateConfig.apiKeyfield, mapped through to the config manager and the native layers.- Web
getLatest()implemented (was a hardcoded{available:false}stub) — now delegates to the shared/v1/updates/checkimplementation. @capacitor/filesystem+@capacitor/preferencesdeclared as peerDependencies — the web path statically imports them, but they were only devDependencies, so consumers got runtime module-resolution failures with no install-time warning.- Legacy-key error is actionable:
INVALID_API_KEY_FORMATnow tells you to mint annu_app_…key in the dashboard.
Added
- Dashboard API-key management (backend + app detail page): create (max 3 active keys per app), rotate (replaces the secret in place; old value dies immediately), delete, and copy-anytime reveal (keys now stored encrypted at rest alongside the lookup hash — keys minted before this release can't be revealed, only rotated). Lifecycle actions are rate-limited per app from a DB-backed audit trail: create 1/hour, rotate 1/hour per key, and 10/30-days + 20/180-days caps per action — friendly 429s with retry timing. The ConfigPage now emits copy-paste snippets with the app's REAL bundle id, API key, and the canonical backend URL.
Changed
-
Canonical
serverUrlrule documented everywhere:https://nativeupdatebe.aoneahsan.com/api— the plugin appends/v1/updates/check. Fixed the wrong…/api/updatesexamples in the website's AI docs dump and the staleapi.nativeupdate.aoneahsan.comhost in MIGRATION.md. -
node-express example implements the real contract (X-API-Key auth, contract field names, SHA-256 checksums at upload, semver comparison) — it previously served the legacy fictional shape.
-
Canonical documentation moved to nativeupdate-docs.aoneahsan.com (Docusaurus on GitHub Pages, CI-deployed on every push). README and all package links now point there;
nativeupdate.aoneahsan.com/docs/*URLs 301-redirect to the matching page, so previously published links keep working. -
npm tarball slimmed.
files[]now ships a curated doc set (docs/CHANGELOG.md,docs/MIGRATION.md,docs/server-requirements.md,docs/KNOWN_LIMITATIONS.md) instead of the wholedocs/tree — 134 files instead of 321, and internal planning material no longer ships to npm. -
Docs corpus consolidated + corrected. The duplicated in-repo consumer guides (quick start, feature guides, api/, getting-started/, guides/, examples set) were deleted in favor of the docs site; the retained docs were corrected to the real API (nested
configureconfig,SyncResult.status,canRequestReview().canRequest, real event names, real/v1/updates/checkwire contract, correct backend host). -
react-capacitor example
capacitor.config.tsdropped inert config keys and the staleanalytics: { provider: 'firebase' }block (Firebase was removed from the SDK in v3.0.0).
[3.1.2] - 2026-07-14
Fixed
- npm README fully refreshed and de-contradicted. Removed the false
"yarn workspace / workspace:*" and "3 examples incl. Firebase backend"
claims (two example apps; the example uses yarn's
portal:protocol), replaced the legacy "What You Need to Build" scaffold section with an accurate "What Ships in This Package", correctedstartImmediateUpdate→performImmediateUpdate, fixed the live-update example to the realSyncResult.status+getLatest()flow, corrected the requirement to Capacitor 8.x and the backend host tonativeupdatebe.aoneahsan.com. - AI-INTEGRATION-GUIDE.md now matches the real API. Nested
UpdateConfigforconfigure(),SyncResult.statusenum,download({url, version, checksum}),canRequestReview().canRequest, the real event names, the/v1/updates/checkwire contract (was a fictionalGET /api/check), no nonexistentbundle uploadcommand, and no legacyplay:coreGradle instruction (the plugin bundles Play App Update + Play Review already). - Website docs viewer no longer renders the SPA shell as markdown.
Hosting rewrites answer
200 text/htmlfor missing files; the viewer now rejects HTML responses and falls through to the real file, so/docs/AI-INTEGRATION-GUIDErenders correctly and "View Raw Markdown" always points at an existing.mdURL.
Changed
- Single source of truth for the version: root
package.json. The CLI already reads it; the website now bakes it into__APP_VERSION__, JSON-LDsoftwareVersion,humans.txt, andllms-full.txtat build time ({{PKG_VERSION}} placeholders), andcli/package.jsonno longer carries its own version field. No file hardcodes the version anymore. - Website
package-docscopies are synced from the repo root on every build (yarn sync:package-docs, refresh-only — internal docs are never newly published), ending the stale-copy drift (the public copy was stuck at the v2.0.0-era content).
[3.1.1] - 2026-07-13
Fixed
- Update checks now share ONE wire-contract implementation. New
src/core/update-check.tsimplements the/v1/updates/checkcontract;plugin.ts, the version manager, and the background scheduler's live fallback all route through it. The latter two previously called paths the backend never served (/check,/updates/latest), so their remote checks could never succeed against the hosted backend.
Changed
- Native app-store version paths documented as extension points. The
/app-versionendpoints used by the native app-store update module are now explicitly documented (JSDoc + docs) as custom-backend extension points rather than hosted-backend routes. No public API changes.
[3.1.0] - 2026-07-02
Added
exportsmap in package.json. Modern conditional exports (types/import/require) with./dist/*and./cli/*wildcard passthrough so existing deep imports keep resolving. Legacymain/module/types/unpkgfields retained.website/.env.production. Production builds now always bake the production backend URL; the dev.envcan safely point at localhost.- Composite index
builds(app_id, channel, status, created_at). Covers the update-check lookup's sort; removes a filesort from the hottest query.
Changed
- Update-check hot path (backend). The two per-request DB writes
(device activity + analytics event) now run after the response is
sent (
dispatch(...)->afterResponse()), and the latest-build lookup is cached for 45s with model-event invalidation. Rollout bucketing stays per-device and uncached. analytics:cleanupnow also prunesanalytics_eventsin 5000-row chunks (previously only device-activity rows; the events table grew unboundedly).- Web validateUpdate() reuses downloaded bundle bytes. Validating a
bundle right after
download()no longer re-downloads it to hash. - Landing page animation engine loads lazily (LazyMotion): ~44 KB of framer-motion moves off the first-paint critical path.
- Firebase Hosting config consolidated to the repo root with long-lived immutable caching for hashed assets and revalidation for HTML.
- CORS
supports_credentialsdisabled (bearer-token auth needs no credentialed CORS).
Removed
- 9 dead env keys from
website/.env(Firebase-era), 10 commented-out debug lines insrc/web.ts, 2 framework scaffold test files.
Fixed
- Doc drift: backend is Laravel 13 (not 11); the test suite is PHPUnit
(not Pest); canonical backend host is
nativeupdatebe.aoneahsan.com.
[3.0.1] - 2026-05-11
Added
- Documentation site. A new public Docusaurus documentation site
ships alongside the marketing site. 58 pages total: SDK reference
(23), CLI reference (9), Backend setup (5), Platform guides (3),
Tutorials (2), How-to guides (6), Concepts (6), Getting Started (2),
About + intro pages. Full Diátaxis coverage. Lives in the sibling
native-update-docs/repository; deployed to Firebase Hosting atdocs.nativeupdate.aoneahsan.com. Discovery files (robots.txtwith 30+ AI-bot allowlist,llms.txt,llms-full.txt,humans.txt,.well-known/security.txt,sitemap-index.json,sitemap.xml) and 3 JSON-LD blocks per page (WebSite, SoftwareApplication, Organization) for AI-search citability. docs/security/PRE-LAUNCH-PENTEST-CHECKLIST.md. 10-section DIY security checklist covering dep hygiene, authentication, authorization, CORS/CSRF, bundle integrity, file safety, data handling, operational safety, mobile platform integration, 9 specific attack paths, and a final sanity sweep.docs/PERFORMANCE.md+tools/bench/. Vitest microbench scaffold for the SHA-256 checksum and RSA-SHA256 signature hot paths, with calibration targets and a regression-investigation workflow.- CI: example-bundle workflow.
.github/workflows/example-bundle.ymlbuilds, signs, and attaches an OTA bundle fromexample-apps/react-capacitorto each GitHub Release. Also runs weekly as a drift-detection canary. - Example-app capability cards.
example-apps/react-capacitor/src/App.tsxnow demonstrates App Update (getAppUpdateInfo+openAppStore), App Review (canRequestReview+requestReview), and Background Update (toggle withallowMeteredConnection+nextCheckTime) alongside the existing Live Update flow. One section per feature area.
Fixed
website/src/pages/ExamplesPage.tsx. Removed the stale third example card advertising theexample-apps/firebase-backenddirectory, which was deleted in v3.0.0. The GitHub source link 404'd and the description ("Firestore manifests") contradicted the website's own v3 architecture rule.
Documentation
docs/project-audit/MARKETING-WEBSITE-FINDINGS-2026-05-11.mdproduced by the production-readiness audit Batch 9 sweep. Documents 3 deferred follow-ups (stale MDX docs mirror, HomePage missing Screenshots/Demo section, DocsPage size violation) and 7 surfaces confirmed healthy.docs/MARKETING_WEBSITE_TRACKER.mdannotated as SUPERSEDED with a redirect block to the new findings doc.example-apps/react-capacitor/README.mddocuments the React 18 vs 19 version-choice reasoning (the plugin is React-version-agnostic; the example pins React 18 to maximise readable audience).
Closed
- Production-readiness audit (10 batches) initiated 2026-05-08 is
complete as of 2026-05-11. Authoritative tracker:
docs/tracking/production-readiness-audit-tracker.json. The flow enters a 7-day cooldown; next allowed re-run 2026-05-18.
[3.0.0] - 2026-05-04
Removed (BREAKING)
- Firestore backend.
backendType: 'firestore'and thefirestore: { … }config field are gone. The plugin now speaks HTTP to a single backend (the hosted Native Update SaaS / Laravel reference implementation inbackend/). Apps using the Firestore branch must switch toserverUrl+apiKey— see MIGRATION.md. src/firestore/module.FirestoreClient,ManifestReader,firestore.rules,firestore.indexes.json, the schema types, and theFirestoreConfigpublic export are removed.example-apps/firebase-backend/. The standalone Firebase Cloud Functions example is removed;example-apps/node-express/is the reference HTTP implementation of the/v1/updates/checkcontract.
Changed
- Rollout / device-info / update-check types moved to
src/types/rollout.tsand are re-exported from the package root.FirestoreTimestampis replaced byRolloutTimestamp(number | string | Date) with atoEpochMs()helper. - Website auth-service drops the Firestore user-profile mirror. The
Laravel
ValidateFirebaseTokenmiddleware already upserts the user record from verified token claims on every authenticated request, so the client mirror was redundant.
[2.0.0] - 2026-04-18
Native (Android + iOS)
- Boot-time bundle re-verification. On every cold start the plugin
re-hashes the active OTA bundle and compares against the checksum
stored at install time. If the host app configured a public key and the
bundle carries a signature, the signature is re-verified too. Mismatch
triggers an immediate rollback to the previous known-good bundle and
emits
updateStateChangedwithstatus: 'ROLLBACK'. Closes an on-disk-tampering gap on rooted/jailbroken devices. - Automatic rollback on crash loop. Activating a bundle marks it as
pending-verify. Each cold start before
notifyAppReady()fires increments a counter; after 2 failed boots the plugin rolls back to the previous bundle automatically. Catches a bundle that crashes the app before it can tell the plugin it booted cleanly. - Bundle metadata now persists the signature alongside checksum so the boot re-verify can run without needing the manifest or a re-download.
- iOS zip-slip + zip-bomb protection. Bundle extraction now inspects every archive entry before writing to disk: rejects symlinks, absolute paths, and any path that resolves outside the destination directory, and caps total uncompressed size at 500 MB. A malicious bundle can no longer overwrite files outside the sandbox or exhaust device storage. (Android does not currently extract bundles in-plugin; when that is added, the same validation must be applied.)
- Secure storage is now always on. Android persists secrets only
through
EncryptedSharedPreferences(Keystore-backed); iOS persists only through the Keychain. ThesecureStorage: falseopt-out is removed — passing it is ignored. On web,initialize()config is sanitised before write-through tolocalStorage:apiKey, top-level andliveUpdate.publicKey, andsecurity.publicKeyare stripped, so a compromised script or stale cache cannot surface the credential. Host apps must pass the apiKey/publicKey on everyinitialize(). - Android background retries are bounded + backoff-aware. All three
enqueue sites (periodic, one-shot, notification-action) now pass
setBackoffCriteria(EXPONENTIAL, 30s). The worker itself caps at 5 retry attempts per run, so a persistently-failing server can no longer burn battery in a tight retry loop — total wall-clock budget for a failing run is ~15 minutes before falling through to the next scheduled tick. - iOS background-task diagnostics.
BGTaskScheduler.submiterrors are now classified (.notPermitted,.tooManyPendingTaskRequests,.unavailable) and logged with actionable messages..notPermittedalmost always means the host app'sInfo.plistis missing the scheduler identifier — previously a silent failure. We also cancel any prior pending request before resubmitting to dodge the "already-scheduled" edge case observed on a few iOS versions. Host apps must declareBGTaskSchedulerPermittedIdentifierswithcom.aoneahsan.nativeupdate.backgroundin their own Info.plist — the framework's plist does not propagate to the app bundle.
Security (BREAKING)
- Signature verification fails closed. If the host app configures
publicKey,requireSignature, orenableSignatureValidation, a missing signature now throws instead of silently passing. Previously, a manifest with the signature field stripped out would bypass all integrity checks. - Checksum verification fails closed.
verifyChecksum(data, '')now throws. Every OTA bundle manifest must ship a SHA-256 checksum; a missing one is treated as tampering. - Monotonic version enforcement persists across reinstalls. The plugin now records the highest version ever applied per channel in Preferences and refuses to apply any older bundle — even if the active-bundle state was wiped. Closes a downgrade-attack path.
setUpdateUrl()is deprecated and ignored at runtime. The update server URL is fixed atinitialize()time. Runtime mutation was an XSS attack path (compromised WebView could repoint updates to a hostile host). Re-callinitialize()to switch servers.- Removed
allowDowngradefromUpdateOptions. Downgrades are a debug/recovery operation and do not belong in a production option flag. - Removed
enableEncryption,encryptionKey,encryptionSaltfrom the public API. Bundle encryption was only wired on the web path and never implemented on Android or iOS — the option shipped a false guarantee. Sign bundles (RSA-PSS/ECDSA) and serve them over HTTPS; the plugin's existing verification is the correct integrity primitive.
Backend (native-update/backend)
- API key checksum comparison uses
hash_equals(timing-safe) — closes a byte-by-byte response-time leak on brute-force against short suffixes. - Bundle download asserts
build->app_id === authenticated app->idas defense-in-depth beyond the signed URL. - Device identifiers are HMAC-SHA256 hashed (keyed by
APP_KEY) via an Eloquent mutator before landing inanalytics_eventsanddevice_activities. Casual DB access can no longer correlate a row back to a specific device. - Build upload rejects versions ≤ current channel head unless an explicit rollback flow is used. Prevents accidental and malicious downgrades on live channels.
GET /api/healthunauth'd endpoint added for Hostinger uptime pings./api/v1/*now carries a route-levelthrottle:600,15as defense-in-depth on top of per-key rate limiting in middleware..env.exampletightened for production defaults:APP_ENV=production,APP_DEBUG=false,LOG_LEVEL=warning,LOG_CHANNEL=daily,DB_CONNECTION=mysql.- Signed bundle URLs include checksum + app_id in their signed payload
and
BundleController::downloadre-asserts both against theBuildrow before streaming. A replayed URL whose Build was flipped to archived now returns 403 instead of serving the old bundle. - Signed URL TTL moved from 5 minutes → 30 minutes and made
configurable via
NATIVE_UPDATE_DOWNLOAD_TTL_MIN. Five minutes frequently expired mid-download on a slow mobile link. - Bundle downloads stream through Laravel, never redirect to the underlying storage URL — comment enforces the invariant. Keeps auth, rate-limit, and audit in one chokepoint.
- Signing-key decryption is now per-request memoised in
SigningKey::getPrivateKey().rotate()andrevoke()clear the cache so stale keys can't leak across a long-running worker.
Optimization
- Resume-download correctness.
resumeDownload()now records whether the server actually returned HTTP 206 (Partial Content) and decides whether to concatenate the partial prefix or discard it accordingly. Previously a server that fell back to HTTP 200 (full response) silently produced a corrupted bundle by double-writing the prefix — a latent corruption bug on CDNs that don't honour Range. - Jittered exponential backoff on download retry.
downloadWithRetrynow adds ±25% random jitter to each retry interval. Avoids the thundering-herd scenario where many clients retry in lockstep after a common failure (CDN blip, server restart) and hammer a recovering server simultaneously. - Auto-cleanup is now the default. After a successful bundle apply
the plugin keeps the last 3 bundles (active + 2 recent) and deletes
the rest. Host apps that want the old every-bundle-forever behavior
can pass
cleanupOldBundles: falseexplicitly.
Payments (BREAKING)
- Stripe + Cashier removed; PayPal (
srmklive/paypal) takes over. NewSubscriptionControllerexposessubscribe → activate → cancel / resume / changePlanwith PayPal's approval-URL flow. Webhook endpoint moved from/api/webhooks/stripe→/api/webhooks/paypal, with a newPayPalWebhookControllerthat verifies transmissions via PayPal's ownverifyWebHookAPI. - New migration
2026_04_18_000001_add_paypal_provider_to_subscriptionsintroduces provider-agnostic columns (provider,provider_subscription_id,provider_status,provider_plan_id) onsubscriptions,subscription_items, andusers. Legacystripe_*columns are kept nullable for one release to ease rollback. .env.examplereplacesSTRIPE_*withPAYPAL_MODE,PAYPAL_LIVE_CLIENT_ID / SECRET / APP_ID,PAYPAL_PLAN_PRO,PAYPAL_PLAN_ENTERPRISE,PAYPAL_WEBHOOK_ID.Usermodel drops the CashierBillabletrait and grows anactiveSubscription()helper against the new schema.
Migration
- Apps relying on
allowDowngrade: trueinUpdateOptionswill fail to apply older bundles. Remove the flag and treat rollback as a separate flow. - Apps passing
enableEncryption,encryptionKey, orencryptionSalttoinitialize()orconfigure()will see them silently dropped. Rely on signature + checksum verification for integrity. - Apps calling
setUpdateUrl()at runtime will receive a deprecation warning and the call is a no-op. PassserverUrltoinitialize().
[1.4.9] - 2026-03-11
Added
- Firestore-backed manifest reads through
backendType: 'firestore' - Secure manifest lookup support via
apiKeys/{apiKey}/manifests/{channel} - Concrete no-cost backend implementation guide for
website + Firestore + Google Drive
Changed
- Website dashboard publish flow aligned to Google Drive asset uploads plus Firestore manifest publishing
- Website dashboard configuration/docs now recommend Firestore instead of the old
/api/updatespath - Package config types now expose Firestore configuration for production app integration
Fixed
- Manifest publishing now writes the full manifest shape to both secure and compatibility Firestore paths
- Google Drive asset uploads now preserve correct filenames for ZIP bundles and generated manifest files
[1.4.5] - 2026-02-25
Changed
- Updated all dependencies to latest versions
- Full verification suite passing (81 tests, 0 lint warnings)
- Android Gradle build verified (assembleDebug successful)
- Website and Firebase Functions builds verified
- Release-ready state confirmed
Maintenance
- Package updates: Capacitor 8.x, TypeScript 5.9.3, Vitest 4.0.18, ESLint 9.39.2
- Node.js engine requirement: >=24.13.0
[1.0.7] - 2025-01-15
Fixed
- Critical Android Build Issues:
- Fixed Google Play app-update library dependencies by using explicit version 2.1.0 instead of variable
- Resolved kotlinVersion resolution issues by hardcoding version in gradle plugin
- Made all Google Play Services dependencies use explicit versions to prevent resolution errors
- Added variables.gradle file for better variable management
- Ensured no manual intervention is required by users
Changed
- Updated package version to 1.0.7
- Hardcoded critical dependency versions in Android build.gradle to prevent resolution issues
[1.0.6] - 2025-01-15
Fixed
- Android Build Issues:
- Fixed Google Play app-update library version error (changed from non-existent 18.2.0 to correct 2.1.0)
- Fixed
kotlinVersionMissingPropertyException by properly defining it in buildscript ext block - Added missing Kotlin stdlib dependency
- Reorganized build.gradle structure to ensure proper variable scope
Documentation Fixes
- Corrected all API method references to match TypeScript definitions
- Fixed method signatures and parameters across all documentation
- Updated event names to match the 4 defined events (downloadProgress, updateStateChanged, backgroundUpdateProgress, backgroundUpdateNotification)
- Removed references to non-existent methods
- Fixed broken documentation links
Android Configuration
- kotlinVersion: 1.9.22 (stable version)
- playAppUpdateVersion: 2.1.0 (correct Google Play version)
- playReviewVersion: 2.0.1
- All dependencies now use proper version variables
[1.4.0] - 2025-01-02
Added
- Complete TypeScript implementation with strict typing
- Core plugin architecture with modular design
- Comprehensive error handling system
- Security validation framework
- Bundle management system
- Download manager with retry logic
- Version management capabilities
- Configuration manager
- Logging system with multiple log levels
- Cache management
- Background update support
- Plugin initialization system
Changed
- Migrated from class-based to functional architecture
- Improved type definitions and interfaces
- Enhanced security with input validation
- Better error messages and error codes
Fixed
- Removed circular dependencies
- Fixed TypeScript compilation issues
- Proper exclusion of test files in tsconfig
- Removed console.log statements in favor of Logger
[0.0.2] - 2024-12-01
Added
- Comprehensive documentation for all features
- Bundle signing and security utilities
- CI/CD pipeline with GitHub Actions
- Example update server implementation
- TypeScript strict mode
- ESLint v9 flat config support
Fixed
- Security vulnerabilities in update process
- TypeScript compilation errors
- Path traversal prevention
Security
- Enforced HTTPS for all update downloads
- Added RSA signature verification
- Implemented checksum validation
- Added certificate pinning support
[0.0.1] - 2024-01-08
Added
- Initial release of Capacitor Native Update plugin
- Live/OTA update functionality
- Bundle download and management
- Multiple update strategies (immediate, background, manual)
- Delta update support
- Automatic rollback on failed updates
- Update channels (production, staging, development)
- End-to-end encryption and signature verification
- Bundle integrity checks with SHA-256/512 checksums
- Native app update functionality
- App store version checking
- Immediate and flexible update flows (Android)
- Direct app store navigation
- Version comparison and update priority
- App review functionality
- In-app review prompts using native APIs
- Smart triggering with configurable conditions
- Rate limiting to respect platform quotas
- Security features
- HTTPS enforcement by default
- Certificate pinning support
- Public key signature verification
- Input validation and sanitization
- Secure storage using platform keystores
- Platform support
- iOS implementation using Swift
- Android implementation using Kotlin
- Web fallback implementation
- Comprehensive TypeScript definitions
- Example application demonstrating all features
- Extensive documentation
- API reference
- Security guidelines
- Migration guide
- Feature overview
Security
- Enforced HTTPS for all update operations by default
- Required checksum validation for all bundle downloads
- Implemented path traversal prevention
- Added secure storage for sensitive configuration
Known Issues
- ESLint configuration needs update for v9 compatibility
- Certificate pinning requires manual certificate configuration
- Delta updates require server-side implementation
Future Releases
[0.1.0] - Planned
- Add delta update server reference implementation
- Improve offline update handling
- Add update scheduling APIs
- Enhanced progress reporting with ETA
- Batch update support
[0.2.0] - Planned
- Machine learning for optimal update timing
- Peer-to-peer update distribution
- Advanced A/B testing framework
- Custom update UI components
- Differential compression algorithms