@capawesome/capacitor-live-update 8.5.0 → 8.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/CapawesomeCapacitorLiveUpdate.podspec +15 -0
  2. package/Package.swift +21 -5
  3. package/README.md +38 -5
  4. package/android/build.gradle +19 -0
  5. package/android/consumer-rules.pro +5 -0
  6. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdate.java +129 -55
  7. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java +31 -1
  8. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePreferences.java +12 -14
  9. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/DownloadBundleOptions.java +11 -5
  10. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/FetchLatestBundleOptions.java +23 -1
  11. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/FetchLatestBundleResult.java +30 -0
  12. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/providers/ionic/LiveUpdateIonicManager.kt +229 -0
  13. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/providers/ionic/LiveUpdateIonicPreferences.kt +85 -0
  14. package/dist/docs.json +1 -1
  15. package/dist/esm/definitions.d.ts +3 -0
  16. package/dist/esm/definitions.js.map +1 -1
  17. package/ios/Plugin/Classes/Options/DownloadBundleOptions.swift +7 -6
  18. package/ios/Plugin/Classes/Options/FetchLatestBundleOptions.swift +19 -3
  19. package/ios/Plugin/Classes/Results/FetchLatestBundleResult.swift +24 -0
  20. package/ios/Plugin/Enums/CustomError.swift +13 -0
  21. package/ios/Plugin/LiveUpdate.swift +66 -39
  22. package/ios/Plugin/LiveUpdatePlugin.swift +2 -2
  23. package/ios/Plugin/Providers/Ionic/LiveUpdateIonicManager.swift +187 -0
  24. package/ios/Plugin/Providers/Ionic/LiveUpdateIonicPreferences.swift +81 -0
  25. package/ios/Plugin/Providers/Ionic/LiveUpdatePlugin+LiveUpdateProvider.swift +15 -0
  26. package/package.json +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAAA,wCAAwC","sourcesContent":["/// <reference types=\"@capacitor/cli\" />\n\nimport type { PluginListenerHandle } from '@capacitor/core';\n\ndeclare module '@capacitor/cli' {\n export interface PluginsConfig {\n LiveUpdate?: {\n /**\n * The app ID is used to identify the app when using [Capawesome Cloud](https://capawesome.io/cloud/).\n *\n * This is **NOT** the same as the app identifier (e.g. `com.example.app`).\n * This is a unique identifier generated by Capawesome Cloud (e.g. `6e351b4f-69a7-415e-a057-4567df7ffe94`).\n *\n * @since 5.0.0\n * @example '6e351b4f-69a7-415e-a057-4567df7ffe94'\n */\n appId?: string;\n /**\n * Whether or not to automatically block bundles that have been rolled back.\n *\n * When enabled, the plugin will automatically block bundles that caused a rollback\n * (up to 100 bundles). When the limit is reached, the oldest blocked bundle is unblocked.\n * Blocked bundles will be skipped in future sync operations.\n *\n * **Attention**: This option has no effect if `readyTimeout` is set to `0`.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.3.0\n * @default false\n */\n autoBlockRolledBackBundles?: boolean;\n /**\n * Whether or not to automatically delete unused bundles.\n *\n * When enabled, the plugin will automatically delete unused bundles after calling `ready()`.\n *\n * @since 5.0.0\n * @default false\n */\n autoDeleteBundles?: boolean;\n /**\n * The auto-update strategy for live updates.\n *\n * - `none`: Live updates will not be applied automatically.\n * - `background`: Live updates will be automatically downloaded\n * and applied in the background at app startup and when the app resumes\n * (if the last check was more than 15 minutes ago).\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.3.0\n * @default 'none'\n * @example 'background'\n */\n autoUpdateStrategy?: 'none' | 'background';\n /**\n * The default channel of the app.\n *\n * This can be overridden by `setChannel()`, the `channel` parameter of `sync()`,\n * or the native channel configuration\n * (`CapawesomeLiveUpdateDefaultChannel` in `Info.plist` on iOS, `capawesome_live_update_default_channel`\n * in `strings.xml` on Android or `electron/capacitor.electron.config.ts` on Electron).\n *\n * @since 6.3.0\n * @example 'production'\n */\n defaultChannel?: string;\n /**\n * The timeout in milliseconds for HTTP requests.\n *\n * @since 6.4.0\n * @default 60000\n */\n httpTimeout?: number;\n /**\n * The public key to verify the integrity of the bundle.\n *\n * The public key must be a PEM-encoded RSA public key.\n *\n * @since 6.1.0\n * @example '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDodf1SD0OOn6hIlDuKBza0Ed0OqtwyVJwiyjmE9BJaZ7y8ZUfcF+SKmd0l2cDPM45XIg2tAFux5n29uoKyHwSt+6tCi5CJA5Z1/1eZruRRqABLonV77KS3HUtvOgqRLDnKSV89dYZkM++NwmzOPgIF422mvc+VukcVOBfc8/AHQIDAQAB-----END PUBLIC KEY-----'\n */\n publicKey?: string;\n /**\n * The timeout in milliseconds to wait for the app to be ready\n * before resetting to the default bundle.\n *\n * It is strongly **recommended** to configure this option (e.g. `10000` ms)\n * so that the plugin can roll back to the default bundle in case of problems.\n * If configured, the plugin will wait for the app to call the `ready()`\n * method before resetting to the default bundle.\n *\n * Set to `0` to disable the timeout.\n *\n * @since 5.0.0\n * @default 0\n * @example 10000\n */\n readyTimeout?: number;\n /**\n * The API domain of the [Capawesome Cloud](https://cloud.capawesome.io) server **without** scheme or path.\n *\n * @since 7.0.0\n * @default 'api.cloud.capawesome.io'\n * @example 'api.cloud.capawesome.eu'\n */\n serverDomain?: string;\n };\n }\n}\n\nexport interface LiveUpdatePlugin {\n /**\n * Clear all blocked bundles from the blocked list.\n *\n * This removes all bundle identifiers that were automatically blocked\n * due to rollbacks when `autoBlockRolledBackBundles` is enabled.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.4.0\n */\n clearBlockedBundles(): Promise<void>;\n /**\n * Delete a bundle from the app.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n deleteBundle(options: DeleteBundleOptions): Promise<void>;\n /**\n * Download a bundle.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n downloadBundle(options: DownloadBundleOptions): Promise<void>;\n /**\n * Fetch channels from [Capawesome Cloud](https://capawesome.io/cloud/).\n *\n * This is primarily intended for development and QA purposes.\n * It allows you to retrieve a list of available channels so you can\n * dynamically switch between them using `setChannel(...)`.\n *\n * **Attention**: Only works for apps with public channels enabled.\n * If channels are private, they can still be set using `setChannel(...)`\n * but won't be returned by this method.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 8.2.0\n */\n fetchChannels(options?: FetchChannelsOptions): Promise<FetchChannelsResult>;\n /**\n * Fetch the latest bundle using the [Capawesome Cloud](https://capawesome.io/cloud/).\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 6.6.0\n */\n fetchLatestBundle(\n options?: FetchLatestBundleOptions,\n ): Promise<FetchLatestBundleResult>;\n /**\n * Get all blocked bundle identifiers.\n *\n * Returns the list of bundle identifiers that were automatically blocked\n * due to rollbacks when `autoBlockRolledBackBundles` is enabled.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.4.0\n */\n getBlockedBundles(): Promise<GetBlockedBundlesResult>;\n /**\n * Get all identifiers of bundles that have been downloaded.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n * @deprecated Use `getDownloadedBundles()` instead.\n */\n getBundles(): Promise<GetBundlesResult>;\n /**\n * Get the channel that is used for the update.\n *\n * The channel is resolved in the following order (highest priority first):\n * 1. `setChannel()` (SharedPreferences on Android / UserDefaults on iOS)\n * 2. Native config (`CapawesomeLiveUpdateDefaultChannel` in `Info.plist` on iOS,\n * `capawesome_live_update_default_channel` in `strings.xml` on Android or\n * `defaultChannel` in `electron/capacitor.electron.config.ts` on Electron)\n * 3. Capacitor config `defaultChannel`\n *\n * **Note**: The `channel` parameter of `sync()` takes the highest priority\n * but is not persisted and therefore not returned by this method.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n getChannel(): Promise<GetChannelResult>;\n /**\n * Get the runtime configuration.\n *\n * Returns the current plugin configuration including any runtime\n * overrides set via `setConfig()`.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.4.0\n */\n getConfig(): Promise<GetConfigResult>;\n /**\n * Get all identifiers of bundles that have been downloaded.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.4.0\n */\n getDownloadedBundles(): Promise<GetDownloadedBundlesResult>;\n /**\n * Get the bundle identifier of the current bundle.\n * The current bundle is the bundle that is currently used by the app.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 6.7.0\n */\n getCurrentBundle(): Promise<GetCurrentBundleResult>;\n /**\n * Get the custom identifier of the device.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n getCustomId(): Promise<GetCustomIdResult>;\n /**\n * Get the unique device identifier.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n getDeviceId(): Promise<GetDeviceIdResult>;\n /**\n * Check whether a sync operation is currently in progress.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.4.0\n */\n isSyncing(): Promise<IsSyncingResult>;\n /**\n * Get the bundle identifier of the next bundle.\n * The next bundle is the bundle that will be used after calling `reload()`\n * or restarting the app.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 6.7.0\n */\n getNextBundle(): Promise<GetNextBundleResult>;\n /**\n * Get the version code of the app.\n *\n * On **Android**, this is the `versionCode` from the `android/app/build.gradle` file.\n * On **iOS**, this is the `CFBundleVersion` from the `Info.plist` file.\n * On **Electron**, this is the `version` from the `electron/package.json` file.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n getVersionCode(): Promise<GetVersionCodeResult>;\n /**\n * Get the version name of the app.\n *\n * On **Android**, this is the `versionName` from the `android/app/build.gradle` file.\n * On **iOS**, this is the `CFBundleShortVersionString` from the `Info.plist` file.\n * On **Electron**, this is the `version` from the `electron/package.json` file.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n getVersionName(): Promise<GetVersionNameResult>;\n /**\n * Notify the plugin that the app is ready to use and no rollback is needed.\n *\n * **Attention**: This method should be called as soon as the app is ready to use\n * to prevent the app from being reset to the default bundle.\n * It must always be called before `sync(...)` or any other method that changes the bundle.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n ready(): Promise<ReadyResult>;\n /**\n * Reload the app to apply the new bundle.\n *\n * This method reloads the web view only, the native process is **not** restarted.\n * Resources of the web layer that hold an exclusive lock (e.g. IndexedDB or WebSockets)\n * are therefore not released automatically and may prevent the new bundle from starting.\n * Release these resources before calling this method or apply the new bundle\n * on the next app start instead.\n *\n * **Attention**: The in-memory state of the app (e.g. unsaved user input) is lost.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n reload(): Promise<void>;\n /**\n * Reset the app to the default bundle.\n *\n * Call `reload()` or restart the app to apply the changes.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n reset(): Promise<void>;\n /**\n * Reset the runtime configuration to the values from the Capacitor config file.\n *\n * This clears any runtime configuration set via `setConfig()`.\n * The changes take effect immediately.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.4.0\n */\n resetConfig(): Promise<void>;\n /**\n * Set the channel to use for the update.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n setChannel(options: SetChannelOptions): Promise<void>;\n /**\n * Set the runtime configuration.\n *\n * This allows updating plugin configuration options at runtime.\n * The changes are persisted across app restarts and take effect immediately.\n *\n * **Important:** Runtime configuration is automatically reset to default values\n * whenever the native app is updated to a new version. This ensures that\n * configuration from previous versions doesn't persist after an app update.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.4.0\n */\n setConfig(options: SetConfigOptions): Promise<void>;\n /**\n * Set the custom identifier of the device.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n setCustomId(options: SetCustomIdOptions): Promise<void>;\n /**\n * Set the next bundle to use for the app.\n *\n * Call `reload()` or restart the app to apply the changes.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 6.7.0\n */\n setNextBundle(options: SetNextBundleOptions): Promise<void>;\n /**\n * Automatically download and set the latest bundle for the app using the [Capawesome Cloud](https://capawesome.io/cloud/).\n *\n * Call `reload()` or restart the app to apply the changes.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n sync(options?: SyncOptions): Promise<SyncResult>;\n /**\n * Listen for the download progress of a bundle.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.0.0\n */\n addListener(\n eventName: 'downloadBundleProgress',\n listenerFunc: DownloadBundleProgressListener,\n ): Promise<PluginListenerHandle>;\n /**\n * Listen for when a bundle is set as the next bundle.\n *\n * This event is triggered whenever a bundle is set to be used on the next app restart,\n * either through automatic updates or manual calls to `setNextBundle()`.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.3.0\n */\n addListener(\n eventName: 'nextBundleSet',\n listenerFunc: NextBundleSetListener,\n ): Promise<PluginListenerHandle>;\n /**\n * Listen for when the app is reloaded.\n *\n * This event is triggered after the `reload()` method is called\n * and the app has been reloaded.\n *\n * **Note:** To verify whether an update was successfully applied after a reload,\n * use the `ready()` method instead. The `ready()` method provides detailed information\n * about the current bundle, previous bundle, and whether a rollback occurred.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.4.0\n */\n addListener(\n eventName: 'reloaded',\n listenerFunc: ReloadedListener,\n ): Promise<PluginListenerHandle>;\n /**\n * Remove all listeners for this plugin.\n *\n * @since 7.2.0\n */\n removeAllListeners(): Promise<void>;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface DeleteBundleOptions {\n /**\n * The unique identifier of the bundle to delete.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string;\n}\n\nexport interface DownloadBundleOptions {\n /**\n * The artifact type of the bundle.\n *\n * On **Electron**, only the `zip` artifact type is supported.\n *\n * @since 6.6.0\n * @default 'zip'\n * @example 'manifest'\n */\n artifactType?: 'manifest' | 'zip';\n /**\n * The unique identifier of the bundle.\n *\n * **Attention**: The value `public` is reserved and cannot be used as a bundle identifier.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string;\n /**\n * The checksum of the self-hosted bundle as a SHA-256 hash\n * in hex format to verify the integrity of the bundle.\n *\n * **Attention**: Only supported for the `zip` artifact type.\n *\n * @since 7.1.0\n */\n checksum?: string;\n /**\n * The signature of the self-hosted bundle as a signed SHA-256 hash\n * in base64 format to verify the integrity of the bundle.\n *\n * **Attention**: Only supported for the `zip` artifact type.\n *\n * @since 7.1.0\n */\n signature?: string;\n /**\n * The URL of the bundle to download.\n *\n * For the `zip` artifact type, the URL must point to a ZIP file.\n * For the `manifest` artifact type, the URL serves as the base URL\n * to download the individual files. For example, if the URL is\n * `https://example.com/download`, the plugin will download the file\n * with the href `index.html` from `https://example.com/download?href=index.html`.\n *\n * To **verify the integrity** of the file, the server should return\n * a `X-Checksum` header with the SHA-256 hash in hex format.\n *\n * To **verify the signature** of the file, the server should return\n * a `X-Signature` header with the signed SHA-256 hash in base64 format.\n *\n * @since 5.0.0\n * @example 'https://example.com/bundle.zip'\n */\n url: string;\n}\n\n/**\n * @since 8.2.0\n */\nexport interface FetchChannelsOptions {\n /**\n * The maximum number of channels to return.\n *\n * @since 8.2.0\n * @default 50\n */\n limit?: number;\n /**\n * The number of channels to skip.\n *\n * @since 8.2.0\n * @default 0\n */\n offset?: number;\n /**\n * The query to filter channels by name.\n *\n * @since 8.2.0\n */\n query?: string;\n}\n\n/**\n * @since 8.2.0\n */\nexport interface FetchChannelsResult {\n /**\n * The list of channels.\n *\n * @since 8.2.0\n */\n channels: Channel[];\n}\n\n/**\n * @since 8.2.0\n */\nexport interface Channel {\n /**\n * The unique identifier of the channel.\n *\n * @since 8.2.0\n */\n id: string;\n /**\n * The name of the channel.\n *\n * @since 8.2.0\n */\n name: string;\n}\n\n/**\n * @since 6.7.0\n */\nexport interface FetchLatestBundleOptions {\n /**\n * The preferred channel name from which the latest bundle should be fetched.\n *\n * This is the SDK preference and may be overridden by a forced channel assignment\n * configured in the Capawesome Cloud Console.\n *\n * @since 6.7.0\n */\n channel?: string;\n}\n\n/**\n * @since 6.6.0\n */\nexport interface FetchLatestBundleResult {\n /**\n * The artifact type of the bundle.\n *\n * On **Electron**, only the `zip` artifact type is supported.\n *\n * @since 6.7.0\n */\n artifactType?: 'manifest' | 'zip';\n /**\n * The unique identifier of the latest bundle.\n *\n * On Capawesome Cloud, this is the ID of the app build artifact.\n *\n * If `null`, no bundle is available.\n *\n * @since 6.6.0\n */\n bundleId: string | null;\n /**\n * The name of the channel that the bundle is actually from.\n *\n * This is the resolved channel after applying any forced channel assignment\n * and may differ from the channel set by the Live Update SDK.\n *\n * @since 8.3.0\n */\n channel?: string;\n /**\n * The checksum of the latest bundle if the bundle is self-hosted.\n *\n * If the bundle is hosted on Capawesome Cloud, the checksum will be\n * returned as response header when downloading the bundle.\n *\n * @since 7.1.0\n */\n checksum?: string;\n /**\n * Custom properties that are associated with the latest bundle.\n *\n * @since 7.0.0\n * @example { \"key\": \"value\" }\n */\n customProperties?: { [key: string]: string };\n /**\n * The URL of the latest bundle to download.\n * Pass this URL to the `downloadBundle(...)` method to download the bundle.\n *\n * @since 6.7.0\n */\n downloadUrl?: string;\n /**\n * The signature of the latest bundle if the bundle is self-hosted.\n *\n * If the bundle is hosted on Capawesome Cloud, the signature will be\n * returned as response header when downloading the bundle.\n *\n * @since 7.1.0\n */\n signature?: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetBundleResult {\n /**\n * The unique identifier of the active bundle.\n *\n * If `null`, the default bundle is being used.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string | null;\n}\n\n/**\n * @since 7.4.0\n */\nexport interface GetBlockedBundlesResult {\n /**\n * An array of unique identifiers of all blocked bundles.\n *\n * @since 7.4.0\n */\n bundleIds: string[];\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetBundlesResult {\n /**\n * An array of unique identifiers of all available bundles.\n *\n * @since 5.0.0\n */\n bundleIds: string[];\n}\n\n/**\n * @since 7.4.0\n */\nexport interface GetDownloadedBundlesResult {\n /**\n * An array of unique identifiers of all downloaded bundles.\n *\n * @since 7.4.0\n */\n bundleIds: string[];\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetChannelResult {\n /**\n * The channel name.\n *\n * If `null`, the app is using the default channel.\n *\n * @since 5.0.0\n * @example 'production'\n */\n channel: string | null;\n}\n\n/**\n * @since 7.4.0\n */\nexport interface GetConfigResult {\n /**\n * The app ID used to identify the app.\n *\n * If `null`, no app ID is configured.\n *\n * @since 7.4.0\n * @example '6e351b4f-69a7-415e-a057-4567df7ffe94'\n */\n appId: string | null;\n /**\n * The auto-update strategy for live updates.\n *\n * @since 7.4.0\n * @example 'background'\n */\n autoUpdateStrategy: 'none' | 'background';\n}\n\n/**\n * @since 6.7.0\n */\nexport interface GetCurrentBundleResult {\n /**\n * The unique identifier of the current bundle.\n *\n * If `null`, the default bundle is being used.\n *\n * @since 6.7.0\n */\n bundleId: string | null;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetDeviceIdResult {\n /**\n * The unique identifier of the device.\n *\n * On iOS, [`identifierForVendor`](https://developer.apple.com/documentation/uikit/uidevice/1620059-identifierforvendor) is used.\n * The value of this property is the same for apps that come from the same vendor running on the same device.\n *\n * On Electron, a random UUID is generated on first use and stored in the app's user data directory.\n * The value changes when the user data is cleared.\n *\n * @since 5.0.0\n *\n * @example '50d2a548-80b7-4dad-adc7-97c0e79d8a89'\n */\n deviceId: string;\n}\n\n/**\n * @since 7.4.0\n */\nexport interface IsSyncingResult {\n /**\n * Whether a sync operation is currently in progress.\n *\n * @since 7.4.0\n */\n syncing: boolean;\n}\n\n/**\n * @since 6.7.0\n */\nexport interface GetNextBundleResult {\n /**\n * The unique identifier of the next bundle.\n *\n * If `null`, the default bundle is being used.\n *\n * @since 6.7.0\n */\n bundleId: string | null;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetVersionCodeResult {\n /**\n * The version code of the app.\n *\n * On **Android**, this is the `versionCode` from the `android/app/build.gradle` file.\n * On **iOS**, this is the `CFBundleVersion` from the `Info.plist` file.\n * On **Electron**, this is the `version` from the `electron/package.json` file.\n *\n * @since 5.0.0\n * @example \"1\"\n */\n versionCode: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetVersionNameResult {\n /**\n * The version name of the app.\n *\n * On **Android**, this is the `versionName` from the `android/app/build.gradle` file.\n * On **iOS**, this is the `CFBundleShortVersionString` from the `Info.plist` file.\n * On **Electron**, this is the `version` from the `electron/package.json` file.\n *\n * @since 5.0.0\n * @example \"1.0.0\"\n */\n versionName: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetCustomIdResult {\n /**\n * The custom identifier of the device.\n *\n * If `null`, no custom identifier is set.\n *\n * @since 5.0.0\n * @example '50d2a548-80b7-4dad-adc7-97c0e79d8a89'\n */\n customId: string | null;\n}\n\n/**\n * @since 7.0.0\n */\nexport interface ReadyResult {\n /**\n * The identifier of the previous bundle used.\n *\n * If `null`, the default bundle was used.\n *\n * @since 7.0.0\n */\n previousBundleId: string | null;\n /**\n * The identifier of the current bundle used.\n *\n * If `null`, the default bundle is being used.\n *\n * @since 7.0.0\n */\n currentBundleId: string | null;\n /**\n * Whether or not the app was reset to the default bundle.\n *\n * @since 7.0.0\n */\n rollback: boolean;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SetBundleOptions {\n /**\n * The unique identifier of the bundle to use.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SetChannelOptions {\n /**\n * The channel name.\n *\n * Set `null` to remove the channel.\n *\n * @since 5.0.0\n */\n channel: string | null;\n}\n\n/**\n * @since 7.4.0\n */\nexport interface SetConfigOptions {\n /**\n * The app ID used to identify the app.\n *\n * Set `null` to reset to the value from the Capacitor config file.\n *\n * @since 7.4.0\n * @example '6e351b4f-69a7-415e-a057-4567df7ffe94'\n */\n appId?: string | null;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SetCustomIdOptions {\n /**\n * The custom identifier of the device.\n *\n * Set `null` to remove the custom identifier.\n *\n * @since 5.0.0\n */\n customId: string | null;\n}\n\n/**\n * @since 6.7.0\n */\nexport interface SetNextBundleOptions {\n /**\n * The unique identifier of the bundle to use.\n *\n * Set `null` to use the default bundle (same as calling `reset()`).\n *\n * @since 6.7.0\n * @example '1.0.0'\n */\n bundleId: string | null;\n}\n\n/**\n * @since 6.7.0\n */\nexport interface SyncOptions {\n /**\n * The preferred channel name from which the latest bundle should be fetched.\n *\n * This is the SDK preference and may be overridden by a forced channel assignment\n * configured in the Capawesome Cloud Console.\n *\n * @since 6.7.0\n */\n channel?: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SyncResult {\n /**\n * The identifier of the next bundle to use.\n *\n * If `null`, the app is up-to-date and no new bundle is available.\n *\n * @since 5.0.0\n */\n nextBundleId: string | null;\n}\n\n/**\n * Listener for the download progress of a bundle.\n *\n * @since 7.0.0\n */\nexport type DownloadBundleProgressListener = (\n event: DownloadBundleProgressEvent,\n) => void;\n\n/**\n * Event that is triggered when the download progress of a bundle changes.\n *\n * @since 7.0.0\n */\nexport interface DownloadBundleProgressEvent {\n /**\n * The unique identifier of the bundle that is being downloaded.\n *\n * @since 7.0.0\n */\n bundleId: string;\n /**\n * The number of bytes that have been downloaded.\n *\n * @since 7.0.0\n */\n downloadedBytes: number;\n /**\n * The progress of the download in percent as a value between `0` and `1`.\n *\n * @since 7.0.0\n * @example 0.5\n */\n progress: number;\n /**\n * The total number of bytes to download.\n *\n * @since 7.0.0\n */\n totalBytes: number;\n}\n\n/**\n * Listener for when a bundle is set as the next bundle.\n *\n * @since 7.3.0\n */\nexport type NextBundleSetListener = (event: NextBundleSetEvent) => void;\n\n/**\n * Event that is triggered when a bundle is set as the next bundle.\n *\n * @since 7.3.0\n */\nexport interface NextBundleSetEvent {\n /**\n * The unique identifier of the bundle that is set as the next bundle.\n *\n * If `null`, the default bundle will be used.\n *\n * @since 7.3.0\n * @example '1.0.0'\n */\n bundleId: string | null;\n}\n\n/**\n * Listener for when the app is reloaded.\n *\n * @since 7.4.0\n */\nexport type ReloadedListener = () => void;\n"]}
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAAA,wCAAwC","sourcesContent":["/// <reference types=\"@capacitor/cli\" />\n\nimport type { PluginListenerHandle } from '@capacitor/core';\n\ndeclare module '@capacitor/cli' {\n export interface PluginsConfig {\n LiveUpdate?: {\n /**\n * The app ID is used to identify the app when using [Capawesome Cloud](https://capawesome.io/cloud/).\n *\n * This is **NOT** the same as the app identifier (e.g. `com.example.app`).\n * This is a unique identifier generated by Capawesome Cloud (e.g. `6e351b4f-69a7-415e-a057-4567df7ffe94`).\n *\n * @since 5.0.0\n * @example '6e351b4f-69a7-415e-a057-4567df7ffe94'\n */\n appId?: string;\n /**\n * Whether or not to automatically block bundles that have been rolled back.\n *\n * When enabled, the plugin will automatically block bundles that caused a rollback\n * (up to 100 bundles). When the limit is reached, the oldest blocked bundle is unblocked.\n * Blocked bundles will be skipped in future sync operations.\n *\n * **Attention**: This option has no effect if `readyTimeout` is set to `0`.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.3.0\n * @default false\n */\n autoBlockRolledBackBundles?: boolean;\n /**\n * Whether or not to automatically delete unused bundles.\n *\n * When enabled, the plugin will automatically delete unused bundles after calling `ready()`.\n *\n * @since 5.0.0\n * @default false\n */\n autoDeleteBundles?: boolean;\n /**\n * The auto-update strategy for live updates.\n *\n * - `none`: Live updates will not be applied automatically.\n * - `background`: Live updates will be automatically downloaded\n * and applied in the background at app startup and when the app resumes\n * (if the last check was more than 15 minutes ago).\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.3.0\n * @default 'none'\n * @example 'background'\n */\n autoUpdateStrategy?: 'none' | 'background';\n /**\n * The default channel of the app.\n *\n * This can be overridden by `setChannel()`, the `channel` parameter of `sync()`,\n * or the native channel configuration\n * (`CapawesomeLiveUpdateDefaultChannel` in `Info.plist` on iOS, `capawesome_live_update_default_channel`\n * in `strings.xml` on Android or `electron/capacitor.electron.config.ts` on Electron).\n *\n * @since 6.3.0\n * @example 'production'\n */\n defaultChannel?: string;\n /**\n * The timeout in milliseconds for HTTP requests.\n *\n * @since 6.4.0\n * @default 60000\n */\n httpTimeout?: number;\n /**\n * The public key to verify the integrity of the bundle.\n *\n * The public key must be a PEM-encoded RSA public key.\n *\n * @since 6.1.0\n * @example '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDodf1SD0OOn6hIlDuKBza0Ed0OqtwyVJwiyjmE9BJaZ7y8ZUfcF+SKmd0l2cDPM45XIg2tAFux5n29uoKyHwSt+6tCi5CJA5Z1/1eZruRRqABLonV77KS3HUtvOgqRLDnKSV89dYZkM++NwmzOPgIF422mvc+VukcVOBfc8/AHQIDAQAB-----END PUBLIC KEY-----'\n */\n publicKey?: string;\n /**\n * The timeout in milliseconds to wait for the app to be ready\n * before resetting to the default bundle.\n *\n * It is strongly **recommended** to configure this option (e.g. `10000` ms)\n * so that the plugin can roll back to the default bundle in case of problems.\n * If configured, the plugin will wait for the app to call the `ready()`\n * method before resetting to the default bundle.\n *\n * Set to `0` to disable the timeout.\n *\n * @since 5.0.0\n * @default 0\n * @example 10000\n */\n readyTimeout?: number;\n /**\n * The API domain of the [Capawesome Cloud](https://cloud.capawesome.io) server **without** scheme or path.\n *\n * @since 7.0.0\n * @default 'api.cloud.capawesome.io'\n * @example 'api.cloud.capawesome.eu'\n */\n serverDomain?: string;\n };\n }\n}\n\nexport interface LiveUpdatePlugin {\n /**\n * Clear all blocked bundles from the blocked list.\n *\n * This removes all bundle identifiers that were automatically blocked\n * due to rollbacks when `autoBlockRolledBackBundles` is enabled.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.4.0\n */\n clearBlockedBundles(): Promise<void>;\n /**\n * Delete a bundle from the app.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n deleteBundle(options: DeleteBundleOptions): Promise<void>;\n /**\n * Download a bundle.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n downloadBundle(options: DownloadBundleOptions): Promise<void>;\n /**\n * Fetch channels from [Capawesome Cloud](https://capawesome.io/cloud/).\n *\n * This is primarily intended for development and QA purposes.\n * It allows you to retrieve a list of available channels so you can\n * dynamically switch between them using `setChannel(...)`.\n *\n * **Attention**: Only works for apps with public channels enabled.\n * If channels are private, they can still be set using `setChannel(...)`\n * but won't be returned by this method.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 8.2.0\n */\n fetchChannels(options?: FetchChannelsOptions): Promise<FetchChannelsResult>;\n /**\n * Fetch the latest bundle using the [Capawesome Cloud](https://capawesome.io/cloud/).\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 6.6.0\n */\n fetchLatestBundle(\n options?: FetchLatestBundleOptions,\n ): Promise<FetchLatestBundleResult>;\n /**\n * Get all blocked bundle identifiers.\n *\n * Returns the list of bundle identifiers that were automatically blocked\n * due to rollbacks when `autoBlockRolledBackBundles` is enabled.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.4.0\n */\n getBlockedBundles(): Promise<GetBlockedBundlesResult>;\n /**\n * Get all identifiers of bundles that have been downloaded.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n * @deprecated Use `getDownloadedBundles()` instead.\n */\n getBundles(): Promise<GetBundlesResult>;\n /**\n * Get the channel that is used for the update.\n *\n * The channel is resolved in the following order (highest priority first):\n * 1. `setChannel()` (SharedPreferences on Android / UserDefaults on iOS)\n * 2. Native config (`CapawesomeLiveUpdateDefaultChannel` in `Info.plist` on iOS,\n * `capawesome_live_update_default_channel` in `strings.xml` on Android or\n * `defaultChannel` in `electron/capacitor.electron.config.ts` on Electron)\n * 3. Capacitor config `defaultChannel`\n *\n * **Note**: The `channel` parameter of `sync()` takes the highest priority\n * but is not persisted and therefore not returned by this method.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n getChannel(): Promise<GetChannelResult>;\n /**\n * Get the runtime configuration.\n *\n * Returns the current plugin configuration including any runtime\n * overrides set via `setConfig()`.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.4.0\n */\n getConfig(): Promise<GetConfigResult>;\n /**\n * Get all identifiers of bundles that have been downloaded.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.4.0\n */\n getDownloadedBundles(): Promise<GetDownloadedBundlesResult>;\n /**\n * Get the bundle identifier of the current bundle.\n * The current bundle is the bundle that is currently used by the app.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 6.7.0\n */\n getCurrentBundle(): Promise<GetCurrentBundleResult>;\n /**\n * Get the custom identifier of the device.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n getCustomId(): Promise<GetCustomIdResult>;\n /**\n * Get the unique device identifier.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n getDeviceId(): Promise<GetDeviceIdResult>;\n /**\n * Check whether a sync operation is currently in progress.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.4.0\n */\n isSyncing(): Promise<IsSyncingResult>;\n /**\n * Get the bundle identifier of the next bundle.\n * The next bundle is the bundle that will be used after calling `reload()`\n * or restarting the app.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 6.7.0\n */\n getNextBundle(): Promise<GetNextBundleResult>;\n /**\n * Get the version code of the app.\n *\n * On **Android**, this is the `versionCode` from the `android/app/build.gradle` file.\n * On **iOS**, this is the `CFBundleVersion` from the `Info.plist` file.\n * On **Electron**, this is the `version` from the `electron/package.json` file.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n getVersionCode(): Promise<GetVersionCodeResult>;\n /**\n * Get the version name of the app.\n *\n * On **Android**, this is the `versionName` from the `android/app/build.gradle` file.\n * On **iOS**, this is the `CFBundleShortVersionString` from the `Info.plist` file.\n * On **Electron**, this is the `version` from the `electron/package.json` file.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n getVersionName(): Promise<GetVersionNameResult>;\n /**\n * Notify the plugin that the app is ready to use and no rollback is needed.\n *\n * **Attention**: This method should be called as soon as the app is ready to use\n * to prevent the app from being reset to the default bundle.\n * It must always be called before `sync(...)` or any other method that changes the bundle.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n ready(): Promise<ReadyResult>;\n /**\n * Reload the app to apply the new bundle.\n *\n * This method reloads the web view only, the native process is **not** restarted.\n * Resources of the web layer that hold an exclusive lock (e.g. IndexedDB or WebSockets)\n * are therefore not released automatically and may prevent the new bundle from starting.\n * Release these resources before calling this method or apply the new bundle\n * on the next app start instead.\n *\n * **Attention**: The in-memory state of the app (e.g. unsaved user input) is lost.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n reload(): Promise<void>;\n /**\n * Reset the app to the default bundle.\n *\n * Call `reload()` or restart the app to apply the changes.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n reset(): Promise<void>;\n /**\n * Reset the runtime configuration to the values from the Capacitor config file.\n *\n * This clears any runtime configuration set via `setConfig()`.\n * The changes take effect immediately.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.4.0\n */\n resetConfig(): Promise<void>;\n /**\n * Set the channel to use for the update.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n setChannel(options: SetChannelOptions): Promise<void>;\n /**\n * Set the runtime configuration.\n *\n * This allows updating plugin configuration options at runtime.\n * The changes are persisted across app restarts and take effect immediately.\n *\n * **Important:** Runtime configuration is automatically reset to default values\n * whenever the native app is updated to a new version. This ensures that\n * configuration from previous versions doesn't persist after an app update.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.4.0\n */\n setConfig(options: SetConfigOptions): Promise<void>;\n /**\n * Set the custom identifier of the device.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n setCustomId(options: SetCustomIdOptions): Promise<void>;\n /**\n * Set the next bundle to use for the app.\n *\n * Call `reload()` or restart the app to apply the changes.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 6.7.0\n */\n setNextBundle(options: SetNextBundleOptions): Promise<void>;\n /**\n * Automatically download and set the latest bundle for the app using the [Capawesome Cloud](https://capawesome.io/cloud/).\n *\n * Call `reload()` or restart the app to apply the changes.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 5.0.0\n */\n sync(options?: SyncOptions): Promise<SyncResult>;\n /**\n * Listen for the download progress of a bundle.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.0.0\n */\n addListener(\n eventName: 'downloadBundleProgress',\n listenerFunc: DownloadBundleProgressListener,\n ): Promise<PluginListenerHandle>;\n /**\n * Listen for when a bundle is set as the next bundle.\n *\n * This event is triggered whenever a bundle is set to be used on the next app restart,\n * either through automatic updates or manual calls to `setNextBundle()`.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.3.0\n */\n addListener(\n eventName: 'nextBundleSet',\n listenerFunc: NextBundleSetListener,\n ): Promise<PluginListenerHandle>;\n /**\n * Listen for when the app is reloaded.\n *\n * This event is triggered after the `reload()` method is called\n * and the app has been reloaded.\n *\n * **Note:** To verify whether an update was successfully applied after a reload,\n * use the `ready()` method instead. The `ready()` method provides detailed information\n * about the current bundle, previous bundle, and whether a rollback occurred.\n *\n * Only available on Android, iOS and Electron.\n *\n * @since 7.4.0\n */\n addListener(\n eventName: 'reloaded',\n listenerFunc: ReloadedListener,\n ): Promise<PluginListenerHandle>;\n /**\n * Remove all listeners for this plugin.\n *\n * @since 7.2.0\n */\n removeAllListeners(): Promise<void>;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface DeleteBundleOptions {\n /**\n * The unique identifier of the bundle to delete.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string;\n}\n\nexport interface DownloadBundleOptions {\n /**\n * The artifact type of the bundle.\n *\n * On **Electron**, only the `zip` artifact type is supported.\n *\n * @since 6.6.0\n * @default 'zip'\n * @example 'manifest'\n */\n artifactType?: 'manifest' | 'zip';\n /**\n * The unique identifier of the bundle.\n *\n * **Attention**: The value `public` is reserved and cannot be used as a bundle identifier.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string;\n /**\n * The checksum of the self-hosted bundle as a SHA-256 hash\n * in hex format to verify the integrity of the bundle.\n *\n * **Attention**: Only supported for the `zip` artifact type.\n *\n * @since 7.1.0\n */\n checksum?: string;\n /**\n * The signature of the self-hosted bundle as a signed SHA-256 hash\n * in base64 format to verify the integrity of the bundle.\n *\n * **Attention**: Only supported for the `zip` artifact type.\n *\n * @since 7.1.0\n */\n signature?: string;\n /**\n * The URL of the bundle to download.\n *\n * For the `zip` artifact type, the URL must point to a ZIP file.\n * For the `manifest` artifact type, the URL serves as the base URL\n * to download the individual files. For example, if the URL is\n * `https://example.com/download`, the plugin will download the file\n * with the href `index.html` from `https://example.com/download?href=index.html`.\n *\n * To **verify the integrity** of the file, the server should return\n * a `X-Checksum` header with the SHA-256 hash in hex format.\n *\n * To **verify the signature** of the file, the server should return\n * a `X-Signature` header with the signed SHA-256 hash in base64 format.\n *\n * @since 5.0.0\n * @example 'https://example.com/bundle.zip'\n */\n url: string;\n}\n\n/**\n * @since 8.2.0\n */\nexport interface FetchChannelsOptions {\n /**\n * The maximum number of channels to return.\n *\n * @since 8.2.0\n * @default 50\n */\n limit?: number;\n /**\n * The number of channels to skip.\n *\n * @since 8.2.0\n * @default 0\n */\n offset?: number;\n /**\n * The query to filter channels by name.\n *\n * @since 8.2.0\n */\n query?: string;\n}\n\n/**\n * @since 8.2.0\n */\nexport interface FetchChannelsResult {\n /**\n * The list of channels.\n *\n * @since 8.2.0\n */\n channels: Channel[];\n}\n\n/**\n * @since 8.2.0\n */\nexport interface Channel {\n /**\n * The unique identifier of the channel.\n *\n * @since 8.2.0\n */\n id: string;\n /**\n * The name of the channel.\n *\n * @since 8.2.0\n */\n name: string;\n}\n\n/**\n * @since 6.7.0\n */\nexport interface FetchLatestBundleOptions {\n /**\n * The preferred channel name from which the latest bundle should be fetched.\n *\n * This is the SDK preference and may be overridden by a forced channel assignment\n * configured in the Capawesome Cloud Console.\n *\n * @since 6.7.0\n */\n channel?: string;\n}\n\n/**\n * @since 6.6.0\n */\nexport interface FetchLatestBundleResult {\n /**\n * The artifact type of the bundle.\n *\n * On **Electron**, only the `zip` artifact type is supported.\n *\n * @since 6.7.0\n */\n artifactType?: 'manifest' | 'zip';\n /**\n * The unique identifier of the latest bundle.\n *\n * On Capawesome Cloud, this is the ID of the app build artifact.\n *\n * If `null`, no bundle is available.\n *\n * @since 6.6.0\n */\n bundleId: string | null;\n /**\n * The name of the channel that the bundle is actually from.\n *\n * This is the resolved channel after applying any forced channel assignment\n * and may differ from the channel set by the Live Update SDK.\n *\n * @since 8.3.0\n */\n channel?: string;\n /**\n * The checksum of the latest bundle if the bundle is self-hosted.\n *\n * If the bundle is hosted on Capawesome Cloud, the checksum will be\n * returned as response header when downloading the bundle.\n *\n * @since 7.1.0\n */\n checksum?: string;\n /**\n * Custom properties that are associated with the latest bundle.\n *\n * @since 7.0.0\n * @example { \"key\": \"value\" }\n */\n customProperties?: { [key: string]: string };\n /**\n * The URL of the latest bundle to download.\n * Pass this URL to the `downloadBundle(...)` method to download the bundle.\n *\n * @since 6.7.0\n */\n downloadUrl?: string;\n /**\n * The signature of the latest bundle if the bundle is self-hosted.\n *\n * If the bundle is hosted on Capawesome Cloud, the signature will be\n * returned as response header when downloading the bundle.\n *\n * @since 7.1.0\n */\n signature?: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetBundleResult {\n /**\n * The unique identifier of the active bundle.\n *\n * If `null`, the default bundle is being used.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string | null;\n}\n\n/**\n * @since 7.4.0\n */\nexport interface GetBlockedBundlesResult {\n /**\n * An array of unique identifiers of all blocked bundles.\n *\n * @since 7.4.0\n */\n bundleIds: string[];\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetBundlesResult {\n /**\n * An array of unique identifiers of all available bundles.\n *\n * @since 5.0.0\n */\n bundleIds: string[];\n}\n\n/**\n * @since 7.4.0\n */\nexport interface GetDownloadedBundlesResult {\n /**\n * An array of unique identifiers of all downloaded bundles.\n *\n * @since 7.4.0\n */\n bundleIds: string[];\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetChannelResult {\n /**\n * The channel name.\n *\n * If `null`, the app is using the default channel.\n *\n * @since 5.0.0\n * @example 'production'\n */\n channel: string | null;\n}\n\n/**\n * @since 7.4.0\n */\nexport interface GetConfigResult {\n /**\n * The app ID used to identify the app.\n *\n * If `null`, no app ID is configured.\n *\n * @since 7.4.0\n * @example '6e351b4f-69a7-415e-a057-4567df7ffe94'\n */\n appId: string | null;\n /**\n * The auto-update strategy for live updates.\n *\n * @since 7.4.0\n * @example 'background'\n */\n autoUpdateStrategy: 'none' | 'background';\n}\n\n/**\n * @since 6.7.0\n */\nexport interface GetCurrentBundleResult {\n /**\n * The unique identifier of the current bundle.\n *\n * If `null`, the default bundle is being used.\n *\n * @since 6.7.0\n */\n bundleId: string | null;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetDeviceIdResult {\n /**\n * The unique identifier of the device.\n *\n * On iOS, [`identifierForVendor`](https://developer.apple.com/documentation/uikit/uidevice/1620059-identifierforvendor) is used.\n * The value of this property is the same for apps that come from the same vendor running on the same device.\n *\n * On Android, a random UUID is generated once per installation and stored by the plugin.\n * The value is shared across all app IDs and changes when the app is reinstalled or its data is cleared.\n *\n * On Electron, a random UUID is generated on first use and stored in the app's user data directory.\n * The value changes when the user data is cleared.\n *\n * @since 5.0.0\n *\n * @example '50d2a548-80b7-4dad-adc7-97c0e79d8a89'\n */\n deviceId: string;\n}\n\n/**\n * @since 7.4.0\n */\nexport interface IsSyncingResult {\n /**\n * Whether a sync operation is currently in progress.\n *\n * @since 7.4.0\n */\n syncing: boolean;\n}\n\n/**\n * @since 6.7.0\n */\nexport interface GetNextBundleResult {\n /**\n * The unique identifier of the next bundle.\n *\n * If `null`, the default bundle is being used.\n *\n * @since 6.7.0\n */\n bundleId: string | null;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetVersionCodeResult {\n /**\n * The version code of the app.\n *\n * On **Android**, this is the `versionCode` from the `android/app/build.gradle` file.\n * On **iOS**, this is the `CFBundleVersion` from the `Info.plist` file.\n * On **Electron**, this is the `version` from the `electron/package.json` file.\n *\n * @since 5.0.0\n * @example \"1\"\n */\n versionCode: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetVersionNameResult {\n /**\n * The version name of the app.\n *\n * On **Android**, this is the `versionName` from the `android/app/build.gradle` file.\n * On **iOS**, this is the `CFBundleShortVersionString` from the `Info.plist` file.\n * On **Electron**, this is the `version` from the `electron/package.json` file.\n *\n * @since 5.0.0\n * @example \"1.0.0\"\n */\n versionName: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetCustomIdResult {\n /**\n * The custom identifier of the device.\n *\n * If `null`, no custom identifier is set.\n *\n * @since 5.0.0\n * @example '50d2a548-80b7-4dad-adc7-97c0e79d8a89'\n */\n customId: string | null;\n}\n\n/**\n * @since 7.0.0\n */\nexport interface ReadyResult {\n /**\n * The identifier of the previous bundle used.\n *\n * If `null`, the default bundle was used.\n *\n * @since 7.0.0\n */\n previousBundleId: string | null;\n /**\n * The identifier of the current bundle used.\n *\n * If `null`, the default bundle is being used.\n *\n * @since 7.0.0\n */\n currentBundleId: string | null;\n /**\n * Whether or not the app was reset to the default bundle.\n *\n * @since 7.0.0\n */\n rollback: boolean;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SetBundleOptions {\n /**\n * The unique identifier of the bundle to use.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SetChannelOptions {\n /**\n * The channel name.\n *\n * Set `null` to remove the channel.\n *\n * @since 5.0.0\n */\n channel: string | null;\n}\n\n/**\n * @since 7.4.0\n */\nexport interface SetConfigOptions {\n /**\n * The app ID used to identify the app.\n *\n * Set `null` to reset to the value from the Capacitor config file.\n *\n * @since 7.4.0\n * @example '6e351b4f-69a7-415e-a057-4567df7ffe94'\n */\n appId?: string | null;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SetCustomIdOptions {\n /**\n * The custom identifier of the device.\n *\n * Set `null` to remove the custom identifier.\n *\n * @since 5.0.0\n */\n customId: string | null;\n}\n\n/**\n * @since 6.7.0\n */\nexport interface SetNextBundleOptions {\n /**\n * The unique identifier of the bundle to use.\n *\n * Set `null` to use the default bundle (same as calling `reset()`).\n *\n * @since 6.7.0\n * @example '1.0.0'\n */\n bundleId: string | null;\n}\n\n/**\n * @since 6.7.0\n */\nexport interface SyncOptions {\n /**\n * The preferred channel name from which the latest bundle should be fetched.\n *\n * This is the SDK preference and may be overridden by a forced channel assignment\n * configured in the Capawesome Cloud Console.\n *\n * @since 6.7.0\n */\n channel?: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SyncResult {\n /**\n * The identifier of the next bundle to use.\n *\n * If `null`, the app is up-to-date and no new bundle is available.\n *\n * @since 5.0.0\n */\n nextBundleId: string | null;\n}\n\n/**\n * Listener for the download progress of a bundle.\n *\n * @since 7.0.0\n */\nexport type DownloadBundleProgressListener = (\n event: DownloadBundleProgressEvent,\n) => void;\n\n/**\n * Event that is triggered when the download progress of a bundle changes.\n *\n * @since 7.0.0\n */\nexport interface DownloadBundleProgressEvent {\n /**\n * The unique identifier of the bundle that is being downloaded.\n *\n * @since 7.0.0\n */\n bundleId: string;\n /**\n * The number of bytes that have been downloaded.\n *\n * @since 7.0.0\n */\n downloadedBytes: number;\n /**\n * The progress of the download in percent as a value between `0` and `1`.\n *\n * @since 7.0.0\n * @example 0.5\n */\n progress: number;\n /**\n * The total number of bytes to download.\n *\n * @since 7.0.0\n */\n totalBytes: number;\n}\n\n/**\n * Listener for when a bundle is set as the next bundle.\n *\n * @since 7.3.0\n */\nexport type NextBundleSetListener = (event: NextBundleSetEvent) => void;\n\n/**\n * Event that is triggered when a bundle is set as the next bundle.\n *\n * @since 7.3.0\n */\nexport interface NextBundleSetEvent {\n /**\n * The unique identifier of the bundle that is set as the next bundle.\n *\n * If `null`, the default bundle will be used.\n *\n * @since 7.3.0\n * @example '1.0.0'\n */\n bundleId: string | null;\n}\n\n/**\n * Listener for when the app is reloaded.\n *\n * @since 7.4.0\n */\nexport type ReloadedListener = () => void;\n"]}
@@ -8,12 +8,13 @@ import Capacitor
8
8
  private var signature: String?
