UNPKG

native-update

Version:

Foundation package for building a comprehensive update system for Capacitor apps. Provides architecture and interfaces but requires backend implementation.

2 lines 53.8 kB
/*! Native Update Plugin v1.1.6 | MIT License */ !function(e,t,a,r){var i,n,s,o,l,c,d,h,u,g,p,f,w;!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 E{constructor(){this.config=this.getDefaultConfig()}static getInstance(){return E.instance||(E.instance=new E),E.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",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"}}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 m{constructor(e){this.configManager=E.getInstance(),this.context=e||"NativeUpdate"}static getInstance(){return m.instance||(m.instance=new m),m.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={},a=e;for(const e in a)t[e]=e.toLowerCase().includes("key")||e.toLowerCase().includes("secret")||e.toLowerCase().includes("password")||e.toLowerCase().includes("token")?"<redacted>":this.sanitize(a[e]);return t}}return e}log(t,a){this.logWithLevel(e.LogLevel.INFO,t,a)}logWithLevel(t,a,r){if(!this.shouldLog())return;const i=(new Date).toISOString(),n=r?this.sanitize(r):void 0,s={timestamp:i,level:e.LogLevel[t],context:this.context,message:a};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,a){this.logWithLevel(e.LogLevel.DEBUG,t,a)}info(t,a){this.logWithLevel(e.LogLevel.INFO,t,a)}warn(t,a){this.logWithLevel(e.LogLevel.WARN,t,a)}error(t,a){const r=a instanceof Error?{name:a.name,message:a.message,stack:a.stack}:a;this.logWithLevel(e.LogLevel.ERROR,t,r)}}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 I extends Error{constructor(e,t,a,r){super(t),this.code=e,this.message=t,this.details=a,this.originalError=r,this.name="NativeUpdateError",Object.setPrototypeOf(this,I.prototype)}toJSON(){return{name:this.name,code:this.code,message:this.message,details:this.details,stack:this.stack}}}class y extends I{constructor(e,t,a,r){super(e,t,a,r),this.name="DownloadError"}}class A extends I{constructor(e,t,a){super(e,t,a),this.name="ValidationError"}}class D extends I{constructor(e,t,a,r){super(e,t,a,r),this.name="StorageError"}}class N extends I{constructor(e,t,a,r){super(e,t,a,r),this.name="UpdateError"}}class v{constructor(){this.configManager=E.getInstance(),this.logger=m.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 a=await this.calculateChecksum(e),r=a===t.toLowerCase();return r||this.logger.error("Checksum verification failed",{expected:t,actual:a}),r}async validateChecksum(e,t){return this.verifyChecksum(e,t)}async verifySignature(t,a){if(!this.configManager.get("enableSignatureValidation"))return!0;const r=this.configManager.get("publicKey");if(!r)throw new A(e.ErrorCode.SIGNATURE_INVALID,"Public key not configured for signature validation");try{const e=await crypto.subtle.importKey("spki",this.pemToArrayBuffer(r),{name:"RSA-PSS",hash:"SHA-256"},!1,["verify"]),i=await crypto.subtle.verify({name:"RSA-PSS",saltLength:32},e,this.base64ToArrayBuffer(a),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),a=new Uint8Array(t.length);for(let e=0;e<t.length;e++)a[e]=t.charCodeAt(e);return a.buffer}sanitizePath(e){return e.split("/").filter(e=>".."!==e&&"."!==e).join("/").replace(/^\/+/,"")}validateBundleId(t){if(!t||"string"!=typeof t)throw new A(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Bundle ID must be a non-empty string");if(!/^[a-zA-Z0-9\-_.]+$/.test(t))throw new A(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Bundle ID contains invalid characters");if(t.length>100)throw new A(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Bundle ID is too long (max 100 characters)")}validateVersion(t){if(!t||"string"!=typeof t)throw new A(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 A(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Version must follow semantic versioning format (e.g., 1.2.3)")}isVersionDowngrade(e,t){const a=this.parseVersion(e),r=this.parseVersion(t);return r.major<a.major||!(r.major>a.major)&&(r.minor<a.minor||!(r.minor>a.minor)&&r.patch<a.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 A(e.ErrorCode.INVALID_URL,"URL must be a non-empty string");let a;try{a=new URL(t)}catch(t){throw new A(e.ErrorCode.INVALID_URL,"Invalid URL format")}if("https:"!==a.protocol)throw new A(e.ErrorCode.INVALID_URL,"Only HTTPS URLs are allowed");const r=this.configManager.get("allowedHosts");if(r.length>0&&!r.includes(a.hostname))throw new A(e.ErrorCode.UNAUTHORIZED_HOST,`Host ${a.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(a.hostname)))throw new A(e.ErrorCode.UNAUTHORIZED_HOST,"Private/local addresses are not allowed")}validateFileSize(t){if("number"!=typeof t||t<0)throw new A(e.ErrorCode.INVALID_BUNDLE_FORMAT,"File size must be a non-negative number");const a=this.configManager.get("maxBundleSize");if(t>a)throw new A(e.ErrorCode.BUNDLE_TOO_LARGE,`File size ${t} exceeds maximum allowed size of ${a} 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 a=this.configManager.certificatePins;if(!a||!Array.isArray(a)||0===a.length)return!0;const r=a.filter(t=>t.hostname===e);if(0===r.length)return!0;const i=await this.calculateCertificateHash(t),n=r.some(e=>e.sha256===i);return n||this.logger.error("Certificate pinning validation failed",{hostname:e,expectedPins:r.map(e=>e.sha256),actualHash:i}),n}async calculateCertificateHash(e){const t=(new TextEncoder).encode(e),a=await crypto.subtle.digest("SHA-256",t),r=Array.from(new Uint8Array(a));return"sha256/"+btoa(String.fromCharCode(...r))}validateMetadata(t){if(t&&"object"!=typeof t)throw new A(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Metadata must be an object");if(JSON.stringify(t||{}).length>10240)throw new A(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Metadata is too large (max 10KB)")}}class C{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=m.getInstance(),this.configManager=E.getInstance()}async initialize(){if(this.preferences=this.configManager.get("preferences"),!this.preferences)throw new D(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 D(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 D(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Invalid bundle ID");if(!t.version||"string"!=typeof t.version)throw new D(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Invalid bundle version");if(!t.path||"string"!=typeof t.path)throw new D(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Invalid bundle path");if("number"!=typeof t.size||t.size<0)throw new D(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 D(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 D(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 D(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Bundle ID is required");const a=await this.getBundle(t);if(!a)throw new D(e.ErrorCode.FILE_NOT_FOUND,`Bundle ${t} not found`);const r=await this.getActiveBundle();r&&r.bundleId!==t&&(r.status="READY",await this.saveBundleInfo(r)),a.status="ACTIVE",await this.saveBundleInfo(a),await this.preferences.set({key:this.ACTIVE_BUNDLE_KEY,value:t}),this.logger.info("Active bundle set",{bundleId:t,version:a.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 D(e.ErrorCode.INVALID_CONFIG,"Keep count must be at least 1");const a=await this.getAllBundles(),r=await this.getActiveBundleId(),i=a.sort((e,t)=>t.downloadTime-e.downloadTime),n=new Set;r&&n.add(r);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 a)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 D(e.ErrorCode.INVALID_CONFIG,"Timestamp must be non-negative");return(await this.getAllBundles()).filter(e=>e.downloadTime<t)}async markBundleAsVerified(t){const a=await this.getBundle(t);if(!a)throw new D(e.ErrorCode.FILE_NOT_FOUND,`Bundle ${t} not found`);a.verified=!0,await this.saveBundleInfo(a),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){return await this.getTotalStorageUsed()+e>3*this.configManager.get("maxBundleSize")}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,a=await this.getBundlesOlderThan(t);for(const e of a){const t=await this.getActiveBundleId();e.bundleId!==t&&await this.deleteBundle(e.bundleId)}}}class U{constructor(){this.activeDownloads=new Map,this.filesystem=null,this.logger=m.getInstance(),this.configManager=E.getInstance()}async initialize(){if(this.filesystem=this.configManager.get("filesystem"),!this.filesystem)throw new y(e.ErrorCode.MISSING_DEPENDENCY,"Filesystem not configured. Please configure the plugin first.")}validateUrl(t){try{const a=new URL(t);if("https:"!==a.protocol)throw new A(e.ErrorCode.INVALID_URL,"Only HTTPS URLs are allowed for security reasons");const r=this.configManager.get("allowedHosts");if(r.length>0&&!r.includes(a.hostname))throw new A(e.ErrorCode.UNAUTHORIZED_HOST,`Host ${a.hostname} is not in the allowed hosts list`)}catch(t){if(t instanceof A)throw t;throw new A(e.ErrorCode.INVALID_URL,"Invalid URL format")}}async download(t,a,r){if(this.validateUrl(t),!a)throw new A(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Bundle ID is required");if(this.activeDownloads.has(a))throw new y(e.ErrorCode.DOWNLOAD_FAILED,`Download already in progress for bundle ${a}`);const i=new AbortController,n={controller:i,startTime:Date.now()};this.activeDownloads.set(a,n);try{const s=this.configManager.get("downloadTimeout"),o=setTimeout(()=>i.abort(),s),l=await fetch(t,{signal:i.signal,headers:{"Cache-Control":"no-cache",Accept:"application/octet-stream, application/zip"}});if(clearTimeout(o),!l.ok)throw new y(e.ErrorCode.DOWNLOAD_FAILED,`Download failed: ${l.status} ${l.statusText}`,{status:l.status,statusText:l.statusText});const c=l.headers.get("content-type");if(c&&!this.isValidContentType(c))throw new A(e.ErrorCode.INVALID_BUNDLE_FORMAT,`Invalid content type: ${c}`);const d=l.headers.get("content-length"),h=d?parseInt(d,10):0;if(h>this.configManager.get("maxBundleSize"))throw new A(e.ErrorCode.BUNDLE_TOO_LARGE,`Bundle size ${h} exceeds maximum allowed size`);if(!h||!l.body){const e=await l.blob();return this.validateBlobSize(e),e}const u=l.body.getReader(),g=[];let p=0;for(;;){const{done:t,value:i}=await u.read();if(t)break;if(g.push(i),p+=i.length,p>this.configManager.get("maxBundleSize"))throw new A(e.ErrorCode.BUNDLE_TOO_LARGE,"Download size exceeds maximum allowed size");r&&r({percent:Math.round(p/h*100),bytesDownloaded:p,totalBytes:h,bundleId:a})}const f=new Blob(g);return this.validateBlobSize(f),this.logger.info("Download completed",{bundleId:a,size:f.size,duration:Date.now()-n.startTime}),f}catch(t){if(t instanceof Error&&"AbortError"===t.name){const a=Date.now()-n.startTime>=this.configManager.get("downloadTimeout");throw new y(a?e.ErrorCode.DOWNLOAD_TIMEOUT:e.ErrorCode.DOWNLOAD_FAILED,a?"Download timed out":"Download cancelled",void 0,t)}throw t}finally{this.activeDownloads.delete(a)}}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 A(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Downloaded file is empty");if(t.size>this.configManager.get("maxBundleSize"))throw new A(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,a,r){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:a,attempt:e,delay:t})}return await this.download(t,a,r)}catch(t){if(s=t,t instanceof A||t instanceof Error&&"AbortError"===t.name)throw t;this.logger.warn(`Download attempt ${e+1} failed`,{bundleId:a,error:t})}throw new y(e.ErrorCode.DOWNLOAD_FAILED,"Download failed after all retries",{attempts:i},s||void 0)}async blobToArrayBuffer(e){return e.arrayBuffer()}async saveBlob(t,r){if(!this.filesystem)throw new y(e.ErrorCode.MISSING_DEPENDENCY,"Filesystem not initialized");const i=await this.blobToArrayBuffer(r),n=btoa(String.fromCharCode(...new Uint8Array(i))),s=`bundles/${t}/bundle.zip`;return await this.filesystem.writeFile({path:s,data:n,directory:a.Directory.Data,recursive:!0}),this.logger.debug("Bundle saved to filesystem",{bundleId:t,path:s,size:r.size}),s}async loadBlob(t){if(!this.filesystem)throw new y(e.ErrorCode.MISSING_DEPENDENCY,"Filesystem not initialized");try{const e=`bundles/${t}/bundle.zip`,r=await this.filesystem.readFile({path:e,directory:a.Directory.Data}),i=atob(r.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 y(e.ErrorCode.MISSING_DEPENDENCY,"Filesystem not initialized");try{const e=`bundles/${t}`;await this.filesystem.rmdir({path:e,directory:a.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})}}}class L{constructor(){this.VERSION_CHECK_CACHE_KEY="capacitor_native_update_version_cache",this.CACHE_DURATION=3e5,this.preferences=null,this.memoryCache=new Map,this.logger=m.getInstance(),this.configManager=E.getInstance(),this.securityValidator=v.getInstance()}static compareVersions(e,t){try{const[a,r]=e.split("-"),[i,n]=t.split("-"),s=a.split(".").map(Number),o=i.split(".").map(Number);for(let e=0;e<3;e++){const t=s[e]||0,a=o[e]||0;if(t>a)return 1;if(t<a)return-1}return r&&!n?-1:!r&&n?1:r&&n?r.localeCompare(n):0}catch(a){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,a){return!(a&&L.compareVersions(e,a)<0)&&L.compareVersions(e,t)<0}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.")}async checkForUpdates(t,a,r,i){if(this.securityValidator.validateUrl(t),this.securityValidator.validateVersion(r),!a||!i)throw new A(e.ErrorCode.INVALID_CONFIG,"Channel and appId are required");const n=`${a}-${i}`,s=await this.getCachedVersionInfo(n);if(s&&s.channel===a&&Date.now()-s.timestamp<this.CACHE_DURATION)return this.logger.debug("Returning cached version info",{channel:a,version:s.data.version}),s.data;try{const s=new URL(`${t}/check`);s.searchParams.append("channel",a),s.searchParams.append("version",r),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":r,"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 A(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,a,l),this.logger.info("Version check completed",{channel:a,currentVersion:r,latestVersion:l.version,updateAvailable:this.isNewerVersion(l.version,r)}),l}catch(e){return this.logger.error("Failed to check for updates",e),null}}isNewerVersion(e,t){try{const a=this.parseVersion(e),r=this.parseVersion(t);return a.major!==r.major?a.major>r.major:a.minor!==r.minor?a.minor>r.minor:a.patch!==r.patch?a.patch>r.patch:!(a.prerelease&&!r.prerelease||(a.prerelease||!r.prerelease)&&(!a.prerelease||!r.prerelease||!(a.prerelease>r.prerelease)))}catch(a){return this.logger.error("Failed to compare versions",{version1:e,version2:t,error:a}),!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 a=t.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/);if(!a)throw new A(e.ErrorCode.INVALID_BUNDLE_FORMAT,"Invalid version format");return{major:parseInt(a[1],10),minor:parseInt(a[2],10),patch:parseInt(a[3],10),prerelease:a[4],build:a[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,a){if(!a)return!0;try{const r=a[e];return!r||(this.securityValidator.validateVersion(t),this.securityValidator.validateVersion(r),!this.isNewerVersion(r,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 a=JSON.parse(t)[e];if(a&&Date.now()-a.timestamp<this.CACHE_DURATION)return this.memoryCache.set(e,a),a}catch(e){this.logger.debug("Failed to load cached version info",e)}return null}async cacheVersionInfo(e,t,a){const r={channel:t,data:a,timestamp:Date.now()};this.memoryCache.set(e,r);try{const{value:t}=await this.preferences.get({key:this.VERSION_CHECK_CACHE_KEY}),a=t?JSON.parse(t):{},i=Date.now();for(const e in a)i-a[e].timestamp>2*this.CACHE_DURATION&&delete a[e];a[e]=r,await this.preferences.set({key:this.VERSION_CHECK_CACHE_KEY,value:JSON.stringify(a)})}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 _{constructor(e){this.config=e,this.logger=new m("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 a=await t.json();return{availableVersion:a.version,updatePriority:a.priority,releaseNotes:a.releaseNotes,updateSize:a.size,updateURL:a.downloadUrl}}catch(e){return this.logger.error("Failed to check server version",e),{}}}compareVersions(e,t){const a=e.split(".").map(Number),r=t.split(".").map(Number);for(let e=0;e<Math.max(a.length,r.length);e++){const t=a[e]||0,i=r[e]||0;if(t>i)return 1;if(t<i)return-1}return 0}isUpdateRequired(e,t,a){return this.compareVersions(e,t)<0||!!(a&&this.compareVersions(e,a)<0)}determineUpdatePriority(e,t){const[a,r]=e.split(".").map(Number);return a>0?5:r>0&&t&&t>30?4:r>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"}}!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"}(w||(w={}));class b{constructor(e){this.logger=new m("AppUpdateInstaller"),this.currentState={installStatus:w.UNKNOWN,packageName:"",availableVersion:""}}async startImmediateUpdate(){if(this.logger.log("Starting immediate update installation"),this.updateState(w.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(w.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!==w.DOWNLOADED)throw new Error("Update not ready for installation");this.updateState(w.INSTALLING),this.isAndroid()?this.logger.log("Completing Android update installation"):setTimeout(()=>{this.updateState(w.INSTALLED)},1e3)}async cancelUpdate(){this.logger.log("Cancelling update"),this.currentState.installStatus===w.DOWNLOADING&&this.updateState(w.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,a=1048576,r=setInterval(()=>{e+=a,e>=t&&(e=t,clearInterval(r),this.updateState(w.DOWNLOADED));const i={bytesDownloaded:e,totalBytesToDownload:t,percentComplete:Math.round(e/t*100),downloadSpeed:a,estimatedTime:Math.ceil((t-e)/a)};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 O{constructor(e){this.config=e,this.logger=new m("PlatformAppUpdate"),this.platform=t.Capacitor.getPlatform()}async checkForUpdate(e){this.logger.log("Checking for platform update: "+this.platform);const t=await this.getVersionInfo(),a={updateAvailable:!1,currentVersion:t.currentVersion,availableVersion:t.currentVersion};if("android"===this.platform)return a;if("ios"===this.platform)return a;if(this.config.webUpdateUrl)try{const e=await fetch(this.config.webUpdateUrl),r=await e.json();r.version&&r.version!==t.currentVersion&&(a.updateAvailable=!0,a.availableVersion=r.version,a.releaseNotes=r.releaseNotes,a.updateURL=r.downloadUrl)}catch(e){this.logger.error("Failed to check web update",e)}return a}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 R{constructor(e){this.listeners=new Map,this.config=e,this.logger=new m("AppUpdateManager"),this.checker=new _(e),this.installer=new b(e),this.platformUpdate=new O(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 a=await this.checker.checkServerVersion(e);return this.mergeUpdateInfo(t,a)}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",a=this.checker.compareVersions(e.currentVersion,t)>=0;return{isMet:a,currentVersion:e.currentVersion,minimumVersion:t,updateRequired:!a&&!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 a=this.listeners.get(e);a&&a.delete(t)}}}async removeAllListeners(e){e?this.listeners.delete(e):this.listeners.clear()}emit(e,t){const a=this.listeners.get(e);a&&a.forEach(a=>{try{a(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 S{constructor(){this.listeners=new Map}static getInstance(){return S.instance||(S.instance=new S),S.instance}addListener(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),()=>{const a=this.listeners.get(e);a&&(a.delete(t),0===a.size&&this.listeners.delete(e))}}emit(e,t){const a=this.listeners.get(e);a&&a.forEach(a=>{try{a(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 T{constructor(){this.bundleManager=null,this.downloadManager=null,this.versionManager=null,this.appUpdateManager=null,this.initialized=!1,this.configManager=E.getInstance(),this.logger=m.getInstance(),this.securityValidator=v.getInstance(),this.eventEmitter=S.getInstance()}static getInstance(){return T.instance||(T.instance=new T),T.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=a.Filesystem),e.preferences||(e.preferences=r.Preferences),this.bundleManager=new C,await this.bundleManager.initialize(),this.downloadManager=new U,await this.downloadManager.initialize(),this.versionManager=new L,await this.versionManager.initialize(),this.appUpdateManager=new R({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 I(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 M{constructor(){this.initialized=!1,this.windowEventListeners=new Map,this.pluginManager=T.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 a;a="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(a):await this.initialize(a)}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(),a=this.pluginManager.getBundleManager(),r=await t.downloadWithRetry(e.url,e.version),i=await t.saveBlob(e.version,r),n={bundleId:e.version,version:e.version,path:i,downloadTime:Date.now(),size:r.size,status:h.READY,checksum:e.checksum,signature:e.signature,verified:!1};return await a.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(),a=await t.getActiveBundle();if(!a)throw new I(e.ErrorCode.FILE_NOT_FOUND,"No active bundle found");return a}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 a=(await t.getAllBundles()).sort((e,t)=>t.downloadTime-e.downloadTime);for(let r=e.keepVersions;r<a.length;r++)await t.deleteBundle(a[r].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 a=await t.validateChecksum(new ArrayBuffer(0),e.checksum);return{isValid:a,details:{checksumValid:a,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 I(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 a=this.pluginManager.getEventEmitter().addListener(e,t);return{remove:async()=>{a()}}}async removeAllListeners(){this.pluginManager.getEventEmitter().removeAllListeners()}setupWindowEventBridge(){const e=this.pluginManager.getEventEmitter();["appUpdateAvailable","appUpdateProgress","appUpdateReady","appUpdateFailed","appUpdateNotificationClicked","appUpdateInstallClicked"].forEach(t=>{const a=a=>{e.emit(t,a.detail)};window.addEventListener(t,a),this.windowEventListeners.set(t,a)})}}const B=t.registerPlugin("NativeUpdate",{web:()=>new M});e.BundleManager=C,e.CacheManager=class{constructor(){this.filesystem=null,this.memoryCache=new Map,this.CACHE_DIR="cache",this.logger=m.getInstance(),this.configManager=E.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:a.Directory.Data,recursive:!0})}catch(e){this.logger.debug("Cache directory may already exist",e)}await this.cleanExpiredCache()}async set(e,t,a){const r=Date.now()+(a||this.configManager.get("cacheExpiration")),i={data:t,timestamp:Date.now(),expiry:r};this.memoryCache.set(e,i),this.shouldPersist(t)&&await this.persistToFile(e,i),this.logger.debug("Cache entry set",{key:e,expiry:new Date(r)})}async get(e){const t=this.memoryCache.get(e);if(t){if(Date.now()<t.expiry)return t.data;this.memoryCache.delete(e)}const a=await this.loadFromFile(e);if(a){if(Date.now()<a.expiry)return this.memoryCache.set(e,a),a.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:a.Directory.Data,recursive:!0}),await this.filesystem.mkdir({path:this.CACHE_DIR,directory:a.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[a,r]of this.memoryCache)e>=r.expiry&&(this.memoryCache.delete(a),t++);try{const r=await this.filesystem.readdir({path:this.CACHE_DIR,directory:a.Directory.Data});for(const a of r.files){const r=a.name.replace(".json",""),i=await this.loadFromFile(r);(!i||e>=i.expiry)&&(await this.removeFile(r),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 r=await this.filesystem.readdir({path:this.CACHE_DIR,directory:a.Directory.Data});e=r.files.length;for(const e of r.files)t+=(await this.filesystem.stat({path:`${this.CACHE_DIR}/${e.name}`,directory:a.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 r=`${this.CACHE_DIR}/${e}.json`,i=JSON.stringify(t);await this.filesystem.writeFile({path:r,data:i,directory:a.Directory.Data,encoding:a.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`,r=await this.filesystem.readFile({path:t,directory:a.Directory.Data,encoding:a.Encoding.UTF8});return JSON.parse(r.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:a.Directory.Data})}catch(t){this.logger.debug("Failed to remove cache file",{key:e,error:t})}}},e.ConfigManager=E,e.ConfigurationError=class extends I{constructor(t,a){super(e.ErrorCode.INVALID_CONFIG,t,a),this.name="ConfigurationError"}},e.DownloadError=y,e.DownloadManager=U,e.Logger=m,e.NativeUpdate=B,e.NativeUpdateError=I,e.PluginManager=T,e.SecurityValidator=v,e.StorageError=D,e.UpdateErrorClass=N,e.UpdateManager=class{constructor(){this.filesystem=null,this.updateInProgress=!1,this.currentState=null,this.pluginManager=T.getInstance(),this.securityValidator=v.getInstance()}async initialize(){if(this.filesystem=this.pluginManager.getConfigManager().get("filesystem"),!this.filesystem)throw new N(e.ErrorCode.MISSING_DEPENDENCY,"Filesystem not configured")}async applyUpdate(t,a){if(this.updateInProgress)throw new N(e.ErrorCode.UPDATE_FAILED,"Another update is already in progress");const r=this.pluginManager.getLogger(),i=this.pluginManager.getBundleManager();try{this.updateInProgress=!0,r.info("Starting bundle update",{bundleId:t});const n=await i.getBundle(t);if(!n)throw new N(e.ErrorCode.FILE_NOT_FOUND,`Bundle ${t} not found`);if("READY"!==n.status&&"ACTIVE"!==n.status)throw new N(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,a),s&&"default"!==s.bundleId&&(this.currentState.backupPath=await this.createBackup(s)),await this.performUpdate(n),await this.verifyUpdate(n),await i.setActiveBundle(t),(null==a?void 0:a.cleanupOldBundles)&&await i.cleanupOldBundles(a.keepBundleCount||3),r.info("Bundle update completed successfully",{bundleId:t,version:n.version,duration:Date.now()-this.currentState.startTime}),this.currentState=null}catch(e){throw r.error("Bundle update failed",e),this.currentState&&await this.rollback(),e}finally{this.updateInProgress=!1}}async validateUpd