From 4c255aa0b6116bd4f572b765279e5ab56f046be1 Mon Sep 17 00:00:00 2001 From: Steven Sherry Date: Mon, 3 Jul 2023 10:49:35 -0500 Subject: [PATCH 1/6] feat: Adding initial development configuration implementation --- Sources/IonicPortals/Portal.swift | 54 +++++++++++++++++++++++++ Sources/IonicPortals/PortalUIView.swift | 38 +++++++++++++---- Sources/IonicPortals/PortalView.swift | 2 +- 3 files changed, 85 insertions(+), 9 deletions(-) diff --git a/Sources/IonicPortals/Portal.swift b/Sources/IonicPortals/Portal.swift index 0d708b9..fcf40fa 100644 --- a/Sources/IonicPortals/Portal.swift +++ b/Sources/IonicPortals/Portal.swift @@ -2,6 +2,56 @@ import Foundation import Capacitor import IonicLiveUpdates +public enum ConfigUrl { + case url(URL) + case environment(String) + + public var url: URL? { + switch self { + case let .url(url): + return url + case let .environment(envVar): + return ProcessInfo.processInfo.environment[envVar].flatMap(URL.init) + } + } +} + +public struct DevelopmentConfiguration { + public var server: ConfigUrl + public var capacitorConfig: ConfigUrl + + public init(server: ConfigUrl, capacitorConfig: ConfigUrl) { + self.server = server + self.capacitorConfig = capacitorConfig + } +} + +extension DevelopmentConfiguration { + public init(baseEnvironmentVariable: String) { + server = .environment("\(baseEnvironmentVariable)_SERVER") + capacitorConfig = .environment("\(baseEnvironmentVariable)_CONFIG") + } +} + +extension DevelopmentConfiguration: ExpressibleByStringLiteral { + public init(stringLiteral value: StringLiteralType) { + self.init(baseEnvironmentVariable: value) + } +} + +public enum DevUrl { + case url(URL) + case environment(String) + + var url: URL? { + switch self { + case let .url(url): + return url + case let .environment(envVar): + return ProcessInfo.processInfo.environment[envVar].flatMap(URL.init) + } + } +} /// The configuration of a web application to be embedded in an iOS application public struct Portal { @@ -11,6 +61,8 @@ public struct Portal { /// The root directory of the ``Portal`` web application relative to the root of ``bundle`` public let startDir: String + + public var devConfig: DevelopmentConfiguration? /// The initial file to load in the Portal. public let index: String @@ -54,6 +106,7 @@ public struct Portal { public init( name: String, startDir: String? = nil, + devConfig: DevelopmentConfiguration? = nil, index: String = "index.html", bundle: Bundle = .main, initialContext: JSObject = [:], @@ -64,6 +117,7 @@ public struct Portal { ) { self.name = name self.startDir = startDir ?? name + self.devConfig = devConfig self.index = index self.initialContext = initialContext self.bundle = bundle diff --git a/Sources/IonicPortals/PortalUIView.swift b/Sources/IonicPortals/PortalUIView.swift index 16afb4c..d65c482 100644 --- a/Sources/IonicPortals/PortalUIView.swift +++ b/Sources/IonicPortals/PortalUIView.swift @@ -22,7 +22,7 @@ public class PortalUIView: UIView { super.init(frame: .zero) initView() } - + /// Creates an instance of ``PortalUIView`` /// - Parameter portal: The ``IONPortal`` to render. @objc public convenience init(portal: IONPortal) { @@ -32,7 +32,7 @@ public class PortalUIView: UIView { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + func initView () { if PortalsRegistrationManager.shared.isRegistered { if let liveUpdateConfig = portal.liveUpdateConfig { @@ -70,7 +70,7 @@ public class PortalUIView: UIView { final class InternalCapWebView: CAPWebView { private var portal: Portal private var liveUpdatePath: URL? - + override var router: Router { PortalRouter(portal: portal) } init(portal: Portal, liveUpdatePath: URL?) { @@ -78,7 +78,7 @@ public class PortalUIView: UIView { self.liveUpdatePath = liveUpdatePath super.init(autoRegisterPlugins: false) } - + override func capacitorDidLoad() { bridge.registerPluginInstance(PortalsPlugin()) @@ -95,15 +95,21 @@ public class PortalUIView: UIView { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - - override func instanceDescriptor() -> InstanceDescriptor { + + private func createInstanceDescriptor() -> InstanceDescriptor { let bundleURL = portal.bundle.url(forResource: portal.startDir, withExtension: nil) - + + #if DEBUG + if let debugConfigUrl = portal.devConfig?.capacitorConfig.url { + return InstanceDescriptor(at: Bundle.main.bundleURL, configuration: debugConfigUrl, cordovaConfiguration: nil) + } + #endif + guard let path = liveUpdatePath ?? bundleURL else { // DCG this should throw or something else return InstanceDescriptor() } - + var capConfigUrl = portal.bundle.url(forResource: "capacitor.config", withExtension: "json", subdirectory: portal.startDir) var cordovaConfigUrl = portal.bundle.url(forResource: "config", withExtension: "xml", subdirectory: portal.startDir) @@ -117,11 +123,27 @@ public class PortalUIView: UIView { cordovaConfigUrl = updatedCordovaConfig } + let descriptor = InstanceDescriptor(at: path, configuration: capConfigUrl, cordovaConfiguration: cordovaConfigUrl) descriptor.handleApplicationNotifications = false return descriptor } + override func instanceDescriptor() -> InstanceDescriptor { + let descriptor = createInstanceDescriptor() + portal.descriptorConfiguration.forEach { $0(descriptor) } + #if DEBUG + if portal.devConfig?.server.url != nil { + descriptor.serverURL = portal.devConfig?.server.url?.absoluteString + // This allows for not having any files on disk during dev + if !FileManager.default.fileExists(atPath: descriptor.appLocation.path) { + descriptor.appLocation = Bundle.main.bundleURL + } + } + #endif + return descriptor + } + override func loadInitialContext(_ userContentViewController: WKUserContentController) { let portalInitialContext: String diff --git a/Sources/IonicPortals/PortalView.swift b/Sources/IonicPortals/PortalView.swift index 97c0849..b7db167 100644 --- a/Sources/IonicPortals/PortalView.swift +++ b/Sources/IonicPortals/PortalView.swift @@ -12,7 +12,7 @@ import Capacitor public struct PortalView: UIViewRepresentable { private let portal: Portal private let onBridgeAvailable: (CAPBridgeProtocol) -> Void - + /// Creates an instance of ``PortalView`` /// - Parameters: /// - portal: The ``Portal`` to render. From 0e13de7a62ba43a58d70bb492132cfd2c3031754 Mon Sep 17 00:00:00 2001 From: Steven Sherry Date: Wed, 8 Nov 2023 16:53:21 -0600 Subject: [PATCH 2/6] feat: Add dev mode to allow overriding Portal content in debug builds --- Sources/IonicPortals/DevConfiguration.swift | 29 ++++++++++ Sources/IonicPortals/Portal.swift | 60 ++------------------- Sources/IonicPortals/PortalUIView.swift | 10 ++-- 3 files changed, 41 insertions(+), 58 deletions(-) create mode 100644 Sources/IonicPortals/DevConfiguration.swift diff --git a/Sources/IonicPortals/DevConfiguration.swift b/Sources/IonicPortals/DevConfiguration.swift new file mode 100644 index 0000000..224accd --- /dev/null +++ b/Sources/IonicPortals/DevConfiguration.swift @@ -0,0 +1,29 @@ +import Foundation + +struct EnvironmentValue { + var variableName: String + var url: URL? { + ProcessInfo.processInfo.environment[variableName].flatMap(URL.init) + } +} + +struct DevelopmentConfiguration { + var server: EnvironmentValue + var capacitorConfig: EnvironmentValue + + init(server: EnvironmentValue, capacitorConfig: EnvironmentValue) { + self.server = server + self.capacitorConfig = capacitorConfig + } +} + +extension DevelopmentConfiguration { + init(baseEnvironmentVariable: String) { + server = .init(variableName: "\(baseEnvironmentVariable.uppercased())_SERVER") + capacitorConfig = .init(variableName: "\(baseEnvironmentVariable.uppercased())_CONFIG") + } +} + +extension DevelopmentConfiguration { + static var `default` = DevelopmentConfiguration(baseEnvironmentVariable: "PORTAL") +} diff --git a/Sources/IonicPortals/Portal.swift b/Sources/IonicPortals/Portal.swift index 422a4c9..48826ed 100644 --- a/Sources/IonicPortals/Portal.swift +++ b/Sources/IonicPortals/Portal.swift @@ -2,57 +2,6 @@ import Foundation import Capacitor import IonicLiveUpdates -public enum ConfigUrl { - case url(URL) - case environment(String) - - public var url: URL? { - switch self { - case let .url(url): - return url - case let .environment(envVar): - return ProcessInfo.processInfo.environment[envVar].flatMap(URL.init) - } - } -} - -public struct DevelopmentConfiguration { - public var server: ConfigUrl - public var capacitorConfig: ConfigUrl - - public init(server: ConfigUrl, capacitorConfig: ConfigUrl) { - self.server = server - self.capacitorConfig = capacitorConfig - } -} - -extension DevelopmentConfiguration { - public init(baseEnvironmentVariable: String) { - server = .environment("\(baseEnvironmentVariable)_SERVER") - capacitorConfig = .environment("\(baseEnvironmentVariable)_CONFIG") - } -} - -extension DevelopmentConfiguration: ExpressibleByStringLiteral { - public init(stringLiteral value: StringLiteralType) { - self.init(baseEnvironmentVariable: value) - } -} - -public enum DevUrl { - case url(URL) - case environment(String) - - var url: URL? { - switch self { - case let .url(url): - return url - case let .environment(envVar): - return ProcessInfo.processInfo.environment[envVar].flatMap(URL.init) - } - } -} - /// The configuration of a web application to be embedded in an iOS application public struct Portal { /// The name of the portal. @@ -64,8 +13,9 @@ public struct Portal { /// The root directory of the ``Portal`` web application relative to the root of ``bundle`` public let startDir: String - public var devConfig: DevelopmentConfiguration? - + /// Enables web developers to override Portal content in debug builds. + public var devModeEnabled: Bool + /// The initial file to load in the Portal. public let index: String @@ -109,7 +59,7 @@ public struct Portal { public init( name: String, startDir: String? = nil, - devConfig: DevelopmentConfiguration? = nil, + devModeEnabled: Bool = true, index: String = "index.html", bundle: Bundle = .main, initialContext: JSObject = [:], @@ -120,7 +70,7 @@ public struct Portal { ) { self.name = name self.startDir = startDir ?? name - self.devConfig = devConfig + self.devModeEnabled = devModeEnabled self.index = index self.initialContext = initialContext self.bundle = bundle diff --git a/Sources/IonicPortals/PortalUIView.swift b/Sources/IonicPortals/PortalUIView.swift index 2510079..93405f8 100644 --- a/Sources/IonicPortals/PortalUIView.swift +++ b/Sources/IonicPortals/PortalUIView.swift @@ -71,6 +71,10 @@ public class PortalUIView: UIView { private var portal: Portal private var liveUpdatePath: URL? + #if DEBUG + private lazy var devConfiguration = DevelopmentConfiguration(baseEnvironmentVariable: portal.name) + #endif + override var router: Router { PortalRouter(portal: portal) } init(portal: Portal, liveUpdatePath: URL?) { @@ -100,7 +104,7 @@ public class PortalUIView: UIView { let bundleURL = portal.bundle.url(forResource: portal.startDir, withExtension: nil) #if DEBUG - if let debugConfigUrl = portal.devConfig?.capacitorConfig.url { + if portal.devModeEnabled, let debugConfigUrl = devConfiguration.capacitorConfig.url ?? DevelopmentConfiguration.default.capacitorConfig.url { return InstanceDescriptor(at: Bundle.main.bundleURL, configuration: debugConfigUrl, cordovaConfiguration: nil) } #endif @@ -133,8 +137,8 @@ public class PortalUIView: UIView { let descriptor = createInstanceDescriptor() portal.descriptorConfiguration.forEach { $0(descriptor) } #if DEBUG - if portal.devConfig?.server.url != nil { - descriptor.serverURL = portal.devConfig?.server.url?.absoluteString + if portal.devModeEnabled, let serverUrl = devConfiguration.server.url ?? DevelopmentConfiguration.default.server.url { + descriptor.serverURL = serverUrl.absoluteString // This allows for not having any files on disk during dev if !FileManager.default.fileExists(atPath: descriptor.appLocation.path) { descriptor.appLocation = Bundle.main.bundleURL From c248e7d4b02c8e19b6575b09d91a978596a1fdcf Mon Sep 17 00:00:00 2001 From: Steven Sherry Date: Wed, 8 Nov 2023 17:00:06 -0600 Subject: [PATCH 3/6] chore: Rename types and initializers --- Sources/IonicPortals/DevConfiguration.swift | 14 +++++++------- Sources/IonicPortals/PortalUIView.swift | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Sources/IonicPortals/DevConfiguration.swift b/Sources/IonicPortals/DevConfiguration.swift index 224accd..ff53c91 100644 --- a/Sources/IonicPortals/DevConfiguration.swift +++ b/Sources/IonicPortals/DevConfiguration.swift @@ -7,7 +7,7 @@ struct EnvironmentValue { } } -struct DevelopmentConfiguration { +struct DevConfiguration { var server: EnvironmentValue var capacitorConfig: EnvironmentValue @@ -17,13 +17,13 @@ struct DevelopmentConfiguration { } } -extension DevelopmentConfiguration { - init(baseEnvironmentVariable: String) { - server = .init(variableName: "\(baseEnvironmentVariable.uppercased())_SERVER") - capacitorConfig = .init(variableName: "\(baseEnvironmentVariable.uppercased())_CONFIG") +extension DevConfiguration { + init(baseName: String) { + server = .init(variableName: "\(baseName.uppercased())_SERVER") + capacitorConfig = .init(variableName: "\(baseName.uppercased())_CONFIG") } } -extension DevelopmentConfiguration { - static var `default` = DevelopmentConfiguration(baseEnvironmentVariable: "PORTAL") +extension DevConfiguration { + static var `default` = DevConfiguration(baseName: "PORTAL") } diff --git a/Sources/IonicPortals/PortalUIView.swift b/Sources/IonicPortals/PortalUIView.swift index 93405f8..4a52787 100644 --- a/Sources/IonicPortals/PortalUIView.swift +++ b/Sources/IonicPortals/PortalUIView.swift @@ -72,7 +72,7 @@ public class PortalUIView: UIView { private var liveUpdatePath: URL? #if DEBUG - private lazy var devConfiguration = DevelopmentConfiguration(baseEnvironmentVariable: portal.name) + private lazy var devConfiguration = DevConfiguration(baseName: portal.name) #endif override var router: Router { PortalRouter(portal: portal) } @@ -104,7 +104,7 @@ public class PortalUIView: UIView { let bundleURL = portal.bundle.url(forResource: portal.startDir, withExtension: nil) #if DEBUG - if portal.devModeEnabled, let debugConfigUrl = devConfiguration.capacitorConfig.url ?? DevelopmentConfiguration.default.capacitorConfig.url { + if portal.devModeEnabled, let debugConfigUrl = devConfiguration.capacitorConfig.url ?? DevConfiguration.default.capacitorConfig.url { return InstanceDescriptor(at: Bundle.main.bundleURL, configuration: debugConfigUrl, cordovaConfiguration: nil) } #endif @@ -137,7 +137,7 @@ public class PortalUIView: UIView { let descriptor = createInstanceDescriptor() portal.descriptorConfiguration.forEach { $0(descriptor) } #if DEBUG - if portal.devModeEnabled, let serverUrl = devConfiguration.server.url ?? DevelopmentConfiguration.default.server.url { + if portal.devModeEnabled, let serverUrl = devConfiguration.server.url ?? DevConfiguration.default.server.url { descriptor.serverURL = serverUrl.absoluteString // This allows for not having any files on disk during dev if !FileManager.default.fileExists(atPath: descriptor.appLocation.path) { From 215ff33fbe99c459c2b5e98da5b8953af12d3d52 Mon Sep 17 00:00:00 2001 From: Steven Sherry Date: Wed, 8 Nov 2023 17:06:14 -0600 Subject: [PATCH 4/6] chore: Update documentation --- Sources/IonicPortals/IonicPortals.docc/Portal.md | 11 ++++++++++- Sources/IonicPortals/Portal.swift | 3 ++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Sources/IonicPortals/IonicPortals.docc/Portal.md b/Sources/IonicPortals/IonicPortals.docc/Portal.md index a6cf8de..8e1109f 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:bundle:initialContext:assetMaps:plugins:liveUpdateManager:liveUpdateConfig:)`` +- ``init(name:startDir:index:devModeEnabled:bundle:initialContext:assetMaps:plugins:liveUpdateManager:liveUpdateConfig:)`` - ``init(stringLiteral:)`` ### Web App Location @@ -15,6 +15,7 @@ ### Plugin Management +- ``plugins`` - ``adding(_:)-72o29`` - ``adding(_:)-9sqqz`` - ``adding(_:)-868wl`` @@ -39,3 +40,11 @@ ### Name - ``name`` + +### DevMode + +- ``devModeEnabled`` + +### Assets + +- ``assetMaps`` diff --git a/Sources/IonicPortals/Portal.swift b/Sources/IonicPortals/Portal.swift index 48826ed..7db9d35 100644 --- a/Sources/IonicPortals/Portal.swift +++ b/Sources/IonicPortals/Portal.swift @@ -50,6 +50,7 @@ public struct Portal { /// - startDir: The starting directory of the ``Portal`` relative to the root of ``bundle``. /// If `nil`, the portal name is used as the starting directory. Defaults to `nil`. /// - 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 `[:]`. @@ -59,8 +60,8 @@ public struct Portal { public init( name: String, startDir: String? = nil, - devModeEnabled: Bool = true, index: String = "index.html", + devModeEnabled: Bool = true, bundle: Bundle = .main, initialContext: JSObject = [:], assetMaps: [AssetMap] = [], From f4bada0d3397b879df976242112db3dc5299ce87 Mon Sep 17 00:00:00 2001 From: Steven Sherry Date: Mon, 27 Nov 2023 12:47:00 -0600 Subject: [PATCH 5/6] chore: The capacitor configuration is now a base64 encoded string that is set in the environment that is decoded and then written to disk. --- Sources/IonicPortals/DevConfiguration.swift | 31 ++++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/Sources/IonicPortals/DevConfiguration.swift b/Sources/IonicPortals/DevConfiguration.swift index ff53c91..e1df9c2 100644 --- a/Sources/IonicPortals/DevConfiguration.swift +++ b/Sources/IonicPortals/DevConfiguration.swift @@ -1,17 +1,40 @@ import Foundation -struct EnvironmentValue { +struct ServerUrl { var variableName: String var url: URL? { ProcessInfo.processInfo.environment[variableName].flatMap(URL.init) } } +struct ConfigUrl { + var variableName: String + + var url: URL? { + guard let value = ProcessInfo.processInfo.environment[variableName], + let decodedData = Data(base64Encoded: value, options: [.ignoreUnknownCharacters]), + let decodedString = String(data: decodedData, encoding: .utf8) + else { return nil } + + let targetTempFile = FileManager.default.temporaryDirectory + .appendingPathComponent(UUID().uuidString) + .appendingPathExtension("json") + + do { + try decodedString.write(to: targetTempFile, atomically: true, encoding: .utf8) + } catch { + return nil + } + + return targetTempFile + } +} + struct DevConfiguration { - var server: EnvironmentValue - var capacitorConfig: EnvironmentValue + var server: ServerUrl + var capacitorConfig: ConfigUrl - init(server: EnvironmentValue, capacitorConfig: EnvironmentValue) { + init(server: ServerUrl, capacitorConfig: ConfigUrl) { self.server = server self.capacitorConfig = capacitorConfig } From 6afecbda891c2649676a84d4677e7cc274cda4e0 Mon Sep 17 00:00:00 2001 From: Steven Sherry Date: Tue, 28 Nov 2023 14:11:23 -0600 Subject: [PATCH 6/6] Update DevConfiguration.swift --- Sources/IonicPortals/DevConfiguration.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/IonicPortals/DevConfiguration.swift b/Sources/IonicPortals/DevConfiguration.swift index e1df9c2..682b0a3 100644 --- a/Sources/IonicPortals/DevConfiguration.swift +++ b/Sources/IonicPortals/DevConfiguration.swift @@ -48,5 +48,5 @@ extension DevConfiguration { } extension DevConfiguration { - static var `default` = DevConfiguration(baseName: "PORTAL") + static let `default` = DevConfiguration(baseName: "PORTAL") }