native-update 1.3.4 → 1.3.6

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 CHANGED
@@ -4,7 +4,7 @@
4
4
  >
5
5
  > This package is now **feature-complete** with significant improvements:
6
6
  >
7
- > - ✅ **pnpm Workspace Monorepo** - Seamless development with workspace:* references
7
+ > - ✅ **yarn Workspace Monorepo** - Seamless development with workspace:* references
8
8
  > - ✅ **3 Complete Examples** - React+Capacitor frontend, Node.js+Express and Firebase backends in `example-apps/`
9
9
  > - ✅ **Native Implementations Complete** - iOS (Swift) and Android (Kotlin) fully implemented
10
10
  > - ✅ **Comprehensive Test Suite** - Unit and integration tests with Vitest
@@ -85,7 +85,7 @@ Increase user engagement with intelligent review prompts:
85
85
  ## Installation
86
86
 
87
87
  ```bash
88
- npm install native-update
88
+ yarn add native-update
89
89
  npx cap sync
90
90
  ```
91
91
 
@@ -290,7 +290,7 @@ This plugin implements multiple security layers:
290
290
 
291
291
  ## 🎯 Complete Example Implementations
292
292
 
293
- This repository uses **pnpm workspace** for seamless development. All examples reference the local plugin via `workspace:*` - no need to publish to npm for testing!
293
+ This repository uses **yarn workspace** for seamless development. All examples reference the local plugin via `workspace:*` - no need to publish to npm for testing!
294
294
 
295
295
  ### Frontend Example: React + Capacitor
296
296
 
