diff --git a/ios/Capacitor/.swiftlint.yml b/ios/Capacitor/.swiftlint.yml deleted file mode 100644 index fcd0fc56cc..0000000000 --- a/ios/Capacitor/.swiftlint.yml +++ /dev/null @@ -1,3 +0,0 @@ -excluded: -- CapacitorTests -- TestsHostApp diff --git a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj index e21e3f220a..c2f49b5af9 100644 --- a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj +++ b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj @@ -10,15 +10,21 @@ 501CBAA71FC0A723009B0D4D /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 501CBAA61FC0A723009B0D4D /* WebKit.framework */; }; 50503EE91FC08595003606DC /* Capacitor.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50503EDF1FC08594003606DC /* Capacitor.framework */; }; 50503EEE1FC08595003606DC /* CapacitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50503EED1FC08595003606DC /* CapacitorTests.swift */; }; + 6214934725509C3F006C36F9 /* CAPInstanceConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6214934625509C3F006C36F9 /* CAPInstanceConfiguration.swift */; }; 621ECCB72542045900D3D615 /* CAPBridgedJSTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = 621ECCB42542045900D3D615 /* CAPBridgedJSTypes.m */; }; - 621ECCB82542045900D3D615 /* CAPBridgedJSTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 621ECCB62542045900D3D615 /* CAPBridgedJSTypes.h */; }; + 621ECCB82542045900D3D615 /* CAPBridgedJSTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 621ECCB62542045900D3D615 /* CAPBridgedJSTypes.h */; settings = {ATTRIBUTES = (Private, ); }; }; 621ECCBC2542046400D3D615 /* JSTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621ECCBB2542046400D3D615 /* JSTypes.swift */; }; 621ECCC3254204B700D3D615 /* BridgedTypesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621ECCC2254204B700D3D615 /* BridgedTypesTests.swift */; }; 621ECCC8254204BE00D3D615 /* JSONSerializationWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 621ECCC6254204BE00D3D615 /* JSONSerializationWrapper.m */; }; 621ECCD6254205BD00D3D615 /* CAPBridgeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621ECCD4254205BD00D3D615 /* CAPBridgeProtocol.swift */; }; 621ECCDA254205C400D3D615 /* CapacitorBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621ECCD9254205C400D3D615 /* CapacitorBridge.swift */; }; 621ECCE3254206A600D3D615 /* CAPApplicationDelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621ECCE2254206A600D3D615 /* CAPApplicationDelegateProxy.swift */; }; - 622BB9C42541FE3000A5DBCA /* capacitor.config.json in CopyFiles */ = {isa = PBXBuildFile; fileRef = 622BB9BE2541FDFE00A5DBCA /* capacitor.config.json */; }; + 623D68FA254C5037002D01D1 /* KeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 623D68F9254C5037002D01D1 /* KeyPath.swift */; }; + 623D6909254C6FDF002D01D1 /* CAPInstanceDescriptor.h in Headers */ = {isa = PBXBuildFile; fileRef = 623D6907254C6FDF002D01D1 /* CAPInstanceDescriptor.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 623D690A254C6FDF002D01D1 /* CAPInstanceDescriptor.m in Sources */ = {isa = PBXBuildFile; fileRef = 623D6908254C6FDF002D01D1 /* CAPInstanceDescriptor.m */; }; + 623D6914254C7030002D01D1 /* CAPInstanceDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 623D6913254C7030002D01D1 /* CAPInstanceDescriptor.swift */; }; + 623D691D254C7462002D01D1 /* CAPInstanceConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 623D691B254C7462002D01D1 /* CAPInstanceConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 623D691E254C7462002D01D1 /* CAPInstanceConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 623D691C254C7462002D01D1 /* CAPInstanceConfiguration.m */; }; 62959B162524DA7800A3D7F1 /* CAPPluginCall.h in Headers */ = {isa = PBXBuildFile; fileRef = 62959AE22524DA7700A3D7F1 /* CAPPluginCall.h */; settings = {ATTRIBUTES = (Public, ); }; }; 62959B172524DA7800A3D7F1 /* JSExport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959AE32524DA7700A3D7F1 /* JSExport.swift */; }; 62959B192524DA7800A3D7F1 /* CAPBridgedPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 62959AE52524DA7700A3D7F1 /* CAPBridgedPlugin.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -34,7 +40,6 @@ 62959B312524DA7800A3D7F1 /* JS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959AFF2524DA7700A3D7F1 /* JS.swift */; }; 62959B332524DA7800A3D7F1 /* CAPPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 62959B012524DA7700A3D7F1 /* CAPPlugin.m */; }; 62959B362524DA7800A3D7F1 /* CAPBridgeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959B042524DA7700A3D7F1 /* CAPBridgeViewController.swift */; }; - 62959B372524DA7800A3D7F1 /* CAPConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959B052524DA7700A3D7F1 /* CAPConfig.swift */; }; 62959B382524DA7800A3D7F1 /* CAPPluginCall.m in Sources */ = {isa = PBXBuildFile; fileRef = 62959B062524DA7700A3D7F1 /* CAPPluginCall.m */; }; 62959B392524DA7800A3D7F1 /* CapacitorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959B072524DA7700A3D7F1 /* CapacitorExtension.swift */; }; 62959B3A2524DA7800A3D7F1 /* CAPLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959B082524DA7700A3D7F1 /* CAPLog.swift */; }; @@ -56,6 +61,12 @@ 6296A785253A2E49005A202A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6296A783253A2E49005A202A /* Main.storyboard */; }; 6296A787253A2E49005A202A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6296A786253A2E49005A202A /* Assets.xcassets */; }; 6296A78A253A2E49005A202A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6296A788253A2E49005A202A /* LaunchScreen.storyboard */; }; + 62A91C3425535F5700861508 /* ConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A91C3325535F5700861508 /* ConfigurationTests.swift */; }; + 62A91C3F2553710E00861508 /* nonjson.json in CopyFiles */ = {isa = PBXBuildFile; fileRef = 62A91C392553710300861508 /* nonjson.json */; }; + 62E0736125535E8700BAAADB /* server.json in CopyFiles */ = {isa = PBXBuildFile; fileRef = 62E0735225535E6500BAAADB /* server.json */; }; + 62E0736225535E8700BAAADB /* bad.json in CopyFiles */ = {isa = PBXBuildFile; fileRef = 62E0735325535E6500BAAADB /* bad.json */; }; + 62E0736325535E8700BAAADB /* flat.json in CopyFiles */ = {isa = PBXBuildFile; fileRef = 62E0735425535E6500BAAADB /* flat.json */; }; + 62E0736425535E8700BAAADB /* hierarchy.json in CopyFiles */ = {isa = PBXBuildFile; fileRef = 62E0735525535E6500BAAADB /* hierarchy.json */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -86,10 +97,14 @@ 622BB9C32541FE1900A5DBCA /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; - dstPath = ""; + dstPath = configurations; dstSubfolderSpec = 1; files = ( - 622BB9C42541FE3000A5DBCA /* capacitor.config.json in CopyFiles */, + 62A91C3F2553710E00861508 /* nonjson.json in CopyFiles */, + 62E0736125535E8700BAAADB /* server.json in CopyFiles */, + 62E0736225535E8700BAAADB /* bad.json in CopyFiles */, + 62E0736325535E8700BAAADB /* flat.json in CopyFiles */, + 62E0736425535E8700BAAADB /* hierarchy.json in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -111,6 +126,7 @@ 50503EDF1FC08594003606DC /* Capacitor.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Capacitor.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 50503EE81FC08595003606DC /* CapacitorTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CapacitorTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 50503EED1FC08595003606DC /* CapacitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapacitorTests.swift; sourceTree = ""; }; + 6214934625509C3F006C36F9 /* CAPInstanceConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CAPInstanceConfiguration.swift; sourceTree = ""; }; 621ECCB42542045900D3D615 /* CAPBridgedJSTypes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CAPBridgedJSTypes.m; sourceTree = ""; }; 621ECCB62542045900D3D615 /* CAPBridgedJSTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CAPBridgedJSTypes.h; sourceTree = ""; }; 621ECCBB2542046400D3D615 /* JSTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSTypes.swift; sourceTree = ""; }; @@ -121,7 +137,12 @@ 621ECCD4254205BD00D3D615 /* CAPBridgeProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAPBridgeProtocol.swift; sourceTree = ""; }; 621ECCD9254205C400D3D615 /* CapacitorBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CapacitorBridge.swift; sourceTree = ""; }; 621ECCE2254206A600D3D615 /* CAPApplicationDelegateProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAPApplicationDelegateProxy.swift; sourceTree = ""; }; - 622BB9BE2541FDFE00A5DBCA /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = ""; }; + 623D68F9254C5037002D01D1 /* KeyPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPath.swift; sourceTree = ""; }; + 623D6907254C6FDF002D01D1 /* CAPInstanceDescriptor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CAPInstanceDescriptor.h; sourceTree = ""; }; + 623D6908254C6FDF002D01D1 /* CAPInstanceDescriptor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CAPInstanceDescriptor.m; sourceTree = ""; }; + 623D6913254C7030002D01D1 /* CAPInstanceDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CAPInstanceDescriptor.swift; sourceTree = ""; }; + 623D691B254C7462002D01D1 /* CAPInstanceConfiguration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CAPInstanceConfiguration.h; sourceTree = ""; }; + 623D691C254C7462002D01D1 /* CAPInstanceConfiguration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CAPInstanceConfiguration.m; sourceTree = ""; }; 62959AE22524DA7700A3D7F1 /* CAPPluginCall.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CAPPluginCall.h; sourceTree = ""; }; 62959AE32524DA7700A3D7F1 /* JSExport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSExport.swift; sourceTree = ""; }; 62959AE42524DA7700A3D7F1 /* CAPBridgedJSTypes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CAPBridgedJSTypes.m; sourceTree = ""; }; @@ -138,7 +159,6 @@ 62959AFF2524DA7700A3D7F1 /* JS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JS.swift; sourceTree = ""; }; 62959B012524DA7700A3D7F1 /* CAPPlugin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CAPPlugin.m; sourceTree = ""; }; 62959B042524DA7700A3D7F1 /* CAPBridgeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAPBridgeViewController.swift; sourceTree = ""; }; - 62959B052524DA7700A3D7F1 /* CAPConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAPConfig.swift; sourceTree = ""; }; 62959B062524DA7700A3D7F1 /* CAPPluginCall.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CAPPluginCall.m; sourceTree = ""; }; 62959B072524DA7700A3D7F1 /* CapacitorExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CapacitorExtension.swift; sourceTree = ""; }; 62959B082524DA7700A3D7F1 /* CAPLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAPLog.swift; sourceTree = ""; }; @@ -165,6 +185,12 @@ 6296A786253A2E49005A202A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 6296A789253A2E49005A202A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 6296A78B253A2E49005A202A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 62A91C3325535F5700861508 /* ConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationTests.swift; sourceTree = ""; }; + 62A91C392553710300861508 /* nonjson.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = nonjson.json; sourceTree = ""; }; + 62E0735225535E6500BAAADB /* server.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = server.json; sourceTree = ""; }; + 62E0735325535E6500BAAADB /* bad.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = bad.json; sourceTree = ""; }; + 62E0735425535E6500BAAADB /* flat.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = flat.json; sourceTree = ""; }; + 62E0735525535E6500BAAADB /* hierarchy.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = hierarchy.json; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -232,6 +258,7 @@ children = ( 50503EED1FC08595003606DC /* CapacitorTests.swift */, 621ECCC2254204B700D3D615 /* BridgedTypesTests.swift */, + 62A91C3325535F5700861508 /* ConfigurationTests.swift */, 621ECCC7254204BE00D3D615 /* JSONSerializationWrapper.h */, 621ECCC6254204BE00D3D615 /* JSONSerializationWrapper.m */, 621ECCCD254204C400D3D615 /* CapacitorTests-Bridging-Header.h */, @@ -263,6 +290,7 @@ 621ECCBB2542046400D3D615 /* JSTypes.swift */, 621ECCB62542045900D3D615 /* CAPBridgedJSTypes.h */, 621ECCB42542045900D3D615 /* CAPBridgedJSTypes.m */, + 623D68F9254C5037002D01D1 /* KeyPath.swift */, 62959AFF2524DA7700A3D7F1 /* JS.swift */, 62959AE32524DA7700A3D7F1 /* JSExport.swift */, 621ECCD4254205BD00D3D615 /* CAPBridgeProtocol.swift */, @@ -272,11 +300,15 @@ 621ECCE2254206A600D3D615 /* CAPApplicationDelegateProxy.swift */, 62959B0A2524DA7700A3D7F1 /* CAPBridgeDelegate.swift */, 62959AEA2524DA7700A3D7F1 /* Plugins */, - 62959B052524DA7700A3D7F1 /* CAPConfig.swift */, + 623D6907254C6FDF002D01D1 /* CAPInstanceDescriptor.h */, + 623D6908254C6FDF002D01D1 /* CAPInstanceDescriptor.m */, + 623D6913254C7030002D01D1 /* CAPInstanceDescriptor.swift */, + 623D691B254C7462002D01D1 /* CAPInstanceConfiguration.h */, + 623D691C254C7462002D01D1 /* CAPInstanceConfiguration.m */, + 6214934625509C3F006C36F9 /* CAPInstanceConfiguration.swift */, 62959B082524DA7700A3D7F1 /* CAPLog.swift */, 62959AE72524DA7700A3D7F1 /* CAPFile.swift */, 62959B0B2524DA7700A3D7F1 /* CAPMessageHandlerWrapper.swift */, - 62959B022524DA7700A3D7F1 /* CAPUNUserNotificationCenterDelegate.swift */, 62959B0D2524DA7700A3D7F1 /* CAPAssetHandler.swift */, 62959B0E2524DA7700A3D7F1 /* TmpViewController.swift */, 62959B102524DA7700A3D7F1 /* DocLinks.swift */, @@ -315,7 +347,7 @@ children = ( 6296A77D253A2E49005A202A /* AppDelegate.swift */, 6296A781253A2E49005A202A /* ViewController.swift */, - 622BB9BE2541FDFE00A5DBCA /* capacitor.config.json */, + 62E0735125535E6500BAAADB /* configurations */, 6296A783253A2E49005A202A /* Main.storyboard */, 6296A786253A2E49005A202A /* Assets.xcassets */, 6296A788253A2E49005A202A /* LaunchScreen.storyboard */, @@ -324,6 +356,18 @@ path = TestsHostApp; sourceTree = ""; }; + 62E0735125535E6500BAAADB /* configurations */ = { + isa = PBXGroup; + children = ( + 62E0735225535E6500BAAADB /* server.json */, + 62E0735325535E6500BAAADB /* bad.json */, + 62E0735425535E6500BAAADB /* flat.json */, + 62E0735525535E6500BAAADB /* hierarchy.json */, + 62A91C392553710300861508 /* nonjson.json */, + ); + path = configurations; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -335,8 +379,10 @@ 62959B452524DA7800A3D7F1 /* CAPPlugin.h in Headers */, 62959B162524DA7800A3D7F1 /* CAPPluginCall.h in Headers */, 62959B3B2524DA7800A3D7F1 /* CAPPluginMethod.h in Headers */, - 621ECCB82542045900D3D615 /* CAPBridgedJSTypes.h in Headers */, + 623D691D254C7462002D01D1 /* CAPInstanceConfiguration.h in Headers */, + 623D6909254C6FDF002D01D1 /* CAPInstanceDescriptor.h in Headers */, 62959B192524DA7800A3D7F1 /* CAPBridgedPlugin.h in Headers */, + 621ECCB82542045900D3D615 /* CAPBridgedJSTypes.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -497,12 +543,12 @@ 62959B362524DA7800A3D7F1 /* CAPBridgeViewController.swift in Sources */, 621ECCB72542045900D3D615 /* CAPBridgedJSTypes.m in Sources */, 62959B402524DA7800A3D7F1 /* TmpViewController.swift in Sources */, - 62959B372524DA7800A3D7F1 /* CAPConfig.swift in Sources */, 621ECCD6254205BD00D3D615 /* CAPBridgeProtocol.swift in Sources */, 62959B432524DA7800A3D7F1 /* Data+Capacitor.swift in Sources */, 621ECCBC2542046400D3D615 /* JSTypes.swift in Sources */, 621ECCDA254205C400D3D615 /* CapacitorBridge.swift in Sources */, 62959B382524DA7800A3D7F1 /* CAPPluginCall.m in Sources */, + 623D690A254C6FDF002D01D1 /* CAPInstanceDescriptor.m in Sources */, 62959B1B2524DA7800A3D7F1 /* CAPFile.swift in Sources */, 62959B462524DA7800A3D7F1 /* CAPBridge.swift in Sources */, 62959B1D2524DA7800A3D7F1 /* UIColor.swift in Sources */, @@ -520,8 +566,12 @@ 62959B3F2524DA7800A3D7F1 /* CAPAssetHandler.swift in Sources */, 62959B3C2524DA7800A3D7F1 /* CAPBridgeDelegate.swift in Sources */, 62959B2F2524DA7800A3D7F1 /* DefaultPlugins.m in Sources */, + 623D691E254C7462002D01D1 /* CAPInstanceConfiguration.m in Sources */, + 623D68FA254C5037002D01D1 /* KeyPath.swift in Sources */, 62959B222524DA7800A3D7F1 /* Console.swift in Sources */, 62959B3A2524DA7800A3D7F1 /* CAPLog.swift in Sources */, + 6214934725509C3F006C36F9 /* CAPInstanceConfiguration.swift in Sources */, + 623D6914254C7030002D01D1 /* CAPInstanceDescriptor.swift in Sources */, 621ECCE3254206A600D3D615 /* CAPApplicationDelegateProxy.swift in Sources */, 62959B262524DA7800A3D7F1 /* WebView.swift in Sources */, ); @@ -533,6 +583,7 @@ files = ( 50503EEE1FC08595003606DC /* CapacitorTests.swift in Sources */, 621ECCC8254204BE00D3D615 /* JSONSerializationWrapper.m in Sources */, + 62A91C3425535F5700861508 /* ConfigurationTests.swift in Sources */, 621ECCC3254204B700D3D615 /* BridgedTypesTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ios/Capacitor/Capacitor/CAPBridgeProtocol.swift b/ios/Capacitor/Capacitor/CAPBridgeProtocol.swift index 77bb7a6a1c..9bf0d773b4 100644 --- a/ios/Capacitor/Capacitor/CAPBridgeProtocol.swift +++ b/ios/Capacitor/Capacitor/CAPBridgeProtocol.swift @@ -4,7 +4,7 @@ import WebKit @objc public protocol CAPBridgeProtocol: NSObjectProtocol { // MARK: Environment Properties var viewController: UIViewController? { get } - var config: CAPConfig { get } + var config: InstanceConfiguration { get } var webView: WKWebView? { get } var isSimEnvironment: Bool { get } var isDevEnvironment: Bool { get } @@ -34,7 +34,7 @@ import WebKit @available(*, deprecated, renamed: "userInterfaceStyle") func getUserInterfaceStyle() -> UIUserInterfaceStyle - @available(*, deprecated, message: "will be moved to config") + @available(*, deprecated, message: "Moved - equivalent is found on config.localURL") func getLocalUrl() -> String // MARK: Call Management diff --git a/ios/Capacitor/Capacitor/CAPBridgeViewController.swift b/ios/Capacitor/Capacitor/CAPBridgeViewController.swift index a44a38131f..5fd263b2ef 100644 --- a/ios/Capacitor/Capacitor/CAPBridgeViewController.swift +++ b/ios/Capacitor/Capacitor/CAPBridgeViewController.swift @@ -18,7 +18,6 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKUID public var bridgedViewController: UIViewController? { return self } - public let cordovaParser = CDVConfigParser.init() private var hostname: String? private var allowNavigationConfig: [String]? private var basePath: String = "" @@ -37,7 +36,6 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKUID @objc public var supportedOrientations: [Int] = [] @objc public var startDir = "" - @objc public var config: String? // Construct the Capacitor runtime private var capacitorBridge: CapacitorBridge? @@ -47,79 +45,91 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKUID private var handler: CAPAssetHandler? override public func loadView() { - let configUrl = Bundle.main.url(forResource: "config", withExtension: "xml") - let configParser = XMLParser(contentsOf: configUrl!)! - configParser.delegate = cordovaParser - configParser.parse() - guard let startPath = self.getStartPath() else { + // load the configuration and set the logging flag + let configDescriptor = InstanceDescriptor.init() + let configuration = InstanceConfiguration(with: configDescriptor) + CAPLog.enableLogging = configuration.enableLogging + logWarnings(for: configDescriptor) + + // get the starting path and configure our environment + guard let startPath = self.getStartPath(deployDisabled: configuration.cordovaDeployDisabled) else { return } - setStatusBarDefaults() setScreenOrientationDefaults() - let capConfig = CAPConfig(self.config) - - HTTPCookieStorage.shared.cookieAcceptPolicy = HTTPCookie.AcceptPolicy.always - let webViewConfiguration = WKWebViewConfiguration() - let messageHandler = CAPMessageHandlerWrapper() - self.handler = CAPAssetHandler() - self.handler!.setAssetPath(startPath) - var specifiedScheme = CapacitorBridge.defaultScheme - let configScheme = capConfig.getString("server.iosScheme") ?? CapacitorBridge.defaultScheme - // check if WebKit handles scheme and if it is valid according to Apple's documentation - if !WKWebView.handlesURLScheme(configScheme) && configScheme.range(of: "^[a-z][a-z0-9.+-]*$", options: [.regularExpression, .caseInsensitive], range: nil, locale: nil) != nil { - specifiedScheme = configScheme.lowercased() - } - webViewConfiguration.setURLSchemeHandler(self.handler, forURLScheme: specifiedScheme) - webViewConfiguration.userContentController = messageHandler.contentController - - configureWebView(configuration: webViewConfiguration) - - if let appendUserAgent = (capConfig.getValue("ios.appendUserAgent") as? String) ?? (capConfig.getValue("appendUserAgent") as? String) { - webViewConfiguration.applicationNameForUserAgent = appendUserAgent - } - - webView = WKWebView(frame: .zero, configuration: webViewConfiguration) - webView?.scrollView.bounces = false - let availableInsets = ["automatic", "scrollableAxes", "never", "always"] - if let contentInset = (capConfig.getValue("ios.contentInset") as? String), - let index = availableInsets.firstIndex(of: contentInset) { - webView?.scrollView.contentInsetAdjustmentBehavior = UIScrollView.ContentInsetAdjustmentBehavior.init(rawValue: index)! - } else { - webView?.scrollView.contentInsetAdjustmentBehavior = .never - } - webView?.uiDelegate = self - webView?.navigationDelegate = self - if let allowsLinkPreview = (capConfig.getValue("ios.allowsLinkPreview") as? Bool) { - webView?.allowsLinkPreview = allowsLinkPreview - } - webView?.configuration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs") + // get the web view + let assetHandler = CAPAssetHandler() + assetHandler.setAssetPath(startPath) + webView = prepareWebView(with: configuration, assetHandler: assetHandler) view = webView - + self.handler = assetHandler + // configure the web view setKeyboardRequiresUserInteraction(false) + // create the bridge + let messageHandler = CAPMessageHandlerWrapper() + capacitorBridge = CapacitorBridge(with: configuration, delegate: self, cordovaConfiguration: configDescriptor.cordovaConfiguration, messageHandler: messageHandler) + } - capacitorBridge = CapacitorBridge(self, messageHandler, capConfig, specifiedScheme) - - if let scrollEnabled = bridge!.config.getValue("ios.scrollEnabled") as? Bool { - webView?.scrollView.isScrollEnabled = scrollEnabled + private func prepareWebView(with configuration: InstanceConfiguration, assetHandler: CAPAssetHandler) -> WKWebView { + // set the cookie policy + HTTPCookieStorage.shared.cookieAcceptPolicy = HTTPCookie.AcceptPolicy.always + // setup the web view configuration + let webViewConfiguration = WKWebViewConfiguration() + webViewConfiguration.allowsInlineMediaPlayback = true + webViewConfiguration.suppressesIncrementalRendering = false + webViewConfiguration.allowsAirPlayForMediaPlayback = true + webViewConfiguration.mediaTypesRequiringUserActionForPlayback = [] + if let appendUserAgent = configuration.appendedUserAgentString { + webViewConfiguration.applicationNameForUserAgent = appendUserAgent } - - if let backgroundColor = (bridge!.config.getValue("ios.backgroundColor") as? String) ?? (bridge!.config.getValue("backgroundColor") as? String) { - webView?.backgroundColor = UIColor.capacitor.color(fromHex: backgroundColor) - webView?.scrollView.backgroundColor = UIColor.capacitor.color(fromHex: backgroundColor) + webViewConfiguration.setURLSchemeHandler(assetHandler, forURLScheme: configuration.localURL.scheme ?? InstanceDescriptorDefaults.scheme) + let messageHandler = CAPMessageHandlerWrapper() + webViewConfiguration.userContentController = messageHandler.contentController + // create the web view and set its properties + let webView = WKWebView(frame: .zero, configuration: webViewConfiguration) + webView.scrollView.bounces = false + webView.scrollView.contentInsetAdjustmentBehavior = configuration.contentInsetAdjustmentBehavior + webView.uiDelegate = self + webView.navigationDelegate = self + webView.allowsLinkPreview = configuration.allowLinkPreviews + webView.configuration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs") + webView.scrollView.isScrollEnabled = configuration.enableScrolling + if let overrideUserAgent = configuration.overridenUserAgentString { + webView.customUserAgent = overrideUserAgent + } + if let backgroundColor = configuration.backgroundColor { + webView.backgroundColor = backgroundColor + webView.scrollView.backgroundColor = backgroundColor } else if #available(iOS 13, *) { // Use the system background colors if background is not set by user - webView?.backgroundColor = UIColor.systemBackground - webView?.scrollView.backgroundColor = UIColor.systemBackground + webView.backgroundColor = UIColor.systemBackground + webView.scrollView.backgroundColor = UIColor.systemBackground } + return webView + } - if let overrideUserAgent = (bridge!.config.getValue("ios.overrideUserAgent") as? String) ?? (bridge!.config.getValue("overrideUserAgent") as? String) { - webView?.customUserAgent = overrideUserAgent + private func logWarnings(for descriptor: InstanceDescriptor) { + if descriptor.warnings.contains(.missingAppDir) { + CAPLog.print("⚡️ ERROR: Unable to find application directory at: \"\(descriptor.appLocation.absoluteString)\"!") + } + if descriptor.instanceType == .fixed { + if descriptor.warnings.contains(.missingFile) { + CAPLog.print("Unable to find capacitor.config.json, make sure it exists and run npx cap copy.") + } + if descriptor.warnings.contains(.invalidFile) { + CAPLog.print("Unable to parse capacitor.config.json. Make sure it's valid JSON.") + } + if descriptor.warnings.contains(.missingCordovaFile) { + CAPLog.print("Unable to find config.xml, make sure it exists and run npx cap copy.") + } + if descriptor.warnings.contains(.invalidCordovaFile) { + CAPLog.print("Unable to parse config.xml. Make sure it's valid XML.") + } } } - private func getStartPath() -> String? { + private func getStartPath(deployDisabled: Bool = false) -> String? { var resourcesPath = assetsFolder if !startDir.isEmpty { resourcesPath = URL(fileURLWithPath: resourcesPath).appendingPathComponent(startDir).relativePath @@ -130,7 +140,7 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKUID return nil } - if !isDeployDisabled() && !isNewBinary() { + if !deployDisabled && !isNewBinary() { let defaults = UserDefaults.standard let persistedPath = defaults.string(forKey: "serverBasePath") if persistedPath != nil && !persistedPath!.isEmpty { @@ -145,11 +155,6 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKUID return startPath } - func isDeployDisabled() -> Bool { - let val = cordovaParser.settings.object(forKey: "DisableDeploy".lowercased()) as? NSString - return val?.boolValue ?? false - } - func isNewBinary() -> Bool { if let plist = Bundle.main.infoDictionary { if let versionCode = plist["CFBundleVersion"] as? String, let versionName = plist["CFBundleShortVersionString"] as? String { @@ -204,12 +209,14 @@ public class CAPBridgeViewController: UIViewController, CAPBridgeDelegate, WKUID fatalLoadError() } - hostname = capacitorBridge!.config.getString("server.url") ?? "\(capacitorBridge!.getLocalUrl())" - allowNavigationConfig = bridge!.config.getValue("server.allowNavigation") as? [String] + guard let url = bridge?.config.serverURL else { + CAPLog.print("⚡️ Unable to load app: Missing URL!") + return + } + hostname = url.absoluteString CAPLog.print("⚡️ Loading app at \(hostname!)...") - let request = URLRequest(url: URL(string: hostname!)!) - _ = webView?.load(request) + _ = webView?.load(URLRequest(url: url)) } func setServerPath(path: String) { diff --git a/ios/Capacitor/Capacitor/CAPConfig.swift b/ios/Capacitor/Capacitor/CAPConfig.swift deleted file mode 100644 index 9046970638..0000000000 --- a/ios/Capacitor/Capacitor/CAPConfig.swift +++ /dev/null @@ -1,89 +0,0 @@ -@objc public class CAPConfig: NSObject { - private static var instance: CAPConfig? - - private var config: [String: Any?]? = [String: Any?]() - - public init(_ configText: String? = nil) { - super.init() - if let contents = configText { - guard let configData = contents.data(using: .utf8) else { - CAPLog.print("Unable to process config JSON string as UTF8") - return - } - - parseAndSetConfig(configData) - } else { - loadGlobalConfig() - } - } - - private func loadGlobalConfig() { - guard let configUrl = Bundle.main.url(forResource: "capacitor.config", withExtension: "json") else { - CAPLog.print("Unable to find capacitor.config.json, make sure it exists and run npx cap copy") - return - } - do { - let contents = try Data(contentsOf: configUrl) - parseAndSetConfig(contents) - } catch { - CAPLog.print("Unable to parse capacitor.config.json. Make sure it's valid JSON") - } - } - - private func parseAndSetConfig(_ data: Data) { - do { - let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] - self.config = json - } catch { - CAPLog.print("Unable to parse config JSON") - CAPLog.print(error.localizedDescription) - } - } - - private func getConfigObjectDeepest(key: String) -> [String: Any?]? { - let parts = key.split(separator: ".") - - var object = self.config - for (_, key) in parts[0.. String { - let parts = key.split(separator: ".") - if parts.last != nil { - return String(parts.last!) - } - return "" - } - - /** - * Get the value of a configuration option for a specific plugin. - */ - @objc public func getPluginConfigValue(_ pluginId: String, _ configKey: String) -> Any? { - guard let plugins = config!["plugins"] as? [String: Any] else { - return nil - } - - guard let pluginOptions = plugins[pluginId] as? [String: Any] else { - return nil - } - - return pluginOptions[configKey] - } - - @objc public func getValue(_ key: String) -> Any? { - let deepestKey = getConfigKey(key) - let object = getConfigObjectDeepest(key: key) - return object?[deepestKey] ?? nil - } - - @objc public func getString(_ key: String) -> String? { - let value = getValue(key) - if value == nil { - return nil - } - return value as? String - } -} diff --git a/ios/Capacitor/Capacitor/CAPInstanceConfiguration.h b/ios/Capacitor/Capacitor/CAPInstanceConfiguration.h new file mode 100644 index 0000000000..2f7d3cd033 --- /dev/null +++ b/ios/Capacitor/Capacitor/CAPInstanceConfiguration.h @@ -0,0 +1,29 @@ +#ifndef CAPInstanceConfiguration_h +#define CAPInstanceConfiguration_h + +@import UIKit; + +@class CAPInstanceDescriptor; + +NS_SWIFT_NAME(InstanceConfiguration) +@interface CAPInstanceConfiguration: NSObject +@property (nonatomic, readonly, nullable) NSString *appendedUserAgentString; +@property (nonatomic, readonly, nullable) NSString *overridenUserAgentString; +@property (nonatomic, readonly, nullable) UIColor *backgroundColor; +@property (nonatomic, readonly, nonnull) NSArray *allowedNavigationHostnames; +@property (nonatomic, readonly, nonnull) NSURL *localURL; +@property (nonatomic, readonly, nonnull) NSURL *serverURL; +@property (nonatomic, readonly, nonnull) NSDictionary *pluginConfigurations; +@property (nonatomic, readonly) BOOL enableLogging; +@property (nonatomic, readonly) BOOL enableScrolling; +@property (nonatomic, readonly) BOOL allowLinkPreviews; +@property (nonatomic, readonly) BOOL cordovaDeployDisabled; +@property (nonatomic, readonly) UIScrollViewContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior; +@property (nonatomic, readonly, nonnull) NSURL *appLocation; + +@property (nonatomic, readonly, nonnull) NSDictionary *legacyConfig DEPRECATED_MSG_ATTRIBUTE("Use direct properties instead"); + +- (instancetype _Nonnull)initWithDescriptor:(CAPInstanceDescriptor* _Nonnull)descriptor NS_SWIFT_NAME(init(with:)); +@end + +#endif /* CAPInstanceConfiguration_h */ diff --git a/ios/Capacitor/Capacitor/CAPInstanceConfiguration.m b/ios/Capacitor/Capacitor/CAPInstanceConfiguration.m new file mode 100644 index 0000000000..ad8c7b5a2b --- /dev/null +++ b/ios/Capacitor/Capacitor/CAPInstanceConfiguration.m @@ -0,0 +1,42 @@ +#import "CAPInstanceConfiguration.h" +#import + +@implementation CAPInstanceConfiguration + +- (instancetype)initWithDescriptor:(CAPInstanceDescriptor *)descriptor { + if (self = [super init]) { + // first, give the descriptor a chance to make itself internally consistent + [descriptor normalize]; + // now copy the simple properties + _appendedUserAgentString = descriptor.appendedUserAgentString; + _overridenUserAgentString = descriptor.overridenUserAgentString; + _backgroundColor = descriptor.backgroundColor; + _allowedNavigationHostnames = descriptor.allowedNavigationHostnames; + _enableLogging = descriptor.enableLogging; + _enableScrolling = descriptor.enableScrolling; + _allowLinkPreviews = descriptor.allowLinkPreviews; + _contentInsetAdjustmentBehavior = descriptor.contentInsetAdjustmentBehavior; + _appLocation = descriptor.appLocation; + _pluginConfigurations = descriptor.pluginConfigurations; + _legacyConfig = descriptor.legacyConfig; + // construct the necessary URLs + _localURL = [[NSURL alloc] initWithString:[NSString stringWithFormat:@"%@://%@", descriptor.urlScheme, descriptor.urlHostname]]; + if (descriptor.serverURL != nil) { + _serverURL = [[NSURL alloc] initWithString:(descriptor.serverURL)]; + } + else { + _serverURL = _localURL; + } + // extract the one value we care about from the cordova configuration + id value = [descriptor.cordovaConfiguration.settings objectForKey:[@"DisableDeploy" lowercaseString]]; + if (value != nil && [value isKindOfClass:[NSString class]]) { + _cordovaDeployDisabled = [(NSString*)value boolValue]; + } + else { + _cordovaDeployDisabled = false; + } + } + return self; +} + +@end diff --git a/ios/Capacitor/Capacitor/CAPInstanceConfiguration.swift b/ios/Capacitor/Capacitor/CAPInstanceConfiguration.swift new file mode 100644 index 0000000000..7c5261e2ab --- /dev/null +++ b/ios/Capacitor/Capacitor/CAPInstanceConfiguration.swift @@ -0,0 +1,17 @@ +import Foundation + +extension InstanceConfiguration { + @objc public func getPluginConfigValue(_ pluginId: String, _ configKey: String) -> Any? { + return (pluginConfigurations as? JSObject)?[keyPath: KeyPath("\(pluginId).\(configKey)")] + } + + @available(*, deprecated, message: "Use direct property accessors") + @objc public func getValue(_ key: String) -> Any? { + return (legacyConfig as? JSObject)?[keyPath: KeyPath(key)] + } + + @available(*, deprecated, message: "Use direct property accessors") + @objc public func getString(_ key: String) -> String? { + return (legacyConfig as? JSObject)?[keyPath: KeyPath(key)] as? String + } +} diff --git a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.h b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.h new file mode 100644 index 0000000000..48433a533d --- /dev/null +++ b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.h @@ -0,0 +1,48 @@ +#ifndef CAPInstanceDescriptor_h +#define CAPInstanceDescriptor_h + +@import UIKit; +@import Cordova; + +typedef NS_ENUM(NSInteger, CAPInstanceType) { + CAPInstanceTypeFixed NS_SWIFT_NAME(fixed), + CAPInstanceTypeVariable NS_SWIFT_NAME(variable) +} NS_SWIFT_NAME(InstanceType); + +typedef NS_OPTIONS(NSUInteger, CAPInstanceWarning) { + CAPInstanceWarningMissingAppDir NS_SWIFT_NAME(missingAppDir) = 1 << 0, + CAPInstanceWarningMissingFile NS_SWIFT_NAME(missingFile) = 1 << 1, + CAPInstanceWarningInvalidFile NS_SWIFT_NAME(invalidFile) = 1 << 2, + CAPInstanceWarningMissingCordovaFile NS_SWIFT_NAME(missingCordovaFile) = 1 << 3, + CAPInstanceWarningInvalidCordovaFile NS_SWIFT_NAME(invalidCordovaFile) = 1 << 4 +} NS_SWIFT_NAME(InstanceWarning); + +extern NSString * _Nonnull const CAPInstanceDescriptorDefaultScheme NS_SWIFT_UNAVAILABLE("Use InstanceDescriptorDefaults"); +extern NSString * _Nonnull const CAPInstanceDescriptorDefaultHostname NS_SWIFT_UNAVAILABLE("Use InstanceDescriptorDefaults"); + +NS_SWIFT_NAME(InstanceDescriptor) +@interface CAPInstanceDescriptor : NSObject + +@property (nonatomic, copy, nullable) NSString *appendedUserAgentString; +@property (nonatomic, copy, nullable) NSString *overridenUserAgentString; +@property (nonatomic, retain, nullable) UIColor *backgroundColor; +@property (nonatomic, copy, nonnull) NSArray *allowedNavigationHostnames; +@property (nonatomic, copy, nullable) NSString *urlScheme; +@property (nonatomic, copy, nullable) NSString *urlHostname; +@property (nonatomic, copy, nullable) NSString *serverURL; +@property (nonatomic, retain, nonnull) NSDictionary *pluginConfigurations; +@property (nonatomic, assign) BOOL enableLogging; +@property (nonatomic, assign) BOOL enableScrolling; +@property (nonatomic, assign) BOOL allowLinkPreviews; +@property (nonatomic, assign) UIScrollViewContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior; +@property (nonatomic, copy, nonnull) NSURL *appLocation; +@property (nonatomic, copy, nonnull) CDVConfigParser *cordovaConfiguration; +@property (nonatomic, assign) CAPInstanceWarning warnings; +@property (nonatomic, readonly) CAPInstanceType instanceType; +@property (nonatomic, retain, nonnull) NSDictionary *legacyConfig; + +- (instancetype _Nonnull)initAsDefault NS_SWIFT_NAME(init()); +- (instancetype _Nonnull)initAtLocation:(NSURL* _Nonnull)appURL configuration:(NSURL* _Nullable)configURL cordovaConfiguration:(NSURL* _Nullable)cordovaURL NS_SWIFT_NAME(init(at:configuration:cordovaConfiguration:)); +@end + +#endif /* CAPInstanceDescriptor_h */ diff --git a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.m b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.m new file mode 100644 index 0000000000..c4c0c08473 --- /dev/null +++ b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.m @@ -0,0 +1,49 @@ +#import "CAPInstanceDescriptor.h" +#import + +// Swift extensions marked as @objc and internal are available to the Obj-C runtime but are not available at compile time. +// so we need this declaration to avoid compiler complaints +@interface CAPInstanceDescriptor (InternalSwiftExtension) +- (void)_parseConfigurationAt:(NSURL *)configURL cordovaConfiguration:(NSURL *)cordovaURL; +@end + +NSString* const CAPInstanceDescriptorDefaultScheme = @"capacitor"; +NSString* const CAPInstanceDescriptorDefaultHostname = @"localhost"; + +@implementation CAPInstanceDescriptor +- (instancetype)initAsDefault { + if (self = [super init]) { + _instanceType = CAPInstanceTypeFixed; + [self _setDefaultsWithAppLocation:[[NSBundle mainBundle] URLForResource:@"public" withExtension:nil]]; + [self _parseConfigurationAt:[[NSBundle mainBundle] URLForResource:@"capacitor.config" withExtension:@"json"] cordovaConfiguration:[[NSBundle mainBundle] URLForResource:@"config" withExtension:@"xml"]]; + } + return self; +} + +- (instancetype)initAtLocation:(NSURL*)appURL configuration:(NSURL*)configURL cordovaConfiguration:(NSURL*)cordovaURL { + if (self = [super init]) { + _instanceType = CAPInstanceTypeVariable; + [self _setDefaultsWithAppLocation:appURL]; + [self _parseConfigurationAt:configURL cordovaConfiguration:cordovaURL]; + } + return self; +} + +- (void)_setDefaultsWithAppLocation:(NSURL*)location { + _allowedNavigationHostnames = @[]; + _urlScheme = CAPInstanceDescriptorDefaultScheme; + _urlHostname = CAPInstanceDescriptorDefaultHostname; + _pluginConfigurations = @{}; + _legacyConfig = @{}; + _enableLogging = YES; + _enableScrolling = YES; + _allowLinkPreviews = YES; + _contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + _appLocation = location; + _cordovaConfiguration = [[CDVConfigParser alloc] init]; + _warnings = 0; + if (location == nil) { + _warnings |= CAPInstanceWarningMissingAppDir; + } +} +@end diff --git a/ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift new file mode 100644 index 0000000000..f8c5f37e4e --- /dev/null +++ b/ios/Capacitor/Capacitor/CAPInstanceDescriptor.swift @@ -0,0 +1,124 @@ +import Foundation + +public enum InstanceDescriptorDefaults { + static let scheme = "capacitor" + static let hostname = "localhost" +} + +internal extension InstanceDescriptor { + // swiftlint:disable:next identifier_name + @objc func _parseConfiguration(at capacitorURL: URL?, cordovaConfiguration cordovaURL: URL?) { + // sanity check that the app directory is valid + var isDirectory: ObjCBool = ObjCBool(false) + if warnings.contains(.missingAppDir) == false, (FileManager.default.fileExists(atPath: appLocation.path, isDirectory: &isDirectory) == false || isDirectory.boolValue == false) { + warnings.update(with: .missingAppDir) + } + + // parse the capacitor configuration + var config: JSObject? + if let capacitorURL = capacitorURL, FileManager.default.fileExists(atPath: capacitorURL.path, isDirectory: &isDirectory), isDirectory.boolValue == false { + do { + let contents = try Data(contentsOf: capacitorURL) + config = JSTypes.coerceDictionaryToJSObject(try JSONSerialization.jsonObject(with: contents) as? [String: Any]) + } catch { + warnings.update(with: .invalidFile) + } + } else { + warnings.update(with: .missingFile) + } + + // parse the cordova configuration + var configParser: XMLParser? + if let cordovaURL = cordovaURL, FileManager.default.fileExists(atPath: cordovaURL.path, isDirectory: &isDirectory), isDirectory.boolValue == false { + configParser = XMLParser(contentsOf: cordovaURL) + } else { + warnings.update(with: .missingCordovaFile) + if let cordovaXML = "".data(using: .utf8) { + configParser = XMLParser(data: cordovaXML) + } + } + configParser?.delegate = cordovaConfiguration + configParser?.parse() + + // extract our configuration values + if let config = config { + // to be removed + legacyConfig = config + + if let agentString = (config[keyPath: "ios.appendUserAgent"] as? String) ?? (config[keyPath: "appendUserAgent"] as? String) { + appendedUserAgentString = agentString + } + if let agentString = (config[keyPath: "ios.overrideUserAgent"] as? String) ?? (config[keyPath: "overrideUserAgent"] as? String) { + overridenUserAgentString = agentString + } + if let colorString = (config[keyPath: "ios.backgroundColor"] as? String) ?? (config[keyPath: "backgroundColor"] as? String), + let color = UIColor.capacitor.color(fromHex: colorString) { + backgroundColor = color + } + if let hideLogs = (config[keyPath: "ios.hideLogs"] as? Bool) ?? (config[keyPath: "hideLogs"] as? Bool) { + enableLogging = !hideLogs + } + if let allowNav = config[keyPath: "server.allowNavigation"] as? [String] { + allowedNavigationHostnames = allowNav + } + if let scheme = (config[keyPath: "server.iosScheme"] as? String)?.lowercased() { + urlScheme = scheme + } + if let host = config[keyPath: "server.hostname"] as? String { + urlHostname = host + } + if let urlString = config[keyPath: "server.url"] as? String { + serverURL = urlString + } + if let insetBehavior = config[keyPath: "ios.contentInset"] as? String { + let availableInsets: [String: UIScrollView.ContentInsetAdjustmentBehavior] = ["automatic": .automatic, + "scrollableAxes": .scrollableAxes, + "never": .never, + "always": .always] + if let option = availableInsets[insetBehavior] { + contentInsetAdjustmentBehavior = option + } + } + if let allowPreviews = config[keyPath: "ios.allowsLinkPreview"] as? Bool { + allowLinkPreviews = allowPreviews + } + if let scrollEnabled = config[keyPath: "ios.scrollEnabled"] as? Bool { + enableScrolling = scrollEnabled + } + if let pluginConfig = config[keyPath: "plugins"] as? JSObject { + pluginConfigurations = pluginConfig + } + } + } +} + +extension InstanceDescriptor { + @objc public func normalize() { + // first, make sure the scheme is valid + var schemeValid = false + if let scheme = urlScheme, WKWebView.handlesURLScheme(scheme) == false, + scheme.range(of: "^[a-z][a-z0-9.+-]*$", options: [.regularExpression, .caseInsensitive], range: nil, locale: nil) != nil { + schemeValid = true + } + if !schemeValid { + // reset to the default + urlScheme = InstanceDescriptorDefaults.scheme + } + // make sure we have a hostname + if urlHostname == nil { + urlHostname = InstanceDescriptorDefaults.hostname + } + // now validate the server.url + var urlValid = false + if let server = serverURL, let _ = URL(string: server) { + urlValid = true + } + if !urlValid { + serverURL = nil + } + // if the plugin configuration was programmatically modified, the necessary type information may have been lost. + // so perform a coercion here to make sure that casting will work as expected + pluginConfigurations = JSTypes.coerceDictionaryToJSObject(pluginConfigurations) ?? [:] + legacyConfig = JSTypes.coerceDictionaryToJSObject(legacyConfig) ?? [:] + } +} diff --git a/ios/Capacitor/Capacitor/CAPLog.swift b/ios/Capacitor/Capacitor/CAPLog.swift index 2a91302a3e..4c8b7d8a82 100644 --- a/ios/Capacitor/Capacitor/CAPLog.swift +++ b/ios/Capacitor/Capacitor/CAPLog.swift @@ -1,19 +1,11 @@ public class CAPLog { - - public static let config = CAPConfig() + public static var enableLogging: Bool = true public static func print(_ items: Any..., separator: String = " ", terminator: String = "\n") { - if !self.hideLogs() { + if enableLogging { for (itemIndex, item) in items.enumerated() { Swift.print(item, terminator: itemIndex == items.count - 1 ? terminator : separator) } } } - - public static func hideLogs() -> Bool { - if let hideLogs = (config.getValue("ios.hideLogs") as? Bool) ?? (config.getValue("hideLogs") as? Bool) { - return hideLogs - } - return false - } } diff --git a/ios/Capacitor/Capacitor/CAPPlugin.h b/ios/Capacitor/Capacitor/CAPPlugin.h index e66eace880..e633fff0a2 100644 --- a/ios/Capacitor/Capacitor/CAPPlugin.h +++ b/ios/Capacitor/Capacitor/CAPPlugin.h @@ -3,7 +3,6 @@ @protocol CAPBridgeProtocol; @class CAPPluginCall; -@class CAPConfig; @interface CAPPlugin : NSObject diff --git a/ios/Capacitor/Capacitor/Capacitor.h b/ios/Capacitor/Capacitor/Capacitor.h index 84e799f21a..343165dd73 100644 --- a/ios/Capacitor/Capacitor/Capacitor.h +++ b/ios/Capacitor/Capacitor/Capacitor.h @@ -10,3 +10,5 @@ FOUNDATION_EXPORT const unsigned char CapacitorVersionString[]; #import #import #import +#import +#import diff --git a/ios/Capacitor/Capacitor/CapacitorBridge.swift b/ios/Capacitor/Capacitor/CapacitorBridge.swift index a2492fe99d..af346bfb5f 100644 --- a/ios/Capacitor/Capacitor/CapacitorBridge.swift +++ b/ios/Capacitor/Capacitor/CapacitorBridge.swift @@ -82,11 +82,9 @@ internal class CapacitorBridge: NSObject, CAPBridgeProtocol { return bridgeDelegate?.bridgedViewController } - private var localUrl: String? - var lastPlugin: CAPPlugin? - @objc public var config: CAPConfig + @objc public var config: InstanceConfiguration // Map of all loaded and instantiated plugins by pluginId -> instance var plugins = [String: CAPPlugin]() // List of known plugins by pluginId -> Plugin Type @@ -95,10 +93,9 @@ internal class CapacitorBridge: NSObject, CAPBridgeProtocol { var cordovaPluginManager: CDVPluginManager? // Calls we are storing to resolve later var storedCalls = [String: CAPPluginCall]() - // Scheme to use when serving content - var scheme: String // Wheter to inject the Cordova files private var injectCordovaFiles = false + private var cordovaParser: CDVConfigParser? // Background dispatch queue for plugin calls var dispatchQueue = DispatchQueue(label: "bridge") @@ -138,7 +135,7 @@ internal class CapacitorBridge: NSObject, CAPBridgeProtocol { } public func getLocalUrl() -> String { - return localUrl! + return config.localURL.absoluteString } @nonobjc public func setStatusBarAnimation(_ animation: UIStatusBarAnimation) { @@ -170,17 +167,17 @@ internal class CapacitorBridge: NSObject, CAPBridgeProtocol { // MARK: - Initialization - init(_ bridgeDelegate: CAPBridgeDelegate, _ messageHandlerWrapper: CAPMessageHandlerWrapper, _ config: CAPConfig, _ scheme: String) { + init(with configuration: InstanceConfiguration, delegate bridgeDelegate: CAPBridgeDelegate, cordovaConfiguration: CDVConfigParser, messageHandler messageHandlerWrapper: CAPMessageHandlerWrapper) { self.bridgeDelegate = bridgeDelegate self.messageHandlerWrapper = messageHandlerWrapper - self.config = config - self.scheme = scheme + self.config = configuration + self.cordovaParser = cordovaConfiguration super.init() self.messageHandlerWrapper.bridge = self - localUrl = "\(self.scheme)://\(config.getString("server.hostname") ?? "localhost")" - exportCoreJS(localUrl: localUrl!) + + exportCoreJS(localUrl: configuration.localURL.absoluteString) registerPlugins() setupCordovaCompatibility() NotificationCenter.default.addObserver(forName: type(of: self).tmpVCAppeared.name, object: .none, queue: .none) { [weak self] _ in @@ -319,12 +316,12 @@ internal class CapacitorBridge: NSObject, CAPBridgeProtocol { } func registerCordovaPlugins() { - guard let bridgeVC = self.viewController as? CAPBridgeViewController else { + guard let cordovaParser = cordovaParser else { return } - cordovaPluginManager = CDVPluginManager.init(parser: bridgeVC.cordovaParser, viewController: self.viewController, webView: self.getWebView()) - if bridgeVC.cordovaParser.startupPluginNames.count > 0 { - for pluginName in bridgeVC.cordovaParser.startupPluginNames { + cordovaPluginManager = CDVPluginManager.init(parser: cordovaParser, viewController: self.viewController, webView: self.getWebView()) + if cordovaParser.startupPluginNames.count > 0 { + for pluginName in cordovaParser.startupPluginNames { _ = cordovaPluginManager?.getCommandInstance(pluginName as? String) } } diff --git a/ios/Capacitor/Capacitor/KeyPath.swift b/ios/Capacitor/Capacitor/KeyPath.swift new file mode 100644 index 0000000000..42c436b808 --- /dev/null +++ b/ios/Capacitor/Capacitor/KeyPath.swift @@ -0,0 +1,62 @@ +import Foundation + +public struct KeyPath { + var segments: [String] + var isEmpty: Bool { return segments.isEmpty } + var path: String { + return segments.joined(separator: ".") + } + + // initializers + init(_ string: String) { + self.segments = string.components(separatedBy: ".") + } + + init(segments: [String]) { + self.segments = segments + } + + // returns a tuple of the first segment and the remaining key path. result is nil if the key path has no segments. + func headAndRemainder() -> (head: String, remainder: KeyPath)? { + guard !isEmpty else { + return nil + } + var paths = segments + let head = paths.removeFirst() + return (head, KeyPath(segments: paths)) + } +} + +extension KeyPath: ExpressibleByStringLiteral { + public init(stringLiteral value: String) { + self.init(value) + } + + public init(unicodeScalarLiteral value: String) { + self.init(value) + } + + public init(extendedGraphemeClusterLiteral value: String) { + self.init(value) + } +} + +extension JSObject { + public subscript(keyPath keyPath: KeyPath) -> JSValue? { + get { + switch keyPath.headAndRemainder() { + case nil: // path is empty + return nil + case let (head, remainder)? where remainder.isEmpty: // reached the end of the path + return self[head] + case let (head, remainder)?: // we have at least one level to traverse + switch self[head] { + case let childObject as JSObject: // iterate down the next level + return childObject[keyPath: remainder] + default: // not an object, can't go any deeper + return nil + } + } + } + } +} diff --git a/ios/Capacitor/CapacitorTests/CapacitorTests.swift b/ios/Capacitor/CapacitorTests/CapacitorTests.swift index 290003af8d..6a2a5d8872 100644 --- a/ios/Capacitor/CapacitorTests/CapacitorTests.swift +++ b/ios/Capacitor/CapacitorTests/CapacitorTests.swift @@ -7,9 +7,6 @@ class MockBridgeViewController: CAPBridgeViewController { class MockBridgeMessageHandler: CAPMessageHandlerWrapper { } -class MockConfig: CAPConfig { -} - class MockBridge: CapacitorBridge { override public func registerPlugins() { Swift.print("REGISTER PLUGINS") @@ -21,7 +18,8 @@ class CapacitorTests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. - bridge = MockBridge(MockBridgeViewController(), MockBridgeMessageHandler(), MockConfig(), MockBridge.defaultScheme) + let descriptor = InstanceDescriptor.init() + bridge = MockBridge(with: InstanceConfiguration(with: descriptor), delegate: MockBridgeViewController(), cordovaConfiguration: descriptor.cordovaConfiguration, messageHandler: MockBridgeMessageHandler()) } override func tearDown() { diff --git a/ios/Capacitor/CapacitorTests/ConfigurationTests.swift b/ios/Capacitor/CapacitorTests/ConfigurationTests.swift new file mode 100644 index 0000000000..d3e2b27d58 --- /dev/null +++ b/ios/Capacitor/CapacitorTests/ConfigurationTests.swift @@ -0,0 +1,133 @@ +import XCTest + +@testable import Capacitor + +class ConfigurationTests: XCTestCase { + enum ConfigFile: String, CaseIterable { + case flat = "flat" + case nested = "hierarchy" + case server = "server" + case invalid = "bad" + case nonparsable = "nonjson" + } + static var files: [ConfigFile: URL] = [:] + + override class func setUp() { + for file in ConfigFile.allCases { + if let url = Bundle.main.url(forResource: file.rawValue, withExtension: "json", subdirectory: "configurations") { + files[file] = url + } + } + } + + override func setUpWithError() throws { + XCTAssert(ConfigurationTests.files.count == ConfigFile.allCases.count, "Not all configuration files were located") + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testDefaultErrors() throws { + let descriptor = InstanceDescriptor.init() + XCTAssertTrue(descriptor.warnings.contains(.missingAppDir)) + XCTAssertTrue(descriptor.warnings.contains(.missingFile)) + XCTAssertTrue(descriptor.warnings.contains(.missingCordovaFile)) + } + + func testMissingAppDetection() throws { + var url = Bundle.main.resourceURL! + url.appendPathComponent("app", isDirectory: true) + let descriptor = InstanceDescriptor.init(at: url, configuration: nil, cordovaConfiguration: nil) + XCTAssertTrue(descriptor.warnings.contains(.missingAppDir), "A missing app directory was ignored") + } + + func testFailedParsing() throws { + let url = Bundle.main.url(forResource: "configurations", withExtension: "")! + let descriptor = InstanceDescriptor.init(at: url, configuration: ConfigurationTests.files[.nonparsable], cordovaConfiguration: nil) + XCTAssertTrue(descriptor.warnings.contains(.invalidFile)) + } + + func testDefaults() throws { + let url = Bundle.main.url(forResource: "configurations", withExtension: "")! + let descriptor = InstanceDescriptor.init(at: url, configuration: nil, cordovaConfiguration: nil) + XCTAssertNil(descriptor.backgroundColor) + XCTAssertEqual(descriptor.urlScheme, "capacitor") + XCTAssertEqual(descriptor.urlHostname, "localhost") + XCTAssertNil(descriptor.serverURL) + XCTAssertTrue(descriptor.enableScrolling) + XCTAssertTrue(descriptor.enableLogging) + XCTAssertTrue(descriptor.allowLinkPreviews) + XCTAssertEqual(descriptor.contentInsetAdjustmentBehavior, .never) + } + + func testTopLevelParsing() throws { + let url = Bundle.main.url(forResource: "configurations", withExtension: "")! + let descriptor = InstanceDescriptor.init(at: url, configuration: ConfigurationTests.files[.flat], cordovaConfiguration: nil) + XCTAssertEqual(descriptor.backgroundColor, UIColor(red: 1, green: 1, blue: 1, alpha: 1)) + XCTAssertEqual(descriptor.overridenUserAgentString, "level 1 override") + XCTAssertEqual(descriptor.appendedUserAgentString, "level 1 append") + XCTAssertFalse(descriptor.enableLogging) + } + + func testNestedParsing() throws { + let url = Bundle.main.url(forResource: "configurations", withExtension: "")! + let descriptor = InstanceDescriptor.init(at: url, configuration: ConfigurationTests.files[.nested], cordovaConfiguration: nil) + XCTAssertEqual(descriptor.backgroundColor, UIColor(red: 0, green: 0, blue: 0, alpha: 1)) + XCTAssertEqual(descriptor.overridenUserAgentString, "level 2 override") + XCTAssertEqual(descriptor.appendedUserAgentString, "level 2 append") + XCTAssertTrue(descriptor.enableLogging) + XCTAssertFalse(descriptor.enableScrolling) + XCTAssertEqual(descriptor.contentInsetAdjustmentBehavior, .scrollableAxes) + } + + func testServerParsing() throws { + let url = Bundle.main.url(forResource: "configurations", withExtension: "")! + let descriptor = InstanceDescriptor.init(at: url, configuration: ConfigurationTests.files[.server], cordovaConfiguration: nil) + XCTAssertEqual(descriptor.urlScheme, "override") + XCTAssertEqual(descriptor.urlHostname, "myhost") + XCTAssertEqual(descriptor.serverURL, "http://192.168.100.1:2057") + } + + func testBadDataParsing() throws { + let url = Bundle.main.url(forResource: "configurations", withExtension: "")! + let descriptor = InstanceDescriptor.init(at: url, configuration: ConfigurationTests.files[.invalid], cordovaConfiguration: nil) + XCTAssertNil(descriptor.backgroundColor) + XCTAssertTrue(descriptor.enableLogging) + XCTAssertEqual(descriptor.contentInsetAdjustmentBehavior, .never) + } + + func testBadDataTransformation() throws { + let url = Bundle.main.url(forResource: "configurations", withExtension: "")! + let descriptor = InstanceDescriptor.init(at: url, configuration: ConfigurationTests.files[.invalid], cordovaConfiguration: nil) + let configuration = InstanceConfiguration(with: descriptor) + XCTAssertEqual(configuration.serverURL, URL(string: "capacitor://myhost"), "Invalid server.url and invalid ioScheme were not ignored") + } + + func testServerTransformation() throws { + let url = Bundle.main.url(forResource: "configurations", withExtension: "")! + let descriptor = InstanceDescriptor.init(at: url, configuration: ConfigurationTests.files[.server], cordovaConfiguration: nil) + let configuration = InstanceConfiguration(with: descriptor) + XCTAssertEqual(configuration.serverURL, URL(string: "http://192.168.100.1:2057")) + XCTAssertEqual(configuration.localURL, URL(string: "override://myhost")) + } + + func testPluginConfig() throws { + let url = Bundle.main.url(forResource: "configurations", withExtension: "")! + let descriptor = InstanceDescriptor.init(at: url, configuration: ConfigurationTests.files[.flat], cordovaConfiguration: nil) + let configuration = InstanceConfiguration(with: descriptor) + let value = configuration.getPluginConfigValue("SplashScreen", "launchShowDuration") as? Int + XCTAssertNotNil(value) + XCTAssertTrue(value == 1) + } + + func testLegacyConfig() throws { + let url = Bundle.main.url(forResource: "configurations", withExtension: "")! + let descriptor = InstanceDescriptor.init(at: url, configuration: ConfigurationTests.files[.nested], cordovaConfiguration: nil) + let configuration = InstanceConfiguration(with: descriptor) + var value = configuration.getValue("overrideUserAgent") as? String + XCTAssertEqual(value, "level 1 override") + value = configuration.getValue("ios.overrideUserAgent") as? String + XCTAssertEqual(value, "level 2 override") + } +} diff --git a/ios/Capacitor/TestsHostApp/capacitor.config.json b/ios/Capacitor/TestsHostApp/capacitor.config.json deleted file mode 100644 index 9e26dfeeb6..0000000000 --- a/ios/Capacitor/TestsHostApp/capacitor.config.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/ios/Capacitor/TestsHostApp/configurations/bad.json b/ios/Capacitor/TestsHostApp/configurations/bad.json new file mode 100644 index 0000000000..609d3f0643 --- /dev/null +++ b/ios/Capacitor/TestsHostApp/configurations/bad.json @@ -0,0 +1,28 @@ +{ + "appId": "com.capacitorjs.testshostapp", + "appName": "testshostapp", + "bundledWebRuntime": false, + "npmClient": "npm", + "webDir": "build", + "overrideUserAgent": "level 1 override", + "appendUserAgent": "level 1 append", + "backgroundColor": "invalid string", + "hideLogs": "yep", + "ios": { + "allowsLinkPreview": false, + "scrollEnabled": false, + "contentInset": "what's an axis?" + }, + "server": { + "iosScheme": "http", + "allowNavigation": ["capacitorjs.com", "ionic.io", "192.168.0.1"], + "hostname": "myhost", + "url": "not a real domain" + }, + "plugins": { + "SplashScreen": { + "launchShowDuration": 0 + } + }, + "cordova": {} +} diff --git a/ios/Capacitor/TestsHostApp/configurations/flat.json b/ios/Capacitor/TestsHostApp/configurations/flat.json new file mode 100644 index 0000000000..08d8b87b9f --- /dev/null +++ b/ios/Capacitor/TestsHostApp/configurations/flat.json @@ -0,0 +1,17 @@ +{ + "appId": "com.capacitorjs.testshostapp", + "appName": "testshostapp", + "bundledWebRuntime": false, + "npmClient": "npm", + "webDir": "build", + "overrideUserAgent": "level 1 override", + "appendUserAgent": "level 1 append", + "backgroundColor": "#ffffff", + "hideLogs": true, + "plugins": { + "SplashScreen": { + "launchShowDuration": 1 + } + }, + "cordova": {} +} diff --git a/ios/Capacitor/TestsHostApp/configurations/hierarchy.json b/ios/Capacitor/TestsHostApp/configurations/hierarchy.json new file mode 100644 index 0000000000..8c68a25702 --- /dev/null +++ b/ios/Capacitor/TestsHostApp/configurations/hierarchy.json @@ -0,0 +1,26 @@ +{ + "appId": "com.capacitorjs.testshostapp", + "appName": "testshostapp", + "bundledWebRuntime": false, + "npmClient": "npm", + "webDir": "build", + "overrideUserAgent": "level 1 override", + "appendUserAgent": "level 1 append", + "backgroundColor": "#ffffff", + "hideLogs": true, + "ios": { + "overrideUserAgent": "level 2 override", + "appendUserAgent": "level 2 append", + "backgroundColor": "#000000", + "hideLogs": false, + "allowsLinkPreview": false, + "scrollEnabled": false, + "contentInset": "scrollableAxes" + }, + "plugins": { + "SplashScreen": { + "launchShowDuration": 0 + } + }, + "cordova": {} +} diff --git a/ios/Capacitor/TestsHostApp/configurations/nonjson.json b/ios/Capacitor/TestsHostApp/configurations/nonjson.json new file mode 100644 index 0000000000..62f00c9f62 --- /dev/null +++ b/ios/Capacitor/TestsHostApp/configurations/nonjson.json @@ -0,0 +1 @@ +This is not even JSON. \ No newline at end of file diff --git a/ios/Capacitor/TestsHostApp/configurations/server.json b/ios/Capacitor/TestsHostApp/configurations/server.json new file mode 100644 index 0000000000..cfd0ce761a --- /dev/null +++ b/ios/Capacitor/TestsHostApp/configurations/server.json @@ -0,0 +1,23 @@ +{ + "appId": "com.capacitorjs.testshostapp", + "appName": "testshostapp", + "bundledWebRuntime": false, + "npmClient": "npm", + "webDir": "build", + "overrideUserAgent": "level 1 override", + "appendUserAgent": "level 1 append", + "backgroundColor": "#ffffff", + "hideLogs": true, + "server": { + "iosScheme": "override", + "allowNavigation": ["capacitorjs.com", "ionic.io", "192.168.0.1"], + "hostname": "myhost", + "url": "http://192.168.100.1:2057" + }, + "plugins": { + "SplashScreen": { + "launchShowDuration": 0 + } + }, + "cordova": {} +} diff --git a/swiftlint.config.js b/swiftlint.config.js index 8a654c8b9e..91ca8fda22 100644 --- a/swiftlint.config.js +++ b/swiftlint.config.js @@ -1,4 +1,5 @@ module.exports = { ...require('@ionic/swiftlint-config'), included: ['ios', 'ios-template'], + excluded: ['ios/Capacitor/CapacitorTests', 'ios/Capacitor/TestsHostApp'], };