From cdf148037fd13445106f978c7f83279df90f87cc Mon Sep 17 00:00:00 2001 From: Trevor Lambert <78672774+trevor-lambert@users.noreply.github.com> Date: Fri, 20 Mar 2026 00:25:00 -0500 Subject: [PATCH 1/8] feat: enable support for 3rd party live update providers --- Package.swift | 8 ++- Sources/IonicPortals/AssetMap.swift | 1 - Sources/IonicPortals/Portal+LiveUpdates.swift | 50 ++++++++++++--- Sources/IonicPortals/Portal.swift | 63 ++++++++++++------- .../PortalView/PortalUIView.swift | 9 +-- 5 files changed, 88 insertions(+), 43 deletions(-) diff --git a/Package.swift b/Package.swift index 93f70bd..02a5bf2 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.5 +// swift-tools-version:5.6 import PackageDescription @@ -14,7 +14,8 @@ let package = Package( dependencies: [ .package(url: "https://github.com/ionic-team/capacitor-swift-pm", .upToNextMajor(from: "8.0.0")), .package(url: "https://github.com/ionic-team/ionic-live-updates-releases", "0.5.0"..<"0.6.0"), - .package(url: "https://github.com/pointfreeco/swift-clocks", .upToNextMajor(from: "1.0.2")) + .package(url: "https://github.com/pointfreeco/swift-clocks", .upToNextMajor(from: "1.0.2")), + .package(path: "../live-updates-provider-sdk") ], targets: [ .target( @@ -22,7 +23,8 @@ let package = Package( dependencies: [ .product(name: "Capacitor", package: "capacitor-swift-pm"), .product(name: "Cordova", package: "capacitor-swift-pm"), - .product(name: "IonicLiveUpdates", package: "ionic-live-updates-releases") + .product(name: "IonicLiveUpdates", package: "ionic-live-updates-releases"), + .product(name: "LiveUpdateProvider", package: "live-updates-provider-sdk") ] ), .testTarget( diff --git a/Sources/IonicPortals/AssetMap.swift b/Sources/IonicPortals/AssetMap.swift index 4358b92..9948647 100644 --- a/Sources/IonicPortals/AssetMap.swift +++ b/Sources/IonicPortals/AssetMap.swift @@ -6,7 +6,6 @@ // import Foundation -import IonicLiveUpdates public struct AssetMap { /// The name to index the asset map by. diff --git a/Sources/IonicPortals/Portal+LiveUpdates.swift b/Sources/IonicPortals/Portal+LiveUpdates.swift index b6d218e..5a6f423 100644 --- a/Sources/IonicPortals/Portal+LiveUpdates.swift +++ b/Sources/IonicPortals/Portal+LiveUpdates.swift @@ -1,17 +1,22 @@ +import Foundation import IonicLiveUpdates +import LiveUpdateProvider extension Portal { /// Error thrown if a ``liveUpdateConfig`` is not present on a ``Portal`` when ``sync()`` is called. public struct LiveUpdateNotConfigured: Error {} - /// Syncs the ``liveUpdateConfig`` if present - /// - Returns: The result of the synchronization operation - /// - Throws: If the portal has no ``liveUpdateConfig``, a ``LiveUpdateNotConfigured`` error will be thrown. - /// Any errors thrown from ``liveUpdateManager`` will be propogated. - public func sync() async throws -> LiveUpdateManager.SyncResult { - if let liveUpdateConfig { - return try await liveUpdateManager.sync(appId: liveUpdateConfig.appId) - } else { + /// Syncs the live update provider if configured. + /// - Returns: The result of the synchronization operation. + /// - Throws: ``LiveUpdateNotConfigured`` if no live update provider is configured. + /// Any errors thrown from the live update provider will be propagated. + public func sync() async throws -> any SyncResult { + switch liveUpdateProvider { + case .ionic(let manager, let config): + return IonicSyncResult(try await manager.sync(appId: config.appId)) + case .custom(let manager): + return try await manager.sync() + case .none: throw LiveUpdateNotConfigured() } } @@ -30,8 +35,35 @@ extension Portal { public static func sync(_ portals: [Portal]) -> ParallelLiveUpdateSyncGroup { .init(portals) } + + /// The directory of the latest synced web application assets for this portal. + /// Returns `nil` if no live update provider is configured or no sync has occurred. + public var latestAppDirectory: URL? { + switch liveUpdateProvider { + case .ionic(let manager, let config): + return manager.latestAppDirectory(for: config.appId) + case .custom(let manager): + return manager.latestAppDirectory + case .none: + return nil + } + } } +/// The result of a sync operation performed by the Ionic live update provider. +/// Contains the outcome of the sync and the underlying `LiveUpdateManager.SyncResult` for ionic-specific details. +public struct IonicSyncResult: SyncResult { + public let didUpdate: Bool + public let ionicSyncResult: LiveUpdateManager.SyncResult + + public init(_ ionicSyncResult: LiveUpdateManager.SyncResult) { + self.didUpdate = ionicSyncResult.source != .cache(latestAppDirectoryChanged: false) + self.ionicSyncResult = ionicSyncResult + } +} + + + extension Array where Element == Portal { /// Synchronizes the ``Portal/liveUpdateConfig`` for the elements in the array /// - Returns: A ``ParallelLiveUpdateSyncGroup`` of the results of each call to ``Portal/sync()`` @@ -49,7 +81,7 @@ extension Array where Element == Portal { } /// Alias for a parallel sequence of Live Update synchronization results -public typealias ParallelLiveUpdateSyncGroup = ParallelAsyncSequence> +public typealias ParallelLiveUpdateSyncGroup = ParallelAsyncSequence> extension ParallelLiveUpdateSyncGroup { init(_ portals: [Portal]) { diff --git a/Sources/IonicPortals/Portal.swift b/Sources/IonicPortals/Portal.swift index 4125300..3288b47 100644 --- a/Sources/IonicPortals/Portal.swift +++ b/Sources/IonicPortals/Portal.swift @@ -1,9 +1,21 @@ import Foundation import Capacitor import IonicLiveUpdates +import LiveUpdateProvider /// The configuration of a web application to be embedded in an iOS application public struct Portal { + /// The live update provider for a ``Portal``. + public enum LiveUpdateProvider { + /// Uses IonicLiveUpdates to sync and locate the latest web application assets. + case ionic( + /// The `LiveUpdateManager` responsible for locating the latest source for the web application. + liveUpdateManager: LiveUpdateManager = .shared, + /// The `LiveUpdate` configuration used to determine the location of updated application assets. + liveUpdateConfig: LiveUpdate) + /// Uses a custom live update provider to sync and locate the latest web application assets. + case custom(liveUpdateManager: any LiveUpdateManaging) + } /// The name of the portal. /// /// This is always provided to the web application @@ -28,17 +40,15 @@ public struct Portal { /// Any Capacitor plugins to load on the ``Portal`` public var plugins: [Plugin] - /// The `LiveUpdateManager` responsible for locating the latest source for the web application - public var liveUpdateManager: LiveUpdateManager - - /// The `LiveUpdate` configuration used to determine the location of updated application assets. - public var liveUpdateConfig: LiveUpdate? = nil { + /// The live update provider responsible for locating and syncing the latest web application assets + public var liveUpdateProvider: LiveUpdateProvider? { didSet { - guard let liveUpdateConfig = liveUpdateConfig else { return } - try? liveUpdateManager.add( - liveUpdateConfig, - existingCacheUrl: bundle.url(forResource: startDir, withExtension: nil) - ) + if case .ionic(let manager, let config) = liveUpdateProvider { + try? manager.add( + config, + existingCacheUrl: bundle.url(forResource: startDir, withExtension: nil) + ) + } } } @@ -58,8 +68,7 @@ public struct Portal { /// - plugins: Any ``Plugin``s to load. Defautls to `[]`. /// - initialContext: Any initial state required by the web application. Defaults to `[:]`. /// - assetMaps: Any ``AssetMap``s needed to share assets with the ``Portal``. Defaults to `[]`. - /// - liveUpdateManager: The `LiveUpdateManager` responsible for locating the source source for the web application. Defaults to `LiveUpdateManager.shared`. - /// - liveUpdateConfig: The `LiveUpdate` configuration used to determine to location of updated application assets. Defaults to `nil`. + /// - liveUpdateProvider: The live update provider responsible for locating and syncing the latest web application assets. Defaults to `nil`. public init( name: String, startDir: String? = nil, @@ -69,8 +78,7 @@ public struct Portal { initialContext: JSObject = [:], assetMaps: [AssetMap] = [], plugins: [Plugin] = [], - liveUpdateManager: LiveUpdateManager = .shared, - liveUpdateConfig: LiveUpdate? = nil + liveUpdateProvider: LiveUpdateProvider? = nil ) { self.name = name self.startDir = startDir ?? name @@ -78,16 +86,16 @@ public struct Portal { self.index = index self.initialContext = initialContext self.bundle = bundle + self.assetMaps = assetMaps self.plugins = plugins - self.liveUpdateManager = liveUpdateManager - self.liveUpdateConfig = liveUpdateConfig - if let liveUpdateConfig = liveUpdateConfig { - try? liveUpdateManager.add( - liveUpdateConfig, + self.liveUpdateProvider = liveUpdateProvider + + if case .ionic(let manager, let config) = liveUpdateProvider { + try? manager.add( + config, existingCacheUrl: bundle.url(forResource: self.startDir, withExtension: nil) ) } - self.assetMaps = assetMaps } } @@ -233,13 +241,21 @@ extension Portal { self.portal = portal } - /// Configures the `LiveUpdate` configuration + /// Configures the Ionic live update provider for this portal. /// - Parameters: /// - appId: The AppFlow id of the web application associated with the ``IONPortal`` /// - channel: The AppFlow channel to check for updates from. /// - syncImmediately: Whether to immediately sync with AppFlow to check for updates. + /// - Note: This method has no effect if a custom live update provider is already configured. @objc public func setLiveUpdateConfiguration(appId: String, channel: String, syncImmediately: Bool) { - portal.liveUpdateConfig = LiveUpdate(appId: appId, channel: channel, syncOnAdd: syncImmediately) + if case .custom = portal.liveUpdateProvider { return } + + let config = LiveUpdate(appId: appId, channel: channel, syncOnAdd: syncImmediately) + if case .ionic(let manager, _) = portal.liveUpdateProvider { + portal.liveUpdateProvider = .ionic(liveUpdateManager: manager, liveUpdateConfig: config) + } else { + portal.liveUpdateProvider = .ionic(liveUpdateConfig: config) + } } } @@ -254,8 +270,7 @@ extension IONPortal { let portal = Portal( name: name, startDir: startDir, - initialContext: initialContext.flatMap { JSTypes.coerceDictionaryToJSObject($0) } ?? [:], - liveUpdateConfig: nil + initialContext: initialContext.flatMap { JSTypes.coerceDictionaryToJSObject($0) } ?? [:] ) self.init(portal: portal) diff --git a/Sources/IonicPortals/PortalView/PortalUIView.swift b/Sources/IonicPortals/PortalView/PortalUIView.swift index 6d917cf..a00c6bf 100644 --- a/Sources/IonicPortals/PortalView/PortalUIView.swift +++ b/Sources/IonicPortals/PortalView/PortalUIView.swift @@ -85,9 +85,7 @@ public class PortalUIView: UIView { private func initView () { if PortalsRegistrationManager.shared.isRegistered { - if let liveUpdateConfig = portal.liveUpdateConfig { - self.liveUpdatePath = portal.liveUpdateManager.latestAppDirectory(for: liveUpdateConfig.appId) - } + self.liveUpdatePath = portal.latestAppDirectory addPinnedSubview(webView) } else { @@ -310,9 +308,8 @@ extension PortalUIView { } /// Reloads the underlying `WKWebView` @objc public func reload() { - if let liveUpdate = portal.liveUpdateConfig, - let latestAppPath = portal.liveUpdateManager.latestAppDirectory(for: liveUpdate.appId), - liveUpdatePath == nil || liveUpdatePath?.path != latestAppPath.path { + if let latestAppPath = portal.latestAppDirectory, + liveUpdatePath != latestAppPath { liveUpdatePath = latestAppPath return setServerBasePath(path: latestAppPath.path) } From abf1559d865cccd97f2f228c3fc8386447bd965e Mon Sep 17 00:00:00 2001 From: Trevor Lambert <78672774+trevor-lambert@users.noreply.github.com> Date: Sun, 29 Mar 2026 11:29:46 -0500 Subject: [PATCH 2/8] feat: use direct LiveUpdateProvider dependency --- IonicPortals.podspec | 1 + Package.swift | 4 +- .../IonicPortals/IonicPortals.docc/Portal.md | 5 +-- Sources/IonicPortals/Portal+LiveUpdates.swift | 37 +++++++------------ Sources/IonicPortals/Portal.swift | 12 +++--- .../PortalView/PortalUIView.swift | 1 - 6 files changed, 25 insertions(+), 35 deletions(-) diff --git a/IonicPortals.podspec b/IonicPortals.podspec index a807597..3f0e09f 100644 --- a/IonicPortals.podspec +++ b/IonicPortals.podspec @@ -11,5 +11,6 @@ Pod::Spec.new do |s| s.source_files = 'Sources/IonicPortals/**/*.swift' s.dependency 'Capacitor', '~> 8.0.0' s.dependency 'IonicLiveUpdates', '>= 0.5.0', '< 0.6.0' + s.dependency 'LiveUpdateProvider' '~> 0.1.0-alpha.1' s.swift_version = '5.7' end diff --git a/Package.swift b/Package.swift index 02a5bf2..bb33a36 100644 --- a/Package.swift +++ b/Package.swift @@ -15,7 +15,7 @@ let package = Package( .package(url: "https://github.com/ionic-team/capacitor-swift-pm", .upToNextMajor(from: "8.0.0")), .package(url: "https://github.com/ionic-team/ionic-live-updates-releases", "0.5.0"..<"0.6.0"), .package(url: "https://github.com/pointfreeco/swift-clocks", .upToNextMajor(from: "1.0.2")), - .package(path: "../live-updates-provider-sdk") + .package(url: "https://github.com/ionic-team/live-update-provider-sdk", exact: "0.1.0-alpha.1") ], targets: [ .target( @@ -24,7 +24,7 @@ let package = Package( .product(name: "Capacitor", package: "capacitor-swift-pm"), .product(name: "Cordova", package: "capacitor-swift-pm"), .product(name: "IonicLiveUpdates", package: "ionic-live-updates-releases"), - .product(name: "LiveUpdateProvider", package: "live-updates-provider-sdk") + .product(name: "LiveUpdateProvider", package: "live-update-provider-sdk") ] ), .testTarget( diff --git a/Sources/IonicPortals/IonicPortals.docc/Portal.md b/Sources/IonicPortals/IonicPortals.docc/Portal.md index 8e1109f..ca0d28c 100644 --- a/Sources/IonicPortals/IonicPortals.docc/Portal.md +++ b/Sources/IonicPortals/IonicPortals.docc/Portal.md @@ -4,7 +4,7 @@ ### Create a Portal -- ``init(name:startDir:index:devModeEnabled:bundle:initialContext:assetMaps:plugins:liveUpdateManager:liveUpdateConfig:)`` +- ``init(name:startDir:index:devModeEnabled:bundle:initialContext:assetMaps:plugins:liveUpdateProvider:)`` - ``init(stringLiteral:)`` ### Web App Location @@ -26,8 +26,7 @@ ### Live Updates -- ``liveUpdateConfig`` -- ``liveUpdateManager`` +- ``liveUpdateProvider`` ### Initial Application State diff --git a/Sources/IonicPortals/Portal+LiveUpdates.swift b/Sources/IonicPortals/Portal+LiveUpdates.swift index 5a6f423..88678c3 100644 --- a/Sources/IonicPortals/Portal+LiveUpdates.swift +++ b/Sources/IonicPortals/Portal+LiveUpdates.swift @@ -3,25 +3,30 @@ import IonicLiveUpdates import LiveUpdateProvider extension Portal { - /// Error thrown if a ``liveUpdateConfig`` is not present on a ``Portal`` when ``sync()`` is called. + public enum PortalSyncResult { + case appflow(LiveUpdateManager.SyncResult) + case custom(any SyncResult) + } + + /// Error thrown if a ``liveUpdateProvider`` is not present on a ``Portal`` when ``sync()`` is called. public struct LiveUpdateNotConfigured: Error {} /// Syncs the live update provider if configured. /// - Returns: The result of the synchronization operation. /// - Throws: ``LiveUpdateNotConfigured`` if no live update provider is configured. /// Any errors thrown from the live update provider will be propagated. - public func sync() async throws -> any SyncResult { + public func sync() async throws -> PortalSyncResult { switch liveUpdateProvider { - case .ionic(let manager, let config): - return IonicSyncResult(try await manager.sync(appId: config.appId)) + case .appflow(let manager, let config): + return .appflow(try await manager.sync(appId: config.appId)) case .custom(let manager): - return try await manager.sync() + return .custom(try await manager.sync()) case .none: throw LiveUpdateNotConfigured() } } - /// Synchronizes the ``liveUpdateConfig``s of the provided ``Portal``s in parallel + /// Synchronizes the ``liveUpdateProvider`` of the provided ``Portal``s in parallel. /// - Parameter portals: The ``Portal``s to ``sync()`` /// - Returns: A ``ParallelLiveUpdateSyncGroup`` of the results of each call to ``Portal/sync()`` /// @@ -40,7 +45,7 @@ extension Portal { /// Returns `nil` if no live update provider is configured or no sync has occurred. public var latestAppDirectory: URL? { switch liveUpdateProvider { - case .ionic(let manager, let config): + case .appflow(let manager, let config): return manager.latestAppDirectory(for: config.appId) case .custom(let manager): return manager.latestAppDirectory @@ -50,22 +55,8 @@ extension Portal { } } -/// The result of a sync operation performed by the Ionic live update provider. -/// Contains the outcome of the sync and the underlying `LiveUpdateManager.SyncResult` for ionic-specific details. -public struct IonicSyncResult: SyncResult { - public let didUpdate: Bool - public let ionicSyncResult: LiveUpdateManager.SyncResult - - public init(_ ionicSyncResult: LiveUpdateManager.SyncResult) { - self.didUpdate = ionicSyncResult.source != .cache(latestAppDirectoryChanged: false) - self.ionicSyncResult = ionicSyncResult - } -} - - - extension Array where Element == Portal { - /// Synchronizes the ``Portal/liveUpdateConfig`` for the elements in the array + /// Synchronizes the ``Portal/liveUpdateProvider`` for the elements in the array /// - Returns: A ``ParallelLiveUpdateSyncGroup`` of the results of each call to ``Portal/sync()`` /// /// Usage @@ -81,7 +72,7 @@ extension Array where Element == Portal { } /// Alias for a parallel sequence of Live Update synchronization results -public typealias ParallelLiveUpdateSyncGroup = ParallelAsyncSequence> +public typealias ParallelLiveUpdateSyncGroup = ParallelAsyncSequence> extension ParallelLiveUpdateSyncGroup { init(_ portals: [Portal]) { diff --git a/Sources/IonicPortals/Portal.swift b/Sources/IonicPortals/Portal.swift index 3288b47..715e3df 100644 --- a/Sources/IonicPortals/Portal.swift +++ b/Sources/IonicPortals/Portal.swift @@ -8,7 +8,7 @@ public struct Portal { /// The live update provider for a ``Portal``. public enum LiveUpdateProvider { /// Uses IonicLiveUpdates to sync and locate the latest web application assets. - case ionic( + case appflow( /// The `LiveUpdateManager` responsible for locating the latest source for the web application. liveUpdateManager: LiveUpdateManager = .shared, /// The `LiveUpdate` configuration used to determine the location of updated application assets. @@ -43,7 +43,7 @@ public struct Portal { /// The live update provider responsible for locating and syncing the latest web application assets public var liveUpdateProvider: LiveUpdateProvider? { didSet { - if case .ionic(let manager, let config) = liveUpdateProvider { + if case .appflow(let manager, let config) = liveUpdateProvider { try? manager.add( config, existingCacheUrl: bundle.url(forResource: startDir, withExtension: nil) @@ -90,7 +90,7 @@ public struct Portal { self.plugins = plugins self.liveUpdateProvider = liveUpdateProvider - if case .ionic(let manager, let config) = liveUpdateProvider { + if case .appflow(let manager, let config) = liveUpdateProvider { try? manager.add( config, existingCacheUrl: bundle.url(forResource: self.startDir, withExtension: nil) @@ -251,10 +251,10 @@ extension Portal { if case .custom = portal.liveUpdateProvider { return } let config = LiveUpdate(appId: appId, channel: channel, syncOnAdd: syncImmediately) - if case .ionic(let manager, _) = portal.liveUpdateProvider { - portal.liveUpdateProvider = .ionic(liveUpdateManager: manager, liveUpdateConfig: config) + if case .appflow(let manager, _) = portal.liveUpdateProvider { + portal.liveUpdateProvider = .appflow(liveUpdateManager: manager, liveUpdateConfig: config) } else { - portal.liveUpdateProvider = .ionic(liveUpdateConfig: config) + portal.liveUpdateProvider = .appflow(liveUpdateConfig: config) } } } diff --git a/Sources/IonicPortals/PortalView/PortalUIView.swift b/Sources/IonicPortals/PortalView/PortalUIView.swift index a00c6bf..ca758a7 100644 --- a/Sources/IonicPortals/PortalView/PortalUIView.swift +++ b/Sources/IonicPortals/PortalView/PortalUIView.swift @@ -86,7 +86,6 @@ public class PortalUIView: UIView { private func initView () { if PortalsRegistrationManager.shared.isRegistered { self.liveUpdatePath = portal.latestAppDirectory - addPinnedSubview(webView) } else { let showRegistrationError = PortalsRegistrationManager.shared.registrationState == .error From 73cf2c03297b81f1fe1e8924fbeaa43494399e6a Mon Sep 17 00:00:00 2001 From: Trevor Lambert <78672774+trevor-lambert@users.noreply.github.com> Date: Sun, 29 Mar 2026 11:50:05 -0500 Subject: [PATCH 3/8] chore: fix podspec --- IonicPortals.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IonicPortals.podspec b/IonicPortals.podspec index 3f0e09f..d185878 100644 --- a/IonicPortals.podspec +++ b/IonicPortals.podspec @@ -11,6 +11,6 @@ Pod::Spec.new do |s| s.source_files = 'Sources/IonicPortals/**/*.swift' s.dependency 'Capacitor', '~> 8.0.0' s.dependency 'IonicLiveUpdates', '>= 0.5.0', '< 0.6.0' - s.dependency 'LiveUpdateProvider' '~> 0.1.0-alpha.1' + s.dependency 'LiveUpdateProvider', '~> 0.1.0-alpha.1' s.swift_version = '5.7' end From a25e54bb9d788d5f870c33cd71f960d33d544f3d Mon Sep 17 00:00:00 2001 From: Trevor Lambert <78672774+trevor-lambert@users.noreply.github.com> Date: Fri, 8 May 2026 12:42:10 -0500 Subject: [PATCH 4/8] feat: make sync path separate for ionic and external providers --- IonicPortals.podspec | 2 +- Package.swift | 2 +- .../IonicPortals/IonicPortals.docc/Portal.md | 77 +++++++++++++++ Sources/IonicPortals/Portal+LiveUpdates.swift | 94 ++++++++++++++----- Sources/IonicPortals/Portal.swift | 41 ++++---- 5 files changed, 174 insertions(+), 42 deletions(-) diff --git a/IonicPortals.podspec b/IonicPortals.podspec index d185878..b1e165f 100644 --- a/IonicPortals.podspec +++ b/IonicPortals.podspec @@ -11,6 +11,6 @@ Pod::Spec.new do |s| s.source_files = 'Sources/IonicPortals/**/*.swift' s.dependency 'Capacitor', '~> 8.0.0' s.dependency 'IonicLiveUpdates', '>= 0.5.0', '< 0.6.0' - s.dependency 'LiveUpdateProvider', '~> 0.1.0-alpha.1' + s.dependency 'LiveUpdateProvider', '~> 0.1.0-alpha.2' s.swift_version = '5.7' end diff --git a/Package.swift b/Package.swift index bb33a36..c47f6c1 100644 --- a/Package.swift +++ b/Package.swift @@ -15,7 +15,7 @@ let package = Package( .package(url: "https://github.com/ionic-team/capacitor-swift-pm", .upToNextMajor(from: "8.0.0")), .package(url: "https://github.com/ionic-team/ionic-live-updates-releases", "0.5.0"..<"0.6.0"), .package(url: "https://github.com/pointfreeco/swift-clocks", .upToNextMajor(from: "1.0.2")), - .package(url: "https://github.com/ionic-team/live-update-provider-sdk", exact: "0.1.0-alpha.1") + .package(url: "https://github.com/ionic-team/live-update-provider-sdk", exact: "0.1.0-alpha.2") ], targets: [ .target( diff --git a/Sources/IonicPortals/IonicPortals.docc/Portal.md b/Sources/IonicPortals/IonicPortals.docc/Portal.md index ca0d28c..a33c25c 100644 --- a/Sources/IonicPortals/IonicPortals.docc/Portal.md +++ b/Sources/IonicPortals/IonicPortals.docc/Portal.md @@ -1,5 +1,73 @@ # ``IonicPortals/Portal`` +Use a `Portal` to configure the web application, plugins, initial context, assets, and optional live update behavior that Portals should load. + +## Live Updates + +Portals supports Ionic Live Updates and external live update providers. + +Use Ionic Live Updates by passing an Ionic-backed live update provider: + +```swift +import IonicLiveUpdates +import IonicPortals + +let portal = Portal( + name: "checkout", + liveUpdateProvider: .ionic( + liveUpdateConfig: LiveUpdate( + appId: "checkout-app", + channel: "production" + ) + ) +) +``` + +Use an external live update provider by passing a manager that conforms to `LiveUpdateManaging` from the Live Update Provider SDK. A provider manager should keep `latestAppDirectory` accurate when it is created and update it before `sync()` returns when new assets are active. + +```swift +import Foundation +import IonicPortals +import LiveUpdateProvider + +struct ProviderSyncResult: SyncResult {} + +final class ProviderLiveUpdateManager: LiveUpdateManaging { + private(set) var latestAppDirectory: URL? + + func sync() async throws -> any SyncResult { + // Fetch, store, and activate the latest web assets. + // Update latestAppDirectory before returning when new assets are active. + ProviderSyncResult() + } +} + +let manager = ProviderLiveUpdateManager() + +let portal = Portal( + name: "checkout", + liveUpdateProvider: .provider(liveUpdateManager: manager) +) +``` + +Call ``sync()`` to synchronize an Ionic-backed portal. + +```swift +let ionicResult = try await portal.sync() +print(ionicResult.source) +``` + +Call ``syncProvider()`` to synchronize an external live update provider. + +```swift +let providerResult = try await portal.syncProvider() +print(providerResult) +``` + +When a `PortalUIView` reloads, Portals uses ``latestAppDirectory`` to switch the web view to newly activated assets. + +Objective-C apps can continue to configure Ionic Live Updates with `setLiveUpdateConfiguration(appId:channel:syncImmediately:)`. That method does not replace an existing external provider. + ## Topics ### Create a Portal @@ -27,6 +95,15 @@ ### Live Updates - ``liveUpdateProvider`` +- ``LiveUpdateProvider`` +- ``sync()`` +- ``syncProvider()`` +- ``sync(_:)`` +- ``syncProvider(_:)`` +- ``latestAppDirectory`` +- ``LiveUpdateNotConfigured`` +- ``ParallelLiveUpdateSyncGroup`` +- ``ParallelLiveUpdateProviderSyncGroup`` ### Initial Application State diff --git a/Sources/IonicPortals/Portal+LiveUpdates.swift b/Sources/IonicPortals/Portal+LiveUpdates.swift index 88678c3..779a78f 100644 --- a/Sources/IonicPortals/Portal+LiveUpdates.swift +++ b/Sources/IonicPortals/Portal+LiveUpdates.swift @@ -3,31 +3,45 @@ import IonicLiveUpdates import LiveUpdateProvider extension Portal { - public enum PortalSyncResult { - case appflow(LiveUpdateManager.SyncResult) - case custom(any SyncResult) - } - - /// Error thrown if a ``liveUpdateProvider`` is not present on a ``Portal`` when ``sync()`` is called. + /// Error thrown when a portal is not configured with the live update provider type required by the sync method. public struct LiveUpdateNotConfigured: Error {} - /// Syncs the live update provider if configured. - /// - Returns: The result of the synchronization operation. - /// - Throws: ``LiveUpdateNotConfigured`` if no live update provider is configured. + /// Syncs a portal configured with ``Portal/LiveUpdateProvider/ionic(liveUpdateManager:liveUpdateConfig:)``. + /// + /// Use this method for Ionic Live Updates. To sync a portal configured with + /// ``Portal/LiveUpdateProvider/provider(liveUpdateManager:)``, call ``syncProvider()``. + /// - Returns: The Ionic Live Updates synchronization result. + /// - Throws: ``LiveUpdateNotConfigured`` if the portal is not configured with the Ionic Live Updates provider. + /// Any errors thrown from Ionic Live Updates will be propagated. + public func sync() async throws -> LiveUpdateManager.SyncResult { + guard case .ionic(let manager, let config) = liveUpdateProvider else { + throw LiveUpdateNotConfigured() + } + + return try await manager.sync(appId: config.appId) + } + + /// Syncs a portal configured with ``Portal/LiveUpdateProvider/provider(liveUpdateManager:)``. + /// + /// Use this method for external live update providers. To sync a portal configured with + /// ``Portal/LiveUpdateProvider/ionic(liveUpdateManager:liveUpdateConfig:)``, call ``sync()``. + /// - Returns: The external provider's synchronization result. + /// - Throws: ``LiveUpdateNotConfigured`` if the portal is not configured with an external live update provider. /// Any errors thrown from the live update provider will be propagated. - public func sync() async throws -> PortalSyncResult { - switch liveUpdateProvider { - case .appflow(let manager, let config): - return .appflow(try await manager.sync(appId: config.appId)) - case .custom(let manager): - return .custom(try await manager.sync()) - case .none: + public func syncProvider() async throws -> any SyncResult { + guard case .provider(let manager) = liveUpdateProvider else { throw LiveUpdateNotConfigured() } + + return try await manager.sync() } - /// Synchronizes the ``liveUpdateProvider`` of the provided ``Portal``s in parallel. - /// - Parameter portals: The ``Portal``s to ``sync()`` + /// Synchronizes portals configured with Ionic Live Updates in parallel. + /// + /// Each portal must be configured with + /// ``Portal/LiveUpdateProvider/ionic(liveUpdateManager:liveUpdateConfig:)``. + /// Use ``syncProvider(_:)`` for portals configured with external live update providers. + /// - Parameter portals: The ``Portal``s to synchronize with ``Portal/sync()`` /// - Returns: A ``ParallelLiveUpdateSyncGroup`` of the results of each call to ``Portal/sync()`` /// /// Usage @@ -40,14 +54,24 @@ extension Portal { public static func sync(_ portals: [Portal]) -> ParallelLiveUpdateSyncGroup { .init(portals) } + + /// Synchronizes portals configured with external live update providers in parallel. + /// + /// Each portal must be configured with ``Portal/LiveUpdateProvider/provider(liveUpdateManager:)``. + /// Use ``sync(_:)`` for portals configured with Ionic Live Updates. + /// - Parameter portals: The ``Portal``s to synchronize with ``Portal/syncProvider()`` + /// - Returns: A ``ParallelLiveUpdateProviderSyncGroup`` of the results of each call to ``Portal/syncProvider()`` + public static func syncProvider(_ portals: [Portal]) -> ParallelLiveUpdateProviderSyncGroup { + .init(portals) + } /// The directory of the latest synced web application assets for this portal. /// Returns `nil` if no live update provider is configured or no sync has occurred. public var latestAppDirectory: URL? { switch liveUpdateProvider { - case .appflow(let manager, let config): + case .ionic(let manager, let config): return manager.latestAppDirectory(for: config.appId) - case .custom(let manager): + case .provider(let manager): return manager.latestAppDirectory case .none: return nil @@ -56,7 +80,11 @@ extension Portal { } extension Array where Element == Portal { - /// Synchronizes the ``Portal/liveUpdateProvider`` for the elements in the array + /// Synchronizes portals configured with Ionic Live Updates in parallel. + /// + /// Each portal must be configured with + /// ``Portal/LiveUpdateProvider/ionic(liveUpdateManager:liveUpdateConfig:)``. + /// Use ``syncProvider()`` for portals configured with external live update providers. /// - Returns: A ``ParallelLiveUpdateSyncGroup`` of the results of each call to ``Portal/sync()`` /// /// Usage @@ -69,10 +97,22 @@ extension Array where Element == Portal { public func sync() -> ParallelLiveUpdateSyncGroup { .init(self) } + + /// Synchronizes portals configured with external live update providers in parallel. + /// + /// Each portal must be configured with ``Portal/LiveUpdateProvider/provider(liveUpdateManager:)``. + /// Use ``sync()`` for portals configured with Ionic Live Updates. + /// - Returns: A ``ParallelLiveUpdateProviderSyncGroup`` of the results of each call to ``Portal/syncProvider()`` + public func syncProvider() -> ParallelLiveUpdateProviderSyncGroup { + .init(self) + } } -/// Alias for a parallel sequence of Live Update synchronization results -public typealias ParallelLiveUpdateSyncGroup = ParallelAsyncSequence> +/// Alias for a parallel sequence of Ionic Live Updates synchronization results +public typealias ParallelLiveUpdateSyncGroup = ParallelAsyncSequence> + +/// Alias for a parallel sequence of external Live Update provider synchronization results +public typealias ParallelLiveUpdateProviderSyncGroup = ParallelAsyncSequence> extension ParallelLiveUpdateSyncGroup { init(_ portals: [Portal]) { @@ -82,6 +122,14 @@ extension ParallelLiveUpdateSyncGroup { } } +extension ParallelLiveUpdateProviderSyncGroup { + init(_ portals: [Portal]) { + work = portals.map { portal in + { await Result(catching: portal.syncProvider) } + } + } +} + /// A sequence that executes its tasks in parallel and yields their results as they complete public struct ParallelAsyncSequence: AsyncSequence { public typealias Element = Iterator.Element diff --git a/Sources/IonicPortals/Portal.swift b/Sources/IonicPortals/Portal.swift index 715e3df..668dec8 100644 --- a/Sources/IonicPortals/Portal.swift +++ b/Sources/IonicPortals/Portal.swift @@ -5,16 +5,20 @@ import LiveUpdateProvider /// The configuration of a web application to be embedded in an iOS application public struct Portal { - /// The live update provider for a ``Portal``. + /// The live update behavior for a ``Portal``. public enum LiveUpdateProvider { - /// Uses IonicLiveUpdates to sync and locate the latest web application assets. - case appflow( + /// Uses Ionic Live Updates to sync and locate the latest web application assets. + /// + /// Portals configured with this case are synchronized with ``Portal/sync()``. + case ionic( /// The `LiveUpdateManager` responsible for locating the latest source for the web application. liveUpdateManager: LiveUpdateManager = .shared, /// The `LiveUpdate` configuration used to determine the location of updated application assets. liveUpdateConfig: LiveUpdate) - /// Uses a custom live update provider to sync and locate the latest web application assets. - case custom(liveUpdateManager: any LiveUpdateManaging) + /// Uses an external live update provider to sync and locate the latest web application assets. + /// + /// Portals configured with this case are synchronized with ``Portal/syncProvider()``. + case provider(liveUpdateManager: any LiveUpdateManaging) } /// The name of the portal. /// @@ -40,10 +44,13 @@ public struct Portal { /// Any Capacitor plugins to load on the ``Portal`` public var plugins: [Plugin] - /// The live update provider responsible for locating and syncing the latest web application assets + /// The live update behavior responsible for locating and syncing the latest web application assets. + /// + /// Use ``LiveUpdateProvider/ionic(liveUpdateManager:liveUpdateConfig:)`` for Ionic Live Updates and + /// ``LiveUpdateProvider/provider(liveUpdateManager:)`` for an external live update provider. public var liveUpdateProvider: LiveUpdateProvider? { didSet { - if case .appflow(let manager, let config) = liveUpdateProvider { + if case .ionic(let manager, let config) = liveUpdateProvider { try? manager.add( config, existingCacheUrl: bundle.url(forResource: startDir, withExtension: nil) @@ -90,7 +97,7 @@ public struct Portal { self.plugins = plugins self.liveUpdateProvider = liveUpdateProvider - if case .appflow(let manager, let config) = liveUpdateProvider { + if case .ionic(let manager, let config) = liveUpdateProvider { try? manager.add( config, existingCacheUrl: bundle.url(forResource: self.startDir, withExtension: nil) @@ -241,20 +248,20 @@ extension Portal { self.portal = portal } - /// Configures the Ionic live update provider for this portal. + /// Configures the Ionic Live Updates provider for this portal. /// - Parameters: - /// - appId: The AppFlow id of the web application associated with the ``IONPortal`` - /// - channel: The AppFlow channel to check for updates from. - /// - syncImmediately: Whether to immediately sync with AppFlow to check for updates. - /// - Note: This method has no effect if a custom live update provider is already configured. + /// - appId: The Ionic app id of the web application associated with the ``IONPortal`` + /// - channel: The Ionic Live Updates channel to check for updates from. + /// - syncImmediately: Whether to immediately sync with Ionic Live Updates to check for updates. + /// - Note: This method has no effect if an external live update provider is already configured. @objc public func setLiveUpdateConfiguration(appId: String, channel: String, syncImmediately: Bool) { - if case .custom = portal.liveUpdateProvider { return } + if case .provider = portal.liveUpdateProvider { return } let config = LiveUpdate(appId: appId, channel: channel, syncOnAdd: syncImmediately) - if case .appflow(let manager, _) = portal.liveUpdateProvider { - portal.liveUpdateProvider = .appflow(liveUpdateManager: manager, liveUpdateConfig: config) + if case .ionic(let manager, _) = portal.liveUpdateProvider { + portal.liveUpdateProvider = .ionic(liveUpdateManager: manager, liveUpdateConfig: config) } else { - portal.liveUpdateProvider = .appflow(liveUpdateConfig: config) + portal.liveUpdateProvider = .ionic(liveUpdateConfig: config) } } } From e96232d666758aec71c9a1b636d7a4cddce8e985 Mon Sep 17 00:00:00 2001 From: Trevor Lambert <78672774+trevor-lambert@users.noreply.github.com> Date: Fri, 8 May 2026 12:49:26 -0500 Subject: [PATCH 5/8] chore: fix comments --- Sources/IonicPortals/Portal.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/IonicPortals/Portal.swift b/Sources/IonicPortals/Portal.swift index 668dec8..01fe9b9 100644 --- a/Sources/IonicPortals/Portal.swift +++ b/Sources/IonicPortals/Portal.swift @@ -250,9 +250,9 @@ extension Portal { /// Configures the Ionic Live Updates provider for this portal. /// - Parameters: - /// - appId: The Ionic app id of the web application associated with the ``IONPortal`` - /// - channel: The Ionic Live Updates channel to check for updates from. - /// - syncImmediately: Whether to immediately sync with Ionic Live Updates to check for updates. + /// - appId: The Appflow id of the web application associated with the ``IONPortal`` + /// - channel: The Appflow channel to check for updates from. + /// - syncImmediately: Whether to immediately sync with Appflow to check for updates. /// - Note: This method has no effect if an external live update provider is already configured. @objc public func setLiveUpdateConfiguration(appId: String, channel: String, syncImmediately: Bool) { if case .provider = portal.liveUpdateProvider { return } From 9b340776e21c8547f5df7b240e65eb66a0e9cfe4 Mon Sep 17 00:00:00 2001 From: Trevor Lambert <78672774+trevor-lambert@users.noreply.github.com> Date: Fri, 8 May 2026 13:07:38 -0500 Subject: [PATCH 6/8] chore: more doc updates --- Sources/IonicPortals/Portal.swift | 15 ++++++--------- .../IonicPortals/PortalView/PortalUIView.swift | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/Sources/IonicPortals/Portal.swift b/Sources/IonicPortals/Portal.swift index 01fe9b9..f4a1964 100644 --- a/Sources/IonicPortals/Portal.swift +++ b/Sources/IonicPortals/Portal.swift @@ -5,7 +5,7 @@ import LiveUpdateProvider /// The configuration of a web application to be embedded in an iOS application public struct Portal { - /// The live update behavior for a ``Portal``. + /// The live update provider for a ``Portal``. public enum LiveUpdateProvider { /// Uses Ionic Live Updates to sync and locate the latest web application assets. /// @@ -44,10 +44,7 @@ public struct Portal { /// Any Capacitor plugins to load on the ``Portal`` public var plugins: [Plugin] - /// The live update behavior responsible for locating and syncing the latest web application assets. - /// - /// Use ``LiveUpdateProvider/ionic(liveUpdateManager:liveUpdateConfig:)`` for Ionic Live Updates and - /// ``LiveUpdateProvider/provider(liveUpdateManager:)`` for an external live update provider. + /// The `LiveUpdateProvider` responsible for locating the latest source for the web application public var liveUpdateProvider: LiveUpdateProvider? { didSet { if case .ionic(let manager, let config) = liveUpdateProvider { @@ -72,9 +69,9 @@ public struct Portal { /// - index: The initial file to load in the Portal. Defaults to `index.html`. /// - devModeEnabled: Enables web developers to override the Portal content in debug builds. Defaults to `true`. /// - bundle: The `Bundle` that contains the web application. Defaults to `Bundle.main`. - /// - plugins: Any ``Plugin``s to load. Defautls to `[]`. /// - initialContext: Any initial state required by the web application. Defaults to `[:]`. /// - assetMaps: Any ``AssetMap``s needed to share assets with the ``Portal``. Defaults to `[]`. + /// - plugins: Any ``Plugin``s to load. Defautls to `[]`. /// - liveUpdateProvider: The live update provider responsible for locating and syncing the latest web application assets. Defaults to `nil`. public init( name: String, @@ -89,10 +86,10 @@ public struct Portal { ) { self.name = name self.startDir = startDir ?? name - self.devModeEnabled = devModeEnabled self.index = index - self.initialContext = initialContext + self.devModeEnabled = devModeEnabled self.bundle = bundle + self.initialContext = initialContext self.assetMaps = assetMaps self.plugins = plugins self.liveUpdateProvider = liveUpdateProvider @@ -248,7 +245,7 @@ extension Portal { self.portal = portal } - /// Configures the Ionic Live Updates provider for this portal. + /// Configures the ``Portal/LiveUpdateProvider/ionic(liveUpdateManager:liveUpdateConfig:)`` for this portal. /// - Parameters: /// - appId: The Appflow id of the web application associated with the ``IONPortal`` /// - channel: The Appflow channel to check for updates from. diff --git a/Sources/IonicPortals/PortalView/PortalUIView.swift b/Sources/IonicPortals/PortalView/PortalUIView.swift index ca758a7..e0fd3b7 100644 --- a/Sources/IonicPortals/PortalView/PortalUIView.swift +++ b/Sources/IonicPortals/PortalView/PortalUIView.swift @@ -308,7 +308,7 @@ extension PortalUIView { /// Reloads the underlying `WKWebView` @objc public func reload() { if let latestAppPath = portal.latestAppDirectory, - liveUpdatePath != latestAppPath { + liveUpdatePath == nil || liveUpdatePath?.path != latestAppPath.path { liveUpdatePath = latestAppPath return setServerBasePath(path: latestAppPath.path) } From 9287e321c24c8ca0caf016f2acf5be64fec9f6d3 Mon Sep 17 00:00:00 2001 From: Trevor Lambert <78672774+trevor-lambert@users.noreply.github.com> Date: Fri, 8 May 2026 13:28:06 -0500 Subject: [PATCH 7/8] chore: move LiveUpdateProvider enum out of Portal directly --- .../IonicPortals.docc/LiveUpdates.md | 54 +++++++++++++ .../IonicPortals/IonicPortals.docc/Portal.md | 77 ------------------- Sources/IonicPortals/Portal+LiveUpdates.swift | 17 ++++ Sources/IonicPortals/Portal.swift | 21 +---- 4 files changed, 74 insertions(+), 95 deletions(-) create mode 100644 Sources/IonicPortals/IonicPortals.docc/LiveUpdates.md diff --git a/Sources/IonicPortals/IonicPortals.docc/LiveUpdates.md b/Sources/IonicPortals/IonicPortals.docc/LiveUpdates.md new file mode 100644 index 0000000..9a41053 --- /dev/null +++ b/Sources/IonicPortals/IonicPortals.docc/LiveUpdates.md @@ -0,0 +1,54 @@ +# Live Updates + +Configure a portal with Ionic Live Updates or an external live update provider. + +## Ionic Live Updates + +Use ``Portal/LiveUpdateProvider/ionic(liveUpdateManager:liveUpdateConfig:)`` with a `LiveUpdate` configuration: + +```swift +import IonicLiveUpdates +import IonicPortals + +let portal = Portal( + name: "checkout", + liveUpdateProvider: .ionic( + liveUpdateConfig: LiveUpdate( + appId: "checkout-app", + channel: "production" + ) + ) +) +``` + +Call ``Portal/sync()`` to synchronize a portal configured with Ionic Live Updates: + +```swift +let result = try await portal.sync() +print(result.source) +``` + +## External Providers + +Use ``Portal/LiveUpdateProvider/provider(liveUpdateManager:)`` with a manager that conforms to `LiveUpdateManaging` from the Live Update Provider SDK: + +```swift +import IonicPortals +import LiveUpdateProvider + +let portal = Portal( + name: "checkout", + liveUpdateProvider: .provider(liveUpdateManager: manager) +) +``` + +Call ``Portal/syncProvider()`` to synchronize a portal configured with an external live update provider: + +```swift +let result = try await portal.syncProvider() +print(result) +``` + +When a `PortalUIView` reloads, Portals uses ``Portal/latestAppDirectory`` to switch the web view to newly activated assets. + +Objective-C apps can continue to configure Ionic Live Updates with `setLiveUpdateConfiguration(appId:channel:syncImmediately:)`. That method does not replace an existing external provider. diff --git a/Sources/IonicPortals/IonicPortals.docc/Portal.md b/Sources/IonicPortals/IonicPortals.docc/Portal.md index a33c25c..ca0d28c 100644 --- a/Sources/IonicPortals/IonicPortals.docc/Portal.md +++ b/Sources/IonicPortals/IonicPortals.docc/Portal.md @@ -1,73 +1,5 @@ # ``IonicPortals/Portal`` -Use a `Portal` to configure the web application, plugins, initial context, assets, and optional live update behavior that Portals should load. - -## Live Updates - -Portals supports Ionic Live Updates and external live update providers. - -Use Ionic Live Updates by passing an Ionic-backed live update provider: - -```swift -import IonicLiveUpdates -import IonicPortals - -let portal = Portal( - name: "checkout", - liveUpdateProvider: .ionic( - liveUpdateConfig: LiveUpdate( - appId: "checkout-app", - channel: "production" - ) - ) -) -``` - -Use an external live update provider by passing a manager that conforms to `LiveUpdateManaging` from the Live Update Provider SDK. A provider manager should keep `latestAppDirectory` accurate when it is created and update it before `sync()` returns when new assets are active. - -```swift -import Foundation -import IonicPortals -import LiveUpdateProvider - -struct ProviderSyncResult: SyncResult {} - -final class ProviderLiveUpdateManager: LiveUpdateManaging { - private(set) var latestAppDirectory: URL? - - func sync() async throws -> any SyncResult { - // Fetch, store, and activate the latest web assets. - // Update latestAppDirectory before returning when new assets are active. - ProviderSyncResult() - } -} - -let manager = ProviderLiveUpdateManager() - -let portal = Portal( - name: "checkout", - liveUpdateProvider: .provider(liveUpdateManager: manager) -) -``` - -Call ``sync()`` to synchronize an Ionic-backed portal. - -```swift -let ionicResult = try await portal.sync() -print(ionicResult.source) -``` - -Call ``syncProvider()`` to synchronize an external live update provider. - -```swift -let providerResult = try await portal.syncProvider() -print(providerResult) -``` - -When a `PortalUIView` reloads, Portals uses ``latestAppDirectory`` to switch the web view to newly activated assets. - -Objective-C apps can continue to configure Ionic Live Updates with `setLiveUpdateConfiguration(appId:channel:syncImmediately:)`. That method does not replace an existing external provider. - ## Topics ### Create a Portal @@ -95,15 +27,6 @@ Objective-C apps can continue to configure Ionic Live Updates with `setLiveUpdat ### Live Updates - ``liveUpdateProvider`` -- ``LiveUpdateProvider`` -- ``sync()`` -- ``syncProvider()`` -- ``sync(_:)`` -- ``syncProvider(_:)`` -- ``latestAppDirectory`` -- ``LiveUpdateNotConfigured`` -- ``ParallelLiveUpdateSyncGroup`` -- ``ParallelLiveUpdateProviderSyncGroup`` ### Initial Application State diff --git a/Sources/IonicPortals/Portal+LiveUpdates.swift b/Sources/IonicPortals/Portal+LiveUpdates.swift index 779a78f..50a8672 100644 --- a/Sources/IonicPortals/Portal+LiveUpdates.swift +++ b/Sources/IonicPortals/Portal+LiveUpdates.swift @@ -3,6 +3,23 @@ import IonicLiveUpdates import LiveUpdateProvider extension Portal { + /// The live update provider for a ``Portal``. + public enum LiveUpdateProvider { + /// Uses Ionic Live Updates to sync and locate the latest web application assets. + /// + /// Portals configured with this case are synchronized with ``Portal/sync()``. + case ionic( + /// The `LiveUpdateManager` responsible for locating the latest source for the web application. + liveUpdateManager: LiveUpdateManager = .shared, + /// The `LiveUpdate` configuration used to determine the location of updated application assets. + liveUpdateConfig: LiveUpdate) + + /// Uses an external live update provider to sync and locate the latest web application assets. + /// + /// Portals configured with this case are synchronized with ``Portal/syncProvider()``. + case provider(liveUpdateManager: any LiveUpdateManaging) + } + /// Error thrown when a portal is not configured with the live update provider type required by the sync method. public struct LiveUpdateNotConfigured: Error {} diff --git a/Sources/IonicPortals/Portal.swift b/Sources/IonicPortals/Portal.swift index f4a1964..58574a3 100644 --- a/Sources/IonicPortals/Portal.swift +++ b/Sources/IonicPortals/Portal.swift @@ -5,21 +5,6 @@ import LiveUpdateProvider /// The configuration of a web application to be embedded in an iOS application public struct Portal { - /// The live update provider for a ``Portal``. - public enum LiveUpdateProvider { - /// Uses Ionic Live Updates to sync and locate the latest web application assets. - /// - /// Portals configured with this case are synchronized with ``Portal/sync()``. - case ionic( - /// The `LiveUpdateManager` responsible for locating the latest source for the web application. - liveUpdateManager: LiveUpdateManager = .shared, - /// The `LiveUpdate` configuration used to determine the location of updated application assets. - liveUpdateConfig: LiveUpdate) - /// Uses an external live update provider to sync and locate the latest web application assets. - /// - /// Portals configured with this case are synchronized with ``Portal/syncProvider()``. - case provider(liveUpdateManager: any LiveUpdateManaging) - } /// The name of the portal. /// /// This is always provided to the web application @@ -44,7 +29,7 @@ public struct Portal { /// Any Capacitor plugins to load on the ``Portal`` public var plugins: [Plugin] - /// The `LiveUpdateProvider` responsible for locating the latest source for the web application + /// The ``Portal/LiveUpdateProvider`` responsible for locating the latest source for the web application. public var liveUpdateProvider: LiveUpdateProvider? { didSet { if case .ionic(let manager, let config) = liveUpdateProvider { @@ -72,7 +57,7 @@ public struct Portal { /// - initialContext: Any initial state required by the web application. Defaults to `[:]`. /// - assetMaps: Any ``AssetMap``s needed to share assets with the ``Portal``. Defaults to `[]`. /// - plugins: Any ``Plugin``s to load. Defautls to `[]`. - /// - liveUpdateProvider: The live update provider responsible for locating and syncing the latest web application assets. Defaults to `nil`. + /// - liveUpdateProvider: The ``Portal/LiveUpdateProvider`` responsible for locating and syncing the latest web application assets. Defaults to `nil`. public init( name: String, startDir: String? = nil, @@ -245,7 +230,7 @@ extension Portal { self.portal = portal } - /// Configures the ``Portal/LiveUpdateProvider/ionic(liveUpdateManager:liveUpdateConfig:)`` for this portal. + /// Sets the ``Portal/LiveUpdateProvider/ionic(liveUpdateManager:liveUpdateConfig:)`` configuration for this portal. /// - Parameters: /// - appId: The Appflow id of the web application associated with the ``IONPortal`` /// - channel: The Appflow channel to check for updates from. From 9576b1eed0ab27355fc796428c3fc41bc4d395ee Mon Sep 17 00:00:00 2001 From: Trevor Lambert <78672774+trevor-lambert@users.noreply.github.com> Date: Fri, 8 May 2026 13:39:48 -0500 Subject: [PATCH 8/8] chore: remove unused imports --- Sources/IonicPortals/Portal.swift | 1 - Sources/IonicPortals/PortalView/PortalUIView.swift | 1 - 2 files changed, 2 deletions(-) diff --git a/Sources/IonicPortals/Portal.swift b/Sources/IonicPortals/Portal.swift index 58574a3..75b7213 100644 --- a/Sources/IonicPortals/Portal.swift +++ b/Sources/IonicPortals/Portal.swift @@ -1,7 +1,6 @@ import Foundation import Capacitor import IonicLiveUpdates -import LiveUpdateProvider /// The configuration of a web application to be embedded in an iOS application public struct Portal { diff --git a/Sources/IonicPortals/PortalView/PortalUIView.swift b/Sources/IonicPortals/PortalView/PortalUIView.swift index e0fd3b7..1a0ac4f 100644 --- a/Sources/IonicPortals/PortalView/PortalUIView.swift +++ b/Sources/IonicPortals/PortalView/PortalUIView.swift @@ -2,7 +2,6 @@ import Foundation import WebKit import UIKit import Capacitor -import IonicLiveUpdates import SwiftUI /// A UIKit UIView to display ``Portal`` content