package/dist/plugin.js CHANGED
@@ -1,3 +1,3 @@
1
- /*! Native Update Plugin v1.1.6 | MIT License */
1
+ /*! Native Update Plugin v1.3.5 | MIT License */
2
2
  !function(e,t,r,a){var i,n,s,o,l,c,d,h,u,g,p,f;!function(e){e.APP_UPDATE="app_update",e.LIVE_UPDATE="live_update",e.BOTH="both"}(i||(i={})),function(e){e.MIN="min",e.LOW="low",e.DEFAULT="default",e.HIGH="high",e.MAX="max"}(n||(n={})),function(e){e.IMMEDIATE="immediate",e.BACKGROUND="background",e.MANUAL="manual"}(s||(s={})),function(e){e.IMMEDIATE="immediate",e.ON_NEXT_RESTART="on_next_restart",e.ON_NEXT_RESUME="on_next_resume"}(o||(o={})),function(e){e.IMMEDIATE="immediate",e.ON_NEXT_RESTART="on_next_restart",e.ON_NEXT_RESUME="on_next_resume"}(l||(l={})),function(e){e.SHA256="SHA-256",e.SHA512="SHA-512"}(c||(c={})),function(e){e.UP_TO_DATE="UP_TO_DATE",e.UPDATE_AVAILABLE="UPDATE_AVAILABLE",e.UPDATE_INSTALLED="UPDATE_INSTALLED",e.ERROR="ERROR"}(d||(d={})),function(e){e.PENDING="PENDING",e.DOWNLOADING="DOWNLOADING",e.READY="READY",e.ACTIVE="ACTIVE",e.FAILED="FAILED"}(h||(h={})),function(e){e.UNKNOWN="UNKNOWN",e.PENDING="PENDING",e.DOWNLOADING="DOWNLOADING",e.DOWNLOADED="DOWNLOADED",e.INSTALLING="INSTALLING",e.INSTALLED="INSTALLED",e.FAILED="FAILED",e.CANCELED="CANCELED"}(u||(u={})),function(e){e.NETWORK_ERROR="NETWORK_ERROR",e.SERVER_ERROR="SERVER_ERROR",e.TIMEOUT_ERROR="TIMEOUT_ERROR",e.DOWNLOAD_ERROR="DOWNLOAD_ERROR",e.STORAGE_ERROR="STORAGE_ERROR",e.SIZE_LIMIT_EXCEEDED="SIZE_LIMIT_EXCEEDED",e.VERIFICATION_ERROR="VERIFICATION_ERROR",e.CHECKSUM_ERROR="CHECKSUM_ERROR",e.SIGNATURE_ERROR="SIGNATURE_ERROR",e.INSECURE_URL="INSECURE_URL",e.INVALID_CERTIFICATE="INVALID_CERTIFICATE",e.PATH_TRAVERSAL="PATH_TRAVERSAL",e.INSTALL_ERROR="INSTALL_ERROR",e.ROLLBACK_ERROR="ROLLBACK_ERROR",e.VERSION_MISMATCH="VERSION_MISMATCH",e.PERMISSION_DENIED="PERMISSION_DENIED",e.UPDATE_NOT_AVAILABLE="UPDATE_NOT_AVAILABLE",e.UPDATE_IN_PROGRESS="UPDATE_IN_PROGRESS",e.UPDATE_CANCELLED="UPDATE_CANCELLED",e.PLATFORM_NOT_SUPPORTED="PLATFORM_NOT_SUPPORTED",e.REVIEW_NOT_SUPPORTED="REVIEW_NOT_SUPPORTED",e.QUOTA_EXCEEDED="QUOTA_EXCEEDED",e.CONDITIONS_NOT_MET="CONDITIONS_NOT_MET",e.INVALID_CONFIG="INVALID_CONFIG",e.UNKNOWN_ERROR="UNKNOWN_ERROR"}(g||(g={}));class m{constructor(){this.config=this.getDefaultConfig()}static getInstance(){return m.instance||(m.instance=new m),m.instance}getDefaultConfig(){return{filesystem:null,preferences:null,baseUrl:"",allowedHosts:[],maxBundleSize:104857600,downloadTimeout:3e4,retryAttempts:3,retryDelay:1e3,enableSignatureValidation:!0,publicKey:"",cacheExpiration:864e5,enableLogging:!1,serverUrl:"",channel:"production",appId:"",autoCheck:!0,autoUpdate:!1,updateStrategy:"background",requireSignature:!0,checksumAlgorithm:"SHA-256",checkInterval:864e5,security:{enforceHttps:!0,validateInputs:!0,secureStorage:!0,logSecurityEvents:!1},promptAfterPositiveEvents:!1,maxPromptsPerVersion:1,minimumDaysSinceLastPrompt:7,isPremiumUser:!1,appStoreId:"",iosAppId:"",packageName:"",webReviewUrl:"",minimumVersion:"1.0.0",backendType:"http",firestore:null,enableDeltaUpdates:!0,enableStagedRollouts:!0}}configure(e){this.config=Object.assign(Object.assign({},this.config),e),this.validateConfig()}validateConfig(){if(this.config.maxBundleSize<=0)throw new Error("maxBundleSize must be greater than 0");if(this.config.downloadTimeout<=0)throw new Error("downloadTimeout must be greater than 0");if(this.config.retryAttempts<0)throw new Error("retryAttempts must be non-negative");if(this.config.retryDelay<0)throw new Error("retryDelay must be non-negative")}get(e){return this.config[e]}set(e,t){this.config[e]=t}getAll(){return Object.assign({},this.config)}isConfigured(){return!(!this.config.filesystem||!this.config.preferences)}}e.LogLevel=void 0,(p=e.LogLevel||(e.LogLevel={}))[p.DEBUG=0]="DEBUG",p[p.INFO=1]="INFO",p[p.WARN=2]="WARN",p[p.ERROR=3]="ERROR";class w{constructor(e){this.configManager=m.getInstance(),this.context=e||"NativeUpdate"}static getInstance(){return w.instance||(w.instance=new w),w.instance}shouldLog(){return this.configManager.get("enableLogging")}sanitize(e){if("string"==typeof e){let t=e;return t=t.replace(/\/[^\s]+\/([\w.-]+)$/g,"/<path>/$1"),t=t.replace(/https?:\/\/[^:]+:[^@]+@/g,"https://***:***@"),t=t.replace(/[a-zA-Z0-9]{32,}/g,"<redacted>"),t}if("object"==typeof e&&null!==e){if(Array.isArray(e))return e.map(e=>this.sanitize(e));{const t={},r=e;for(const e in r)t[e]=e.toLowerCase().includes("key")||e.toLowerCase().includes("secret")||e.toLowerCase().includes("password")||e.toLowerCase().includes("token")?"<redacted>":this.sanitize(r[e]);return t}}return e}log(t,r){this.logWithLevel(e.LogLevel.INFO,t,r)}logWithLevel(t,r,a){if(!this.shouldLog())return;const i=(new Date).toISOString(),n=a?this.sanitize(a):void 0,s={timestamp:i,level:e.LogLevel[t],context:this.context,message:r};switch(void 0!==n&&(s.data=n),t){case e.LogLevel.DEBUG:console.debug(`[${this.context}]`,s);break;case e.LogLevel.INFO:console.info(`[${this.context}]`,s);break;case e.LogLevel.WARN:console.warn(`[${this.context}]`,s);break;case e.LogLevel.ERROR:console.error(`[${this.context}]`,s)}}debug(t,r){this.logWithLevel(e.LogLevel.DEBUG,t,r)}info(t,r){this.logWithLevel(e.LogLevel.INFO,t,r)}warn(t,r){this.logWithLevel(e.LogLevel.WARN,t,r)}error(t,r){const a=r instanceof Error?{name:r.name,message:r.message,stack:r.stack}:r;this.logWithLevel(e.LogLevel.ERROR,t,a)}}e.ErrorCode=void 0,(f=e.ErrorCode||(e.ErrorCode={})).NOT_CONFIGURED="NOT_CONFIGURED",f.INVALID_CONFIG="INVALID_CONFIG",f.MISSING_DEPENDENCY="MISSING_DEPENDENCY",f.DOWNLOAD_FAILED="DOWNLOAD_FAILED",f.DOWNLOAD_TIMEOUT="DOWNLOAD_TIMEOUT",f.INVALID_URL="INVALID_URL",f.UNAUTHORIZED_HOST="UNAUTHORIZED_HOST",f.BUNDLE_TOO_LARGE="BUNDLE_TOO_LARGE",f.CHECKSUM_MISMATCH="CHECKSUM_MISMATCH",f.SIGNATURE_INVALID="SIGNATURE_INVALID",f.VERSION_DOWNGRADE="VERSION_DOWNGRADE",f.INVALID_BUNDLE_FORMAT="INVALID_BUNDLE_FORMAT",f.STORAGE_FULL="STORAGE_FULL",f.FILE_NOT_FOUND="FILE_NOT_FOUND",f.PERMISSION_DENIED="PERMISSION_DENIED",f.UPDATE_FAILED="UPDATE_FAILED",f.ROLLBACK_FAILED="ROLLBACK_FAILED",f.BUNDLE_NOT_READY="BUNDLE_NOT_READY",f.PLATFORM_NOT_SUPPORTED="PLATFORM_NOT_SUPPORTED",f.NATIVE_ERROR="NATIVE_ERROR";class E extends Error{constructor(e,t,r,a){super(t),this.code=e,this.message=t,this.details=r,this.originalError=a,this.name="NativeUpdateError",Object.setPrototypeOf(this,E.prototype)}toJSON(){return{name:this.name,code:this.code,message:this.message,details:this.details,stack:this.stack}}}class I extends E{constructor(e,t,r,a){super(e,t,r,a),this.name="DownloadError"}}class y extends E{constructor(e,t,r){super(e,t,r),this.name="ValidationError"}}class A extends E{constructor(e,t,r,a){super(e,t,r,a),this.name="StorageError"}}class D extends E{constructor(e,t,r,a){super(e,t,r,a),this.name="UpdateError"}}class v{constructor(){this.configManager=m.getInstance(),this.logger=w.getInstance()}static getInstance(){return v.instance||(v.instance=new v),v.instance}static validateUrl(e){try{return"https:"===new URL(e).protocol}catch(e){return!1}}static validateChecksum(e){return/^[a-f0-9]{64}$/i.test(e)}static sanitizeInput(e){return e?e.replace(/<[^>]*>/g,"").replace(/[^\w\s/.-]/g,""):""}static validateBundleSize(e){return e>0&&e<=104857600}async calculateChecksum(e){const t=await crypto.subtle.digest("SHA-256",e);return Array.from(new Uint8Array(t)).map(e=>e.toString(16).padStart(2,"0")).join("")}async verifyChecksum(e,t){if(!t)return this.logger.warn("No checksum provided for verification"),!0;const r=await this.calculateChecksum(e),a=r===t.toLowerCase();return a||this.logger.error("Checksum verification failed",{expected:t,actual:r}),a}async validateChecksum(e,t){return this.verifyChecksum(e,t)}async verifySignature(t,r){if(!this.configManager.get("enableSignatureValidation"))return!0;const a=this.configManager.get("publicKey");if(!a)throw new y(e.ErrorCode.SIGNATURE_INVALID,"Public key not configured for signature validation");try{const e=await crypto.subtle.importKey("spki",this.pemToArrayBuffer(a),{name:"RSA-PSS",hash:"SHA-256"},!1,["verify"]),i=await crypto.subtle.verify({name:"RSA-PSS",saltLength:32},e,this.base64ToArrayBuffer(r),t);return i||this.logger.error("Signature verification failed"),i}catch(e){return this.logger.error("Signature verification error",e),!1}}pemToArrayBuffer(e){const t=e.replace(/-----BEGIN PUBLIC KEY-----/g,"").replace(/-----END PUBLIC KEY-----/g,"").replace(/\s/g,"");return this.base64ToArrayBuffer(t)}base64ToArrayBuffer(e){const t=atob(e),r=new Uint8Array(t.length);for(let e=0;e<t.length;e++)r[e]=t.charCodeAt(e);return r.buffer}sanitizePath(e){return e.split("/").filter(e=>".."!==e&&"."!==e).join("/").replace(/^\/+/,"")}validateBundleId(t){if(!t||"string"!=typeof t)throw new y(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Bundle ID must be a non-empty string");if(!/^[a-zA-Z0-9\-_.]+$/.test(t))throw new y(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Bundle ID contains invalid characters");if(t.length>100)throw new y(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Bundle ID is too long (max 100 characters)")}validateVersion(t){if(!t||"string"!=typeof t)throw new y(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Version must be a non-empty string");if(!/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/.test(t))throw new y(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Version must follow semantic versioning format (e.g., 1.2.3)")}isVersionDowngrade(e,t){const r=this.parseVersion(e),a=this.parseVersion(t);return a.major<r.major||!(a.major>r.major)&&(a.minor<r.minor||!(a.minor>r.minor)&&a.patch<r.patch)}parseVersion(e){const t=e.split("-")[0].split(".");return{major:parseInt(t[0],10)||0,minor:parseInt(t[1],10)||0,patch:parseInt(t[2],10)||0}}validateUrl(t){if(!t||"string"!=typeof t)throw new y(e.ErrorCode.INVALID_URL,"URL must be a non-empty string");let r;try{r=new URL(t)}catch(t){throw new y(e.ErrorCode.INVALID_URL,"Invalid URL format")}if("https:"!==r.protocol)throw new y(e.ErrorCode.INVALID_URL,"Only HTTPS URLs are allowed");const a=this.configManager.get("allowedHosts");if(a.length>0&&!a.includes(r.hostname))throw new y(e.ErrorCode.UNAUTHORIZED_HOST,`Host ${r.hostname} is not in the allowed hosts list`);if([/^localhost$/i,/^127\./,/^10\./,/^172\.(1[6-9]|2[0-9]|3[0-1])\./,/^192\.168\./,/^::1$/,/^fc00:/i,/^fe80:/i].some(e=>e.test(r.hostname)))throw new y(e.ErrorCode.UNAUTHORIZED_HOST,"Private/local addresses are not allowed")}validateFileSize(t){if("number"!=typeof t||t<0)throw new y(e.ErrorCode.INVALID_BUNDLE_FORMAT,"File size must be a non-negative number");const r=this.configManager.get("maxBundleSize");if(t>r)throw new y(e.ErrorCode.BUNDLE_TOO_LARGE,`File size ${t} exceeds maximum allowed size of ${r} bytes`)}generateSecureId(){const e=new Uint8Array(16);return crypto.getRandomValues(e),Array.from(e,e=>e.toString(16).padStart(2,"0")).join("")}async validateCertificatePin(e,t){const r=this.configManager.certificatePins;if(!r||!Array.isArray(r)||0===r.length)return!0;const a=r.filter(t=>t.hostname===e);if(0===a.length)return!0;const i=await this.calculateCertificateHash(t),n=a.some(e=>e.sha256===i);return n||this.logger.error("Certificate pinning validation failed",{hostname:e,expectedPins:a.map(e=>e.sha256),actualHash:i}),n}async calculateCertificateHash(e){const t=(new TextEncoder).encode(e),r=await crypto.subtle.digest("SHA-256",t),a=Array.from(new Uint8Array(r));return"sha256/"+btoa(String.fromCharCode(...a))}validateMetadata(t){if(t&&"object"!=typeof t)throw new y(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Metadata must be an object");if(JSON.stringify(t||{}).length>10240)throw new y(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Metadata is too large (max 10KB)")}}class N{constructor(){this.STORAGE_KEY="capacitor_native_update_bundles",this.ACTIVE_BUNDLE_KEY="capacitor_native_update_active",this.preferences=null,this.cache=new Map,this.cacheExpiry=0,this.logger=w.getInstance(),this.configManager=m.getInstance()}async initialize(){if(this.preferences=this.configManager.get("preferences"),!this.preferences)throw new A(e.ErrorCode.MISSING_DEPENDENCY,"Preferences not configured. Please configure the plugin first.");await this.loadCache()}async loadCache(){if(!(Date.now()<this.cacheExpiry))try{const{value:e}=await this.preferences.get({key:this.STORAGE_KEY});if(e){const t=JSON.parse(e);this.cache.clear(),t.forEach(e=>this.cache.set(e.bundleId,e))}this.cacheExpiry=Date.now()+5e3}catch(e){this.logger.error("Failed to load bundles from storage",e),this.cache.clear()}}async saveCache(){try{const e=Array.from(this.cache.values());await this.preferences.set({key:this.STORAGE_KEY,value:JSON.stringify(e)}),this.logger.debug("Saved bundles to storage",{count:e.length})}catch(t){throw new A(e.ErrorCode.STORAGE_FULL,"Failed to save bundles to storage",void 0,t)}}async saveBundleInfo(e){this.validateBundleInfo(e),this.cache.set(e.bundleId,e),await this.saveCache(),this.logger.info("Bundle saved",{bundleId:e.bundleId,version:e.version})}validateBundleInfo(t){if(!t.bundleId||"string"!=typeof t.bundleId)throw new A(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Invalid bundle ID");if(!t.version||"string"!=typeof t.version)throw new A(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Invalid bundle version");if(!t.path||"string"!=typeof t.path)throw new A(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Invalid bundle path");if("number"!=typeof t.size||t.size<0)throw new A(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Invalid bundle size")}async getAllBundles(){return await this.loadCache(),Array.from(this.cache.values())}async getBundle(t){if(!t)throw new A(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Bundle ID is required");return await this.loadCache(),this.cache.get(t)||null}async deleteBundle(t){if(!t)throw new A(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Bundle ID is required");await this.loadCache(),this.cache.get(t)?(this.cache.delete(t),await this.saveCache(),await this.getActiveBundleId()===t&&await this.clearActiveBundle(),this.logger.info("Bundle deleted",{bundleId:t})):this.logger.warn("Attempted to delete non-existent bundle",{bundleId:t})}async getActiveBundle(){const e=await this.getActiveBundleId();return e?this.getBundle(e):null}async setActiveBundle(t){if(!t)throw new A(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Bundle ID is required");const r=await this.getBundle(t);if(!r)throw new A(e.ErrorCode.FILE_NOT_FOUND,`Bundle ${t} not found`);const a=await this.getActiveBundle();a&&a.bundleId!==t&&(a.status="READY",await this.saveBundleInfo(a)),r.status="ACTIVE",await this.saveBundleInfo(r),await this.preferences.set({key:this.ACTIVE_BUNDLE_KEY,value:t}),this.logger.info("Active bundle set",{bundleId:t,version:r.version})}async getActiveBundleId(){try{const{value:e}=await this.preferences.get({key:this.ACTIVE_BUNDLE_KEY});return e}catch(e){return this.logger.error("Failed to get active bundle ID",e),null}}async clearActiveBundle(){await this.preferences.remove({key:this.ACTIVE_BUNDLE_KEY}),this.logger.info("Active bundle cleared")}async clearAllBundles(){await this.preferences.remove({key:this.STORAGE_KEY}),await this.preferences.remove({key:this.ACTIVE_BUNDLE_KEY}),this.cache.clear(),this.cacheExpiry=0,this.logger.info("All bundles cleared")}async cleanupOldBundles(t){if(t<1)throw new A(e.ErrorCode.INVALID_CONFIG,"Keep count must be at least 1");const r=await this.getAllBundles(),a=await this.getActiveBundleId(),i=r.sort((e,t)=>t.downloadTime-e.downloadTime),n=new Set;a&&n.add(a);let s=n.size;for(const e of i){if(s>=t)break;n.has(e.bundleId)||(n.add(e.bundleId),s++)}let o=0;for(const e of r)n.has(e.bundleId)||(await this.deleteBundle(e.bundleId),o++);o>0&&this.logger.info("Cleaned up old bundles",{deleted:o,kept:s})}async getBundlesOlderThan(t){if(t<0)throw new A(e.ErrorCode.INVALID_CONFIG,"Timestamp must be non-negative");return(await this.getAllBundles()).filter(e=>e.downloadTime<t)}async markBundleAsVerified(t){const r=await this.getBundle(t);if(!r)throw new A(e.ErrorCode.FILE_NOT_FOUND,`Bundle ${t} not found`);r.verified=!0,await this.saveBundleInfo(r),this.logger.info("Bundle marked as verified",{bundleId:t})}async getTotalStorageUsed(){return(await this.getAllBundles()).reduce((e,t)=>e+t.size,0)}async isStorageLimitExceeded(e=0){const t=await this.getTotalStorageUsed();let r=3*this.configManager.get("maxBundleSize");try{if("storage"in navigator&&"estimate"in navigator.storage){const e=await navigator.storage.estimate();e.quota&&(r=Math.max(r,e.quota-104857600))}}catch(e){this.logger.warn("Storage API not available for quota check, using config limit")}return t+e>r}createDefaultBundle(){return{bundleId:"default",version:"1.0.0",path:"/",downloadTime:Date.now(),size:0,status:"ACTIVE",checksum:"",verified:!0}}async cleanExpiredBundles(){const e=this.configManager.get("cacheExpiration"),t=Date.now()-e,r=await this.getBundlesOlderThan(t);for(const e of r){const t=await this.getActiveBundleId();e.bundleId!==t&&await this.deleteBundle(e.bundleId)}}}class b{constructor(){this.activeDownloads=new Map,this.filesystem=null,this.logger=w.getInstance(),this.configManager=m.getInstance()}async initialize(){if(this.filesystem=this.configManager.get("filesystem"),!this.filesystem)throw new I(e.ErrorCode.MISSING_DEPENDENCY,"Filesystem not configured. Please configure the plugin first.")}validateUrl(t){try{const r=new URL(t);if("https:"!==r.protocol)throw new y(e.ErrorCode.INVALID_URL,"Only HTTPS URLs are allowed for security reasons");const a=this.configManager.get("allowedHosts");if(a.length>0&&!a.includes(r.hostname))throw new y(e.ErrorCode.UNAUTHORIZED_HOST,`Host ${r.hostname} is not in the allowed hosts list`)}catch(t){if(t instanceof y)throw t;throw new y(e.ErrorCode.INVALID_URL,"Invalid URL format")}}async download(t,r,a){if(this.validateUrl(t),!r)throw new y(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Bundle ID is required");if(this.activeDownloads.has(r))throw new I(e.ErrorCode.DOWNLOAD_FAILED,`Download already in progress for bundle ${r}`);const i=new AbortController,n={controller:i,startTime:Date.now()};this.activeDownloads.set(r,n);try{const s=this.configManager.get("downloadTimeout"),o=setTimeout(()=>i.abort(),s),l={"Cache-Control":"no-cache",Accept:"application/octet-stream, application/zip"};n.resumePosition&&n.resumePosition>0&&(l.Range=`bytes=${n.resumePosition}-`);const c=await fetch(t,{signal:i.signal,headers:l});if(clearTimeout(o),!c.ok)throw new I(e.ErrorCode.DOWNLOAD_FAILED,`Download failed: ${c.status} ${c.statusText}`,{status:c.status,statusText:c.statusText});const d=c.headers.get("content-type");if(d&&!this.isValidContentType(d))throw new y(e.ErrorCode.INVALID_BUNDLE_FORMAT,`Invalid content type: ${d}`);const h=c.headers.get("content-length"),u=h?parseInt(h,10):0;if(u>this.configManager.get("maxBundleSize"))throw new y(e.ErrorCode.BUNDLE_TOO_LARGE,`Bundle size ${u} exceeds maximum allowed size`);if(!u||!c.body){const e=await c.blob();return this.validateBlobSize(e),e}const g=c.body.getReader(),p=[];let f=0;for(;;){const{done:t,value:i}=await g.read();if(t)break;if(p.push(i),f+=i.length,f>this.configManager.get("maxBundleSize"))throw new y(e.ErrorCode.BUNDLE_TOO_LARGE,"Download size exceeds maximum allowed size");if(a){const e=n.resumePosition||0,t=e+f,i=e+u;a({percent:Math.round(t/i*100),bytesDownloaded:t,totalBytes:i,bundleId:r})}}const m=new Blob(p);return this.validateBlobSize(m),this.logger.info("Download completed",{bundleId:r,size:m.size,duration:Date.now()-n.startTime}),m}catch(t){if(t instanceof Error&&"AbortError"===t.name){const r=Date.now()-n.startTime>=this.configManager.get("downloadTimeout");throw new I(r?e.ErrorCode.DOWNLOAD_TIMEOUT:e.ErrorCode.DOWNLOAD_FAILED,r?"Download timed out":"Download cancelled",void 0,t)}throw t}finally{this.activeDownloads.delete(r)}}async resumeDownload(e,t,r,a){const i=this.activeDownloads.get(t);i&&(i.resumePosition=r.size);try{const i=await this.download(e,t,a);return new Blob([r,i])}catch(e){throw this.activeDownloads.delete(t),e}}async canResume(e){try{return"bytes"===(await fetch(e,{method:"HEAD"})).headers.get("Accept-Ranges")}catch(e){return!1}}isValidContentType(e){return["application/octet-stream","application/zip","application/x-zip-compressed","application/x-zip"].some(t=>e.includes(t))}validateBlobSize(t){if(0===t.size)throw new y(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Downloaded file is empty");if(t.size>this.configManager.get("maxBundleSize"))throw new y(e.ErrorCode.BUNDLE_TOO_LARGE,`File size ${t.size} exceeds maximum allowed size`)}cancelDownload(e){const t=this.activeDownloads.get(e);t&&(t.controller.abort(),this.activeDownloads.delete(e),this.logger.info("Download cancelled",{bundleId:e}))}cancelAllDownloads(){for(const e of this.activeDownloads.values())e.controller.abort();const e=this.activeDownloads.size;this.activeDownloads.clear(),e>0&&this.logger.info("All downloads cancelled",{count:e})}isDownloading(e){return this.activeDownloads.has(e)}getActiveDownloadCount(){return this.activeDownloads.size}async downloadWithRetry(t,r,a){const i=this.configManager.get("retryAttempts"),n=this.configManager.get("retryDelay");let s=null;for(let e=0;e<i;e++)try{if(e>0){const t=Math.min(n*Math.pow(2,e-1),3e4);await new Promise(e=>setTimeout(e,t)),this.logger.debug("Retrying download",{bundleId:r,attempt:e,delay:t})}return await this.download(t,r,a)}catch(t){if(s=t,t instanceof y||t instanceof Error&&"AbortError"===t.name)throw t;this.logger.warn(`Download attempt ${e+1} failed`,{bundleId:r,error:t})}throw new I(e.ErrorCode.DOWNLOAD_FAILED,"Download failed after all retries",{attempts:i},s||void 0)}async blobToArrayBuffer(e){return e.arrayBuffer()}async saveBlob(t,a){if(!this.filesystem)throw new I(e.ErrorCode.MISSING_DEPENDENCY,"Filesystem not initialized");const i=await this.blobToArrayBuffer(a),n=btoa(String.fromCharCode(...new Uint8Array(i))),s=`bundles/${t}/bundle.zip`;return await this.filesystem.writeFile({path:s,data:n,directory:r.Directory.Data,recursive:!0}),this.logger.debug("Bundle saved to filesystem",{bundleId:t,path:s,size:a.size}),s}async loadBlob(t){if(!this.filesystem)throw new I(e.ErrorCode.MISSING_DEPENDENCY,"Filesystem not initialized");try{const e=`bundles/${t}/bundle.zip`,a=await this.filesystem.readFile({path:e,directory:r.Directory.Data}),i=atob(a.data),n=new Uint8Array(i.length);for(let e=0;e<i.length;e++)n[e]=i.charCodeAt(e);return new Blob([n],{type:"application/zip"})}catch(e){return this.logger.debug("Failed to load bundle from filesystem",{bundleId:t,error:e}),null}}async deleteBlob(t){if(!this.filesystem)throw new I(e.ErrorCode.MISSING_DEPENDENCY,"Filesystem not initialized");try{const e=`bundles/${t}`;await this.filesystem.rmdir({path:e,directory:r.Directory.Data,recursive:!0}),this.logger.debug("Bundle deleted from filesystem",{bundleId:t})}catch(e){this.logger.warn("Failed to delete bundle from filesystem",{bundleId:t,error:e})}}}function C(e){return new Date(1e3*e.seconds+e.nanoseconds/1e6)}const U="manifests";function L(e,t){return`${e}_${t}`}class _{constructor(e){var t;this.cache=new Map,this.config={projectId:e.projectId,databaseId:e.databaseId||"(default)",appId:e.appId,channel:e.channel,cacheDuration:e.cacheDuration||3e5,enableOffline:null===(t=e.enableOffline)||void 0===t||t},this.logger=w.getInstance()}static getInstance(e){if(!_.instance&&e&&(_.instance=new _(e)),!_.instance)throw new Error("FirestoreClient not initialized. Call with config first.");return _.instance}static resetInstance(){_.instance=null}getBaseUrl(){return`https://firestore.googleapis.com/v1/projects/${this.config.projectId}/databases/${this.config.databaseId}/documents`}async getDocument(e,t){const r=`${e}/${t}`,a=this.getFromCache(r);if(null!==a)return this.logger.debug("Firestore cache hit",{collection:e,documentId:t}),a;const i=`${this.getBaseUrl()}/${e}/${t}`;try{this.logger.debug("Fetching Firestore document",{url:i});const a=await fetch(i,{method:"GET",headers:{"Content-Type":"application/json"}});if(404===a.status)return this.logger.debug("Firestore document not found",{collection:e,documentId:t}),null;if(!a.ok)throw new Error(`Firestore error: ${a.status} ${a.statusText}`);const n=await a.json(),s=this.parseDocument(n);return this.setCache(r,s),s}catch(e){this.logger.error("Firestore fetch error",e);const t=this.getFromCache(r,!0);if(null!==t)return this.logger.warn("Using stale cache due to fetch error"),t;throw e}}async getManifest(){const e=L(this.config.appId,this.config.channel);return this.getDocument(U,e)}async getManifestFor(e,t){const r=L(e,t);return this.getDocument(U,r)}parseDocument(e){return this.parseValue({mapValue:{fields:e.fields}})}parseValue(e){if("stringValue"in e)return e.stringValue;if("integerValue"in e)return parseInt(e.integerValue,10);if("doubleValue"in e)return e.doubleValue;if("booleanValue"in e)return e.booleanValue;if("nullValue"in e)return null;if("timestampValue"in e)return this.parseTimestamp(e.timestampValue);if("mapValue"in e){const t={},r=e.mapValue.fields||{};for(const[e,a]of Object.entries(r))t[e]=this.parseValue(a);return t}return"arrayValue"in e?(e.arrayValue.values||[]).map(e=>this.parseValue(e)):null}parseTimestamp(e){const t=new Date(e);return{seconds:Math.floor(t.getTime()/1e3),nanoseconds:t.getTime()%1e3*1e6}}getFromCache(e,t=!1){const r=this.cache.get(e);return r&&(Date.now()<r.expiresAt||t)?r.data:null}setCache(e,t){const r=Date.now();this.cache.set(e,{data:t,timestamp:r,expiresAt:r+this.config.cacheDuration})}clearCache(){this.cache.clear(),this.logger.debug("Firestore cache cleared")}clearCacheEntry(e,t){this.cache.delete(`${e}/${t}`)}getCacheStats(){return{size:this.cache.size,keys:Array.from(this.cache.keys())}}getConfig(){return Object.assign({},this.config)}setChannel(e){this.config.channel=e,this.clearCache()}isConfigured(){return!!(this.config.projectId&&this.config.appId&&this.config.channel)}}_.instance=null;class R{constructor(e){this.firestoreClient=e,this.logger=w.getInstance()}async checkForUpdates(e){const{currentVersion:t,deviceInfo:r,checkDeltas:a=!0}=e;try{e.forceRefresh&&this.firestoreClient.clearCache();const i=await this.firestoreClient.getManifest();if(!i)return this.logger.debug("No manifest found"),{updateAvailable:!1};if(!this.isNewerVersion(t,i.current.version))return{updateAvailable:!1,version:i.current.version};const n=await this.checkRolloutEligibility(i.rollout,r);if(!n.eligible)return{updateAvailable:!0,version:i.current.version,rolloutEligible:!1,rolloutReason:n.reason};const s={updateAvailable:!0,version:i.current.version,bundleUrl:i.current.bundleUrl,minNativeVersion:i.current.minNativeVersion,releaseNotes:i.current.releaseNotes,signature:i.current.signature,checksum:i.current.checksum,mandatory:i.current.mandatory,size:i.current.size,rolloutEligible:!0};if(a&&i.deltas){const e=i.deltas[t];e?(s.delta={available:!0,patchUrl:e.patchUrl,patchSize:e.patchSize,patchChecksum:e.patchChecksum,targetChecksum:e.targetChecksum},this.logger.debug("Delta update available",{from:t,to:i.current.version,patchSize:e.patchSize})):s.delta={available:!1}}return s}catch(e){throw this.logger.error("Error checking for updates",e),e}}async checkRolloutEligibility(e,t){var r,a;if(!e.enabled)return{eligible:!0,reason:"Rollout not enabled, all devices eligible"};const i=Date.now();if(i<C(e.startTime).getTime())return{eligible:!1,reason:"Rollout not started yet"};if(e.endTime&&i>C(e.endTime).getTime())return{eligible:!1,reason:"Rollout ended"};if("scheduled"===(null===(r=e.schedule)||void 0===r?void 0:r.type)&&e.schedule.scheduledTime&&i<C(e.schedule.scheduledTime).getTime())return{eligible:!1,reason:"Scheduled for later"};if(e.targetSegments){const r=this.checkSegments(e.targetSegments,t);if(!r.eligible)return r}let n=e.percentage;"gradual"===(null===(a=e.schedule)||void 0===a?void 0:a.type)&&(n=this.calculateGradualPercentage(e));const s=await this.getDevicePercentile(t.deviceId);return s>n?{eligible:!1,reason:`Device percentile ${s.toFixed(1)}% > rollout ${n}%`}:{eligible:!0,reason:"All checks passed"}}checkSegments(e,t){return e.platforms&&e.platforms.length>0&&!e.platforms.includes(t.platform)?{eligible:!1,reason:`Platform ${t.platform} not targeted`}:e.minAppVersion&&this.compareVersions(t.appVersion,e.minAppVersion)<0?{eligible:!1,reason:`App version below minimum ${e.minAppVersion}`}:e.maxAppVersion&&this.compareVersions(t.appVersion,e.maxAppVersion)>0?{eligible:!1,reason:`App version above maximum ${e.maxAppVersion}`}:e.deviceIds&&e.deviceIds.length>0&&!e.deviceIds.includes(t.deviceId)?{eligible:!1,reason:"Device not in whitelist"}:!(e.regions&&e.regions.length>0)||t.region&&e.regions.includes(t.region)?{eligible:!0,reason:"Segment checks passed"}:{eligible:!1,reason:`Region ${t.region||"unknown"} not targeted`}}calculateGradualPercentage(e){var t,r;if(!(null===(t=e.schedule)||void 0===t?void 0:t.gradualSteps)||!(null===(r=e.schedule)||void 0===r?void 0:r.gradualInterval))return e.percentage;const a=C(e.startTime).getTime(),i=(Date.now()-a)/36e5,n=Math.floor(i/e.schedule.gradualInterval),s=e.schedule.gradualSteps;return n>=s.length?s[s.length-1]:s[n]}async getDevicePercentile(e){const t=await this.hashString(e);return this.hashToPercentile(t)}async hashString(e){const t=(new TextEncoder).encode(e),r=await crypto.subtle.digest("SHA-256",t);return Array.from(new Uint8Array(r)).map(e=>e.toString(16).padStart(2,"0")).join("")}hashToPercentile(e){return parseInt(e.substring(0,8),16)/parseInt("ffffffff",16)*100}compareVersions(e,t){const r=e.split("-")[0],a=t.split("-")[0],i=r.split(".").map(Number),n=a.split(".").map(Number);for(let e=0;e<Math.max(i.length,n.length);e++){const t=i[e]||0,r=n[e]||0;if(t>r)return 1;if(t<r)return-1}const s=e.includes("-"),o=t.includes("-");return s&&!o?-1:!s&&o?1:0}isNewerVersion(e,t){return this.compareVersions(e,t)<0}async getManifest(){return this.firestoreClient.getManifest()}async getManifestFor(e,t){return this.firestoreClient.getManifestFor(e,t)}clearCache(){this.firestoreClient.clearCache()}}class O{constructor(){this.VERSION_CHECK_CACHE_KEY="capacitor_native_update_version_cache",this.CACHE_DURATION=3e5,this.preferences=null,this.memoryCache=new Map,this.firestoreClient=null,this.manifestReader=null,this.logger=w.getInstance(),this.configManager=m.getInstance(),this.securityValidator=v.getInstance()}static compareVersions(e,t){try{const[r,a]=e.split("-"),[i,n]=t.split("-"),s=r.split(".").map(Number),o=i.split(".").map(Number);for(let e=0;e<3;e++){const t=s[e]||0,r=o[e]||0;if(t>r)return 1;if(t<r)return-1}return a&&!n?-1:!a&&n?1:a&&n?a.localeCompare(n):0}catch(r){return e===t?0:e>t?1:-1}}static isValidVersion(e){return/^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/.test(e)}static shouldUpdate(e,t,r){return!(r&&O.compareVersions(e,r)<0)&&O.compareVersions(e,t)<0}async initialize(){if(this.preferences=this.configManager.get("preferences"),!this.preferences)throw new y(e.ErrorCode.MISSING_DEPENDENCY,"Preferences not configured. Please configure the plugin first.");if("firestore"===this.configManager.get("backendType")){const t=this.configManager.get("firestore");if(!t)throw new y(e.ErrorCode.INVALID_CONFIG,"Firestore configuration required when using firestore backend");this.firestoreClient=new _(t),this.manifestReader=new R(this.firestoreClient),this.logger.debug("Firestore backend initialized")}}async checkForUpdatesFromFirestore(t,r){if(!this.manifestReader)throw new y(e.ErrorCode.INVALID_CONFIG,"Firestore backend not initialized. Call initialize() first or configure firestore backend.");this.securityValidator.validateVersion(t);const a={currentVersion:t,deviceInfo:r,checkDeltas:this.configManager.get("enableDeltaUpdates")};try{const e=await this.manifestReader.checkForUpdates(a);return e&&this.logger.info("Firestore update check completed",{currentVersion:t,latestVersion:e.version,updateAvailable:e.updateAvailable,eligible:e.rolloutEligible}),e}catch(e){return this.logger.error("Failed to check for updates from Firestore",e),null}}async checkForUpdatesAuto(t,r){if("firestore"===(this.configManager.get("backendType")||"http")){if(!r)throw new y(e.ErrorCode.INVALID_CONFIG,"Device info is required for Firestore backend");return this.checkForUpdatesFromFirestore(t,r)}const a=this.configManager.get("serverUrl"),i=this.configManager.get("channel")||"production",n=this.configManager.get("appId");if(!a||!n)throw new y(e.ErrorCode.INVALID_CONFIG,"Server URL and App ID are required for HTTP backend");return this.checkForUpdates(a,i,t,n)}async checkForUpdates(t,r,a,i){if(this.securityValidator.validateUrl(t),this.securityValidator.validateVersion(a),!r||!i)throw new y(e.ErrorCode.INVALID_CONFIG,"Channel and appId are required");const n=`${r}-${i}`,s=await this.getCachedVersionInfo(n);if(s&&s.channel===r&&Date.now()-s.timestamp<this.CACHE_DURATION)return this.logger.debug("Returning cached version info",{channel:r,version:s.data.version}),s.data;try{const s=new URL(`${t}/check`);s.searchParams.append("channel",r),s.searchParams.append("version",a),s.searchParams.append("appId",i),s.searchParams.append("platform","web");const o=await fetch(s.toString(),{method:"GET",headers:{"Content-Type":"application/json","X-App-Version":a,"X-App-Id":i},signal:AbortSignal.timeout(this.configManager.get("downloadTimeout"))});if(!o.ok)throw new Error(`Version check failed: ${o.status}`);const l=await o.json();if(!l.version)throw new y(e.ErrorCode.INVALID_BUNDLE_FORMAT,"No version in server response");return this.securityValidator.validateVersion(l.version),l.bundleUrl&&this.securityValidator.validateUrl(l.bundleUrl),l.minAppVersion&&this.securityValidator.validateVersion(l.minAppVersion),await this.cacheVersionInfo(n,r,l),this.logger.info("Version check completed",{channel:r,currentVersion:a,latestVersion:l.version,updateAvailable:this.isNewerVersion(l.version,a)}),l}catch(e){return this.logger.error("Failed to check for updates",e),null}}isNewerVersion(e,t){try{const r=this.parseVersion(e),a=this.parseVersion(t);return r.major!==a.major?r.major>a.major:r.minor!==a.minor?r.minor>a.minor:r.patch!==a.patch?r.patch>a.patch:!(r.prerelease&&!a.prerelease||(r.prerelease||!a.prerelease)&&(!r.prerelease||!a.prerelease||!(r.prerelease>a.prerelease)))}catch(r){return this.logger.error("Failed to compare versions",{version1:e,version2:t,error:r}),!1}}isUpdateMandatory(e,t){if(!t)return!1;try{return this.securityValidator.validateVersion(e),this.securityValidator.validateVersion(t),!this.isNewerVersion(e,t)&&e!==t}catch(e){return this.logger.error("Failed to check mandatory update",e),!1}}parseVersion(t){const r=t.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/);if(!r)throw new y(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Invalid version format");return{major:parseInt(r[1],10),minor:parseInt(r[2],10),patch:parseInt(r[3],10),prerelease:r[4],build:r[5]}}buildVersionString(e){let t=`${e.major}.${e.minor}.${e.patch}`;return e.prerelease&&(t+=`-${e.prerelease}`),e.build&&(t+=`+${e.build}`),t}isCompatibleWithNativeVersion(e,t,r){if(!r)return!0;try{const a=r[e];return!a||(this.securityValidator.validateVersion(t),this.securityValidator.validateVersion(a),!this.isNewerVersion(a,t))}catch(e){return this.logger.error("Failed to check compatibility",e),!1}}async getCachedVersionInfo(e){const t=this.memoryCache.get(e);if(t&&Date.now()-t.timestamp<this.CACHE_DURATION)return t;try{const{value:t}=await this.preferences.get({key:this.VERSION_CHECK_CACHE_KEY});if(!t)return null;const r=JSON.parse(t)[e];if(r&&Date.now()-r.timestamp<this.CACHE_DURATION)return this.memoryCache.set(e,r),r}catch(e){this.logger.debug("Failed to load cached version info",e)}return null}async cacheVersionInfo(e,t,r){const a={channel:t,data:r,timestamp:Date.now()};this.memoryCache.set(e,a);try{const{value:t}=await this.preferences.get({key:this.VERSION_CHECK_CACHE_KEY}),r=t?JSON.parse(t):{},i=Date.now();for(const e in r)i-r[e].timestamp>2*this.CACHE_DURATION&&delete r[e];r[e]=a,await this.preferences.set({key:this.VERSION_CHECK_CACHE_KEY,value:JSON.stringify(r)})}catch(e){this.logger.warn("Failed to cache version info",e)}}async clearVersionCache(){this.memoryCache.clear();try{await this.preferences.remove({key:this.VERSION_CHECK_CACHE_KEY})}catch(e){this.logger.warn("Failed to clear version cache",e)}}shouldBlockDowngrade(e,t){try{return this.securityValidator.isVersionDowngrade(e,t)}catch(e){return this.logger.error("Failed to check downgrade",e),!0}}}class S{constructor(e){this.config=e,this.logger=new w("AppUpdateChecker")}async checkServerVersion(e){if(!this.config.updateUrl)return{};try{const e=new URL(`${this.config.updateUrl}/app-version`);e.searchParams.append("platform",this.getPlatform()),e.searchParams.append("current",await this.getCurrentVersion()),this.config.channel&&e.searchParams.append("channel",this.config.channel);const t=await fetch(e.toString(),{method:"GET",headers:{Accept:"application/json","X-App-Version":await this.getCurrentVersion(),"X-App-Platform":this.getPlatform()}});if(!t.ok)throw new Error(`Server returned ${t.status}`);const r=await t.json();return{availableVersion:r.version,updatePriority:r.priority,releaseNotes:r.releaseNotes,updateSize:r.size,updateURL:r.downloadUrl}}catch(e){return this.logger.error("Failed to check server version",e),{}}}compareVersions(e,t){const r=e.split(".").map(Number),a=t.split(".").map(Number);for(let e=0;e<Math.max(r.length,a.length);e++){const t=r[e]||0,i=a[e]||0;if(t>i)return 1;if(t<i)return-1}return 0}isUpdateRequired(e,t,r){return this.compareVersions(e,t)<0||!!(r&&this.compareVersions(e,r)<0)}determineUpdatePriority(e,t){const[r,a]=e.split(".").map(Number);return r>0?5:a>0&&t&&t>30?4:a>0?3:1}async getCurrentVersion(){return"1.0.0"}getPlatform(){if("undefined"!=typeof window){const e=window.navigator.userAgent;if(/android/i.test(e))return"android";if(/iPad|iPhone|iPod/.test(e))return"ios"}return"web"}}var T;!function(e){e[e.UNKNOWN=0]="UNKNOWN",e[e.PENDING=1]="PENDING",e[e.DOWNLOADING=2]="DOWNLOADING",e[e.INSTALLING=3]="INSTALLING",e[e.INSTALLED=4]="INSTALLED",e[e.FAILED=5]="FAILED",e[e.CANCELED=6]="CANCELED",e[e.DOWNLOADED=11]="DOWNLOADED"}(T||(T={}));class M{constructor(e){this.logger=new w("AppUpdateInstaller"),this.currentState={installStatus:T.UNKNOWN,packageName:"",availableVersion:""}}async startImmediateUpdate(){if(this.logger.log("Starting immediate update installation"),this.updateState(T.PENDING),this.isAndroid())this.logger.log("Triggering Android immediate update");else{if(!this.isIOS())throw new Error("Immediate updates not supported on web platform");this.logger.log("Opening iOS App Store for update")}}async startFlexibleUpdate(){if(this.logger.log("Starting flexible update download"),this.updateState(T.DOWNLOADING),this.isWeb())this.simulateFlexibleUpdate();else{if(!this.isAndroid())throw new Error("Flexible updates not supported on iOS");this.logger.log("Starting Android flexible update")}}async completeFlexibleUpdate(){if(this.logger.log("Completing flexible update installation"),this.currentState.installStatus!==T.DOWNLOADED)throw new Error("Update not ready for installation");this.updateState(T.INSTALLING),this.isAndroid()?this.logger.log("Completing Android update installation"):setTimeout(()=>{this.updateState(T.INSTALLED)},1e3)}async cancelUpdate(){this.logger.log("Cancelling update"),this.currentState.installStatus===T.DOWNLOADING&&this.updateState(T.CANCELED)}async getInstallState(){return Object.assign({},this.currentState)}onProgress(e){this.progressCallback=e}updateState(e,t){this.currentState.installStatus=e,void 0!==t&&(this.currentState.installErrorCode=t),this.logger.log("Update state changed",this.currentState)}simulateFlexibleUpdate(){let e=0;const t=52428800,r=1048576,a=setInterval(()=>{e+=r,e>=t&&(e=t,clearInterval(a),this.updateState(T.DOWNLOADED));const i={bytesDownloaded:e,totalBytesToDownload:t,percentComplete:Math.round(e/t*100),downloadSpeed:r,estimatedTime:Math.ceil((t-e)/r)};this.progressCallback&&this.progressCallback(i)},1e3)}isAndroid(){return"undefined"!=typeof window&&/android/i.test(window.navigator.userAgent)}isIOS(){return"undefined"!=typeof window&&/iPad|iPhone|iPod/.test(window.navigator.userAgent)}isWeb(){return!this.isAndroid()&&!this.isIOS()}}class V{constructor(e){this.config=e,this.logger=new w("PlatformAppUpdate"),this.platform=t.Capacitor.getPlatform()}async checkForUpdate(e){this.logger.log("Checking for platform update: "+this.platform);const t=await this.getVersionInfo(),r={updateAvailable:!1,currentVersion:t.currentVersion,availableVersion:t.currentVersion};if("android"===this.platform)return r;if("ios"===this.platform)return r;if(this.config.webUpdateUrl)try{const e=await fetch(this.config.webUpdateUrl),a=await e.json();a.version&&a.version!==t.currentVersion&&(r.updateAvailable=!0,r.availableVersion=a.version,r.releaseNotes=a.releaseNotes,r.updateURL=a.downloadUrl)}catch(e){this.logger.error("Failed to check web update",e)}return r}async getVersionInfo(){return{currentVersion:"1.0.0",buildNumber:"1",packageName:"com.example.app",platform:this.platform,minimumVersion:this.config.minimumVersion}}async getAppStoreUrl(){const e=this.platform;let t="";if("ios"===e){const e=this.config.appStoreId||this.config.iosAppId;if(!e)throw new Error("App Store ID not configured");t=`https://apps.apple.com/app/id${e}`}else t="android"===e?`https://play.google.com/store/apps/details?id=${this.config.packageName||(await this.getVersionInfo()).packageName}`:this.config.webUpdateUrl||window.location.origin;return{url:t,platform:e}}async openUrl(e){if("undefined"==typeof window||!window.open)throw new Error("Cannot open URL on this platform");window.open(e,"_blank")}isUpdateSupported(){return"android"===this.platform||"ios"!==this.platform}getUpdateCapabilities(){const e={immediateUpdate:!1,flexibleUpdate:!1,backgroundDownload:!1,inAppReview:!1};return"android"===this.platform?(e.immediateUpdate=!0,e.flexibleUpdate=!0,e.backgroundDownload=!0,e.inAppReview=!0):"ios"===this.platform&&(e.inAppReview=!0),e}}class B{constructor(e){this.listeners=new Map,this.config=e,this.logger=new w("AppUpdateManager"),this.checker=new S(e),this.installer=new M(e),this.platformUpdate=new V(e)}async checkAppUpdate(e){try{this.logger.log("Checking for app updates",e);const t=await this.platformUpdate.checkForUpdate(e);if(!t.updateAvailable&&this.config.updateUrl){const r=await this.checker.checkServerVersion(e);return this.mergeUpdateInfo(t,r)}return t}catch(e){throw this.logger.error("Failed to check app update",e),e}}async startImmediateUpdate(){try{this.logger.log("Starting immediate update");const e=await this.checkAppUpdate();if(!e.immediateUpdateAllowed)throw new Error("Immediate update not allowed");await this.installer.startImmediateUpdate(),this.emit("appUpdateStateChanged",{installStatus:1,packageName:this.config.packageName||"",availableVersion:e.availableVersion||""})}catch(e){throw this.logger.error("Failed to start immediate update",e),e}}async startFlexibleUpdate(){try{this.logger.log("Starting flexible update");const e=await this.checkAppUpdate();if(!e.flexibleUpdateAllowed)throw new Error("Flexible update not allowed");await this.installer.startFlexibleUpdate(),this.installer.onProgress(e=>{this.emit("appUpdateProgress",e)}),this.emit("appUpdateStateChanged",{installStatus:2,packageName:this.config.packageName||"",availableVersion:e.availableVersion||""})}catch(e){throw this.logger.error("Failed to start flexible update",e),e}}async completeFlexibleUpdate(){try{this.logger.log("Completing flexible update"),await this.installer.completeFlexibleUpdate(),this.emit("appUpdateStateChanged",{installStatus:3,packageName:this.config.packageName||"",availableVersion:""})}catch(e){throw this.logger.error("Failed to complete flexible update",e),e}}async getVersionInfo(){try{return this.logger.log("Getting version info"),await this.platformUpdate.getVersionInfo()}catch(e){throw this.logger.error("Failed to get version info",e),e}}async isMinimumVersionMet(){try{this.logger.log("Checking minimum version");const e=await this.getVersionInfo(),t=this.config.minimumVersion||"0.0.0",r=this.checker.compareVersions(e.currentVersion,t)>=0;return{isMet:r,currentVersion:e.currentVersion,minimumVersion:t,updateRequired:!r&&!0===this.config.enforceMinVersion}}catch(e){throw this.logger.error("Failed to check minimum version",e),e}}async getAppUpdateInfo(){return this.checkAppUpdate()}async performImmediateUpdate(){return this.startImmediateUpdate()}async openAppStore(e){try{this.logger.log("Opening app store");const e=await this.getAppStoreUrl();await this.platformUpdate.openUrl(e.url)}catch(e){throw this.logger.error("Failed to open app store",e),e}}async getAppStoreUrl(){try{return this.logger.log("Getting app store URL"),await this.platformUpdate.getAppStoreUrl()}catch(e){throw this.logger.error("Failed to get app store URL",e),e}}async getUpdateInstallState(){try{return this.logger.log("Getting update install state"),await this.installer.getInstallState()}catch(e){throw this.logger.error("Failed to get update install state",e),e}}addListener(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),{remove:async()=>{const r=this.listeners.get(e);r&&r.delete(t)}}}async removeAllListeners(e){e?this.listeners.delete(e):this.listeners.clear()}emit(e,t){const r=this.listeners.get(e);r&&r.forEach(r=>{try{r(t)}catch(t){this.logger.error(`Error in ${e} listener`,t)}})}mergeUpdateInfo(e,t){return Object.assign(Object.assign(Object.assign({},e),t),{updateAvailable:e.updateAvailable||!!t.availableVersion,availableVersion:t.availableVersion||e.availableVersion})}}class F{constructor(){this.listeners=new Map}static getInstance(){return F.instance||(F.instance=new F),F.instance}addListener(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),()=>{const r=this.listeners.get(e);r&&(r.delete(t),0===r.size&&this.listeners.delete(e))}}emit(e,t){const r=this.listeners.get(e);r&&r.forEach(r=>{try{r(t)}catch(t){console.error(`Error in event listener for ${e}:`,t)}})}removeListeners(e){this.listeners.delete(e)}removeAllListeners(){this.listeners.clear()}listenerCount(e){var t;return(null===(t=this.listeners.get(e))||void 0===t?void 0:t.size)||0}eventNames(){return Array.from(this.listeners.keys())}}class k{constructor(){this.bundleManager=null,this.downloadManager=null,this.versionManager=null,this.appUpdateManager=null,this.initialized=!1,this.configManager=m.getInstance(),this.logger=w.getInstance(),this.securityValidator=v.getInstance(),this.eventEmitter=F.getInstance()}static getInstance(){return k.instance||(k.instance=new k),k.instance}async initialize(e){var t,i;if(this.initialized)this.logger.warn("Plugin already initialized");else try{this.configManager.configure(e),e.filesystem||(e.filesystem=r.Filesystem),e.preferences||(e.preferences=a.Preferences),this.bundleManager=new N,await this.bundleManager.initialize(),this.downloadManager=new b,await this.downloadManager.initialize(),this.versionManager=new O,await this.versionManager.initialize(),this.appUpdateManager=new B({serverUrl:e.serverUrl||e.baseUrl||"",channel:e.channel||"production",autoCheck:null===(t=e.autoCheck)||void 0===t||t,autoUpdate:null!==(i=e.autoUpdate)&&void 0!==i&&i,updateStrategy:e.updateStrategy,publicKey:e.publicKey,requireSignature:e.requireSignature,checksumAlgorithm:e.checksumAlgorithm,checkInterval:e.checkInterval,security:e.security}),this.setupAppUpdateEventBridge(),this.initialized=!0,this.logger.info("Plugin initialized successfully")}catch(e){throw this.logger.error("Failed to initialize plugin",e),e}}isInitialized(){return this.initialized&&this.configManager.isConfigured()}ensureInitialized(){if(!this.isInitialized())throw new E(e.ErrorCode.NOT_CONFIGURED,"Plugin not initialized. Please call initialize() first.")}getBundleManager(){return this.ensureInitialized(),this.bundleManager}getDownloadManager(){return this.ensureInitialized(),this.downloadManager}getVersionManager(){return this.ensureInitialized(),this.versionManager}getConfigManager(){return this.configManager}getLogger(){return this.logger}getSecurityValidator(){return this.securityValidator}getAppUpdateManager(){return this.ensureInitialized(),this.appUpdateManager}getEventEmitter(){return this.eventEmitter}async reset(){this.logger.info("Resetting plugin state"),this.bundleManager&&await this.bundleManager.clearAllBundles(),this.versionManager&&await this.versionManager.clearVersionCache(),this.downloadManager&&this.downloadManager.cancelAllDownloads(),this.bundleManager=null,this.downloadManager=null,this.versionManager=null,this.appUpdateManager=null,this.initialized=!1,this.eventEmitter.removeAllListeners(),this.logger.info("Plugin reset complete")}async cleanup(){this.logger.info("Cleaning up plugin resources"),this.downloadManager&&this.downloadManager.cancelAllDownloads(),this.bundleManager&&await this.bundleManager.cleanExpiredBundles(),this.logger.info("Cleanup complete")}setupAppUpdateEventBridge(){this.appUpdateManager&&(this.appUpdateManager.addListener("appUpdateStateChanged",e=>{this.eventEmitter.emit("appUpdateStateChanged",e)}),this.appUpdateManager.addListener("appUpdateProgress",e=>{this.eventEmitter.emit("appUpdateProgress",e)}))}}class P{constructor(){this.initialized=!1,this.windowEventListeners=new Map,this.pluginManager=k.getInstance()}async initialize(e){await this.pluginManager.initialize(e),this.initialized=!0,this.setupWindowEventBridge()}isInitialized(){return this.initialized&&this.pluginManager.isInitialized()}async reset(){await this.pluginManager.reset()}async cleanup(){await this.pluginManager.cleanup()}async configure(e){var t;let r;r="config"in e&&"object"==typeof e.config?e.config:{baseUrl:null===(t=e.liveUpdate)||void 0===t?void 0:t.serverUrl},this.initialized?this.pluginManager.getConfigManager().configure(r):await this.initialize(r)}async getSecurityInfo(){return{enforceHttps:!0,certificatePinning:{enabled:!1,pins:[]},validateInputs:!0,secureStorage:!0}}async sync(e){const t=this.pluginManager.getBundleManager();try{const e=await t.getActiveBundle();return{status:d.UP_TO_DATE,version:(null==e?void 0:e.version)||"1.0.0"}}catch(e){return{status:d.ERROR,error:{code:g.UNKNOWN_ERROR,message:e instanceof Error?e.message:"Sync failed"}}}}async download(e){const t=this.pluginManager.getDownloadManager(),r=this.pluginManager.getBundleManager(),a=await t.downloadWithRetry(e.url,e.version),i=await t.saveBlob(e.version,a),n={bundleId:e.version,version:e.version,path:i,downloadTime:Date.now(),size:a.size,status:h.READY,checksum:e.checksum,signature:e.signature,verified:!1};return await r.saveBundleInfo(n),n}async set(e){const t=this.pluginManager.getBundleManager();await t.setActiveBundle(e.bundleId)}async reload(){"undefined"!=typeof window&&window.location.reload()}async current(){const t=this.pluginManager.getBundleManager(),r=await t.getActiveBundle();if(!r)throw new E(e.ErrorCode.FILE_NOT_FOUND,"No active bundle found");return r}async list(){return this.pluginManager.getBundleManager().getAllBundles()}async delete(e){const t=this.pluginManager.getBundleManager();if(e.bundleId)await t.deleteBundle(e.bundleId);else if(void 0!==e.keepVersions){const r=(await t.getAllBundles()).sort((e,t)=>t.downloadTime-e.downloadTime);for(let a=e.keepVersions;a<r.length;a++)await t.deleteBundle(r[a].bundleId)}}async notifyAppReady(){const e=this.pluginManager.getBundleManager(),t=await e.getActiveBundle();t&&(t.status=h.ACTIVE,await e.saveBundleInfo(t))}async getLatest(){return{available:!1}}async setChannel(e){const t=this.pluginManager.getConfigManager().get("preferences");t&&await t.set({key:"update_channel",value:e})}async setUpdateUrl(e){this.pluginManager.getConfigManager().configure({baseUrl:e})}async validateUpdate(e){const t=this.pluginManager.getSecurityValidator();try{const r=await t.validateChecksum(new ArrayBuffer(0),e.checksum);return{isValid:r,details:{checksumValid:r,signatureValid:!0,sizeValid:!0,versionValid:!0}}}catch(e){return{isValid:!1,error:e instanceof Error?e.message:"Validation failed"}}}async getAppUpdateInfo(){return this.pluginManager.getAppUpdateManager().getAppUpdateInfo()}async performImmediateUpdate(){return this.pluginManager.getAppUpdateManager().performImmediateUpdate()}async startFlexibleUpdate(){return this.pluginManager.getAppUpdateManager().startFlexibleUpdate()}async completeFlexibleUpdate(){return this.pluginManager.getAppUpdateManager().completeFlexibleUpdate()}async openAppStore(e){return this.pluginManager.getAppUpdateManager().openAppStore(e)}async requestReview(){return{displayed:!1,error:"Reviews are not supported on web"}}async canRequestReview(){return{canRequest:!1,reason:"Reviews are not supported on web"}}async enableBackgroundUpdates(e){const t=this.pluginManager.getConfigManager().get("preferences");t&&await t.set({key:"background_update_config",value:JSON.stringify(e)})}async disableBackgroundUpdates(){const e=this.pluginManager.getConfigManager().get("preferences");e&&await e.remove({key:"background_update_config"})}async getBackgroundUpdateStatus(){return{enabled:!1,isRunning:!1,checkCount:0,failureCount:0}}async scheduleBackgroundCheck(t){throw new E(e.ErrorCode.PLATFORM_NOT_SUPPORTED,"Background updates are not supported on web")}async triggerBackgroundCheck(){return{success:!1,updatesFound:!1,notificationSent:!1,error:{code:g.PLATFORM_NOT_SUPPORTED,message:"Background updates are not supported on web"}}}async setNotificationPreferences(e){const t=this.pluginManager.getConfigManager().get("preferences");t&&await t.set({key:"notification_preferences",value:JSON.stringify(e)})}async getNotificationPermissions(){return{granted:!1,canRequest:!1}}async requestNotificationPermissions(){return!1}async addListener(e,t){const r=this.pluginManager.getEventEmitter().addListener(e,t);return{remove:async()=>{r()}}}async removeAllListeners(){this.pluginManager.getEventEmitter().removeAllListeners()}setupWindowEventBridge(){const e=this.pluginManager.getEventEmitter();["appUpdateAvailable","appUpdateProgress","appUpdateReady","appUpdateFailed","appUpdateNotificationClicked","appUpdateInstallClicked"].forEach(t=>{const r=r=>{e.emit(t,r.detail)};window.addEventListener(t,r),this.windowEventListeners.set(t,r)})}}const z=t.registerPlugin("NativeUpdate",{web:()=>new P});e.BundleManager=N,e.CacheManager=class{constructor(){this.filesystem=null,this.memoryCache=new Map,this.CACHE_DIR="cache",this.logger=w.getInstance(),this.configManager=m.getInstance()}async initialize(){if(this.filesystem=this.configManager.get("filesystem"),!this.filesystem)throw new Error("Filesystem not configured");try{await this.filesystem.mkdir({path:this.CACHE_DIR,directory:r.Directory.Data,recursive:!0})}catch(e){this.logger.debug("Cache directory may already exist",e)}await this.cleanExpiredCache()}async set(e,t,r){const a=Date.now()+(r||this.configManager.get("cacheExpiration")),i={data:t,timestamp:Date.now(),expiry:a};this.memoryCache.set(e,i),this.shouldPersist(t)&&await this.persistToFile(e,i),this.logger.debug("Cache entry set",{key:e,expiry:new Date(a)})}async get(e){const t=this.memoryCache.get(e);if(t){if(Date.now()<t.expiry)return t.data;this.memoryCache.delete(e)}const r=await this.loadFromFile(e);if(r){if(Date.now()<r.expiry)return this.memoryCache.set(e,r),r.data;await this.removeFile(e)}return null}async has(e){return null!==await this.get(e)}async remove(e){this.memoryCache.delete(e),await this.removeFile(e),this.logger.debug("Cache entry removed",{key:e})}async clear(){this.memoryCache.clear();try{await this.filesystem.rmdir({path:this.CACHE_DIR,directory:r.Directory.Data,recursive:!0}),await this.filesystem.mkdir({path:this.CACHE_DIR,directory:r.Directory.Data,recursive:!0})}catch(e){this.logger.warn("Failed to clear cache directory",e)}this.logger.info("Cache cleared")}async cleanExpiredCache(){const e=Date.now();let t=0;for(const[r,a]of this.memoryCache)e>=a.expiry&&(this.memoryCache.delete(r),t++);try{const a=await this.filesystem.readdir({path:this.CACHE_DIR,directory:r.Directory.Data});for(const r of a.files){const a=r.name.replace(".json",""),i=await this.loadFromFile(a);(!i||e>=i.expiry)&&(await this.removeFile(a),t++)}}catch(e){this.logger.debug("Failed to clean filesystem cache",e)}t>0&&this.logger.info("Cleaned expired cache entries",{count:t})}async getStats(){let e=0,t=0;try{const a=await this.filesystem.readdir({path:this.CACHE_DIR,directory:r.Directory.Data});e=a.files.length;for(const e of a.files)t+=(await this.filesystem.stat({path:`${this.CACHE_DIR}/${e.name}`,directory:r.Directory.Data})).size||0}catch(e){this.logger.debug("Failed to get cache stats",e)}return{memoryEntries:this.memoryCache.size,fileEntries:e,totalSize:t}}async cacheBundleMetadata(e){const t=`bundle_meta_${e.bundleId}`;await this.set(t,e,864e5)}async getCachedBundleMetadata(e){return this.get(`bundle_meta_${e}`)}shouldPersist(e){return"object"==typeof e||"string"==typeof e&&e.length>1024}async persistToFile(e,t){if(this.filesystem)try{const a=`${this.CACHE_DIR}/${e}.json`,i=JSON.stringify(t);await this.filesystem.writeFile({path:a,data:i,directory:r.Directory.Data,encoding:r.Encoding.UTF8})}catch(t){this.logger.warn("Failed to persist cache to file",{key:e,error:t})}}async loadFromFile(e){if(!this.filesystem)return null;try{const t=`${this.CACHE_DIR}/${e}.json`,a=await this.filesystem.readFile({path:t,directory:r.Directory.Data,encoding:r.Encoding.UTF8});return JSON.parse(a.data)}catch(e){return null}}async removeFile(e){if(this.filesystem)try{const t=`${this.CACHE_DIR}/${e}.json`;await this.filesystem.deleteFile({path:t,directory:r.Directory.Data})}catch(t){this.logger.debug("Failed to remove cache file",{key:e,error:t})}}},e.ConfigManager=m,e.ConfigurationError=class extends E{constructor(t,r){super(e.ErrorCode.INVALID_CONFIG,t,r),this.name="ConfigurationError"}},e.DownloadError=I,e.DownloadManager=b,e.Logger=w,e.NativeUpdate=z,e.NativeUpdateError=E,e.PluginManager=k,e.SecurityValidator=v,e.StorageError=A,e.UpdateErrorClass=D,e.UpdateManager=class{constructor(){this.filesystem=null,this.updateInProgress=!1,this.currentState=null,this.pluginManager=k.getInstance(),this.securityValidator=v.getInstance()}async initialize(){if(this.filesystem=this.pluginManager.getConfigManager().get("filesystem"),!this.filesystem)throw new D(e.ErrorCode.MISSING_DEPENDENCY,"Filesystem not configured")}async applyUpdate(t,r){if(this.updateInProgress)throw new D(e.ErrorCode.UPDATE_FAILED,"Another update is already in progress");const a=this.pluginManager.getLogger(),i=this.pluginManager.getBundleManager();try{this.updateInProgress=!0,a.info("Starting bundle update",{bundleId:t});const n=await i.getBundle(t);if(!n)throw new D(e.ErrorCode.FILE_NOT_FOUND,`Bundle ${t} not found`);if("READY"!==n.status&&"ACTIVE"!==n.status)throw new D(e.ErrorCode.BUNDLE_NOT_READY,`Bundle ${t} is not ready for installation`);const s=await i.getActiveBundle();this.currentState={currentBundle:s,newBundle:n,backupPath:null,startTime:Date.now()},await this.validateUpdate(s,n,r),s&&"default"!==s.bundleId&&(this.currentState.backupPath=await this.createBackup(s)),await this.performUpdate(n),await this.verifyUpdate(n),await i.setActiveBundle(t),(null==r?void 0:r.cleanupOldBundles)&&await i.cleanupOldBundles(r.keepBundleCount||3),a.info("Bundle update completed successfully",{bundleId:t,version:n.version,duration:Date.now()-this.currentState.startTime}),this.currentState=null}catch(e){throw a.error("Bundle update failed",e),this.currentState&&await this.rollback(),e}finally{this.updateInProgress=!1}}async validateUpdate(t,r,a){const i=this.pluginManager.getLogger(),n=this.pluginManager.getVersionManager();if(t&&!(null==a?void 0:a.allowDowngrade)&&n.shouldBlockDowngrade(t.version,r.version))throw new y(e.ErrorCode.VERSION_DOWNGRADE,`Cannot downgrade from ${t.version} to ${r.version}`);if(!r.verified){i.warn("Bundle not verified, verifying now",{bundleId:r.bundleId});const t=this.pluginManager.getDownloadManager(),a=await t.loadBlob(r.bundleId);if(!a)throw new D(e.ErrorCode.FILE_NOT_FOUND,"Bundle data not found");const n=await a.arrayBuffer();if(!await this.securityValidator.verifyChecksum(n,r.checksum))throw new y(e.ErrorCode.CHECKSUM_MISMATCH,"Bundle checksum verification failed");if(r.signature&&!await this.securityValidator.verifySignature(n,r.signature))throw new y(e.ErrorCode.SIGNATURE_INVALID,"Bundle signature verification failed");await this.pluginManager.getBundleManager().markBundleAsVerified(r.bundleId)}i.debug("Bundle validation passed",{bundleId:r.bundleId})}async createBackup(t){const a=`backups/${t.bundleId}_${Date.now()}`,i=this.pluginManager.getLogger();try{return await this.filesystem.mkdir({path:a,directory:r.Directory.Data,recursive:!0}),await this.filesystem.copy({from:t.path,to:a,directory:r.Directory.Data}),i.info("Backup created",{bundleId:t.bundleId,backupPath:a}),a}catch(t){throw i.error("Failed to create backup",t),new D(e.ErrorCode.UPDATE_FAILED,"Failed to create backup",void 0,t)}}async performUpdate(t){const a=this.pluginManager.getLogger();try{const e=`active/${t.bundleId}`;await this.filesystem.mkdir({path:e,directory:r.Directory.Data,recursive:!0}),await this.filesystem.copy({from:t.path,to:e,directory:r.Directory.Data}),t.path=e,a.debug("Bundle files installed",{bundleId:t.bundleId,targetPath:e})}catch(t){throw new D(e.ErrorCode.UPDATE_FAILED,"Failed to install bundle files",void 0,t)}}async verifyUpdate(t){try{const e=`${t.path}/index.html`;await this.filesystem.stat({path:e,directory:r.Directory.Data})}catch(t){throw new D(e.ErrorCode.UPDATE_FAILED,"Bundle verification failed after installation",void 0,t)}}async rollback(){var t;if(!this.currentState)throw new D(e.ErrorCode.ROLLBACK_FAILED,"No update state to rollback");const a=this.pluginManager.getLogger();a.warn("Starting rollback",{from:this.currentState.newBundle.bundleId,to:(null===(t=this.currentState.currentBundle)||void 0===t?void 0:t.bundleId)||"default"});try{const e=this.pluginManager.getBundleManager();if(this.currentState.backupPath&&this.currentState.currentBundle){const t=`active/${this.currentState.currentBundle.bundleId}`;await this.filesystem.copy({from:this.currentState.backupPath,to:t,directory:r.Directory.Data}),this.currentState.currentBundle.path=t,await e.saveBundleInfo(this.currentState.currentBundle)}this.currentState.currentBundle?await e.setActiveBundle(this.currentState.currentBundle.bundleId):await e.clearActiveBundle(),a.info("Rollback completed successfully")}catch(t){throw a.error("Rollback failed",t),new D(e.ErrorCode.ROLLBACK_FAILED,"Failed to rollback update",void 0,t)}finally{if(this.currentState.backupPath)try{await this.filesystem.rmdir({path:this.currentState.backupPath,directory:r.Directory.Data,recursive:!0})}catch(e){a.warn("Failed to clean up backup",e)}}}getUpdateProgress(){var e,t;return{inProgress:this.updateInProgress,bundleId:null===(e=this.currentState)||void 0===e?void 0:e.newBundle.bundleId,startTime:null===(t=this.currentState)||void 0===t?void 0:t.startTime}}async cancelUpdate(){this.updateInProgress&&this.currentState&&(this.pluginManager.getLogger().warn("Cancelling update",{bundleId:this.currentState.newBundle.bundleId}),await this.rollback(),this.updateInProgress=!1,this.currentState=null)}},e.ValidationError=y,e.VersionManager=O}({},capacitorExports,capacitorFilesystem,capacitorPreferences);
3
3
  //# sourceMappingURL=plugin.js.map
@@ -76,7 +76,7 @@ The App Review feature allows users to rate and review your app without leaving
76
76
  ### Installation
77
77
 
78
78
  ```bash
79
- npm install native-update
79
+ yarn add native-update
80
80
  npx cap sync
81
81
  ```
82
82
 
@@ -69,7 +69,7 @@ sequenceDiagram
69
69
  ### 1. Install the Plugin
70
70
 
71
71
  ```bash
72
- npm install native-update
72
+ yarn add native-update
73
73
  npx cap sync
74
74
  ```
75
75
 
@@ -329,7 +329,7 @@ export class UpdateStrategies {
329
329
 
330
330
  ```bash
331
331
  cd server-example
332
- npm install
332
+ yarn install
333
333
  npm start
334
334
  ```
335
335
 
@@ -389,7 +389,7 @@ Response:
389
389
 
390
390
  ```bash
391
391
  # 1. Build your web app
392
- npm run build
392
+ yarn build
393
393
 
394
394
  # 2. Create bundle
395
395
  cd dist
@@ -61,7 +61,7 @@ Native App Updates allow your app to:
61
61
  ### Installation
62
62
 
63
63
  ```bash
64
- npm install native-update
64
+ yarn add native-update
65
65
  npx cap sync
66
66
  ```
67
67
 
@@ -10,7 +10,7 @@ Get up and running with Capacitor Native Update in 5 minutes! This guide covers
10
10
 
11
11
  ```bash
12
12
  # Install the plugin
13
- npm install native-update
13
+ yarn add native-update
14
14
 
15
15
  # Sync with native projects
16
16
  npx cap sync
@@ -150,8 +150,8 @@ Use our example server or implement your own:
150
150
  ```bash
151
151
  # Using example server
152
152
  cd server-example
153
- npm install
154
- npm start
153
+ yarn install
154
+ yarn start
155
155
 
156
156
  # Upload a bundle
157
157
  curl -X POST http://localhost:3000/api/v1/bundles \
@@ -504,7 +504,7 @@ export class AppComponent implements OnInit {
504
504
 
505
505
  ```bash
506
506
  # Build your web app
507
- npm run build
507
+ yarn build
508
508
 
509
509
  # Create update bundle
510
510
  cd www && zip -r ../update-1.0.1.zip . && cd ..
@@ -11,10 +11,10 @@ The CLI is included with the native-update package. You can use it via npx witho
11
11
  npx native-update <command>
12
12
 
13
13
  # Or install globally
14
- npm install -g native-update
14
+ yarn global add native-update
15
15
 
16
16
  # Or use locally in your project
17
- npm install native-update
17
+ yarn add native-update
18
18
  npx native-update <command>
19
19
  ```
20
20
 
@@ -218,11 +218,11 @@ npx native-update backend create express --with-admin
218
218
 
219
219
  # 4. Start the backend (in another terminal)
220
220
  cd native-update-backend
221
- npm install
222
- npm run dev
221
+ yarn install
222
+ yarn dev
223
223
 
224
224
  # 5. Build your app
225
- npm run build
225
+ yarn build
226
226
 
227
227
  # 6. Create and sign a bundle
228
228
  npx native-update bundle create ./www --version 1.0.1
@@ -292,7 +292,7 @@ chmod 600 ./keys/private-*.pem
292
292
  Ensure your web directory contains built files:
293
293
  ```bash
294
294
  # Build first
295
- npm run build
295
+ yarn build
296
296
 
297
297
  # Then create bundle
298
298
  npx native-update bundle create ./www
@@ -370,7 +370,7 @@ async function getFeatureFlags() {
370
370
 
371
371
  ```bash
372
372
  # Use production builds
373
- npm run build -- --mode production
373
+ yarn build -- --mode production
374
374
 
375
375
  # Enable compression
376
376
  gzip -9 bundle.js
@@ -548,8 +548,8 @@ Set up a local update server for testing:
548
548
  ```bash
549
549
  # See server-example directory
550
550
  cd server-example
551
- npm install
552
- npm run dev
551
+ yarn install
552
+ yarn dev
553
553
  ```
554
554
 
555
555
  ### Test Scenarios
@@ -29,14 +29,6 @@ Before installing the plugin, ensure you have:
29
29
 
30
30
  ### 1. Install the Plugin
31
31
 
32
- Using npm:
33
-
34
- ```bash
35
- npm install native-update
36
- ```
37
-
38
- Using yarn:
39
-
40
32
  ```bash
41
33
  yarn add native-update
42
34
  ```
@@ -267,7 +259,7 @@ import type {
267
259
  ### Common Issues
268
260
 
269
261
  1. **"Module not found" error**
270
- - Run `npm install` or `yarn install` again
262
+ - Run `yarn install` again
271
263
  - Delete `node_modules` and reinstall
272
264
  - Ensure you've run `npx cap sync`
273
265
 
@@ -360,8 +360,8 @@ npx native-update backend create firebase --with-monitoring
360
360
 
361
361
  # Start the development server
362
362
  cd native-update-backend
363
- npm install
364
- npm run dev
363
+ yarn install
364
+ yarn dev
365
365
  ```
366
366
 
367
367
  ### Creating and Deploying Updates
@@ -374,7 +374,7 @@ npm run dev
374
374
 
375
375
  2. **Build and create update bundle**:
376
376
  ```bash
377
- npm run build
377
+ yarn build
378
378
  npx native-update bundle create ./www --version 1.0.1
379
379
  ```
380
380
 
@@ -18,7 +18,7 @@ This guide covers deploying the Capacitor Native Update plugin to production.
18
18
  cd production-backend
19
19
 
20
20
  # Install dependencies
21
- npm install
21
+ yarn install
22
22
 
23
23
  # Set environment variables
24
24
  cp .env.example .env
@@ -29,7 +29,7 @@ cp .env.example .env
29
29
 
30
30
  ```bash
31
31
  # Initialize production database
32
- npm run db:init
32
+ yarn db:init
33
33
 
34
34
  # For PostgreSQL (recommended for production)
35
35
  # Update DB_PATH in .env to PostgreSQL connection string
@@ -76,7 +76,7 @@ docker run -p 3000:3000 update-server
76
76
 
77
77
  ```bash
78
78
  # Install PM2
79
- npm install -g pm2
79
+ yarn install -g pm2
80
80
 
81
81
  # Start server
82
82
  pm2 start src/index.js --name update-server
@@ -143,7 +143,7 @@ bundle.downloadUrl = `${cdnUrl}/${bundle.id}`;
143
143
 
144
144
  ```bash
145
145
  # Install monitoring dependencies
146
- npm install @opentelemetry/api @opentelemetry/sdk-node
146
+ yarn install @opentelemetry/api @opentelemetry/sdk-node
147
147
 
148
148
  # Configure in server
149
149
  const { NodeSDK } = require('@opentelemetry/sdk-node');
@@ -180,7 +180,7 @@ ServerDown:
180
180
 
181
181
  ```bash
182
182
  # Build your app
183
- npm run build
183
+ yarn build
184
184
 
185
185
  # Create bundle
186
186
  npx native-update bundle create ./www
@@ -29,8 +29,8 @@ npx native-update backend create express --with-admin
29
29
  npx native-update backend create firebase --with-monitoring
30
30
 
31
31
  cd native-update-backend
32
- npm install
33
- npm run dev
32
+ yarn install
33
+ yarn dev
34
34
  ```
35
35
 
36
36
  ### 2. Update Your App Code
@@ -10,13 +10,13 @@ The plugin includes unit tests for core functionality:
10
10
 
11
11
  ```bash
12
12
  # Run all tests
13
- npm test
13
+ yarn test
14
14
 
15
15
  # Run with coverage
16
- npm run test:coverage
16
+ yarn test:coverage
17
17
 
18
18
  # Watch mode for development
19
- npm run test:watch
19
+ yarn test:watch
20
20
  ```
21
21
 
22
22
  #### Test Structure
@@ -38,8 +38,8 @@ src/__tests__/
38
38
  ```bash
39
39
  # Start the test server
40
40
  cd production-backend
41
- npm install
42
- npm run db:init
41
+ yarn install
42
+ yarn db:init
43
43
  npm start
44
44
  ```
45
45
 
@@ -256,9 +256,9 @@ jobs:
256
256
  steps:
257
257
  - uses: actions/checkout@v2
258
258
  - uses: actions/setup-node@v2
259
- - run: npm install
260
- - run: npm test
261
- - run: npm run test:e2e
259
+ - run: yarn install
260
+ - run: yarn test
261
+ - run: yarn test:e2e
262
262
  ```
263
263
 
264
264
  ### 10. Manual Testing Checklist
@@ -322,7 +322,7 @@ curl -X POST http://localhost:3000/api/analytics/event \
322
322
  ### Load Testing
323
323
  ```bash
324
324
  # Install artillery
325
- npm install -g artillery
325
+ yarn install -g artillery
326
326
 
327
327
  # Run load test
328
328
  artillery quick --count 100 --num 10 \
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "native-update",
3
- "version": "1.3.4",
3
+ "version": "1.3.6",
4
4
  "description": "Foundation package for building a comprehensive update system for Capacitor apps. Provides architecture and interfaces but requires backend implementation.",
5
5
  "type": "module",
6
6
  "main": "dist/plugin.cjs.js",