feat(live-update): integrate Ionic Live Update Provider SDK - #818
Open
robingenz wants to merge 12 commits into
Open
feat(live-update): integrate Ionic Live Update Provider SDK#818robingenz wants to merge 12 commits into
robingenz wants to merge 12 commits into
Conversation
Adds optional integration with the Ionic Live Update Provider SDK (`io.ionic:liveupdateprovider` / `pod 'LiveUpdateProvider'`) so the plugin can register itself as a Capawesome provider for Federated Capacitor and Ionic Portals. The SDK is an optional native dependency on both platforms: - Android: `compileOnly` by default; opt in via the `capawesomeCapacitorLiveUpdateIncludeIonicProvider` Gradle property. - iOS CocoaPods: new `IonicProvider` subspec adds the `LiveUpdateProvider` pod and the `CAPAWESOME_INCLUDE_IONIC_PROVIDER` compile flag. - iOS SPM: new `IonicProvider` package trait wires the dependency and the same compile flag conditionally. Provider mode keeps its own bundle pointer scoped by `managerKey` and never touches the standalone `currentBundleId` / `nextBundleId` state.
@capawesome/capacitor-accessibility-preferences
@capawesome/capacitor-action-sheet
@capawesome/capacitor-age-signals
@capawesome/capacitor-alarm
@capawesome-team/capacitor-android-battery-optimization
@capawesome/capacitor-android-dark-mode-support
@capawesome/capacitor-android-edge-to-edge-support
@capawesome-team/capacitor-android-foreground-service
@capawesome/capacitor-android-intent-launcher
@capawesome/capacitor-android-sms-retriever
@capawesome/capacitor-app-icon
@capawesome/capacitor-app-integrity
@capawesome/capacitor-app-language
@capawesome/capacitor-app-launcher
@capawesome/capacitor-app-review
@capawesome/capacitor-app-shortcuts
@capawesome/capacitor-app-tracking-transparency
@capawesome/capacitor-app-update
@capawesome/capacitor-apple-sign-in
@capawesome/capacitor-asset-manager
@capawesome/capacitor-audio-session
@capawesome/capacitor-background-task
@capawesome/capacitor-badge
@capawesome/capacitor-battery
@capawesome/capacitor-clipboard
@capawesome/capacitor-cloudinary
@capawesome/capacitor-compass
@capawesome/capacitor-crisp
@capawesome-team/capacitor-datetime-picker
@capawesome/capacitor-device-info
@capawesome/capacitor-dialog
@capawesome/capacitor-exif
@capawesome/capacitor-facebook-sign-in
@capawesome-team/capacitor-file-opener
@capawesome/capacitor-file-picker
@capawesome/capacitor-formbricks
@capawesome/capacitor-google-sign-in
@capawesome/capacitor-grafana-faro
@capawesome/capacitor-gyroscope
@capawesome/capacitor-haptics
@capawesome/capacitor-home-indicator
@capawesome/capacitor-in-app-browser
@capawesome/capacitor-install-referrer
@capawesome/capacitor-intercom
@capawesome/capacitor-intune
@capawesome/capacitor-keep-awake
@capawesome/capacitor-libsql
@capawesome/capacitor-light-sensor
@capawesome/capacitor-live-update
@capawesome/capacitor-localization
@capawesome/capacitor-mail-composer
@capawesome/capacitor-managed-configurations
@capawesome/capacitor-maps-launcher
@capawesome/capacitor-navigation-bar
@capawesome/capacitor-network
@capawesome/capacitor-nodejs
@capawesome/capacitor-passkeys
@capawesome/capacitor-password-autofill
@capawesome/capacitor-pdf-generator
@capawesome/capacitor-pdf-viewer
@capawesome/capacitor-permissions
@capawesome/capacitor-phone-dialer
@capawesome/capacitor-photo-editor
@capawesome/capacitor-photo-manipulator
@capawesome/capacitor-pixlive
@capawesome/capacitor-posthog
@capawesome/capacitor-privacy-screen
@capawesome/capacitor-proximity-sensor
@capawesome/capacitor-realtimekit
@capawesome/capacitor-root-detection
@capawesome/capacitor-screen-brightness
@capawesome/capacitor-screen-orientation
@capawesome/capacitor-screen-reader
@capawesome/capacitor-screenshot
@capawesome/capacitor-settings-launcher
@capawesome/capacitor-shake
@capawesome/capacitor-silent-mode
@capawesome/capacitor-sim
@capawesome/capacitor-sms-composer
@capawesome/capacitor-square-mobile-payments
@capawesome/capacitor-superwall
@capawesome/capacitor-system-webview
@capawesome/capacitor-text-interaction
@capawesome/capacitor-text-zoom
@capawesome/capacitor-thermal-state
@capawesome/capacitor-toast
@capawesome/capacitor-torch
@capawesome/capacitor-volume
@capawesome/capacitor-wallet
@capawesome/capacitor-youtube-player
commit: |
Contributor
There was a problem hiding this comment.
Pull request overview
Adds optional integration with the Ionic Live Update Provider SDK so @capawesome/capacitor-live-update can act as a Capawesome provider (providerId = "capawesome") for Federated Capacitor and Ionic Portals, while keeping the SDK out of builds unless explicitly enabled.
Changes:
- Adds optional Ionic Provider SDK wiring for Android (Gradle flag), iOS (CocoaPods subspec + SPM trait), and registers the provider on plugin load.
- Implements provider + manager on Android and iOS, including managerKey-scoped persisted “latest bundle” pointers isolated from the standalone plugin state.
- Adds per-call
appIdoverride plumbing and additional getters for fetch results.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/live-update/README.md | Documents opt-in steps + provider config/usage for Portals and Federated Capacitor. |
| packages/live-update/Package.swift | Adds SPM trait, conditional product dependency, and compile define for provider build. |
| packages/live-update/ios/Plugin/Providers/Ionic/LiveUpdateIonicProvider.swift | iOS provider wrapper (trait-gated). |
| packages/live-update/ios/Plugin/Providers/Ionic/LiveUpdateIonicManager.swift | iOS manager implementation + managerKey-scoped persistence (trait-gated). |
| packages/live-update/ios/Plugin/LiveUpdatePlugin.swift | Registers the provider with LiveUpdateProviderRegistry when enabled. |
| packages/live-update/ios/Plugin/LiveUpdate.swift | Adds bundle-directory helper and supports per-call appId override for latest-bundle fetch. |
| packages/live-update/ios/Plugin/Classes/Results/FetchLatestBundleResult.swift | Adds getters for provider/manager usage. |
| packages/live-update/ios/Plugin/Classes/Options/FetchLatestBundleOptions.swift | Adds optional per-call appId. |
| packages/live-update/ios/Plugin.xcodeproj/project.pbxproj | Adds new provider source files to the Xcode project. |
| packages/live-update/CapawesomeCapacitorLiveUpdate.podspec | Introduces Default + IonicProvider subspecs with a Swift compile flag. |
| packages/live-update/android/src/main/java/.../providers/ionic/LiveUpdateIonicProviderRegistration.java | Android registration helper for the optional SDK. |
| packages/live-update/android/src/main/java/.../providers/ionic/LiveUpdateIonicProvider.java | Android provider implementation. |
| packages/live-update/android/src/main/java/.../providers/ionic/LiveUpdateIonicManager.java | Android manager implementation + managerKey-scoped persistence. |
| packages/live-update/android/src/main/java/.../LiveUpdatePlugin.java | Hooks registration into plugin load. |
| packages/live-update/android/src/main/java/.../LiveUpdate.java | Adds bundle-directory helper and supports per-call appId override for latest-bundle fetch. |
| packages/live-update/android/src/main/java/.../classes/results/FetchLatestBundleResult.java | Adds getters for provider/manager usage. |
| packages/live-update/android/src/main/java/.../classes/options/FetchLatestBundleOptions.java | Adds optional per-call appId. |
| packages/live-update/android/build.gradle | Adds optional dependency wiring (implementation vs compileOnly) controlled by a Gradle property. |
| .changeset/tall-doors-clean.md | Declares a minor version bump for the package. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Adds a new sibling example app under `packages/live-update/example-ionic-portals`
that exercises the Ionic Live Update Provider SDK integration end-to-end without
requiring a real Federated Capacitor or Portals host.
The app opts into the integration on both platforms:
- Android: `capawesomeCapacitorLiveUpdateIncludeIonicProvider = true` in
`variables.gradle`.
- iOS: `pod 'CapawesomeCapacitorLiveUpdate/IonicProvider'` in the Podfile.
It ships a small custom Capacitor plugin (`IonicProviderTest`) implemented
natively in Java and Swift that exposes the SDK to JavaScript:
- `isProviderRegistered()` — confirms the provider is in
`LiveUpdateProviderRegistry`.
- `getLatestAppDirectory({ managerKey, appId?, channel? })` — constructs a
manager and returns its restored `latestAppDirectory` without syncing.
- `syncManager({ managerKey, appId?, channel? })` — full sync; returns the
new `latestAppDirectory` and any `FederatedCapacitorSyncResult.metadata`.
The web UI provides input fields for the per-manager config plus three
buttons backed by these methods, with a log pane for results.
Member
Author
|
… to 1.0.0 The 1.0.0 SDK removes the provider registry: Federated Capacitor now resolves providers by Capacitor plugin name, so the plugin class itself implements the LiveUpdateProvider contract. Since ART resolves superinterfaces at class-definition time, the SDK can no longer be an optional (compileOnly) dependency on Android — it is now a regular dependency on both platforms. - Android: LiveUpdatePlugin implements LiveUpdateProvider; manager rewritten in Kotlin (suspend sync() bridged via stdlib suspendCoroutine, no kotlinx-coroutines needed); registry and reflection gate removed; opt-in Gradle property removed. - iOS: conformance via extension; registry registration removed; subspecs, SPM trait, and CAPAWESOME_INCLUDE_IONIC_PROVIDER guards removed; Package.swift back to swift-tools 5.9. - sync() returns nil/null when no update is available, a MetadataSyncResult on success, and propagates errors unwrapped. - Docs and example app updated to the new contract. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Federated Capacitor now falls back to reflection when the resolved plugin does not conform to LiveUpdateProvider, so the SDK no longer needs to be a hard dependency: - Android: SDK back to compileOnly with zero configuration — the plugin skips the interface conformance and exposes createManager with the exact interface signature for FedCap's reflection fallback. Hosts (FedCap/Portals) provide the SDK at runtime. Consumer proguard rules suppress R8 missing-class errors for apps without it. - iOS: opt-in restored via the IonicProvider CocoaPods subspec and SPM package trait with CAPAWESOME_INCLUDE_IONIC_PROVIDER guards. The plugin's own Xcode project now sets the flag so verify compiles the gated provider code. - Docs: confirmed Federated Capacitor config shape (pluginName/config/ autoUpdateMethod); Android install is now zero-config; iOS opt-in instructions restored. - Example app resolves the provider like FedCap does: cast first, then reflection on createManager(Context, Map). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…onic Portals Ionic Portals 0.14.0 attaches a ProviderManager directly to the Portal configuration (liveUpdateSource: .provider(manager:) on iOS, setLiveUpdateProviderManager(...) on Android) — there is no resolution step, so hosts must be able to construct the manager without a loaded Capacitor plugin: - LiveUpdate core is now headless-capable: the Android Context is injected instead of pulled from the plugin, the plugin reference is nullable on both platforms, and WebView/bridge operations (server path reads/writes, reload) and plugin events degrade gracefully without one. - LiveUpdateIonicManager gains a public constructor/initializer taking only (context,) configuration, backed by a headless LiveUpdate with default config; appId and channel come from the configuration map. - Docs: Portals usage section now has concrete snippets for the Portals 0.14.0 API on both platforms. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds integration with the Ionic Live Update Provider SDK (
1.0.0) so that@capawesome/capacitor-live-updatecan serve as a live update provider for Federated Capacitor and Ionic Portals apps.Under the 1.0.0 contract there is no registry and no provider id: Federated Capacitor resolves providers by their Capacitor plugin name (
LiveUpdateon both platforms) and callscreateManager(...)natively on the resolved plugin instance.Optional dependency design
The SDK integration is optional on both platforms, in different ways:
compileOnly; Federated Capacitor and Ionic Portals bring it transitively at runtime. The plugin deliberately does not implement the SDK'sLiveUpdateProviderinterface (ART resolves superinterfaces eagerly at class-definition time, which would crash the plugin whenever the SDK is absent). Instead,LiveUpdatePlugin.createManager(Context, Map)matches the interface's exact signature, and Federated Capacitor invokes it via its reflection fallback when the cast fails (added by the Ionic team based on our feedback on 1.0.0). Method references resolve lazily, so the plugin class loads fine without the SDK, and the provider classes are only loaded when a host actually callscreateManager. Aconsumer-rules.pro(-dontwarn io.ionic.liveupdateprovider.**) keeps R8 full mode happy for apps without the SDK. Unlike the alpha design, there is no opt-in Gradle property and no reflection gate on our side anymore.IonicProviderCocoaPods subspec / SPM package trait links theLiveUpdateProviderpackage and setsCAPAWESOME_INCLUDE_IONIC_PROVIDER, which compiles in the#if-gated conformance extension and manager. The SPM trait requires swift-tools 6.1 (Capacitor CLI 8.3.0+, Xcode 16.3+).What's included
Dependency wiring
compileOnly "io.ionic:liveupdateprovider:$ionicLiveUpdateProviderVersion"(default1.0.0, documented in the README variables section) + consumer ProGuard rules. The module applies the Kotlin plugin.Default(no-op) andIonicProvidersubspecs; the latter addsLiveUpdateProvider, '~> 1.0.0'and the compile flag.IonicProviderpackage trait that conditionally pulls in the SDK and defines the flag.Provider implementation
LiveUpdatePlugin.createManager(context, configuration)(reflection contract, see above) returnsproviders/ionic/LiveUpdateIonicManager.kt, a Kotlin implementation ofProviderManager: the suspendingsync()bridges the plugin's callback-basedfetchLatestBundle/downloadBundlevia stdlibkotlin.coroutines.suspendCoroutine— no kotlinx-coroutines dependency (the underlying calls are async OkHttpenqueue).LiveUpdatePluginconforms toLiveUpdateProvidervia a#if-gated extension (Providers/Ionic/LiveUpdatePlugin+LiveUpdateProvider.swift);LiveUpdateIonicManagerconforms toProviderManagerwithsync() async throws -> (any ProviderSyncResult)?. The plugin's own Xcode project sets the compile flag sonpm run verify:ioscompiles the gated code.ProviderManagerdirectly to the Portal configuration (liveUpdateSource: .provider(manager:)on iOS,setLiveUpdateProviderManager(...)on Android). The manager therefore has a public constructor/initializer taking only (context,)configuration, backed by a headlessLiveUpdatecore (injectedContext, nullable plugin, WebView/bridge operations and plugin events degrade gracefully).appId/channelcome from the configuration map; other settings use defaults.sync()semantics per the 1.0.0 contract: returnsnil/nullwhen no update is available (nothing to report), aMetadataSyncResult(metadata:bundleId,channel,customProperties) when a bundle was applied, and propagates failures unwrapped.createManageris not exposed as a bridged plugin method — Federated Capacitor invokes it natively.Plumbing changes
LiveUpdate.{java,swift}: new publicgetBundleDirectory(bundleId)helper.FetchLatestBundleOptions.{java,swift}: new optional per-callappIdoverride so each manager can target a different Capawesome Cloud app without touching global preferences.FetchLatestBundleResult.{java,swift}: getters added (the classes previously only exposedtoJSObject).State isolation
Provider-mode managers keep their own bundle pointer in a separate
SharedPreferencesfile (Android) /UserDefaultskey (iOS) namespaced bymanagerKey. They never touch the standalone plugin'scurrentBundleId/nextBundleIdstate, so standalone JS sync and provider mode can coexist without interfering.Provider configuration (V1)
createManager(configuration)accepts a minimal map:managerKeyappIdappIdfromcapacitor.config.json.channeldefaultChannel.The Federated Capacitor side selects the provider by plugin name and passes this map (config shape confirmed by the Ionic team):
All other settings come from the plugin-wide capacitor config.
customIdstays a global per-device value; consumers set it via the existingLiveUpdate.setCustomId({...})JS API.Documentation
$ionicLiveUpdateProviderVersionvariable, and a pointer to the full setup guide.docs/ionic-live-update-provider-sdk-integration.mdrewritten for 1.0.0: resolution by plugin name, zero-config Android, iOS opt-in (subspec/trait), confirmed FedCap config, simplified native-host (Portals) wiring. The Ionic Portals usage section has concrete direct-construction snippets for the Portals 0.14.0 API on both platforms.Example app
example-ionic-provideris a plain Capacitor app with a smallIonicProviderTestplugin that exercises the provider contract end-to-end without a FedCap/Portals host. It resolves the provider exactly like Federated Capacitor does:bridge.getPlugin("LiveUpdate"), cast toLiveUpdateProviderfirst, then the reflection fallback oncreateManager(Context, Map)(the path this plugin actually takes on Android). The example app declares the SDK itself since the plugin's dependency is compile-only.Deferred (follow-ups)
deleteBundleIfUnusedhelper) — V1 leaks previous bundles to disk on each sync; matches the reference mock plugin behavior.serverDomain,publicKey,httpTimeout, etc.Test plan
npm run verify(iOS + Android + Web) passes locally inpackages/live-update; iOS verified withCAPAWESOME_INCLUDE_IONIC_PROVIDERactive (gated code compiled).example-ionic-providerAndroid app builds end-to-end (assembleDebug), including the reflection-based test plugin.isProviderAvailable(exercises the reflection lookup),syncManager, state restoration across relaunch,managerKeyisolation.pluginName: 'LiveUpdate'via the reflection fallback, trigger sync, verifylatestAppDirectoryupdates and metadata reaches JS.io.ionicout of the consumer APK and R8 full mode passes with the shipped consumer rules.🤖 Generated with Claude Code