@capgo/capacitor-updater 7.23.3 → 7.24.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.
- package/README.md +117 -94
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +16 -18
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +149 -3
- package/android/src/main/java/ee/forgr/capacitor_updater/DeviceIdHelper.java +221 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +1 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +1 -1
- package/dist/docs.json +1 -1
- package/dist/esm/definitions.d.ts +10 -1
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +14 -21
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +140 -4
- package/ios/Sources/CapacitorUpdaterPlugin/DeviceIdHelper.swift +120 -0
- package/package.json +3 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAAA;;;;GAIG","sourcesContent":["/*\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/.\n */\n\n/// <reference types=\"@capacitor/cli\" />\n\nimport type { PluginListenerHandle } from '@capacitor/core';\n\ndeclare module '@capacitor/cli' {\n export interface PluginsConfig {\n /**\n * CapacitorUpdater can be configured with these options:\n */\n CapacitorUpdater?: {\n /**\n * Configure the number of milliseconds the native plugin should wait before considering an update 'failed'.\n *\n * Only available for Android and iOS.\n *\n * @default 10000 // (10 seconds)\n * @example 1000 // (1 second)\n */\n appReadyTimeout?: number;\n /**\n * Configure the number of seconds the native plugin should wait before considering API timeout.\n *\n * Only available for Android and iOS.\n *\n * @default 20 // (20 second)\n * @example 10 // (10 second)\n */\n responseTimeout?: number;\n /**\n * Configure whether the plugin should use automatically delete failed bundles.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n autoDeleteFailed?: boolean;\n\n /**\n * Configure whether the plugin should use automatically delete previous bundles after a successful update.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n autoDeletePrevious?: boolean;\n\n /**\n * Configure whether the plugin should use Auto Update via an update server.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n autoUpdate?: boolean;\n\n /**\n * Automatically delete previous downloaded bundles when a newer native app bundle is installed to the device.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n resetWhenUpdate?: boolean;\n\n /**\n * Configure the URL / endpoint to which update checks are sent.\n *\n * Only available for Android and iOS.\n *\n * @default https://plugin.capgo.app/updates\n * @example https://example.com/api/auto_update\n */\n updateUrl?: string;\n\n /**\n * Configure the URL / endpoint for channel operations.\n *\n * Only available for Android and iOS.\n *\n * @default https://plugin.capgo.app/channel_self\n * @example https://example.com/api/channel\n */\n channelUrl?: string;\n\n /**\n * Configure the URL / endpoint to which update statistics are sent.\n *\n * Only available for Android and iOS. Set to \"\" to disable stats reporting.\n *\n * @default https://plugin.capgo.app/stats\n * @example https://example.com/api/stats\n */\n statsUrl?: string;\n /**\n * Configure the public key for end to end live update encryption Version 2\n *\n * Only available for Android and iOS.\n *\n * @default undefined\n * @since 6.2.0\n */\n publicKey?: string;\n\n /**\n * Configure the current version of the app. This will be used for the first update request.\n * If not set, the plugin will get the version from the native code.\n *\n * Only available for Android and iOS.\n *\n * @default undefined\n * @since 4.17.48\n */\n version?: string;\n /**\n * Configure when the plugin should direct install updates. Only for autoUpdate mode.\n * Works well for apps less than 10MB and with uploads done using --partial flag.\n * Zip or apps more than 10MB will be relatively slow for users to update.\n * - false: Never do direct updates (default behavior)\n * - atInstall: Direct update only when app is installed/updated from store, otherwise use normal background update\n * - always: Always do direct updates immediately when available\n * - true: (deprecated) Same as \"always\" for backward compatibility\n *\n * Only available for Android and iOS.\n *\n * @default false\n * @since 5.1.0\n */\n directUpdate?: boolean | 'atInstall' | 'always';\n\n /**\n * Automatically handle splashscreen hiding when using directUpdate. When enabled, the plugin will automatically hide the splashscreen after updates are applied or when no update is needed.\n * This removes the need to manually listen for appReady events and call SplashScreen.hide().\n * Only works when directUpdate is set to \"atInstall\", \"always\", or true.\n * Requires the @capacitor/splash-screen plugin to be installed and configured with launchAutoHide: false.\n * Requires autoUpdate and directUpdate to be enabled.\n *\n * Only available for Android and iOS.\n *\n * @default false\n * @since 7.6.0\n */\n autoSplashscreen?: boolean;\n\n /**\n * Display a native loading indicator on top of the splashscreen while automatic direct updates are running.\n * Only takes effect when {@link autoSplashscreen} is enabled.\n * Requires the @capacitor/splash-screen plugin to be installed and configured with launchAutoHide: false.\n *\n * Only available for Android and iOS.\n *\n * @default false\n * @since 7.19.0\n */\n autoSplashscreenLoader?: boolean;\n\n /**\n * Automatically hide the splashscreen after the specified number of milliseconds when using automatic direct updates.\n * If the timeout elapses, the update continues to download in the background while the splashscreen is dismissed.\n * Set to `0` (zero) to disable the timeout.\n * When the timeout fires, the direct update flow is skipped and the downloaded bundle is installed on the next background/launch.\n * Requires {@link autoSplashscreen} to be enabled.\n *\n * Only available for Android and iOS.\n *\n * @default 10000 // (10 seconds)\n * @since 7.19.0\n */\n autoSplashscreenTimeout?: number;\n\n /**\n * Configure the delay period for period update check. the unit is in seconds.\n *\n * Only available for Android and iOS.\n * Cannot be less than 600 seconds (10 minutes).\n *\n * @default 0 (disabled)\n * @example 3600 (1 hour)\n * @example 86400 (24 hours)\n */\n periodCheckDelay?: number;\n\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localS3?: boolean;\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localHost?: string;\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localWebHost?: string;\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localSupa?: string;\n /**\n * Configure the CLI to use a local server for testing.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localSupaAnon?: string;\n /**\n * Configure the CLI to use a local api for testing.\n *\n *\n * @default undefined\n * @since 6.3.3\n */\n localApi?: string;\n /**\n * Configure the CLI to use a local file api for testing.\n *\n *\n * @default undefined\n * @since 6.3.3\n */\n localApiFiles?: string;\n /**\n * Allow the plugin to modify the updateUrl, statsUrl and channelUrl dynamically from the JavaScript side.\n *\n *\n * @default false\n * @since 5.4.0\n */\n allowModifyUrl?: boolean;\n\n /**\n * Allow the plugin to modify the appId dynamically from the JavaScript side.\n *\n *\n * @default false\n * @since 7.14.0\n */\n allowModifyAppId?: boolean;\n\n /**\n * Allow marking bundles as errored from JavaScript while using manual update flows.\n * When enabled, {@link CapacitorUpdaterPlugin.setBundleError} can change a bundle status to `error`.\n *\n * @default false\n * @since 7.20.0\n */\n allowManualBundleError?: boolean;\n\n /**\n * Persist the customId set through {@link CapacitorUpdaterPlugin.setCustomId} across app restarts.\n *\n * Only available for Android and iOS.\n *\n * @default false (will be true by default in a future major release v8.x.x)\n * @since 7.17.3\n */\n persistCustomId?: boolean;\n\n /**\n * Persist the updateUrl, statsUrl and channelUrl set through {@link CapacitorUpdaterPlugin.setUpdateUrl},\n * {@link CapacitorUpdaterPlugin.setStatsUrl} and {@link CapacitorUpdaterPlugin.setChannelUrl} across app restarts.\n *\n * Only available for Android and iOS.\n *\n * @default false\n * @since 7.20.0\n */\n persistModifyUrl?: boolean;\n\n /**\n * Set the default channel for the app in the config. Case sensitive.\n * This will setting will override the default channel set in the cloud, but will still respect overrides made in the cloud.\n * This requires the channel to allow devices to self dissociate/associate in the channel settings. https://capgo.app/docs/public-api/channels/#channel-configuration-options\n *\n *\n * @default undefined\n * @since 5.5.0\n */\n defaultChannel?: string;\n /**\n * Configure the app id for the app in the config.\n *\n * @default undefined\n * @since 6.0.0\n */\n appId?: string;\n\n /**\n * Configure the plugin to keep the URL path after a reload.\n * WARNING: When a reload is triggered, 'window.history' will be cleared.\n *\n * @default false\n * @since 6.8.0\n */\n keepUrlPathAfterReload?: boolean;\n /**\n * Disable the JavaScript logging of the plugin. if true, the plugin will not log to the JavaScript console. only the native log will be done\n *\n * @default false\n * @since 7.3.0\n */\n disableJSLogging?: boolean;\n /**\n * Enable shake gesture to show update menu for debugging/testing purposes\n *\n * @default false\n * @since 7.5.0\n */\n shakeMenu?: boolean;\n };\n }\n}\n\nexport interface CapacitorUpdaterPlugin {\n /**\n * Notify Capacitor Updater that the current bundle is working (a rollback will occur if this method is not called on every app launch)\n * By default this method should be called in the first 10 sec after app launch, otherwise a rollback will occur.\n * Change this behaviour with {@link appReadyTimeout}\n *\n * @returns {Promise<AppReadyResult>} an Promise resolved directly\n * @throws {Error}\n */\n notifyAppReady(): Promise<AppReadyResult>;\n\n /**\n * Set the updateUrl for the app, this will be used to check for updates.\n *\n * @param options contains the URL to use for checking for updates.\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 5.4.0\n */\n setUpdateUrl(options: UpdateUrl): Promise<void>;\n\n /**\n * Set the statsUrl for the app, this will be used to send statistics. Passing an empty string will disable statistics gathering.\n *\n * @param options contains the URL to use for sending statistics.\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 5.4.0\n */\n setStatsUrl(options: StatsUrl): Promise<void>;\n\n /**\n * Set the channelUrl for the app, this will be used to set the channel.\n *\n * @param options contains the URL to use for setting the channel.\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 5.4.0\n */\n setChannelUrl(options: ChannelUrl): Promise<void>;\n\n /**\n * Download a new bundle from the provided URL, it should be a zip file, with files inside or with a unique id inside with all your files\n *\n * @example const bundle = await CapacitorUpdater.download({ url: `https://example.com/versions/${version}/dist.zip`, version });\n * @returns {Promise<BundleInfo>} The {@link BundleInfo} for the specified bundle.\n * @param options The {@link DownloadOptions} for downloading a new bundle zip.\n */\n download(options: DownloadOptions): Promise<BundleInfo>;\n\n /**\n * Set the next bundle to be used when the app is reloaded.\n *\n * @param options Contains the ID of the next Bundle to set on next app launch. {@link BundleInfo.id}\n * @returns {Promise<BundleInfo>} The {@link BundleInfo} for the specified bundle id.\n * @throws {Error} When there is no index.html file inside the bundle folder.\n */\n next(options: BundleId): Promise<BundleInfo>;\n\n /**\n * Set the current bundle and immediately reloads the app.\n *\n * @param options A {@link BundleId} object containing the new bundle id to set as current.\n * @returns {Promise<void>}\n * @throws {Error} When there are is no index.html file inside the bundle folder.\n */\n set(options: BundleId): Promise<void>;\n\n /**\n * Deletes the specified bundle from the native app storage. Use with {@link list} to get the stored Bundle IDs.\n *\n * @param options A {@link BundleId} object containing the ID of a bundle to delete (note, this is the bundle id, NOT the version name)\n * @returns {Promise<void>} When the bundle is deleted\n * @throws {Error}\n */\n delete(options: BundleId): Promise<void>;\n\n /**\n * Mark an installed bundle as errored. Only available when {@link PluginsConfig.CapacitorUpdater.allowManualBundleError} is true.\n *\n * @param options A {@link BundleId} object containing the bundle id to mark as errored.\n * @returns {Promise<BundleInfo>} The updated {@link BundleInfo} for the bundle.\n * @throws {Error} When the bundle does not exist or the feature is disabled.\n * @since 7.20.0\n */\n setBundleError(options: BundleId): Promise<BundleInfo>;\n\n /**\n * Get all locally downloaded bundles in your app\n *\n * @returns {Promise<BundleListResult>} A Promise containing the {@link BundleListResult.bundles}\n * @param options The {@link ListOptions} for listing bundles\n * @throws {Error}\n */\n list(options?: ListOptions): Promise<BundleListResult>;\n\n /**\n * Reset the app to the `builtin` bundle (the one sent to Apple App Store / Google Play Store ) or the last successfully loaded bundle.\n *\n * @param options Containing {@link ResetOptions.toLastSuccessful}, `true` resets to the builtin bundle and `false` will reset to the last successfully loaded bundle.\n * @returns {Promise<void>}\n * @throws {Error}\n */\n reset(options?: ResetOptions): Promise<void>;\n\n /**\n * Get the current bundle, if none are set it returns `builtin`. currentNative is the original bundle installed on the device\n *\n * @returns {Promise<CurrentBundleResult>} A Promise evaluating to the {@link CurrentBundleResult}\n * @throws {Error}\n */\n current(): Promise<CurrentBundleResult>;\n\n /**\n * Reload the view\n *\n * @returns {Promise<void>} A Promise which is resolved when the view is reloaded\n * @throws {Error}\n */\n reload(): Promise<void>;\n\n /**\n * Sets a {@link DelayCondition} array containing conditions that the Plugin will use to delay the update.\n * After all conditions are met, the update process will run start again as usual, so update will be installed after a backgrounding or killing the app.\n * For the `date` kind, the value should be an iso8601 date string.\n * For the `background` kind, the value should be a number in milliseconds.\n * For the `nativeVersion` kind, the value should be the version number.\n * For the `kill` kind, the value is not used.\n * The function has unconsistent behavior the option kill do trigger the update after the first kill and not after the next background like other options. This will be fixed in a future major release.\n *\n * @example\n * // Delay the update after the user kills the app or after a background of 300000 ms (5 minutes)\n * await CapacitorUpdater.setMultiDelay({ delayConditions: [{ kind: 'kill' }, { kind: 'background', value: '300000' }] })\n * @example\n * // Delay the update after the specific iso8601 date is expired\n * await CapacitorUpdater.setMultiDelay({ delayConditions: [{ kind: 'date', value: '2022-09-14T06:14:11.920Z' }] })\n * @example\n * // Delay the update after the first background (default behaviour without setting delay)\n * await CapacitorUpdater.setMultiDelay({ delayConditions: [{ kind: 'background' }] })\n * @param options Containing the {@link MultiDelayConditions} array of conditions to set\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 4.3.0\n */\n setMultiDelay(options: MultiDelayConditions): Promise<void>;\n\n /**\n * Cancels a {@link DelayCondition} to process an update immediately.\n *\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 4.0.0\n */\n cancelDelay(): Promise<void>;\n\n /**\n * Get Latest bundle available from update Url\n *\n * @returns {Promise<LatestVersion>} A Promise resolved when url is loaded\n * @throws {Error}\n * @since 4.0.0\n */\n getLatest(options?: GetLatestOptions): Promise<LatestVersion>;\n\n /**\n * Sets the channel for this device. The channel has to allow for self assignment for this to work.\n * Do not use this method to set the channel at boot.\n * This method is to set the channel after the app is ready, and user interacted.\n * If you want to set the channel at boot, use the {@link PluginsConfig} to set the default channel.\n * This methods send to Capgo backend a request to link the device ID to the channel. Capgo can accept or refuse depending of the setting of your channel.\n *\n *\n *\n * @param options Is the {@link SetChannelOptions} channel to set\n * @returns {Promise<ChannelRes>} A Promise which is resolved when the new channel is set\n * @throws {Error}\n * @since 4.7.0\n */\n setChannel(options: SetChannelOptions): Promise<ChannelRes>;\n\n /**\n * Unset the channel for this device. The device will then return to the default channel\n *\n * @returns {Promise<ChannelRes>} A Promise resolved when channel is set\n * @throws {Error}\n * @since 4.7.0\n */\n unsetChannel(options: UnsetChannelOptions): Promise<void>;\n\n /**\n * Get the channel for this device\n *\n * @returns {Promise<ChannelRes>} A Promise that resolves with the channel info\n * @throws {Error}\n * @since 4.8.0\n */\n getChannel(): Promise<GetChannelRes>;\n\n /**\n * List all channels available for this device that allow self-assignment\n *\n * @returns {Promise<ListChannelsResult>} A Promise that resolves with the available channels\n * @throws {Error}\n * @since 7.5.0\n */\n listChannels(): Promise<ListChannelsResult>;\n\n /**\n * Set a custom ID for this device\n *\n * When {@link PluginsConfig.CapacitorUpdater.persistCustomId} is true, the value will be stored natively and restored on the next app launch.\n * Pass an empty string to remove any previously stored customId.\n *\n * @param options is the {@link SetCustomIdOptions} customId to set\n * @returns {Promise<void>} an Promise resolved instantly\n * @throws {Error}\n * @since 4.9.0\n */\n setCustomId(options: SetCustomIdOptions): Promise<void>;\n\n /**\n * Get the native app version or the builtin version if set in config\n *\n * @returns {Promise<BuiltinVersion>} A Promise with version for this device\n * @since 5.2.0\n */\n getBuiltinVersion(): Promise<BuiltinVersion>;\n\n /**\n * Get unique ID used to identify device (sent to auto update server), this ID is made following Apple and Google privacy best practices, and not persisted between installs\n *\n * @returns {Promise<DeviceId>} A Promise with id for this device\n * @throws {Error}\n */\n getDeviceId(): Promise<DeviceId>;\n\n /**\n * Get the native Capacitor Updater plugin version (sent to auto update server)\n *\n * @returns {Promise<PluginVersion>} A Promise with Plugin version\n * @throws {Error}\n */\n getPluginVersion(): Promise<PluginVersion>;\n\n /**\n * Get the state of auto update config.\n *\n * @returns {Promise<AutoUpdateEnabled>} The status for auto update. Evaluates to `false` in manual mode.\n * @throws {Error}\n */\n isAutoUpdateEnabled(): Promise<AutoUpdateEnabled>;\n\n /**\n * Remove all listeners for this plugin.\n *\n * @since 1.0.0\n */\n removeAllListeners(): Promise<void>;\n\n /**\n * Listen for bundle download event in the App. Fires once a download has started, during downloading and when finished.\n * This will return you all download percent during the download\n *\n * @since 2.0.11\n */\n addListener(eventName: 'download', listenerFunc: (state: DownloadEvent) => void): Promise<PluginListenerHandle>;\n\n /**\n * Listen for no need to update event, useful when you want force check every time the app is launched\n *\n * @since 4.0.0\n */\n addListener(eventName: 'noNeedUpdate', listenerFunc: (state: NoNeedEvent) => void): Promise<PluginListenerHandle>;\n\n /**\n * Listen for available update event, useful when you want to force check every time the app is launched\n *\n * @since 4.0.0\n */\n addListener(\n eventName: 'updateAvailable',\n listenerFunc: (state: UpdateAvailableEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for downloadComplete events.\n *\n * @since 4.0.0\n */\n addListener(\n eventName: 'downloadComplete',\n listenerFunc: (state: DownloadCompleteEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for breaking update events when the backend flags an update as incompatible with the current app.\n * Emits the same payload as the legacy `majorAvailable` listener.\n *\n * @since 7.22.0\n */\n addListener(\n eventName: 'breakingAvailable',\n listenerFunc: (state: BreakingAvailableEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for Major update event in the App, let you know when major update is blocked by setting disableAutoUpdateBreaking\n *\n * @deprecated Deprecated alias for {@link addListener} with `breakingAvailable`. Emits the same payload. will be removed in v8\n * @since 2.3.0\n */\n addListener(\n eventName: 'majorAvailable',\n listenerFunc: (state: MajorAvailableEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for update fail event in the App, let you know when update has fail to install at next app start\n *\n * @since 2.3.0\n */\n addListener(\n eventName: 'updateFailed',\n listenerFunc: (state: UpdateFailedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for download fail event in the App, let you know when a bundle download has failed\n *\n * @since 4.0.0\n */\n addListener(\n eventName: 'downloadFailed',\n listenerFunc: (state: DownloadFailedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for reload event in the App, let you know when reload has happened\n *\n * @since 4.3.0\n */\n addListener(eventName: 'appReloaded', listenerFunc: () => void): Promise<PluginListenerHandle>;\n\n /**\n * Listen for app ready event in the App, let you know when app is ready to use, this event is retain till consumed.\n *\n * @since 5.1.0\n */\n addListener(eventName: 'appReady', listenerFunc: (state: AppReadyEvent) => void): Promise<PluginListenerHandle>;\n\n /**\n * Get if auto update is available (not disabled by serverUrl).\n *\n * @returns {Promise<AutoUpdateAvailable>} The availability status for auto update. Evaluates to `false` when serverUrl is set.\n * @throws {Error}\n */\n isAutoUpdateAvailable(): Promise<AutoUpdateAvailable>;\n\n /**\n * Get the next bundle that will be used when the app reloads.\n * Returns null if no next bundle is set.\n *\n * @returns {Promise<BundleInfo | null>} A Promise that resolves with the next bundle information or null\n * @throws {Error}\n * @since 6.8.0\n */\n getNextBundle(): Promise<BundleInfo | null>;\n\n /**\n * Get the most recent update that failed to install, if any. The stored value is cleared after it is retrieved once.\n *\n * @returns {Promise<UpdateFailedEvent | null>} The last failed update or null when no failure has been recorded. Value is cleared after it is returned once.\n * @throws {Error}\n * @since 7.22.0\n */\n getFailedUpdate(): Promise<UpdateFailedEvent | null>;\n\n /**\n * Enable or disable the shake menu for debugging/testing purposes\n *\n * @param options Contains enabled boolean to enable or disable shake menu\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 7.5.0\n */\n setShakeMenu(options: SetShakeMenuOptions): Promise<void>;\n\n /**\n * Get the current state of the shake menu\n *\n * @returns {Promise<ShakeMenuEnabled>} The current state of shake menu\n * @throws {Error}\n * @since 7.5.0\n */\n isShakeMenuEnabled(): Promise<ShakeMenuEnabled>;\n\n /**\n * Get the configured App ID\n *\n * @returns {Promise<GetAppIdRes>} The current App ID\n * @throws {Error}\n * @since 7.14.0\n */\n getAppId(): Promise<GetAppIdRes>;\n\n /**\n * Set the App ID for the app (requires allowModifyAppId to be true in config)\n *\n * @param options The new App ID to set\n * @returns {Promise<void>}\n * @throws {Error} If allowModifyAppId is false or if the operation fails\n * @since 7.14.0\n */\n setAppId(options: SetAppIdOptions): Promise<void>;\n}\n\n/**\n * pending: The bundle is pending to be **SET** as the next bundle.\n * downloading: The bundle is being downloaded.\n * success: The bundle has been downloaded and is ready to be **SET** as the next bundle.\n * error: The bundle has failed to download.\n */\nexport type BundleStatus = 'success' | 'error' | 'pending' | 'downloading';\n\nexport type DelayUntilNext = 'background' | 'kill' | 'nativeVersion' | 'date';\n\nexport interface NoNeedEvent {\n /**\n * Current status of download, between 0 and 100.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface UpdateAvailableEvent {\n /**\n * Current status of download, between 0 and 100.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface ChannelRes {\n /**\n * Current status of set channel\n *\n * @since 4.7.0\n */\n status: string;\n error?: string;\n message?: string;\n}\n\nexport interface GetChannelRes {\n /**\n * Current status of get channel\n *\n * @since 4.8.0\n */\n channel?: string;\n error?: string;\n message?: string;\n status?: string;\n allowSet?: boolean;\n}\n\nexport interface ChannelInfo {\n /**\n * The channel ID\n *\n * @since 7.5.0\n */\n id: string;\n /**\n * The channel name\n *\n * @since 7.5.0\n */\n name: string;\n /**\n * Whether this is a public channel\n *\n * @since 7.5.0\n */\n public: boolean;\n /**\n * Whether devices can self-assign to this channel\n *\n * @since 7.5.0\n */\n allow_self_set: boolean;\n}\n\nexport interface ListChannelsResult {\n /**\n * List of available channels\n *\n * @since 7.5.0\n */\n channels: ChannelInfo[];\n}\n\nexport interface DownloadEvent {\n /**\n * Current status of download, between 0 and 100.\n *\n * @since 4.0.0\n */\n percent: number;\n bundle: BundleInfo;\n}\n\nexport interface MajorAvailableEvent {\n /**\n * Emit when a breaking update is available.\n *\n * @deprecated Deprecated alias for {@link BreakingAvailableEvent}. Receives the same payload.\n * @since 4.0.0\n */\n version: string;\n}\n\n/**\n * Payload emitted by {@link CapacitorUpdaterPlugin.addListener} with `breakingAvailable`.\n *\n * @since 7.22.0\n */\nexport type BreakingAvailableEvent = MajorAvailableEvent;\n\nexport interface DownloadFailedEvent {\n /**\n * Emit when a download fail.\n *\n * @since 4.0.0\n */\n version: string;\n}\n\nexport interface DownloadCompleteEvent {\n /**\n * Emit when a new update is available.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface UpdateFailedEvent {\n /**\n * Emit when a update failed to install.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface AppReadyEvent {\n /**\n * Emitted when the app is ready to use.\n *\n * @since 5.2.0\n */\n bundle: BundleInfo;\n status: string;\n}\n\nexport interface ManifestEntry {\n file_name: string | null;\n file_hash: string | null;\n download_url: string | null;\n}\n\nexport interface LatestVersion {\n /**\n * Result of getLatest method\n *\n * @since 4.0.0\n */\n version: string;\n /**\n * @since 6\n */\n checksum?: string;\n /**\n * Indicates whether the update was flagged as breaking by the backend.\n *\n * @since 7.22.0\n */\n breaking?: boolean;\n /**\n * @deprecated Use {@link LatestVersion.breaking} instead.\n */\n major?: boolean;\n message?: string;\n sessionKey?: string;\n error?: string;\n old?: string;\n url?: string;\n /**\n * @since 6.1\n */\n manifest?: ManifestEntry[];\n}\n\nexport interface BundleInfo {\n id: string;\n version: string;\n downloaded: string;\n checksum: string;\n status: BundleStatus;\n}\n\nexport interface SetChannelOptions {\n channel: string;\n triggerAutoUpdate?: boolean;\n}\n\nexport interface UnsetChannelOptions {\n triggerAutoUpdate?: boolean;\n}\n\nexport interface SetCustomIdOptions {\n /**\n * Custom identifier to associate with the device. Use an empty string to clear any saved value.\n */\n customId: string;\n}\n\nexport interface DelayCondition {\n /**\n * Set up delay conditions in setMultiDelay\n * @param value is useless for @param kind \"kill\", optional for \"background\" (default value: \"0\") and required for \"nativeVersion\" and \"date\"\n */\n kind: DelayUntilNext;\n value?: string;\n}\n\nexport interface GetLatestOptions {\n /**\n * The channel to get the latest version for\n * The channel must allow 'self_assign' for this to work\n * @since 6.8.0\n * @default undefined\n */\n channel?: string;\n}\n\nexport interface AppReadyResult {\n bundle: BundleInfo;\n}\n\nexport interface UpdateUrl {\n url: string;\n}\n\nexport interface StatsUrl {\n url: string;\n}\n\nexport interface ChannelUrl {\n url: string;\n}\n\n/**\n * This URL and versions are used to download the bundle from the server, If you use backend all information will be gived by the method getLatest.\n * If you don't use backend, you need to provide the URL and version of the bundle. Checksum and sessionKey are required if you encrypted the bundle with the CLI command encrypt, you should receive them as result of the command.\n */\nexport interface DownloadOptions {\n /**\n * The URL of the bundle zip file (e.g: dist.zip) to be downloaded. (This can be any URL. E.g: Amazon S3, a GitHub tag, any other place you've hosted your bundle.)\n */\n url: string;\n /**\n * The version code/name of this bundle/version\n */\n version: string;\n /**\n * The session key for the update, when the bundle is encrypted with a session key\n * @since 4.0.0\n * @default undefined\n */\n sessionKey?: string;\n /**\n * The checksum for the update, it should be in sha256 and encrypted with private key if the bundle is encrypted\n * @since 4.0.0\n * @default undefined\n */\n checksum?: string;\n /**\n * The manifest for multi-file downloads\n * @since 6.1.0\n * @default undefined\n */\n manifest?: ManifestEntry[];\n}\n\nexport interface BundleId {\n id: string;\n}\n\nexport interface BundleListResult {\n bundles: BundleInfo[];\n}\n\nexport interface ResetOptions {\n toLastSuccessful: boolean;\n}\n\nexport interface ListOptions {\n /**\n * Whether to return the raw bundle list or the manifest. If true, the list will attempt to read the internal database instead of files on disk.\n * @since 6.14.0\n * @default false\n */\n raw?: boolean;\n}\n\nexport interface CurrentBundleResult {\n bundle: BundleInfo;\n native: string;\n}\n\nexport interface MultiDelayConditions {\n delayConditions: DelayCondition[];\n}\n\nexport interface BuiltinVersion {\n version: string;\n}\n\nexport interface DeviceId {\n deviceId: string;\n}\n\nexport interface PluginVersion {\n version: string;\n}\n\nexport interface AutoUpdateEnabled {\n enabled: boolean;\n}\n\nexport interface AutoUpdateAvailable {\n available: boolean;\n}\n\nexport interface SetShakeMenuOptions {\n enabled: boolean;\n}\n\nexport interface ShakeMenuEnabled {\n enabled: boolean;\n}\n\nexport interface GetAppIdRes {\n appId: string;\n}\n\nexport interface SetAppIdOptions {\n appId: string;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAAA;;;;GAIG","sourcesContent":["/*\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/.\n */\n\n/// <reference types=\"@capacitor/cli\" />\n\nimport type { PluginListenerHandle } from '@capacitor/core';\n\ndeclare module '@capacitor/cli' {\n export interface PluginsConfig {\n /**\n * CapacitorUpdater can be configured with these options:\n */\n CapacitorUpdater?: {\n /**\n * Configure the number of milliseconds the native plugin should wait before considering an update 'failed'.\n *\n * Only available for Android and iOS.\n *\n * @default 10000 // (10 seconds)\n * @example 1000 // (1 second)\n */\n appReadyTimeout?: number;\n /**\n * Configure the number of seconds the native plugin should wait before considering API timeout.\n *\n * Only available for Android and iOS.\n *\n * @default 20 // (20 second)\n * @example 10 // (10 second)\n */\n responseTimeout?: number;\n /**\n * Configure whether the plugin should use automatically delete failed bundles.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n autoDeleteFailed?: boolean;\n\n /**\n * Configure whether the plugin should use automatically delete previous bundles after a successful update.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n autoDeletePrevious?: boolean;\n\n /**\n * Configure whether the plugin should use Auto Update via an update server.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n autoUpdate?: boolean;\n\n /**\n * Automatically delete previous downloaded bundles when a newer native app bundle is installed to the device.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n resetWhenUpdate?: boolean;\n\n /**\n * Configure the URL / endpoint to which update checks are sent.\n *\n * Only available for Android and iOS.\n *\n * @default https://plugin.capgo.app/updates\n * @example https://example.com/api/auto_update\n */\n updateUrl?: string;\n\n /**\n * Configure the URL / endpoint for channel operations.\n *\n * Only available for Android and iOS.\n *\n * @default https://plugin.capgo.app/channel_self\n * @example https://example.com/api/channel\n */\n channelUrl?: string;\n\n /**\n * Configure the URL / endpoint to which update statistics are sent.\n *\n * Only available for Android and iOS. Set to \"\" to disable stats reporting.\n *\n * @default https://plugin.capgo.app/stats\n * @example https://example.com/api/stats\n */\n statsUrl?: string;\n /**\n * Configure the public key for end to end live update encryption Version 2\n *\n * Only available for Android and iOS.\n *\n * @default undefined\n * @since 6.2.0\n */\n publicKey?: string;\n\n /**\n * Configure the current version of the app. This will be used for the first update request.\n * If not set, the plugin will get the version from the native code.\n *\n * Only available for Android and iOS.\n *\n * @default undefined\n * @since 4.17.48\n */\n version?: string;\n /**\n * Configure when the plugin should direct install updates. Only for autoUpdate mode.\n * Works well for apps less than 10MB and with uploads done using --partial flag.\n * Zip or apps more than 10MB will be relatively slow for users to update.\n * - false: Never do direct updates (default behavior)\n * - atInstall: Direct update only when app is installed/updated from store, otherwise use normal background update\n * - always: Always do direct updates immediately when available\n * - true: (deprecated) Same as \"always\" for backward compatibility\n *\n * Only available for Android and iOS.\n *\n * @default false\n * @since 5.1.0\n */\n directUpdate?: boolean | 'atInstall' | 'always';\n\n /**\n * Automatically handle splashscreen hiding when using directUpdate. When enabled, the plugin will automatically hide the splashscreen after updates are applied or when no update is needed.\n * This removes the need to manually listen for appReady events and call SplashScreen.hide().\n * Only works when directUpdate is set to \"atInstall\", \"always\", or true.\n * Requires the @capacitor/splash-screen plugin to be installed and configured with launchAutoHide: false.\n * Requires autoUpdate and directUpdate to be enabled.\n *\n * Only available for Android and iOS.\n *\n * @default false\n * @since 7.6.0\n */\n autoSplashscreen?: boolean;\n\n /**\n * Display a native loading indicator on top of the splashscreen while automatic direct updates are running.\n * Only takes effect when {@link autoSplashscreen} is enabled.\n * Requires the @capacitor/splash-screen plugin to be installed and configured with launchAutoHide: false.\n *\n * Only available for Android and iOS.\n *\n * @default false\n * @since 7.19.0\n */\n autoSplashscreenLoader?: boolean;\n\n /**\n * Automatically hide the splashscreen after the specified number of milliseconds when using automatic direct updates.\n * If the timeout elapses, the update continues to download in the background while the splashscreen is dismissed.\n * Set to `0` (zero) to disable the timeout.\n * When the timeout fires, the direct update flow is skipped and the downloaded bundle is installed on the next background/launch.\n * Requires {@link autoSplashscreen} to be enabled.\n *\n * Only available for Android and iOS.\n *\n * @default 10000 // (10 seconds)\n * @since 7.19.0\n */\n autoSplashscreenTimeout?: number;\n\n /**\n * Configure the delay period for period update check. the unit is in seconds.\n *\n * Only available for Android and iOS.\n * Cannot be less than 600 seconds (10 minutes).\n *\n * @default 0 (disabled)\n * @example 3600 (1 hour)\n * @example 86400 (24 hours)\n */\n periodCheckDelay?: number;\n\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localS3?: boolean;\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localHost?: string;\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localWebHost?: string;\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localSupa?: string;\n /**\n * Configure the CLI to use a local server for testing.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localSupaAnon?: string;\n /**\n * Configure the CLI to use a local api for testing.\n *\n *\n * @default undefined\n * @since 6.3.3\n */\n localApi?: string;\n /**\n * Configure the CLI to use a local file api for testing.\n *\n *\n * @default undefined\n * @since 6.3.3\n */\n localApiFiles?: string;\n /**\n * Allow the plugin to modify the updateUrl, statsUrl and channelUrl dynamically from the JavaScript side.\n *\n *\n * @default false\n * @since 5.4.0\n */\n allowModifyUrl?: boolean;\n\n /**\n * Allow the plugin to modify the appId dynamically from the JavaScript side.\n *\n *\n * @default false\n * @since 7.14.0\n */\n allowModifyAppId?: boolean;\n\n /**\n * Allow marking bundles as errored from JavaScript while using manual update flows.\n * When enabled, {@link CapacitorUpdaterPlugin.setBundleError} can change a bundle status to `error`.\n *\n * @default false\n * @since 7.20.0\n */\n allowManualBundleError?: boolean;\n\n /**\n * Persist the customId set through {@link CapacitorUpdaterPlugin.setCustomId} across app restarts.\n *\n * Only available for Android and iOS.\n *\n * @default false (will be true by default in a future major release v8.x.x)\n * @since 7.17.3\n */\n persistCustomId?: boolean;\n\n /**\n * Persist the updateUrl, statsUrl and channelUrl set through {@link CapacitorUpdaterPlugin.setUpdateUrl},\n * {@link CapacitorUpdaterPlugin.setStatsUrl} and {@link CapacitorUpdaterPlugin.setChannelUrl} across app restarts.\n *\n * Only available for Android and iOS.\n *\n * @default false\n * @since 7.20.0\n */\n persistModifyUrl?: boolean;\n\n /**\n * Set the default channel for the app in the config. Case sensitive.\n * This will setting will override the default channel set in the cloud, but will still respect overrides made in the cloud.\n * This requires the channel to allow devices to self dissociate/associate in the channel settings. https://capgo.app/docs/public-api/channels/#channel-configuration-options\n *\n *\n * @default undefined\n * @since 5.5.0\n */\n defaultChannel?: string;\n /**\n * Configure the app id for the app in the config.\n *\n * @default undefined\n * @since 6.0.0\n */\n appId?: string;\n\n /**\n * Configure the plugin to keep the URL path after a reload.\n * WARNING: When a reload is triggered, 'window.history' will be cleared.\n *\n * @default false\n * @since 6.8.0\n */\n keepUrlPathAfterReload?: boolean;\n /**\n * Disable the JavaScript logging of the plugin. if true, the plugin will not log to the JavaScript console. only the native log will be done\n *\n * @default false\n * @since 7.3.0\n */\n disableJSLogging?: boolean;\n /**\n * Enable shake gesture to show update menu for debugging/testing purposes\n *\n * @default false\n * @since 7.5.0\n */\n shakeMenu?: boolean;\n };\n }\n}\n\nexport interface CapacitorUpdaterPlugin {\n /**\n * Notify Capacitor Updater that the current bundle is working (a rollback will occur if this method is not called on every app launch)\n * By default this method should be called in the first 10 sec after app launch, otherwise a rollback will occur.\n * Change this behaviour with {@link appReadyTimeout}\n *\n * @returns {Promise<AppReadyResult>} an Promise resolved directly\n * @throws {Error}\n */\n notifyAppReady(): Promise<AppReadyResult>;\n\n /**\n * Set the updateUrl for the app, this will be used to check for updates.\n *\n * @param options contains the URL to use for checking for updates.\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 5.4.0\n */\n setUpdateUrl(options: UpdateUrl): Promise<void>;\n\n /**\n * Set the statsUrl for the app, this will be used to send statistics. Passing an empty string will disable statistics gathering.\n *\n * @param options contains the URL to use for sending statistics.\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 5.4.0\n */\n setStatsUrl(options: StatsUrl): Promise<void>;\n\n /**\n * Set the channelUrl for the app, this will be used to set the channel.\n *\n * @param options contains the URL to use for setting the channel.\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 5.4.0\n */\n setChannelUrl(options: ChannelUrl): Promise<void>;\n\n /**\n * Download a new bundle from the provided URL, it should be a zip file, with files inside or with a unique id inside with all your files\n *\n * @example const bundle = await CapacitorUpdater.download({ url: `https://example.com/versions/${version}/dist.zip`, version });\n * @returns {Promise<BundleInfo>} The {@link BundleInfo} for the specified bundle.\n * @param options The {@link DownloadOptions} for downloading a new bundle zip.\n */\n download(options: DownloadOptions): Promise<BundleInfo>;\n\n /**\n * Set the next bundle to be used when the app is reloaded.\n *\n * @param options Contains the ID of the next Bundle to set on next app launch. {@link BundleInfo.id}\n * @returns {Promise<BundleInfo>} The {@link BundleInfo} for the specified bundle id.\n * @throws {Error} When there is no index.html file inside the bundle folder.\n */\n next(options: BundleId): Promise<BundleInfo>;\n\n /**\n * Set the current bundle and immediately reloads the app.\n *\n * @param options A {@link BundleId} object containing the new bundle id to set as current.\n * @returns {Promise<void>}\n * @throws {Error} When there are is no index.html file inside the bundle folder.\n */\n set(options: BundleId): Promise<void>;\n\n /**\n * Deletes the specified bundle from the native app storage. Use with {@link list} to get the stored Bundle IDs.\n *\n * @param options A {@link BundleId} object containing the ID of a bundle to delete (note, this is the bundle id, NOT the version name)\n * @returns {Promise<void>} When the bundle is deleted\n * @throws {Error}\n */\n delete(options: BundleId): Promise<void>;\n\n /**\n * Mark an installed bundle as errored. Only available when {@link PluginsConfig.CapacitorUpdater.allowManualBundleError} is true.\n *\n * @param options A {@link BundleId} object containing the bundle id to mark as errored.\n * @returns {Promise<BundleInfo>} The updated {@link BundleInfo} for the bundle.\n * @throws {Error} When the bundle does not exist or the feature is disabled.\n * @since 7.20.0\n */\n setBundleError(options: BundleId): Promise<BundleInfo>;\n\n /**\n * Get all locally downloaded bundles in your app\n *\n * @returns {Promise<BundleListResult>} A Promise containing the {@link BundleListResult.bundles}\n * @param options The {@link ListOptions} for listing bundles\n * @throws {Error}\n */\n list(options?: ListOptions): Promise<BundleListResult>;\n\n /**\n * Reset the app to the `builtin` bundle (the one sent to Apple App Store / Google Play Store ) or the last successfully loaded bundle.\n *\n * @param options Containing {@link ResetOptions.toLastSuccessful}, `true` resets to the builtin bundle and `false` will reset to the last successfully loaded bundle.\n * @returns {Promise<void>}\n * @throws {Error}\n */\n reset(options?: ResetOptions): Promise<void>;\n\n /**\n * Get the current bundle, if none are set it returns `builtin`. currentNative is the original bundle installed on the device\n *\n * @returns {Promise<CurrentBundleResult>} A Promise evaluating to the {@link CurrentBundleResult}\n * @throws {Error}\n */\n current(): Promise<CurrentBundleResult>;\n\n /**\n * Reload the view\n *\n * @returns {Promise<void>} A Promise which is resolved when the view is reloaded\n * @throws {Error}\n */\n reload(): Promise<void>;\n\n /**\n * Sets a {@link DelayCondition} array containing conditions that the Plugin will use to delay the update.\n * After all conditions are met, the update process will run start again as usual, so update will be installed after a backgrounding or killing the app.\n * For the `date` kind, the value should be an iso8601 date string.\n * For the `background` kind, the value should be a number in milliseconds.\n * For the `nativeVersion` kind, the value should be the version number.\n * For the `kill` kind, the value is not used.\n * The function has unconsistent behavior the option kill do trigger the update after the first kill and not after the next background like other options. This will be fixed in a future major release.\n *\n * @example\n * // Delay the update after the user kills the app or after a background of 300000 ms (5 minutes)\n * await CapacitorUpdater.setMultiDelay({ delayConditions: [{ kind: 'kill' }, { kind: 'background', value: '300000' }] })\n * @example\n * // Delay the update after the specific iso8601 date is expired\n * await CapacitorUpdater.setMultiDelay({ delayConditions: [{ kind: 'date', value: '2022-09-14T06:14:11.920Z' }] })\n * @example\n * // Delay the update after the first background (default behaviour without setting delay)\n * await CapacitorUpdater.setMultiDelay({ delayConditions: [{ kind: 'background' }] })\n * @param options Containing the {@link MultiDelayConditions} array of conditions to set\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 4.3.0\n */\n setMultiDelay(options: MultiDelayConditions): Promise<void>;\n\n /**\n * Cancels a {@link DelayCondition} to process an update immediately.\n *\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 4.0.0\n */\n cancelDelay(): Promise<void>;\n\n /**\n * Get Latest bundle available from update Url\n *\n * @returns {Promise<LatestVersion>} A Promise resolved when url is loaded\n * @throws {Error}\n * @since 4.0.0\n */\n getLatest(options?: GetLatestOptions): Promise<LatestVersion>;\n\n /**\n * Sets the channel for this device. The channel has to allow for self assignment for this to work.\n * Do not use this method to set the channel at boot.\n * This method is to set the channel after the app is ready, and user interacted.\n * If you want to set the channel at boot, use the {@link PluginsConfig} to set the default channel.\n * This methods send to Capgo backend a request to link the device ID to the channel. Capgo can accept or refuse depending of the setting of your channel.\n *\n *\n *\n * @param options Is the {@link SetChannelOptions} channel to set\n * @returns {Promise<ChannelRes>} A Promise which is resolved when the new channel is set\n * @throws {Error}\n * @since 4.7.0\n */\n setChannel(options: SetChannelOptions): Promise<ChannelRes>;\n\n /**\n * Unset the channel for this device. The device will then return to the default channel\n *\n * @returns {Promise<ChannelRes>} A Promise resolved when channel is set\n * @throws {Error}\n * @since 4.7.0\n */\n unsetChannel(options: UnsetChannelOptions): Promise<void>;\n\n /**\n * Get the channel for this device\n *\n * @returns {Promise<ChannelRes>} A Promise that resolves with the channel info\n * @throws {Error}\n * @since 4.8.0\n */\n getChannel(): Promise<GetChannelRes>;\n\n /**\n * List all channels available for this device that allow self-assignment\n *\n * @returns {Promise<ListChannelsResult>} A Promise that resolves with the available channels\n * @throws {Error}\n * @since 7.5.0\n */\n listChannels(): Promise<ListChannelsResult>;\n\n /**\n * Set a custom ID for this device\n *\n * When {@link PluginsConfig.CapacitorUpdater.persistCustomId} is true, the value will be stored natively and restored on the next app launch.\n * Pass an empty string to remove any previously stored customId.\n *\n * @param options is the {@link SetCustomIdOptions} customId to set\n * @returns {Promise<void>} an Promise resolved instantly\n * @throws {Error}\n * @since 4.9.0\n */\n setCustomId(options: SetCustomIdOptions): Promise<void>;\n\n /**\n * Get the native app version or the builtin version if set in config\n *\n * @returns {Promise<BuiltinVersion>} A Promise with version for this device\n * @since 5.2.0\n */\n getBuiltinVersion(): Promise<BuiltinVersion>;\n\n /**\n * Get unique ID used to identify device (sent to auto update server).\n *\n * This ID is privacy-friendly and follows Apple and Google best practices:\n * - Generated as a UUID and stored securely\n * - Android: Uses EncryptedSharedPreferences with Auto Backup (persists across reinstalls)\n * - iOS: Uses Keychain with kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly (persists across reinstalls)\n * - Data stays on device (not synced to cloud on iOS)\n * - Can be cleared by user via system settings (Android) or keychain access (iOS)\n *\n * The device ID now persists between app reinstalls to maintain consistent device identity.\n *\n * @returns {Promise<DeviceId>} A Promise with id for this device\n * @throws {Error}\n */\n getDeviceId(): Promise<DeviceId>;\n\n /**\n * Get the native Capacitor Updater plugin version (sent to auto update server)\n *\n * @returns {Promise<PluginVersion>} A Promise with Plugin version\n * @throws {Error}\n */\n getPluginVersion(): Promise<PluginVersion>;\n\n /**\n * Get the state of auto update config.\n *\n * @returns {Promise<AutoUpdateEnabled>} The status for auto update. Evaluates to `false` in manual mode.\n * @throws {Error}\n */\n isAutoUpdateEnabled(): Promise<AutoUpdateEnabled>;\n\n /**\n * Remove all listeners for this plugin.\n *\n * @since 1.0.0\n */\n removeAllListeners(): Promise<void>;\n\n /**\n * Listen for bundle download event in the App. Fires once a download has started, during downloading and when finished.\n * This will return you all download percent during the download\n *\n * @since 2.0.11\n */\n addListener(eventName: 'download', listenerFunc: (state: DownloadEvent) => void): Promise<PluginListenerHandle>;\n\n /**\n * Listen for no need to update event, useful when you want force check every time the app is launched\n *\n * @since 4.0.0\n */\n addListener(eventName: 'noNeedUpdate', listenerFunc: (state: NoNeedEvent) => void): Promise<PluginListenerHandle>;\n\n /**\n * Listen for available update event, useful when you want to force check every time the app is launched\n *\n * @since 4.0.0\n */\n addListener(\n eventName: 'updateAvailable',\n listenerFunc: (state: UpdateAvailableEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for downloadComplete events.\n *\n * @since 4.0.0\n */\n addListener(\n eventName: 'downloadComplete',\n listenerFunc: (state: DownloadCompleteEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for breaking update events when the backend flags an update as incompatible with the current app.\n * Emits the same payload as the legacy `majorAvailable` listener.\n *\n * @since 7.22.0\n */\n addListener(\n eventName: 'breakingAvailable',\n listenerFunc: (state: BreakingAvailableEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for Major update event in the App, let you know when major update is blocked by setting disableAutoUpdateBreaking\n *\n * @deprecated Deprecated alias for {@link addListener} with `breakingAvailable`. Emits the same payload. will be removed in v8\n * @since 2.3.0\n */\n addListener(\n eventName: 'majorAvailable',\n listenerFunc: (state: MajorAvailableEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for update fail event in the App, let you know when update has fail to install at next app start\n *\n * @since 2.3.0\n */\n addListener(\n eventName: 'updateFailed',\n listenerFunc: (state: UpdateFailedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for download fail event in the App, let you know when a bundle download has failed\n *\n * @since 4.0.0\n */\n addListener(\n eventName: 'downloadFailed',\n listenerFunc: (state: DownloadFailedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for reload event in the App, let you know when reload has happened\n *\n * @since 4.3.0\n */\n addListener(eventName: 'appReloaded', listenerFunc: () => void): Promise<PluginListenerHandle>;\n\n /**\n * Listen for app ready event in the App, let you know when app is ready to use, this event is retain till consumed.\n *\n * @since 5.1.0\n */\n addListener(eventName: 'appReady', listenerFunc: (state: AppReadyEvent) => void): Promise<PluginListenerHandle>;\n\n /**\n * Get if auto update is available (not disabled by serverUrl).\n *\n * @returns {Promise<AutoUpdateAvailable>} The availability status for auto update. Evaluates to `false` when serverUrl is set.\n * @throws {Error}\n */\n isAutoUpdateAvailable(): Promise<AutoUpdateAvailable>;\n\n /**\n * Get the next bundle that will be used when the app reloads.\n * Returns null if no next bundle is set.\n *\n * @returns {Promise<BundleInfo | null>} A Promise that resolves with the next bundle information or null\n * @throws {Error}\n * @since 6.8.0\n */\n getNextBundle(): Promise<BundleInfo | null>;\n\n /**\n * Get the most recent update that failed to install, if any. The stored value is cleared after it is retrieved once.\n *\n * @returns {Promise<UpdateFailedEvent | null>} The last failed update or null when no failure has been recorded. Value is cleared after it is returned once.\n * @throws {Error}\n * @since 7.22.0\n */\n getFailedUpdate(): Promise<UpdateFailedEvent | null>;\n\n /**\n * Enable or disable the shake menu for debugging/testing purposes\n *\n * @param options Contains enabled boolean to enable or disable shake menu\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 7.5.0\n */\n setShakeMenu(options: SetShakeMenuOptions): Promise<void>;\n\n /**\n * Get the current state of the shake menu\n *\n * @returns {Promise<ShakeMenuEnabled>} The current state of shake menu\n * @throws {Error}\n * @since 7.5.0\n */\n isShakeMenuEnabled(): Promise<ShakeMenuEnabled>;\n\n /**\n * Get the configured App ID\n *\n * @returns {Promise<GetAppIdRes>} The current App ID\n * @throws {Error}\n * @since 7.14.0\n */\n getAppId(): Promise<GetAppIdRes>;\n\n /**\n * Set the App ID for the app (requires allowModifyAppId to be true in config)\n *\n * @param options The new App ID to set\n * @returns {Promise<void>}\n * @throws {Error} If allowModifyAppId is false or if the operation fails\n * @since 7.14.0\n */\n setAppId(options: SetAppIdOptions): Promise<void>;\n}\n\n/**\n * pending: The bundle is pending to be **SET** as the next bundle.\n * downloading: The bundle is being downloaded.\n * success: The bundle has been downloaded and is ready to be **SET** as the next bundle.\n * error: The bundle has failed to download.\n */\nexport type BundleStatus = 'success' | 'error' | 'pending' | 'downloading';\n\nexport type DelayUntilNext = 'background' | 'kill' | 'nativeVersion' | 'date';\n\nexport interface NoNeedEvent {\n /**\n * Current status of download, between 0 and 100.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface UpdateAvailableEvent {\n /**\n * Current status of download, between 0 and 100.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface ChannelRes {\n /**\n * Current status of set channel\n *\n * @since 4.7.0\n */\n status: string;\n error?: string;\n message?: string;\n}\n\nexport interface GetChannelRes {\n /**\n * Current status of get channel\n *\n * @since 4.8.0\n */\n channel?: string;\n error?: string;\n message?: string;\n status?: string;\n allowSet?: boolean;\n}\n\nexport interface ChannelInfo {\n /**\n * The channel ID\n *\n * @since 7.5.0\n */\n id: string;\n /**\n * The channel name\n *\n * @since 7.5.0\n */\n name: string;\n /**\n * Whether this is a public channel\n *\n * @since 7.5.0\n */\n public: boolean;\n /**\n * Whether devices can self-assign to this channel\n *\n * @since 7.5.0\n */\n allow_self_set: boolean;\n}\n\nexport interface ListChannelsResult {\n /**\n * List of available channels\n *\n * @since 7.5.0\n */\n channels: ChannelInfo[];\n}\n\nexport interface DownloadEvent {\n /**\n * Current status of download, between 0 and 100.\n *\n * @since 4.0.0\n */\n percent: number;\n bundle: BundleInfo;\n}\n\nexport interface MajorAvailableEvent {\n /**\n * Emit when a breaking update is available.\n *\n * @deprecated Deprecated alias for {@link BreakingAvailableEvent}. Receives the same payload.\n * @since 4.0.0\n */\n version: string;\n}\n\n/**\n * Payload emitted by {@link CapacitorUpdaterPlugin.addListener} with `breakingAvailable`.\n *\n * @since 7.22.0\n */\nexport type BreakingAvailableEvent = MajorAvailableEvent;\n\nexport interface DownloadFailedEvent {\n /**\n * Emit when a download fail.\n *\n * @since 4.0.0\n */\n version: string;\n}\n\nexport interface DownloadCompleteEvent {\n /**\n * Emit when a new update is available.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface UpdateFailedEvent {\n /**\n * Emit when a update failed to install.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface AppReadyEvent {\n /**\n * Emitted when the app is ready to use.\n *\n * @since 5.2.0\n */\n bundle: BundleInfo;\n status: string;\n}\n\nexport interface ManifestEntry {\n file_name: string | null;\n file_hash: string | null;\n download_url: string | null;\n}\n\nexport interface LatestVersion {\n /**\n * Result of getLatest method\n *\n * @since 4.0.0\n */\n version: string;\n /**\n * @since 6\n */\n checksum?: string;\n /**\n * Indicates whether the update was flagged as breaking by the backend.\n *\n * @since 7.22.0\n */\n breaking?: boolean;\n /**\n * @deprecated Use {@link LatestVersion.breaking} instead.\n */\n major?: boolean;\n message?: string;\n sessionKey?: string;\n error?: string;\n old?: string;\n url?: string;\n /**\n * @since 6.1\n */\n manifest?: ManifestEntry[];\n}\n\nexport interface BundleInfo {\n id: string;\n version: string;\n downloaded: string;\n checksum: string;\n status: BundleStatus;\n}\n\nexport interface SetChannelOptions {\n channel: string;\n triggerAutoUpdate?: boolean;\n}\n\nexport interface UnsetChannelOptions {\n triggerAutoUpdate?: boolean;\n}\n\nexport interface SetCustomIdOptions {\n /**\n * Custom identifier to associate with the device. Use an empty string to clear any saved value.\n */\n customId: string;\n}\n\nexport interface DelayCondition {\n /**\n * Set up delay conditions in setMultiDelay\n * @param value is useless for @param kind \"kill\", optional for \"background\" (default value: \"0\") and required for \"nativeVersion\" and \"date\"\n */\n kind: DelayUntilNext;\n value?: string;\n}\n\nexport interface GetLatestOptions {\n /**\n * The channel to get the latest version for\n * The channel must allow 'self_assign' for this to work\n * @since 6.8.0\n * @default undefined\n */\n channel?: string;\n}\n\nexport interface AppReadyResult {\n bundle: BundleInfo;\n}\n\nexport interface UpdateUrl {\n url: string;\n}\n\nexport interface StatsUrl {\n url: string;\n}\n\nexport interface ChannelUrl {\n url: string;\n}\n\n/**\n * This URL and versions are used to download the bundle from the server, If you use backend all information will be gived by the method getLatest.\n * If you don't use backend, you need to provide the URL and version of the bundle. Checksum and sessionKey are required if you encrypted the bundle with the CLI command encrypt, you should receive them as result of the command.\n */\nexport interface DownloadOptions {\n /**\n * The URL of the bundle zip file (e.g: dist.zip) to be downloaded. (This can be any URL. E.g: Amazon S3, a GitHub tag, any other place you've hosted your bundle.)\n */\n url: string;\n /**\n * The version code/name of this bundle/version\n */\n version: string;\n /**\n * The session key for the update, when the bundle is encrypted with a session key\n * @since 4.0.0\n * @default undefined\n */\n sessionKey?: string;\n /**\n * The checksum for the update, it should be in sha256 and encrypted with private key if the bundle is encrypted\n * @since 4.0.0\n * @default undefined\n */\n checksum?: string;\n /**\n * The manifest for multi-file downloads\n * @since 6.1.0\n * @default undefined\n */\n manifest?: ManifestEntry[];\n}\n\nexport interface BundleId {\n id: string;\n}\n\nexport interface BundleListResult {\n bundles: BundleInfo[];\n}\n\nexport interface ResetOptions {\n toLastSuccessful: boolean;\n}\n\nexport interface ListOptions {\n /**\n * Whether to return the raw bundle list or the manifest. If true, the list will attempt to read the internal database instead of files on disk.\n * @since 6.14.0\n * @default false\n */\n raw?: boolean;\n}\n\nexport interface CurrentBundleResult {\n bundle: BundleInfo;\n native: string;\n}\n\nexport interface MultiDelayConditions {\n delayConditions: DelayCondition[];\n}\n\nexport interface BuiltinVersion {\n version: string;\n}\n\nexport interface DeviceId {\n deviceId: string;\n}\n\nexport interface PluginVersion {\n version: string;\n}\n\nexport interface AutoUpdateEnabled {\n enabled: boolean;\n}\n\nexport interface AutoUpdateAvailable {\n available: boolean;\n}\n\nexport interface SetShakeMenuOptions {\n enabled: boolean;\n}\n\nexport interface ShakeMenuEnabled {\n enabled: boolean;\n}\n\nexport interface GetAppIdRes {\n appId: string;\n}\n\nexport interface SetAppIdOptions {\n appId: string;\n}\n"]}
|
|
@@ -54,7 +54,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
54
54
|
CAPPluginMethod(name: "isShakeMenuEnabled", returnType: CAPPluginReturnPromise)
|
|
55
55
|
]
|
|
56
56
|
public var implementation = CapgoUpdater()
|
|
57
|
-
private let
|
|
57
|
+
private let pluginVersion: String = "7.24.0"
|
|
58
58
|
static let updateUrlDefault = "https://plugin.capgo.app/updates"
|
|
59
59
|
static let statsUrlDefault = "https://plugin.capgo.app/stats"
|
|
60
60
|
static let channelUrlDefault = "https://plugin.capgo.app/channel_self"
|
|
@@ -113,9 +113,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
113
113
|
#endif
|
|
114
114
|
|
|
115
115
|
self.semaphoreUp()
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
UserDefaults.standard.synchronize()
|
|
116
|
+
// Use DeviceIdHelper to get or create device ID that persists across reinstalls
|
|
117
|
+
self.implementation.deviceID = DeviceIdHelper.getOrCreateDeviceId()
|
|
119
118
|
persistCustomId = getConfig().getBoolean("persistCustomId", false)
|
|
120
119
|
if persistCustomId {
|
|
121
120
|
let storedCustomId = UserDefaults.standard.string(forKey: customIdDefaultsKey) ?? ""
|
|
@@ -183,7 +182,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
183
182
|
|
|
184
183
|
implementation.publicKey = getConfig().getString("publicKey", "")!
|
|
185
184
|
implementation.notifyDownloadRaw = notifyDownload
|
|
186
|
-
implementation.
|
|
185
|
+
implementation.pluginVersion = self.pluginVersion
|
|
187
186
|
|
|
188
187
|
// Set logger for shared classes
|
|
189
188
|
implementation.setLogger(logger)
|
|
@@ -236,7 +235,11 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
236
235
|
let nc = NotificationCenter.default
|
|
237
236
|
nc.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
|
|
238
237
|
nc.addObserver(self, selector: #selector(appMovedToForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
|
|
239
|
-
|
|
238
|
+
|
|
239
|
+
// Check for 'kill' delay condition on app launch
|
|
240
|
+
// This handles cases where the app was killed (willTerminateNotification is not reliable for system kills)
|
|
241
|
+
self.delayUpdateUtils.checkCancelDelay(source: .killed)
|
|
242
|
+
|
|
240
243
|
self.appMovedToForeground()
|
|
241
244
|
self.checkForUpdateAfterDelay()
|
|
242
245
|
}
|
|
@@ -432,7 +435,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
432
435
|
}
|
|
433
436
|
|
|
434
437
|
@objc func getPluginVersion(_ call: CAPPluginCall) {
|
|
435
|
-
call.resolve(["version": self.
|
|
438
|
+
call.resolve(["version": self.pluginVersion])
|
|
436
439
|
}
|
|
437
440
|
|
|
438
441
|
@objc func download(_ call: CAPPluginCall) {
|
|
@@ -1377,20 +1380,6 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1377
1380
|
}
|
|
1378
1381
|
}
|
|
1379
1382
|
|
|
1380
|
-
@objc func appKilled() {
|
|
1381
|
-
logger.info("onActivityDestroyed: all activity destroyed")
|
|
1382
|
-
self.delayUpdateUtils.checkCancelDelay(source: .killed)
|
|
1383
|
-
|
|
1384
|
-
// Clean up resources
|
|
1385
|
-
periodicUpdateTimer?.invalidate()
|
|
1386
|
-
periodicUpdateTimer = nil
|
|
1387
|
-
backgroundWork?.cancel()
|
|
1388
|
-
backgroundWork = nil
|
|
1389
|
-
|
|
1390
|
-
// Signal any waiting semaphores to prevent deadlocks
|
|
1391
|
-
semaphoreReady.signal()
|
|
1392
|
-
}
|
|
1393
|
-
|
|
1394
1383
|
private func installNext() {
|
|
1395
1384
|
let delayUpdatePreferences = UserDefaults.standard.string(forKey: DelayUpdateUtils.DELAY_CONDITION_PREFERENCES) ?? "[]"
|
|
1396
1385
|
let delayConditionList: [DelayCondition] = fromJsonArr(json: delayUpdatePreferences).map { obj -> DelayCondition in
|
|
@@ -1446,6 +1435,10 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1446
1435
|
if self._isAutoUpdateEnabled() {
|
|
1447
1436
|
self.backgroundDownload()
|
|
1448
1437
|
} else {
|
|
1438
|
+
let instanceDescriptor = (self.bridge?.viewController as? CAPBridgeViewController)?.instanceDescriptor()
|
|
1439
|
+
if instanceDescriptor?.serverURL != nil {
|
|
1440
|
+
self.implementation.sendStats(action: "blocked_by_server_url", versionName: current.getVersionName())
|
|
1441
|
+
}
|
|
1449
1442
|
logger.info("Auto update is disabled")
|
|
1450
1443
|
self.sendReadyToJs(current: current, msg: "disabled")
|
|
1451
1444
|
}
|
|
@@ -33,7 +33,7 @@ import UIKit
|
|
|
33
33
|
public let CAP_SERVER_PATH: String = "serverBasePath"
|
|
34
34
|
public var versionBuild: String = ""
|
|
35
35
|
public var customId: String = ""
|
|
36
|
-
public var
|
|
36
|
+
public var pluginVersion: String = ""
|
|
37
37
|
public var timeout: Double = 20
|
|
38
38
|
public var statsUrl: String = ""
|
|
39
39
|
public var channelUrl: String = ""
|
|
@@ -42,8 +42,14 @@ import UIKit
|
|
|
42
42
|
public var deviceID = ""
|
|
43
43
|
public var publicKey: String = ""
|
|
44
44
|
|
|
45
|
+
// Flag to track if we received a 429 response - stops requests until app restart
|
|
46
|
+
private static var rateLimitExceeded = false
|
|
47
|
+
|
|
48
|
+
// Flag to track if we've already sent the rate limit statistic - prevents infinite loop
|
|
49
|
+
private static var rateLimitStatisticSent = false
|
|
50
|
+
|
|
45
51
|
private var userAgent: String {
|
|
46
|
-
let safePluginVersion =
|
|
52
|
+
let safePluginVersion = pluginVersion.isEmpty ? "unknown" : pluginVersion
|
|
47
53
|
let safeAppId = appId.isEmpty ? "unknown" : appId
|
|
48
54
|
return "CapacitorUpdater/\(safePluginVersion) (\(safeAppId)) ios/\(versionOs)"
|
|
49
55
|
}
|
|
@@ -85,6 +91,58 @@ import UIKit
|
|
|
85
91
|
return !self.isDevEnvironment && !self.isAppStoreReceiptSandbox() && !self.hasEmbeddedMobileProvision()
|
|
86
92
|
}
|
|
87
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Check if a 429 (Too Many Requests) response was received and set the flag
|
|
96
|
+
*/
|
|
97
|
+
private func checkAndHandleRateLimitResponse(statusCode: Int?) -> Bool {
|
|
98
|
+
if statusCode == 429 {
|
|
99
|
+
// Send a statistic about the rate limit BEFORE setting the flag
|
|
100
|
+
// Only send once to prevent infinite loop if the stat request itself gets rate limited
|
|
101
|
+
if !CapgoUpdater.rateLimitExceeded && !CapgoUpdater.rateLimitStatisticSent {
|
|
102
|
+
CapgoUpdater.rateLimitStatisticSent = true
|
|
103
|
+
self.sendRateLimitStatistic()
|
|
104
|
+
}
|
|
105
|
+
CapgoUpdater.rateLimitExceeded = true
|
|
106
|
+
logger.warn("Rate limit exceeded (429). Stopping all stats and channel requests until app restart.")
|
|
107
|
+
return true
|
|
108
|
+
}
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Send a synchronous statistic about rate limiting
|
|
114
|
+
*/
|
|
115
|
+
private func sendRateLimitStatistic() {
|
|
116
|
+
guard !statsUrl.isEmpty else {
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let current = getCurrentBundle()
|
|
121
|
+
var parameters = createInfoObject()
|
|
122
|
+
parameters.action = "rate_limit_reached"
|
|
123
|
+
parameters.version_name = current.getVersionName()
|
|
124
|
+
parameters.old_version_name = ""
|
|
125
|
+
|
|
126
|
+
// Send synchronously to ensure it goes out before the flag is set
|
|
127
|
+
let semaphore = DispatchSemaphore(value: 0)
|
|
128
|
+
self.alamofireSession.request(
|
|
129
|
+
self.statsUrl,
|
|
130
|
+
method: .post,
|
|
131
|
+
parameters: parameters,
|
|
132
|
+
encoder: JSONParameterEncoder.default,
|
|
133
|
+
requestModifier: { $0.timeoutInterval = self.timeout }
|
|
134
|
+
).responseData { response in
|
|
135
|
+
switch response.result {
|
|
136
|
+
case .success:
|
|
137
|
+
self.logger.info("Rate limit statistic sent")
|
|
138
|
+
case let .failure(error):
|
|
139
|
+
self.logger.error("Error sending rate limit statistic: \(error.localizedDescription)")
|
|
140
|
+
}
|
|
141
|
+
semaphore.signal()
|
|
142
|
+
}
|
|
143
|
+
semaphore.wait()
|
|
144
|
+
}
|
|
145
|
+
|
|
88
146
|
// MARK: Private
|
|
89
147
|
private func hasEmbeddedMobileProvision() -> Bool {
|
|
90
148
|
guard Bundle.main.path(forResource: "embedded", ofType: "mobileprovision") == nil else {
|
|
@@ -255,7 +313,7 @@ import UIKit
|
|
|
255
313
|
version_code: self.versionCode,
|
|
256
314
|
version_os: self.versionOs,
|
|
257
315
|
version_name: self.getCurrentBundle().getVersionName(),
|
|
258
|
-
plugin_version: self.
|
|
316
|
+
plugin_version: self.pluginVersion,
|
|
259
317
|
is_emulator: self.isEmulator(),
|
|
260
318
|
is_prod: self.isProd(),
|
|
261
319
|
action: nil,
|
|
@@ -1011,6 +1069,15 @@ import UIKit
|
|
|
1011
1069
|
|
|
1012
1070
|
func unsetChannel() -> SetChannel {
|
|
1013
1071
|
let setChannel: SetChannel = SetChannel()
|
|
1072
|
+
|
|
1073
|
+
// Check if rate limit was exceeded
|
|
1074
|
+
if CapgoUpdater.rateLimitExceeded {
|
|
1075
|
+
logger.debug("Skipping unsetChannel due to rate limit (429). Requests will resume after app restart.")
|
|
1076
|
+
setChannel.message = "Rate limit exceeded"
|
|
1077
|
+
setChannel.error = "rate_limit_exceeded"
|
|
1078
|
+
return setChannel
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1014
1081
|
if (self.channelUrl ).isEmpty {
|
|
1015
1082
|
logger.error("Channel URL is not set")
|
|
1016
1083
|
setChannel.message = "Channel URL is not set"
|
|
@@ -1023,6 +1090,14 @@ import UIKit
|
|
|
1023
1090
|
let request = alamofireSession.request(self.channelUrl, method: .delete, parameters: parameters, encoder: JSONParameterEncoder.default, requestModifier: { $0.timeoutInterval = self.timeout })
|
|
1024
1091
|
|
|
1025
1092
|
request.validate().responseDecodable(of: SetChannelDec.self) { response in
|
|
1093
|
+
// Check for 429 rate limit
|
|
1094
|
+
if self.checkAndHandleRateLimitResponse(statusCode: response.response?.statusCode) {
|
|
1095
|
+
setChannel.message = "Rate limit exceeded"
|
|
1096
|
+
setChannel.error = "rate_limit_exceeded"
|
|
1097
|
+
semaphore.signal()
|
|
1098
|
+
return
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1026
1101
|
switch response.result {
|
|
1027
1102
|
case .success:
|
|
1028
1103
|
if let responseValue = response.value {
|
|
@@ -1045,6 +1120,15 @@ import UIKit
|
|
|
1045
1120
|
|
|
1046
1121
|
func setChannel(channel: String) -> SetChannel {
|
|
1047
1122
|
let setChannel: SetChannel = SetChannel()
|
|
1123
|
+
|
|
1124
|
+
// Check if rate limit was exceeded
|
|
1125
|
+
if CapgoUpdater.rateLimitExceeded {
|
|
1126
|
+
logger.debug("Skipping setChannel due to rate limit (429). Requests will resume after app restart.")
|
|
1127
|
+
setChannel.message = "Rate limit exceeded"
|
|
1128
|
+
setChannel.error = "rate_limit_exceeded"
|
|
1129
|
+
return setChannel
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1048
1132
|
if (self.channelUrl ).isEmpty {
|
|
1049
1133
|
logger.error("Channel URL is not set")
|
|
1050
1134
|
setChannel.message = "Channel URL is not set"
|
|
@@ -1058,6 +1142,14 @@ import UIKit
|
|
|
1058
1142
|
let request = alamofireSession.request(self.channelUrl, method: .post, parameters: parameters, encoder: JSONParameterEncoder.default, requestModifier: { $0.timeoutInterval = self.timeout })
|
|
1059
1143
|
|
|
1060
1144
|
request.validate().responseDecodable(of: SetChannelDec.self) { response in
|
|
1145
|
+
// Check for 429 rate limit
|
|
1146
|
+
if self.checkAndHandleRateLimitResponse(statusCode: response.response?.statusCode) {
|
|
1147
|
+
setChannel.message = "Rate limit exceeded"
|
|
1148
|
+
setChannel.error = "rate_limit_exceeded"
|
|
1149
|
+
semaphore.signal()
|
|
1150
|
+
return
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1061
1153
|
switch response.result {
|
|
1062
1154
|
case .success:
|
|
1063
1155
|
if let responseValue = response.value {
|
|
@@ -1080,6 +1172,15 @@ import UIKit
|
|
|
1080
1172
|
|
|
1081
1173
|
func getChannel() -> GetChannel {
|
|
1082
1174
|
let getChannel: GetChannel = GetChannel()
|
|
1175
|
+
|
|
1176
|
+
// Check if rate limit was exceeded
|
|
1177
|
+
if CapgoUpdater.rateLimitExceeded {
|
|
1178
|
+
logger.debug("Skipping getChannel due to rate limit (429). Requests will resume after app restart.")
|
|
1179
|
+
getChannel.message = "Rate limit exceeded"
|
|
1180
|
+
getChannel.error = "rate_limit_exceeded"
|
|
1181
|
+
return getChannel
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1083
1184
|
if (self.channelUrl ).isEmpty {
|
|
1084
1185
|
logger.error("Channel URL is not set")
|
|
1085
1186
|
getChannel.message = "Channel URL is not set"
|
|
@@ -1094,6 +1195,14 @@ import UIKit
|
|
|
1094
1195
|
defer {
|
|
1095
1196
|
semaphore.signal()
|
|
1096
1197
|
}
|
|
1198
|
+
|
|
1199
|
+
// Check for 429 rate limit
|
|
1200
|
+
if self.checkAndHandleRateLimitResponse(statusCode: response.response?.statusCode) {
|
|
1201
|
+
getChannel.message = "Rate limit exceeded"
|
|
1202
|
+
getChannel.error = "rate_limit_exceeded"
|
|
1203
|
+
return
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1097
1206
|
switch response.result {
|
|
1098
1207
|
case .success:
|
|
1099
1208
|
if let responseValue = response.value {
|
|
@@ -1125,6 +1234,14 @@ import UIKit
|
|
|
1125
1234
|
|
|
1126
1235
|
func listChannels() -> ListChannels {
|
|
1127
1236
|
let listChannels: ListChannels = ListChannels()
|
|
1237
|
+
|
|
1238
|
+
// Check if rate limit was exceeded
|
|
1239
|
+
if CapgoUpdater.rateLimitExceeded {
|
|
1240
|
+
logger.debug("Skipping listChannels due to rate limit (429). Requests will resume after app restart.")
|
|
1241
|
+
listChannels.error = "rate_limit_exceeded"
|
|
1242
|
+
return listChannels
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1128
1245
|
if (self.channelUrl).isEmpty {
|
|
1129
1246
|
logger.error("Channel URL is not set")
|
|
1130
1247
|
listChannels.error = "Channel URL is not set"
|
|
@@ -1160,6 +1277,13 @@ import UIKit
|
|
|
1160
1277
|
defer {
|
|
1161
1278
|
semaphore.signal()
|
|
1162
1279
|
}
|
|
1280
|
+
|
|
1281
|
+
// Check for 429 rate limit
|
|
1282
|
+
if self.checkAndHandleRateLimitResponse(statusCode: response.response?.statusCode) {
|
|
1283
|
+
listChannels.error = "rate_limit_exceeded"
|
|
1284
|
+
return
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1163
1287
|
switch response.result {
|
|
1164
1288
|
case .success:
|
|
1165
1289
|
if let responseValue = response.value {
|
|
@@ -1193,6 +1317,12 @@ import UIKit
|
|
|
1193
1317
|
private let operationQueue = OperationQueue()
|
|
1194
1318
|
|
|
1195
1319
|
func sendStats(action: String, versionName: String? = nil, oldVersionName: String? = "") {
|
|
1320
|
+
// Check if rate limit was exceeded
|
|
1321
|
+
if CapgoUpdater.rateLimitExceeded {
|
|
1322
|
+
logger.debug("Skipping sendStats due to rate limit (429). Stats will resume after app restart.")
|
|
1323
|
+
return
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1196
1326
|
guard !statsUrl.isEmpty else {
|
|
1197
1327
|
return
|
|
1198
1328
|
}
|
|
@@ -1214,6 +1344,12 @@ import UIKit
|
|
|
1214
1344
|
encoder: JSONParameterEncoder.default,
|
|
1215
1345
|
requestModifier: { $0.timeoutInterval = self.timeout }
|
|
1216
1346
|
).responseData { response in
|
|
1347
|
+
// Check for 429 rate limit
|
|
1348
|
+
if self.checkAndHandleRateLimitResponse(statusCode: response.response?.statusCode) {
|
|
1349
|
+
semaphore.signal()
|
|
1350
|
+
return
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1217
1353
|
switch response.result {
|
|
1218
1354
|
case .success:
|
|
1219
1355
|
self.logger.info("Stats sent for \(action), version \(versionName)")
|
|
@@ -1222,7 +1358,7 @@ import UIKit
|
|
|
1222
1358
|
}
|
|
1223
1359
|
semaphore.signal()
|
|
1224
1360
|
}
|
|
1225
|
-
semaphore.
|
|
1361
|
+
semaphore.wait()
|
|
1226
1362
|
}
|
|
1227
1363
|
operationQueue.addOperation(operation)
|
|
1228
1364
|
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import Foundation
|
|
8
|
+
import Security
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Helper class to manage device ID persistence across app installations.
|
|
12
|
+
* Uses iOS Keychain to persist the device ID.
|
|
13
|
+
*/
|
|
14
|
+
class DeviceIdHelper {
|
|
15
|
+
private static let keychainService = "app.capgo.updater"
|
|
16
|
+
private static let keychainAccount = "deviceId"
|
|
17
|
+
private static let legacyDefaultsKey = "appUUID"
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Gets or creates a device ID that persists across reinstalls.
|
|
21
|
+
*
|
|
22
|
+
* This method:
|
|
23
|
+
* 1. First checks for an existing ID in Keychain (persists across reinstalls)
|
|
24
|
+
* 2. Falls back to UserDefaults (for migration from older versions)
|
|
25
|
+
* 3. Generates a new UUID if neither exists
|
|
26
|
+
* 4. Stores the ID in Keychain for future use
|
|
27
|
+
*
|
|
28
|
+
* @return Device ID as a lowercase UUID string
|
|
29
|
+
*/
|
|
30
|
+
static func getOrCreateDeviceId() -> String {
|
|
31
|
+
// Try to get device ID from Keychain first
|
|
32
|
+
if let keychainDeviceId = getDeviceIdFromKeychain() {
|
|
33
|
+
return keychainDeviceId.lowercased()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Migration: Check UserDefaults for existing device ID
|
|
37
|
+
var deviceId = UserDefaults.standard.string(forKey: legacyDefaultsKey)
|
|
38
|
+
|
|
39
|
+
if deviceId == nil || deviceId!.isEmpty {
|
|
40
|
+
// Generate new device ID if none exists
|
|
41
|
+
deviceId = UUID().uuidString
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Ensure lowercase for consistency
|
|
45
|
+
deviceId = deviceId!.lowercased()
|
|
46
|
+
|
|
47
|
+
// Save to Keychain for persistence across reinstalls
|
|
48
|
+
saveDeviceIdToKeychain(deviceId: deviceId!)
|
|
49
|
+
|
|
50
|
+
return deviceId!
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Retrieves the device ID from iOS Keychain.
|
|
55
|
+
*
|
|
56
|
+
* @return Device ID string or nil if not found
|
|
57
|
+
*/
|
|
58
|
+
private static func getDeviceIdFromKeychain() -> String? {
|
|
59
|
+
let query: [String: Any] = [
|
|
60
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
61
|
+
kSecAttrService as String: keychainService,
|
|
62
|
+
kSecAttrAccount as String: keychainAccount,
|
|
63
|
+
kSecReturnData as String: true,
|
|
64
|
+
kSecMatchLimit as String: kSecMatchLimitOne
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
var result: AnyObject?
|
|
68
|
+
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
69
|
+
|
|
70
|
+
guard status == errSecSuccess,
|
|
71
|
+
let data = result as? Data,
|
|
72
|
+
let deviceId = String(data: data, encoding: .utf8) else {
|
|
73
|
+
return nil
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return deviceId
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Saves the device ID to iOS Keychain with appropriate accessibility settings.
|
|
81
|
+
*
|
|
82
|
+
* Uses kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly:
|
|
83
|
+
* - Data persists across reinstalls
|
|
84
|
+
* - Data is NOT synced to iCloud
|
|
85
|
+
* - Data is accessible after first device unlock
|
|
86
|
+
* - Data stays on this device only (privacy-friendly)
|
|
87
|
+
*
|
|
88
|
+
* @param deviceId The device ID to save
|
|
89
|
+
*/
|
|
90
|
+
private static func saveDeviceIdToKeychain(deviceId: String) {
|
|
91
|
+
guard let data = deviceId.data(using: .utf8) else {
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Delete any existing entry first
|
|
96
|
+
let deleteQuery: [String: Any] = [
|
|
97
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
98
|
+
kSecAttrService as String: keychainService,
|
|
99
|
+
kSecAttrAccount as String: keychainAccount
|
|
100
|
+
]
|
|
101
|
+
SecItemDelete(deleteQuery as CFDictionary)
|
|
102
|
+
|
|
103
|
+
// Add new entry with appropriate accessibility
|
|
104
|
+
let addQuery: [String: Any] = [
|
|
105
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
106
|
+
kSecAttrService as String: keychainService,
|
|
107
|
+
kSecAttrAccount as String: keychainAccount,
|
|
108
|
+
kSecValueData as String: data,
|
|
109
|
+
// This ensures data persists across reinstalls but stays on device (not synced)
|
|
110
|
+
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
let status = SecItemAdd(addQuery as CFDictionary, nil)
|
|
114
|
+
|
|
115
|
+
if status != errSecSuccess {
|
|
116
|
+
// Log error but don't crash - we'll fall back to UserDefaults on next launch
|
|
117
|
+
print("Failed to save device ID to Keychain: \(status)")
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/capacitor-updater",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.24.0",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"description": "Live update for capacitor apps",
|
|
6
6
|
"main": "dist/plugin.cjs.js",
|
|
@@ -48,12 +48,11 @@
|
|
|
48
48
|
"test:ios": "./scripts/test-ios.sh",
|
|
49
49
|
"test:android": "cd android && ./gradlew test && cd ..",
|
|
50
50
|
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
|
|
51
|
-
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --
|
|
51
|
+
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
|
|
52
52
|
"eslint": "eslint . --ext .ts",
|
|
53
53
|
"prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
|
|
54
54
|
"swiftlint": "node-swiftlint",
|
|
55
|
-
"docgen": "
|
|
56
|
-
"docgen:api": "node scripts/generate-docs.js",
|
|
55
|
+
"docgen": "node scripts/generate-docs.js",
|
|
57
56
|
"build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
|
|
58
57
|
"clean": "rimraf ./dist",
|
|
59
58
|
"watch": "tsc --watch",
|