9
9
  private var url: String
10
10
 
11
- init(artifactType: String, bundleId: String, checksum: String?, signature: String?, url: String) {
12
- if artifactType == "manifest" {
13
- self.artifactType = .manifest
14
- } else {
15
- self.artifactType = .zip
16
- }
11
+ convenience init(artifactType: String, bundleId: String, checksum: String?, signature: String?, url: String) {
12
+ let artifactType: ArtifactType = artifactType == "manifest" ? .manifest : .zip
13
+ self.init(artifactType: artifactType, bundleId: bundleId, checksum: checksum, signature: signature, url: url)
14
+ }
15
+
16
+ init(artifactType: ArtifactType, bundleId: String, checksum: String?, signature: String?, url: String) {
17
+ self.artifactType = artifactType
17
18
  self.bundleId = bundleId
18
19
  self.checksum = checksum
19
20
  self.signature = signature
@@ -2,16 +2,32 @@ import Foundation
2
2
  import Capacitor
3
3
 
4
4
  @objc public class FetchLatestBundleOptions: NSObject {
5
+ private var appId: String?
6
+ private var bundleId: String?
5
7
  private var channel: String?
6
8
 
7
- init(_ call: CAPPluginCall) {
8
- self.channel = call.getString("channel")
9
+ convenience init(_ call: CAPPluginCall) {
10
+ self.init(appId: nil, bundleId: nil, channel: call.getString("channel"))
9
11
  }
10
12
 
11
- init(channel: String?) {
13
+ convenience init(channel: String?) {
14
+ self.init(appId: nil, bundleId: nil, channel: channel)
15
+ }
16
+
17
+ init(appId: String?, bundleId: String?, channel: String?) {
18
+ self.appId = appId
19
+ self.bundleId = bundleId
12
20
  self.channel = channel
13
21
  }
14
22
 
23
+ func getAppId() -> String? {
24
+ return appId
25
+ }
26
+
27
+ func getBundleId() -> String? {
28
+ return bundleId
29
+ }
30
+
15
31
  func getChannel() -> String? {
16
32
  return channel
17
33
  }
@@ -20,6 +20,30 @@ import Capacitor
20
20
  self.signature = signature
21
21
  }
22
22
 
23
+ func getArtifactType() -> ArtifactType? {
24
+ return artifactType
25
+ }
26
+
27
+ public func getBundleId() -> String? {
28
+ return bundleId
29
+ }
30
+
31
+ public func getChecksum() -> String? {
32
+ return checksum
33
+ }
34
+
35
+ public func getCustomProperties() -> [String: Any]? {
36
+ return customProperties
37
+ }
38
+
39
+ public func getDownloadUrl() -> String? {
40
+ return downloadUrl
41
+ }
42
+
43
+ public func getSignature() -> String? {
44
+ return signature
45
+ }
46
+
23
47
  public func toJSObject() -> AnyObject {
24
48
  var result = JSObject()
25
49
  if artifactType == .manifest {
@@ -3,6 +3,7 @@ import Foundation
3
3
  public enum CustomError: Error {
4
4
  case appIdMissing
5
5
  case bundleAlreadyExists
6
+ case bundleDirectoryNotFound
6
7
  case bundleIdMissing
7
8
  case bundleIndexHtmlMissing
8
9
  case bundleNotFound
@@ -10,9 +11,12 @@ public enum CustomError: Error {
10
11
  case channelMissing
11
12
  case checksumCalculationFailed
12
13
  case checksumMismatch
14
+ case configurationValueInvalid(key: String)
13
15
  case customIdMissing
14
16
  case downloadFailed
17
+ case downloadUrlMissing
15
18
  case httpTimeout
19
+ case pluginNotInitialized
16
20
  case signatureMissing
17
21
  case signatureVerificationFailed
18
22
  case syncInProgress
@@ -27,6 +31,8 @@ extension CustomError: LocalizedError {
27
31
  return NSLocalizedString("appId must be configured.", comment: "appIdMissing")
28
32
  case .bundleAlreadyExists:
29
33
  return NSLocalizedString("bundle already exists.", comment: "bundleAlreadyExists")
34
+ case .bundleDirectoryNotFound:
35
+ return NSLocalizedString("Bundle directory could not be resolved.", comment: "bundleDirectoryNotFound")
30
36
  case .bundleIdMissing:
31
37
  return NSLocalizedString("bundleId must be provided.", comment: "bundleIdMissing")
32
38
  case .bundleIndexHtmlMissing:
@@ -41,12 +47,19 @@ extension CustomError: LocalizedError {
41
47
  return NSLocalizedString("Failed to calculate checksum.", comment: "checksumCalculationFailed")
42
48
  case .checksumMismatch:
43
49
  return NSLocalizedString("Checksum mismatch.", comment: "checksumMismatch")
50
+ case .configurationValueInvalid(let key):
51
+ let format = NSLocalizedString("%@ has an invalid type.", comment: "configurationValueInvalid")
52
+ return String(format: format, key)
44
53
  case .customIdMissing:
45
54
  return NSLocalizedString("customId must be provided.", comment: "customIdMissing")
46
55
  case .downloadFailed:
47
56
  return NSLocalizedString("Bundle could not be downloaded.", comment: "downloadFailed")
57
+ case .downloadUrlMissing:
58
+ return NSLocalizedString("Bundle does not have a valid download URL.", comment: "downloadUrlMissing")
48
59
  case .httpTimeout:
49
60
  return NSLocalizedString("Request timed out.", comment: "httpTimeout")
61
+ case .pluginNotInitialized:
62
+ return NSLocalizedString("Plugin is not initialized.", comment: "pluginNotInitialized")
50
63
  case .signatureMissing:
51
64
  return NSLocalizedString("Bundle does not contain a signature.", comment: "signatureMissing")
52
65
  case .signatureVerificationFailed:
@@ -7,17 +7,24 @@ import CommonCrypto
7
7
 
8
8
  // swiftlint:disable type_body_length
9
9
  @objc public class LiveUpdate: NSObject {
10
+ private static let downloadsDirectoryUrl = FileManager.default
11
+ .urls(for: .cachesDirectory, in: .userDomainMask).first!
12
+ .appendingPathComponent("capawesome_capacitor_live_update_downloads")
13
+ /// Deletes the leftovers of downloads that a process kill interrupted. Runs once per process and
14
+ /// blocks concurrent initializers until it is done, so no other instance can have a download in flight.
15
+ private static let interruptedDownloadsDeleted: Void = {
16
+ try? FileManager.default.removeItem(at: downloadsDirectoryUrl)
17
+ }()
18
+
10
19
  private let autoUpdateIntervalMs: Int64 = 15 * 60 * 1000 // 15 minutes
11
20
  private let bundlesDirectory = "NoCloud/ionic_built_snapshots" // DO NOT CHANGE! (See https://dub.sh/BLluidt)
12
- private let cachesDirectoryUrl = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
13
21
  private let config: LiveUpdateConfig
14
22
  private let defaultWebAssetDir = "public" // DO NOT CHANGE! (See https://dub.sh/Buvz4yj)
15
23
  private let defaultServerPathKey = "serverBasePath" // DO NOT CHANGE! (See https://dub.sh/ceDl0zT)
16
- private let downloadsDirectory = "capawesome_capacitor_live_update_downloads"
17
24
  private let httpClient: LiveUpdateHttpClient
18
25
  private let libraryDirectoryUrl = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!
19
26
  private let manifestFileName = "capawesome-live-update-manifest.json" // DO NOT CHANGE!
20
- private let plugin: LiveUpdatePlugin
27
+ private let plugin: LiveUpdatePlugin?
21
28
  private let preferences: LiveUpdatePreferences
22
29
 
23
30
  private var rollbackDispatchWorkItem: DispatchWorkItem?
@@ -25,7 +32,10 @@ import CommonCrypto
25
32
  private var lastAutoUpdateCheckTimestamp: Int64 = 0
26
33
  private var syncInProgress = false
27
34
 
28
- init(config: LiveUpdateConfig, plugin: LiveUpdatePlugin) {
35
+ /// - Parameter plugin: The Capacitor plugin, or `nil` for a headless instance
36
+ /// (e.g. for Ionic Portals hosts that construct a provider manager directly).
37
+ /// In headless mode, WebView-related operations and plugin events are skipped.
38
+ init(config: LiveUpdateConfig, plugin: LiveUpdatePlugin? = nil) {
29
39
  self.config = config
30
40
  self.httpClient = LiveUpdateHttpClient(config: config)
31
41
  self.plugin = plugin
@@ -38,8 +48,7 @@ import CommonCrypto
38
48
  // Set the device ID on the HTTP client (after any potential config reset)
39
49
  self.httpClient.setDeviceId(getDeviceId())
40
50
 
41
- // Delete the leftovers of downloads that were interrupted by a process kill
42
- tryDeleteDownloadsDirectory()
51
+ _ = Self.interruptedDownloadsDeleted
43
52
 
44
53
  // Start the rollback timer to rollback to the default bundle
45
54
  // if the app is not ready after a certain time
@@ -166,6 +175,14 @@ import CommonCrypto
166
175
  completion(result, nil)
167
176
  }
168
177
 
178
+ /// - Returns: The on-disk URL of a downloaded bundle, or `nil` if the bundle does not exist.
179
+ public func getBundleDirectory(bundleId: String) -> URL? {
180
+ guard hasBundleById(bundleId) else {
181
+ return nil
182
+ }
183
+ return buildBundleURLFor(bundleId: bundleId)
184
+ }
185
+
169
186
  @objc public func getCustomId(completion: @escaping (Result?, Error?) -> Void) {
170
187
  let customId = preferences.getCustomId()
171
188
 
@@ -351,7 +368,12 @@ import CommonCrypto
351
368
 
352
369
  // Move the unzipped files to the bundles directory
353
370
  let bundlePath = self.buildBundlePathFor(bundleId: bundleId)
354
- try FileManager.default.moveItem(atPath: indexHtmlFile.deletingLastPathComponent().path, toPath: bundlePath)
371
+ do {
372
+ try FileManager.default.moveItem(atPath: indexHtmlFile.deletingLastPathComponent().path, toPath: bundlePath)
373
+ } catch where hasBundleById(bundleId) {
374
+ // A concurrent download of the same bundle was installed first
375
+ CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Bundle already installed: \(bundleId)")
376
+ }
355
377
  }
356
378
 
357
379
  private func addBundleOfTypeManifest(bundleId: String, directory: URL) async throws {
@@ -385,7 +407,7 @@ import CommonCrypto
385
407
  }
386
408
 
387
409
  private func buildDownloadsDirectoryUrl() -> URL {
388
- return cachesDirectoryUrl.appendingPathComponent(downloadsDirectory)
410
+ return Self.downloadsDirectoryUrl
389
411
  }
390
412
 
391
413
  private func copyCurrentBundleFile(fileToCopy: ManifestItem, toDirectory: URL) throws {
@@ -455,18 +477,8 @@ import CommonCrypto
455
477
  }
456
478
 
457
479
  private func deleteUnusedBundles() {
458
- let bundleIds = getDownloadedBundleIds()
459
- let currentBundleId = getCurrentBundleId()
460
- let nextBundleId = getNextBundleId()
461
-
462
- for bundleId in bundleIds {
463
- if bundleId != currentBundleId && bundleId != nextBundleId {
464
- do {
465
- try deleteBundleById(bundleId)
466
- } catch {
467
- CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to delete bundle with id: \(bundleId)")
468
- }
469
- }
480
+ for bundleId in getDownloadedBundleIds() {
481
+ tryDeleteUnusedBundle(bundleId: bundleId)
470
482
  }
471
483
  }
472
484
 
@@ -593,18 +605,18 @@ import CommonCrypto
593
605
  }
594
606
 
595
607
  private func fetchLatestBundle(_ options: FetchLatestBundleOptions) async throws -> GetLatestBundleResponse? {
608
+ let appId = try requireAppId(options.getAppId())
596
609
  let channel = options.getChannel() ?? getChannel()
597
610
  var parameters = [String: String]()
598
611
  parameters["appVersionCode"] = getVersionCode()
599
612
  parameters["appVersionName"] = getVersionName()
600
- parameters["bundleId"] = getCurrentBundleId()
613
+ parameters["bundleId"] = options.getBundleId() ?? getCurrentBundleId()
601
614
  parameters["channelName"] = channel
602
615
  parameters["customId"] = preferences.getCustomId()
603
616
  parameters["deviceId"] = getDeviceId()
604
617
  parameters["osVersion"] = await UIDevice.current.systemVersion
605
618
  parameters["platform"] = "1"
606
619
  parameters["pluginVersion"] = LiveUpdatePlugin.version
607
- let appId = try requireAppId()
608
620
  var urlComponents = URLComponents(string: "https://\(config.serverDomain)/v1/apps/\(appId)/bundles/latest")!
609
621
  urlComponents.queryItems = parameters.map { URLQueryItem(name: $0.key, value: $0.value) }
610
622
  let url = try urlComponents.asURL()
@@ -614,12 +626,12 @@ import CommonCrypto
614
626
  CAPLog.print("[", LiveUpdatePlugin.tag, "] Latest bundle response: ", String(decoding: data, as: UTF8.self))
615
627
  }
616
628
  if let error = response.error {
617
- if let urlError = error.underlyingError as? URLError {
618
- if urlError.code == .timedOut {
619
- throw urlError
620
- }
629
+ // The server answered with an error status, so no update is available
630
+ if let statusCode = response.response?.statusCode, !(200..<300).contains(statusCode) {
631
+ return nil
621
632
  }
622
- return nil
633
+ // The server could not be reached (e.g. offline or timed out) or sent an invalid body
634
+ throw error.underlyingError ?? error
623
635
  }
624
636
  if let value = response.value {
625
637
  return value
@@ -642,14 +654,14 @@ import CommonCrypto
642
654
  }
643
655
  }
644
656
 
645
- private func getAppId() -> String? {
657
+ func getAppId() -> String? {
646
658
  if let appId = preferences.getAppId() {
647
659
  return appId
648
660
  }
649
661
  return config.appId
650
662
  }
651
663
 
652
- private func getChannel() -> String? {
664
+ func getChannel() -> String? {
653
665
  var channel: String?
654
666
  if let _ = config.defaultChannel {
655
667
  channel = config.defaultChannel
@@ -695,7 +707,7 @@ import CommonCrypto
695
707
 
696
708
  /// - Returns: The path to the current bundle directory or `nil` if no view controller was found.
697
709
  private func getCurrentCapacitorServerPath() -> String? {
698
- guard let viewController = self.plugin.bridge?.viewController as? CAPBridgeViewController else {
710
+ guard let viewController = self.plugin?.bridge?.viewController as? CAPBridgeViewController else {
699
711
  return nil
700
712
  }
701
713
  return viewController.getServerBasePath()
@@ -730,7 +742,7 @@ import CommonCrypto
730
742
  return preferences.getPreviousBundleId()
731
743
  }
732
744
 
733
- private func getVersionCode() -> String {
745
+ func getVersionCode() -> String {
734
746
  guard let appVersionCode = Bundle.main.infoDictionary?["CFBundleVersion"] as? String else {
735
747
  return ""
736
748
  }
@@ -777,7 +789,7 @@ import CommonCrypto
777
789
  }
778
790
 
779
791
  private func notifyDownloadBundleProgressListeners(_ event: DownloadBundleProgressEvent) {
780
- plugin.notifyDownloadBundleProgressListeners(event)
792
+ plugin?.notifyDownloadBundleProgressListeners(event)
781
793
  }
782
794
 
783
795
  private func performAutoUpdate() {
@@ -804,8 +816,8 @@ import CommonCrypto
804
816
  }
805
817
  }
806
818
 
807
- private func requireAppId() throws -> String {
808
- guard let appId = getAppId(), !appId.isEmpty else {
819
+ private func requireAppId(_ appIdOverride: String? = nil) throws -> String {
820
+ guard let appId = appIdOverride ?? getAppId(), !appId.isEmpty else {
809
821
  throw CustomError.appIdMissing
810
822
  }
811
823
  return appId
@@ -861,7 +873,7 @@ import CommonCrypto
861
873
  }
862
874
 
863
875
  private func setCurrentCapacitorServerPath(path: String) {
864
- guard let viewController = self.plugin.bridge?.viewController as? CAPBridgeViewController else {
876
+ guard let viewController = self.plugin?.bridge?.viewController as? CAPBridgeViewController else {
865
877
  return
866
878
  }
867
879
  viewController.setServerBasePath(path: path)
@@ -880,11 +892,11 @@ import CommonCrypto
880
892
 
881
893
  private func notifyNextBundleSetListeners(_ bundleId: String?) {
882
894
  let event = NextBundleSetEvent(bundleId: bundleId)
883
- plugin.notifyNextBundleSetListeners(event)
895
+ plugin?.notifyNextBundleSetListeners(event)
884
896
  }
885
897
 
886
898
  private func notifyReloadedListeners() {
887
- plugin.notifyReloadedListeners()
899
+ plugin?.notifyReloadedListeners()
888
900
  }
889
901
 
890
902
  private func addBlockedBundleId(_ bundleId: String) {
@@ -994,8 +1006,23 @@ import CommonCrypto
994
1006
  }
995
1007
  }
996
1008
 
997
- private func tryDeleteDownloadsDirectory() {
998
- tryDeleteDirectory(buildDownloadsDirectoryUrl())
1009
+ /// Deletes the bundle unless it is the current or next bundle.
1010
+ /// - Returns: `true` if the bundle does not exist anymore.
1011
+ @discardableResult
1012
+ func tryDeleteUnusedBundle(bundleId: String) -> Bool {
1013
+ guard hasBundleById(bundleId) else {
1014
+ return true
1015
+ }
1016
+ guard bundleId != getCurrentBundleId() && bundleId != getNextBundleId() else {
1017
+ return false
1018
+ }
1019
+ do {
1020
+ try deleteBundleById(bundleId)
1021
+ return true
1022
+ } catch {
1023
+ CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to delete bundle with id: \(bundleId)")
1024
+ return false
1025
+ }
999
1026
  }
1000
1027
 
1001
1028
  private func unzipDownloadedBundle(zipFile: URL) throws -> URL {
@@ -8,7 +8,7 @@ import Capacitor
8
8
  @objc(LiveUpdatePlugin)
9
9
  public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
10
10
  public static let tag = "LiveUpdate"
11
- public static let version = "8.5.0"
11
+ public static let version = "8.7.0"
12
12
  public static let userDefaultsPrefix = "CapawesomeLiveUpdate" // DO NOT CHANGE
13
13
 
14
14
  public let identifier = "LiveUpdatePlugin"
@@ -46,7 +46,7 @@ public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
46
46
  private let eventNextBundleSet = "nextBundleSet"
47
47
  private let eventReloaded = "reloaded"
48
48
 
49
- private var implementation: LiveUpdate?
49
+ var implementation: LiveUpdate?
50
50
 
51
51
  override public func load() {
52
52
  self.implementation = LiveUpdate(config: liveUpdateConfig(), plugin: self)