UNPKG

native-update

Version:

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

1 lines 52.9 kB
import{Capacitor as e,registerPlugin as t}from"@capacitor/core";import{Directory as a,Filesystem as i,Encoding as r}from"@capacitor/filesystem";import{Preferences as n}from"@capacitor/preferences";var s,o,l,c,d,h,u,g,p,f,w,m,I;!function(e){e.APP_UPDATE="app_update",e.LIVE_UPDATE="live_update",e.BOTH="both"}(s||(s={})),function(e){e.MIN="min",e.LOW="low",e.DEFAULT="default",e.HIGH="high",e.MAX="max"}(o||(o={})),function(e){e.IMMEDIATE="immediate",e.BACKGROUND="background",e.MANUAL="manual"}(l||(l={})),function(e){e.IMMEDIATE="immediate",e.ON_NEXT_RESTART="on_next_restart",e.ON_NEXT_RESUME="on_next_resume"}(c||(c={})),function(e){e.IMMEDIATE="immediate",e.ON_NEXT_RESTART="on_next_restart",e.ON_NEXT_RESUME="on_next_resume"}(d||(d={})),function(e){e.SHA256="SHA-256",e.SHA512="SHA-512"}(h||(h={})),function(e){e.UP_TO_DATE="UP_TO_DATE",e.UPDATE_AVAILABLE="UPDATE_AVAILABLE",e.UPDATE_INSTALLED="UPDATE_INSTALLED",e.ERROR="ERROR"}(u||(u={})),function(e){e.PENDING="PENDING",e.DOWNLOADING="DOWNLOADING",e.READY="READY",e.ACTIVE="ACTIVE",e.FAILED="FAILED"}(g||(g={})),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"}(p||(p={})),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"}(f||(f={}));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)}}!function(e){e[e.DEBUG=0]="DEBUG",e[e.INFO=1]="INFO",e[e.WARN=2]="WARN",e[e.ERROR=3]="ERROR"}(w||(w={}));class y{constructor(e){this.configManager=E.getInstance(),this.context=e||"NativeUpdate"}static getInstance(){return y.instance||(y.instance=new y),y.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(e,t){this.logWithLevel(w.INFO,e,t)}logWithLevel(e,t,a){if(!this.shouldLog())return;const i=(new Date).toISOString(),r=a?this.sanitize(a):void 0,n={timestamp:i,level:w[e],context:this.context,message:t};switch(void 0!==r&&(n.data=r),e){case w.DEBUG:console.debug(`[${this.context}]`,n);break;case w.INFO:console.info(`[${this.context}]`,n);break;case w.WARN:console.warn(`[${this.context}]`,n);break;case w.ERROR:console.error(`[${this.context}]`,n)}}debug(e,t){this.logWithLevel(w.DEBUG,e,t)}info(e,t){this.logWithLevel(w.INFO,e,t)}warn(e,t){this.logWithLevel(w.WARN,e,t)}error(e,t){const a=t instanceof Error?{name:t.name,message:t.message,stack:t.stack}:t;this.logWithLevel(w.ERROR,e,a)}}!function(e){e.NOT_CONFIGURED="NOT_CONFIGURED",e.INVALID_CONFIG="INVALID_CONFIG",e.MISSING_DEPENDENCY="MISSING_DEPENDENCY",e.DOWNLOAD_FAILED="DOWNLOAD_FAILED",e.DOWNLOAD_TIMEOUT="DOWNLOAD_TIMEOUT",e.INVALID_URL="INVALID_URL",e.UNAUTHORIZED_HOST="UNAUTHORIZED_HOST",e.BUNDLE_TOO_LARGE="BUNDLE_TOO_LARGE",e.CHECKSUM_MISMATCH="CHECKSUM_MISMATCH",e.SIGNATURE_INVALID="SIGNATURE_INVALID",e.VERSION_DOWNGRADE="VERSION_DOWNGRADE",e.INVALID_BUNDLE_FORMAT="INVALID_BUNDLE_FORMAT",e.STORAGE_FULL="STORAGE_FULL",e.FILE_NOT_FOUND="FILE_NOT_FOUND",e.PERMISSION_DENIED="PERMISSION_DENIED",e.UPDATE_FAILED="UPDATE_FAILED",e.ROLLBACK_FAILED="ROLLBACK_FAILED",e.BUNDLE_NOT_READY="BUNDLE_NOT_READY",e.PLATFORM_NOT_SUPPORTED="PLATFORM_NOT_SUPPORTED",e.NATIVE_ERROR="NATIVE_ERROR"}(m||(m={}));class A extends Error{constructor(e,t,a,i){super(t),this.code=e,this.message=t,this.details=a,this.originalError=i,this.name="NativeUpdateError",Object.setPrototypeOf(this,A.prototype)}toJSON(){return{name:this.name,code:this.code,message:this.message,details:this.details,stack:this.stack}}}class D extends A{constructor(e,t){super(m.INVALID_CONFIG,e,t),this.name="ConfigurationError"}}class N extends A{constructor(e,t,a,i){super(e,t,a,i),this.name="DownloadError"}}class v extends A{constructor(e,t,a){super(e,t,a),this.name="ValidationError"}}class U extends A{constructor(e,t,a,i){super(e,t,a,i),this.name="StorageError"}}class _ extends A{constructor(e,t,a,i){super(e,t,a,i),this.name="UpdateError"}}class b{constructor(){this.configManager=E.getInstance(),this.logger=y.getInstance()}static getInstance(){return b.instance||(b.instance=new b),b.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),i=a===t.toLowerCase();return i||this.logger.error("Checksum verification failed",{expected:t,actual:a}),i}async validateChecksum(e,t){return this.verifyChecksum(e,t)}async verifySignature(e,t){if(!this.configManager.get("enableSignatureValidation"))return!0;const a=this.configManager.get("publicKey");if(!a)throw new v(m.SIGNATURE_INVALID,"Public key not configured for signature validation");try{const i=await crypto.subtle.importKey("spki",this.pemToArrayBuffer(a),{name:"RSA-PSS",hash:"SHA-256"},!1,["verify"]),r=await crypto.subtle.verify({name:"RSA-PSS",saltLength:32},i,this.base64ToArrayBuffer(t),e);return r||this.logger.error("Signature verification failed"),r}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(e){if(!e||"string"!=typeof e)throw new v(m.INVALID_BUNDLE_FORMAT,"Bundle ID must be a non-empty string");if(!/^[a-zA-Z0-9\-_.]+$/.test(e))throw new v(m.INVALID_BUNDLE_FORMAT,"Bundle ID contains invalid characters");if(e.length>100)throw new v(m.INVALID_BUNDLE_FORMAT,"Bundle ID is too long (max 100 characters)")}validateVersion(e){if(!e||"string"!=typeof e)throw new v(m.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(e))throw new v(m.INVALID_BUNDLE_FORMAT,"Version must follow semantic versioning format (e.g., 1.2.3)")}isVersionDowngrade(e,t){const a=this.parseVersion(e),i=this.parseVersion(t);return i.major<a.major||!(i.major>a.major)&&(i.minor<a.minor||!(i.minor>a.minor)&&i.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(e){if(!e||"string"!=typeof e)throw new v(m.INVALID_URL,"URL must be a non-empty string");let t;try{t=new URL(e)}catch(e){throw new v(m.INVALID_URL,"Invalid URL format")}if("https:"!==t.protocol)throw new v(m.INVALID_URL,"Only HTTPS URLs are allowed");const a=this.configManager.get("allowedHosts");if(a.length>0&&!a.includes(t.hostname))throw new v(m.UNAUTHORIZED_HOST,`Host ${t.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(t.hostname)))throw new v(m.UNAUTHORIZED_HOST,"Private/local addresses are not allowed")}validateFileSize(e){if("number"!=typeof e||e<0)throw new v(m.INVALID_BUNDLE_FORMAT,"File size must be a non-negative number");const t=this.configManager.get("maxBundleSize");if(e>t)throw new v(m.BUNDLE_TOO_LARGE,`File size ${e} exceeds maximum allowed size of ${t} 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 i=a.filter(t=>t.hostname===e);if(0===i.length)return!0;const r=await this.calculateCertificateHash(t),n=i.some(e=>e.sha256===r);return n||this.logger.error("Certificate pinning validation failed",{hostname:e,expectedPins:i.map(e=>e.sha256),actualHash:r}),n}async calculateCertificateHash(e){const t=(new TextEncoder).encode(e),a=await crypto.subtle.digest("SHA-256",t),i=Array.from(new Uint8Array(a));return"sha256/"+btoa(String.fromCharCode(...i))}validateMetadata(e){if(e&&"object"!=typeof e)throw new v(m.INVALID_BUNDLE_FORMAT,"Metadata must be an object");if(JSON.stringify(e||{}).length>10240)throw new v(m.INVALID_BUNDLE_FORMAT,"Metadata is too large (max 10KB)")}}class O{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=y.getInstance(),this.configManager=E.getInstance()}async initialize(){if(this.preferences=this.configManager.get("preferences"),!this.preferences)throw new U(m.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(e){throw new U(m.STORAGE_FULL,"Failed to save bundles to storage",void 0,e)}}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(e){if(!e.bundleId||"string"!=typeof e.bundleId)throw new U(m.INVALID_BUNDLE_FORMAT,"Invalid bundle ID");if(!e.version||"string"!=typeof e.version)throw new U(m.INVALID_BUNDLE_FORMAT,"Invalid bundle version");if(!e.path||"string"!=typeof e.path)throw new U(m.INVALID_BUNDLE_FORMAT,"Invalid bundle path");if("number"!=typeof e.size||e.size<0)throw new U(m.INVALID_BUNDLE_FORMAT,"Invalid bundle size")}async getAllBundles(){return await this.loadCache(),Array.from(this.cache.values())}async getBundle(e){if(!e)throw new U(m.INVALID_BUNDLE_FORMAT,"Bundle ID is required");return await this.loadCache(),this.cache.get(e)||null}async deleteBundle(e){if(!e)throw new U(m.INVALID_BUNDLE_FORMAT,"Bundle ID is required");await this.loadCache(),this.cache.get(e)?(this.cache.delete(e),await this.saveCache(),await this.getActiveBundleId()===e&&await this.clearActiveBundle(),this.logger.info("Bundle deleted",{bundleId:e})):this.logger.warn("Attempted to delete non-existent bundle",{bundleId:e})}async getActiveBundle(){const e=await this.getActiveBundleId();return e?this.getBundle(e):null}async setActiveBundle(e){if(!e)throw new U(m.INVALID_BUNDLE_FORMAT,"Bundle ID is required");const t=await this.getBundle(e);if(!t)throw new U(m.FILE_NOT_FOUND,`Bundle ${e} not found`);const a=await this.getActiveBundle();a&&a.bundleId!==e&&(a.status="READY",await this.saveBundleInfo(a)),t.status="ACTIVE",await this.saveBundleInfo(t),await this.preferences.set({key:this.ACTIVE_BUNDLE_KEY,value:e}),this.logger.info("Active bundle set",{bundleId:e,version:t.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(e){if(e<1)throw new U(m.INVALID_CONFIG,"Keep count must be at least 1");const t=await this.getAllBundles(),a=await this.getActiveBundleId(),i=t.sort((e,t)=>t.downloadTime-e.downloadTime),r=new Set;a&&r.add(a);let n=r.size;for(const t of i){if(n>=e)break;r.has(t.bundleId)||(r.add(t.bundleId),n++)}let s=0;for(const e of t)r.has(e.bundleId)||(await this.deleteBundle(e.bundleId),s++);s>0&&this.logger.info("Cleaned up old bundles",{deleted:s,kept:n})}async getBundlesOlderThan(e){if(e<0)throw new U(m.INVALID_CONFIG,"Timestamp must be non-negative");return(await this.getAllBundles()).filter(t=>t.downloadTime<e)}async markBundleAsVerified(e){const t=await this.getBundle(e);if(!t)throw new U(m.FILE_NOT_FOUND,`Bundle ${e} not found`);t.verified=!0,await this.saveBundleInfo(t),this.logger.info("Bundle marked as verified",{bundleId:e})}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 R{constructor(){this.activeDownloads=new Map,this.filesystem=null,this.logger=y.getInstance(),this.configManager=E.getInstance()}async initialize(){if(this.filesystem=this.configManager.get("filesystem"),!this.filesystem)throw new N(m.MISSING_DEPENDENCY,"Filesystem not configured. Please configure the plugin first.")}validateUrl(e){try{const t=new URL(e);if("https:"!==t.protocol)throw new v(m.INVALID_URL,"Only HTTPS URLs are allowed for security reasons");const a=this.configManager.get("allowedHosts");if(a.length>0&&!a.includes(t.hostname))throw new v(m.UNAUTHORIZED_HOST,`Host ${t.hostname} is not in the allowed hosts list`)}catch(e){if(e instanceof v)throw e;throw new v(m.INVALID_URL,"Invalid URL format")}}async download(e,t,a){if(this.validateUrl(e),!t)throw new v(m.INVALID_BUNDLE_FORMAT,"Bundle ID is required");if(this.activeDownloads.has(t))throw new N(m.DOWNLOAD_FAILED,`Download already in progress for bundle ${t}`);const i=new AbortController,r={controller:i,startTime:Date.now()};this.activeDownloads.set(t,r);try{const n=this.configManager.get("downloadTimeout"),s=setTimeout(()=>i.abort(),n),o=await fetch(e,{signal:i.signal,headers:{"Cache-Control":"no-cache",Accept:"application/octet-stream, application/zip"}});if(clearTimeout(s),!o.ok)throw new N(m.DOWNLOAD_FAILED,`Download failed: ${o.status} ${o.statusText}`,{status:o.status,statusText:o.statusText});const l=o.headers.get("content-type");if(l&&!this.isValidContentType(l))throw new v(m.INVALID_BUNDLE_FORMAT,`Invalid content type: ${l}`);const c=o.headers.get("content-length"),d=c?parseInt(c,10):0;if(d>this.configManager.get("maxBundleSize"))throw new v(m.BUNDLE_TOO_LARGE,`Bundle size ${d} exceeds maximum allowed size`);if(!d||!o.body){const e=await o.blob();return this.validateBlobSize(e),e}const h=o.body.getReader(),u=[];let g=0;for(;;){const{done:e,value:i}=await h.read();if(e)break;if(u.push(i),g+=i.length,g>this.configManager.get("maxBundleSize"))throw new v(m.BUNDLE_TOO_LARGE,"Download size exceeds maximum allowed size");a&&a({percent:Math.round(g/d*100),bytesDownloaded:g,totalBytes:d,bundleId:t})}const p=new Blob(u);return this.validateBlobSize(p),this.logger.info("Download completed",{bundleId:t,size:p.size,duration:Date.now()-r.startTime}),p}catch(e){if(e instanceof Error&&"AbortError"===e.name){const t=Date.now()-r.startTime>=this.configManager.get("downloadTimeout");throw new N(t?m.DOWNLOAD_TIMEOUT:m.DOWNLOAD_FAILED,t?"Download timed out":"Download cancelled",void 0,e)}throw e}finally{this.activeDownloads.delete(t)}}isValidContentType(e){return["application/octet-stream","application/zip","application/x-zip-compressed","application/x-zip"].some(t=>e.includes(t))}validateBlobSize(e){if(0===e.size)throw new v(m.INVALID_BUNDLE_FORMAT,"Downloaded file is empty");if(e.size>this.configManager.get("maxBundleSize"))throw new v(m.BUNDLE_TOO_LARGE,`File size ${e.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(e,t,a){const i=this.configManager.get("retryAttempts"),r=this.configManager.get("retryDelay");let n=null;for(let s=0;s<i;s++)try{if(s>0){const e=Math.min(r*Math.pow(2,s-1),3e4);await new Promise(t=>setTimeout(t,e)),this.logger.debug("Retrying download",{bundleId:t,attempt:s,delay:e})}return await this.download(e,t,a)}catch(e){if(n=e,e instanceof v||e instanceof Error&&"AbortError"===e.name)throw e;this.logger.warn(`Download attempt ${s+1} failed`,{bundleId:t,error:e})}throw new N(m.DOWNLOAD_FAILED,"Download failed after all retries",{attempts:i},n||void 0)}async blobToArrayBuffer(e){return e.arrayBuffer()}async saveBlob(e,t){if(!this.filesystem)throw new N(m.MISSING_DEPENDENCY,"Filesystem not initialized");const i=await this.blobToArrayBuffer(t),r=btoa(String.fromCharCode(...new Uint8Array(i))),n=`bundles/${e}/bundle.zip`;return await this.filesystem.writeFile({path:n,data:r,directory:a.Data,recursive:!0}),this.logger.debug("Bundle saved to filesystem",{bundleId:e,path:n,size:t.size}),n}async loadBlob(e){if(!this.filesystem)throw new N(m.MISSING_DEPENDENCY,"Filesystem not initialized");try{const t=`bundles/${e}/bundle.zip`,i=await this.filesystem.readFile({path:t,directory:a.Data}),r=atob(i.data),n=new Uint8Array(r.length);for(let e=0;e<r.length;e++)n[e]=r.charCodeAt(e);return new Blob([n],{type:"application/zip"})}catch(t){return this.logger.debug("Failed to load bundle from filesystem",{bundleId:e,error:t}),null}}async deleteBlob(e){if(!this.filesystem)throw new N(m.MISSING_DEPENDENCY,"Filesystem not initialized");try{const t=`bundles/${e}`;await this.filesystem.rmdir({path:t,directory:a.Data,recursive:!0}),this.logger.debug("Bundle deleted from filesystem",{bundleId:e})}catch(t){this.logger.warn("Failed to delete bundle from filesystem",{bundleId:e,error:t})}}}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=y.getInstance(),this.configManager=E.getInstance(),this.securityValidator=b.getInstance()}static compareVersions(e,t){try{const[a,i]=e.split("-"),[r,n]=t.split("-"),s=a.split(".").map(Number),o=r.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 i&&!n?-1:!i&&n?1:i&&n?i.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 v(m.MISSING_DEPENDENCY,"Preferences not configured. Please configure the plugin first.")}async checkForUpdates(e,t,a,i){if(this.securityValidator.validateUrl(e),this.securityValidator.validateVersion(a),!t||!i)throw new v(m.INVALID_CONFIG,"Channel and appId are required");const r=`${t}-${i}`,n=await this.getCachedVersionInfo(r);if(n&&n.channel===t&&Date.now()-n.timestamp<this.CACHE_DURATION)return this.logger.debug("Returning cached version info",{channel:t,version:n.data.version}),n.data;try{const n=new URL(`${e}/check`);n.searchParams.append("channel",t),n.searchParams.append("version",a),n.searchParams.append("appId",i),n.searchParams.append("platform","web");const s=await fetch(n.toString(),{method:"GET",headers:{"Content-Type":"application/json","X-App-Version":a,"X-App-Id":i},signal:AbortSignal.timeout(this.configManager.get("downloadTimeout"))});if(!s.ok)throw new Error(`Version check failed: ${s.status}`);const o=await s.json();if(!o.version)throw new v(m.INVALID_BUNDLE_FORMAT,"No version in server response");return this.securityValidator.validateVersion(o.version),o.bundleUrl&&this.securityValidator.validateUrl(o.bundleUrl),o.minAppVersion&&this.securityValidator.validateVersion(o.minAppVersion),await this.cacheVersionInfo(r,t,o),this.logger.info("Version check completed",{channel:t,currentVersion:a,latestVersion:o.version,updateAvailable:this.isNewerVersion(o.version,a)}),o}catch(e){return this.logger.error("Failed to check for updates",e),null}}isNewerVersion(e,t){try{const a=this.parseVersion(e),i=this.parseVersion(t);return a.major!==i.major?a.major>i.major:a.minor!==i.minor?a.minor>i.minor:a.patch!==i.patch?a.patch>i.patch:!(a.prerelease&&!i.prerelease||(a.prerelease||!i.prerelease)&&(!a.prerelease||!i.prerelease||!(a.prerelease>i.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(e){const t=e.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/);if(!t)throw new v(m.INVALID_BUNDLE_FORMAT,"Invalid version format");return{major:parseInt(t[1],10),minor:parseInt(t[2],10),patch:parseInt(t[3],10),prerelease:t[4],build:t[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 i=a[e];return!i||(this.securityValidator.validateVersion(t),this.securityValidator.validateVersion(i),!this.isNewerVersion(i,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 i={channel:t,data:a,timestamp:Date.now()};this.memoryCache.set(e,i);try{const{value:t}=await this.preferences.get({key:this.VERSION_CHECK_CACHE_KEY}),a=t?JSON.parse(t):{},r=Date.now();for(const e in a)r-a[e].timestamp>2*this.CACHE_DURATION&&delete a[e];a[e]=i,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 S{constructor(e){this.config=e,this.logger=new y("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),i=t.split(".").map(Number);for(let e=0;e<Math.max(a.length,i.length);e++){const t=a[e]||0,r=i[e]||0;if(t>r)return 1;if(t<r)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,i]=e.split(".").map(Number);return a>0?5:i>0&&t&&t>30?4:i>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"}(I||(I={}));class C{constructor(e){this.logger=new y("AppUpdateInstaller"),this.currentState={installStatus:I.UNKNOWN,packageName:"",availableVersion:""}}async startImmediateUpdate(){if(this.logger.log("Starting immediate update installation"),this.updateState(I.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(I.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!==I.DOWNLOADED)throw new Error("Update not ready for installation");this.updateState(I.INSTALLING),this.isAndroid()?this.logger.log("Completing Android update installation"):setTimeout(()=>{this.updateState(I.INSTALLED)},1e3)}async cancelUpdate(){this.logger.log("Cancelling update"),this.currentState.installStatus===I.DOWNLOADING&&this.updateState(I.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,i=setInterval(()=>{e+=a,e>=t&&(e=t,clearInterval(i),this.updateState(I.DOWNLOADED));const r={bytesDownloaded:e,totalBytesToDownload:t,percentComplete:Math.round(e/t*100),downloadSpeed:a,estimatedTime:Math.ceil((t-e)/a)};this.progressCallback&&this.progressCallback(r)},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 T{constructor(t){this.config=t,this.logger=new y("PlatformAppUpdate"),this.platform=e.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),i=await e.json();i.version&&i.version!==t.currentVersion&&(a.updateAvailable=!0,a.availableVersion=i.version,a.releaseNotes=i.releaseNotes,a.updateURL=i.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 M{constructor(e){this.listeners=new Map,this.config=e,this.logger=new y("AppUpdateManager"),this.checker=new S(e),this.installer=new C(e),this.platformUpdate=new T(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 B{constructor(){this.listeners=new Map}static getInstance(){return B.instance||(B.instance=new B),B.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 V{constructor(){this.bundleManager=null,this.downloadManager=null,this.versionManager=null,this.appUpdateManager=null,this.initialized=!1,this.configManager=E.getInstance(),this.logger=y.getInstance(),this.securityValidator=b.getInstance(),this.eventEmitter=B.getInstance()}static getInstance(){return V.instance||(V.instance=new V),V.instance}async initialize(e){var t,a;if(this.initialized)this.logger.warn("Plugin already initialized");else try{this.configManager.configure(e),e.filesystem||(e.filesystem=i),e.preferences||(e.preferences=n),this.bundleManager=new O,await this.bundleManager.initialize(),this.downloadManager=new R,await this.downloadManager.initialize(),this.versionManager=new L,await this.versionManager.initialize(),this.appUpdateManager=new M({serverUrl:e.serverUrl||e.baseUrl||"",channel:e.channel||"production",autoCheck:null===(t=e.autoCheck)||void 0===t||t,autoUpdate:null!==(a=e.autoUpdate)&&void 0!==a&&a,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 A(m.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 F{constructor(){this.initialized=!1,this.windowEventListeners=new Map,this.pluginManager=V.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:u.UP_TO_DATE,version:(null==e?void 0:e.version)||"1.0.0"}}catch(e){return{status:u.ERROR,error:{code:f.UNKNOWN_ERROR,message:e instanceof Error?e.message:"Sync failed"}}}}async download(e){const t=this.pluginManager.getDownloadManager(),a=this.pluginManager.getBundleManager(),i=await t.downloadWithRetry(e.url,e.version),r=await t.saveBlob(e.version,i),n={bundleId:e.version,version:e.version,path:r,downloadTime:Date.now(),size:i.size,status:g.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 e=this.pluginManager.getBundleManager(),t=await e.getActiveBundle();if(!t)throw new A(m.FILE_NOT_FOUND,"No active bundle found");return t}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 i=e.keepVersions;i<a.length;i++)await t.deleteBundle(a[i].bundleId)}}async notifyAppReady(){const e=this.pluginManager.getBundleManager(),t=await e.getActiveBundle();t&&(t.status=g.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(e){throw new A(m.PLATFORM_NOT_SUPPORTED,"Background updates are not supported on web")}async triggerBackgroundCheck(){return{success:!1,updatesFound:!1,notificationSent:!1,error:{code:f.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 k=t("NativeUpdate",{web:()=>new F});class P{constructor(){this.filesystem=null,this.memoryCache=new Map,this.CACHE_DIR="cache",this.logger=y.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.Data,recursive:!0})}catch(e){this.logger.debug("Cache directory may already exist",e)}await this.cleanExpiredCache()}async set(e,t,a){const i=Date.now()+(a||this.configManager.get("cacheExpiration")),r={data:t,timestamp:Date.now(),expiry:i};this.memoryCache.set(e,r),this.shouldPersist(t)&&await this.persistToFile(e,r),this.logger.debug("Cache entry set",{key:e,expiry:new Date(i)})}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.Data,recursive:!0}),await this.filesystem.mkdir({path:this.CACHE_DIR,directory:a.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,i]of this.memoryCache)e>=i.expiry&&(this.memoryCache.delete(a),t++);try{const i=await this.filesystem.readdir({path:this.CACHE_DIR,directory:a.Data});for(const a of i.files){const i=a.name.replace(".json",""),r=await this.loadFromFile(i);(!r||e>=r.expiry)&&(await this.removeFile(i),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 i=await this.filesystem.readdir({path:this.CACHE_DIR,directory:a.Data});e=i.files.length;for(const e of i.files)t+=(await this.filesystem.stat({path:`${this.CACHE_DIR}/${e.name}`,directory:a.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 i=`${this.CACHE_DIR}/${e}.json`,n=JSON.stringify(t);await this.filesystem.writeFile({path:i,data:n,directory:a.Data,encoding:r.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`,i=await this.filesystem.readFile({path:t,directory:a.Data,encoding:r.UTF8});return JSON.parse(i.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.Data})}catch(t){this.logger.debug("Failed to remove cache file",{key:e,error:t})}}}class z{constructor(){this.filesystem=null,this.updateInProgress=!1,this.currentState=null,this.pluginManager=V.getInstance(),this.securityValidator=b.getInstance()}async initialize(){if(this.filesystem=this.pluginManager.getConfigManager().get("filesystem"),!this.filesystem)throw new _(m.MISSING_DEPENDENCY,"Filesystem not configured")}async applyUpdate(e,t){if(this.updateInProgress)throw new _(m.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:e});const r=await i.getBundle(e);if(!r)throw new _(m.FILE_NOT_FOUND,`Bundle ${e} not found`);if("READY"!==r.status&&"ACTIVE"!==r.status)throw new _(m.BUNDLE_NOT_READY,`Bundle ${e} is not ready for installation`);const n=await i.getActiveBundle();this.currentState={currentBundle:n,newBundle:r,backupPath:null,startTime:Date.now()},await this.validateUpdate(n,r,t),n&&"default"!==n.bundleId&&(this.currentState.backupPath=await this.createBackup(n)),await this.performUpdate(r),await this.verifyUpdate(r),await i.setActiveBundle(e),(null==t?void 0:t.cleanupOldBundles)&&await i.cleanupOldBundles(t.keepBundleCount||3),a.info("Bundle update completed successfully",{bundleId:e,version:r.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(e,t,a){const i=this.pluginManager.getLogger(),r=this.pluginManager.getVersionManager();if(e&&!(null==a?void 0:a.allowDowngrade)&&r.shouldBlockDowngrade(e.version,t.version))throw new v(m.VERSION_DOWNGRADE,`Cannot downgrade from ${e.version} to ${t.version}`);if(!t.verified){i.warn("Bundle not verified, verifying now",{bundleId:t.bundleId});const e=this.pluginManager.getDownloadManager(),a=await e.loadBlob(t.bundleId);if(!a)throw new _(m.FILE_NOT_FOUND,"Bundle data not found");const r=await a.arrayBuffer();if(!await this.securityValidator.verifyChecksum(r,t.checksum))throw new v(m.CHECKSUM_MISMATCH,"Bundle checksum verification failed");if(t.signature&&!await this.securityValidator.verifySignature(r,t.signature))throw new v(m.SIGNATURE_INVALID,"Bundle signature verification failed");await this.pluginManager.getBundleManager().markBundleAsVerified(t.bundleId)}i.debug("Bundle validation passed",{bundleId:t.bundleId})}async createBackup(e){const t=`backups/${e.bundleId}_${Date.now()}`,i=this.pluginManager.getLo