@capacitor/ios 3.5.1 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,46 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [3.8.0](https://github.com/ionic-team/capacitor/compare/3.7.0...3.8.0) (2022-09-08)
7
+
8
+
9
+ ### Features
10
+
11
+ * **ios:** Add `setServerBasePath(_:)` to CAPBridgeProtocol ([#5860](https://github.com/ionic-team/capacitor/issues/5860)) ([#5892](https://github.com/ionic-team/capacitor/issues/5892)) ([9954281](https://github.com/ionic-team/capacitor/commit/995428129de9c06e199b67264b9840bb4a87939e))
12
+
13
+
14
+
15
+
16
+
17
+ # [3.7.0](https://github.com/ionic-team/capacitor/compare/3.6.0...3.7.0) (2022-08-01)
18
+
19
+
20
+ ### Features
21
+
22
+ * **ios:** Add `setServerBasePath(path:)` to CAPWebView ([#5742](https://github.com/ionic-team/capacitor/issues/5742)) ([#5748](https://github.com/ionic-team/capacitor/issues/5748)) ([9c25aad](https://github.com/ionic-team/capacitor/commit/9c25aad0ca8f97416753e7589a92efa401ad75bc))
23
+ * **ios:** Add overrideable router var for CAPWebView. ([#5743](https://github.com/ionic-team/capacitor/issues/5743)) ([#5760](https://github.com/ionic-team/capacitor/issues/5760)) ([6ba286d](https://github.com/ionic-team/capacitor/commit/6ba286d0429f70c9d2df9132f38c04f156f572b6))
24
+ * Add CapWebView ([#5715](https://github.com/ionic-team/capacitor/issues/5715)) ([#5730](https://github.com/ionic-team/capacitor/issues/5730)) ([bb54ac1](https://github.com/ionic-team/capacitor/commit/bb54ac1c9973dc01262119f0b0925f67c942264c))
25
+
26
+
27
+
28
+
29
+
30
+ # [3.6.0](https://github.com/ionic-team/capacitor/compare/3.5.1...3.6.0) (2022-06-17)
31
+
32
+
33
+ ### Bug Fixes
34
+
35
+ * **ios:** Use `URL(fileURLWithPath:)` instead of `URL(string:)` ([#5603](https://github.com/ionic-team/capacitor/issues/5603)) ([5fac1b2](https://github.com/ionic-team/capacitor/commit/5fac1b2da5aa5882087716cb2aa862d89173f4a1))
36
+
37
+
38
+ ### Features
39
+
40
+ * **iOS, Android:** add AppUUID Lib for plugins ([#5690](https://github.com/ionic-team/capacitor/issues/5690)) ([05e76cf](https://github.com/ionic-team/capacitor/commit/05e76cf526a44e07fa75f9482fa2223a13918638))
41
+
42
+
43
+
44
+
45
+
6
46
  ## [3.5.1](https://github.com/ionic-team/capacitor/compare/3.5.0...3.5.1) (2022-05-04)
7
47
 
8
48
  **Note:** Version bump only for package @capacitor/ios
@@ -0,0 +1,53 @@
1
+ import CommonCrypto
2
+ import Foundation
3
+
4
+ private func hexString(_ iterator: Array<UInt8>.Iterator) -> String {
5
+ return iterator.map { String(format: "%02x", $0) }.joined()
6
+ }
7
+
8
+ extension Data {
9
+ public var sha256: String {
10
+ var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
11
+ self.withUnsafeBytes { bytes in
12
+ _ = CC_SHA256(bytes.baseAddress, CC_LONG(self.count), &digest)
13
+ }
14
+ return hexString(digest.makeIterator())
15
+ }
16
+ }
17
+
18
+ public class AppUUID {
19
+ private static let key: String = "CapacitorAppUUID"
20
+
21
+ public static func getAppUUID() -> String {
22
+ assertAppUUID()
23
+ return readUUID()
24
+ }
25
+
26
+ public static func regenerateAppUUID() {
27
+ let uuid = generateUUID()
28
+ writeUUID(uuid)
29
+ }
30
+
31
+ private static func assertAppUUID() {
32
+ let uuid = readUUID()
33
+ if uuid == "" {
34
+ regenerateAppUUID()
35
+ }
36
+ }
37
+
38
+ private static func generateUUID() -> String {
39
+ let uuid: String = UUID.init().uuidString
40
+ return uuid.data(using: .utf8)!.sha256
41
+ }
42
+
43
+ private static func readUUID() -> String {
44
+ let defaults = UserDefaults.standard
45
+ return defaults.string(forKey: key) ?? ""
46
+ }
47
+
48
+ private static func writeUUID(_ uuid: String) {
49
+ let defaults = UserDefaults.standard
50
+ defaults.set(uuid, forKey: key)
51
+ }
52
+
53
+ }
@@ -70,6 +70,7 @@ import WebKit
70
70
  // MARK: - Paths, Files, Assets
71
71
  func localURL(fromWebURL webURL: URL?) -> URL?
72
72
  func portablePath(fromLocalURL localURL: URL?) -> URL?
73
+ func setServerBasePath(_ path: String)
73
74
 
74
75
  // MARK: - View Presentation
75
76
  func showAlertWith(title: String, message: String, buttonTitle: String)
@@ -258,16 +258,10 @@ extension CAPBridgeViewController {
258
258
  }
259
259
 
260
260
  @objc public func setServerBasePath(path: String) {
261
- let url = URL(fileURLWithPath: path, isDirectory: true)
262
- guard let capBridge = capacitorBridge, FileManager.default.fileExists(atPath: url.path) else {
263
- return
264
- }
265
- capBridge.config = capBridge.config.updatingAppLocation(url)
266
- capBridge.webViewAssetHandler.setAssetPath(url.path)
267
- if let url = capacitorBridge?.config.serverURL {
268
- DispatchQueue.main.async { [weak self] in
269
- _ = self?.webView?.load(URLRequest(url: url))
270
- }
261
+ guard let capBridge = capacitorBridge else { return }
262
+ capBridge.setServerBasePath(path)
263
+ DispatchQueue.main.async { [weak self] in
264
+ _ = self?.webView?.load(URLRequest(url: capBridge.config.serverURL))
271
265
  }
272
266
  }
273
267
  }
@@ -0,0 +1,242 @@
1
+ import Foundation
2
+ import WebKit
3
+ import UIKit
4
+
5
+ open class CAPWebView: UIView {
6
+ lazy var webView: WKWebView = createWebView(
7
+ with: configuration,
8
+ assetHandler: assetHandler,
9
+ delegationHandler: delegationHandler
10
+ )
11
+
12
+ private lazy var capacitorBridge = CapacitorBridge(
13
+ with: configuration,
14
+ delegate: self,
15
+ cordovaConfiguration: configDescriptor.cordovaConfiguration,
16
+ assetHandler: assetHandler,
17
+ delegationHandler: delegationHandler
18
+ )
19
+
20
+ public final var bridge: CAPBridgeProtocol {
21
+ return capacitorBridge
22
+ }
23
+
24
+ private lazy var configDescriptor = instanceDescriptor()
25
+ private lazy var configuration = InstanceConfiguration(with: configDescriptor, isDebug: CapacitorBridge.isDevEnvironment)
26
+
27
+ private lazy var assetHandler: WebViewAssetHandler = {
28
+ let handler = WebViewAssetHandler(router: router)
29
+ handler.setAssetPath(configuration.appLocation.path)
30
+ return handler
31
+ }()
32
+
33
+ private lazy var delegationHandler = WebViewDelegationHandler()
34
+
35
+ open var router: Router { _Router() }
36
+
37
+ public required init?(coder: NSCoder) {
38
+ super.init(coder: coder)
39
+ setup()
40
+ }
41
+
42
+ public init() {
43
+ super.init(frame: .zero)
44
+ setup()
45
+ }
46
+
47
+ private func setup() {
48
+ CAPLog.enableLogging = configuration.loggingEnabled
49
+ logWarnings(for: configDescriptor)
50
+
51
+ if configDescriptor.instanceType == .fixed { updateBinaryVersion() }
52
+
53
+ addSubview(webView)
54
+ webView.translatesAutoresizingMaskIntoConstraints = false
55
+ NSLayoutConstraint.activate([
56
+ webView.topAnchor.constraint(equalTo: topAnchor),
57
+ webView.bottomAnchor.constraint(equalTo: bottomAnchor),
58
+ webView.leadingAnchor.constraint(equalTo: leadingAnchor),
59
+ webView.trailingAnchor.constraint(equalTo: trailingAnchor)
60
+ ])
61
+
62
+ guard FileManager.default.fileExists(atPath: bridge.config.appStartFileURL.path) else { fatalLoadError() }
63
+ capacitorDidLoad()
64
+
65
+ let url = bridge.config.appStartServerURL
66
+ CAPLog.print("⚡️ Loading app at \(url.absoluteString)")
67
+ capacitorBridge.webViewDelegationHandler.willLoadWebview(webView)
68
+ _ = webView.load(URLRequest(url: url))
69
+ }
70
+
71
+ public lazy final var isNewBinary: Bool = {
72
+ if let curVersionCode = Bundle.main.infoDictionary?["CFBundleVersion"] as? String,
73
+ let curVersionName = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
74
+ if let lastVersionCode = UserDefaults.standard.string(forKey: "lastBinaryVersionCode"),
75
+ let lastVersionName = UserDefaults.standard.string(forKey: "lastBinaryVersionName") {
76
+ return (curVersionCode.isEqual(lastVersionCode) == false || curVersionName.isEqual(lastVersionName) == false)
77
+ }
78
+ }
79
+ return false
80
+ }()
81
+
82
+ open func instanceDescriptor() -> InstanceDescriptor {
83
+ let descriptor = InstanceDescriptor.init()
84
+ if !isNewBinary && !descriptor.cordovaDeployDisabled {
85
+ if let persistedPath = UserDefaults.standard.string(forKey: "serverBasePath"), !persistedPath.isEmpty {
86
+ if let libPath = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true).first {
87
+ descriptor.appLocation = URL(fileURLWithPath: libPath, isDirectory: true)
88
+ .appendingPathComponent("NoCloud")
89
+ .appendingPathComponent("ionic_built_snapshots")
90
+ .appendingPathComponent(URL(fileURLWithPath: persistedPath, isDirectory: true).lastPathComponent)
91
+ }
92
+ }
93
+ }
94
+ return descriptor
95
+ }
96
+
97
+ /**
98
+ Allows any additional configuration to be performed. The `webView` and `bridge` properties will be set by this point.
99
+
100
+ - Note: This is called before the webview has been added to the view hierarchy. Not all operations may be possible at
101
+ this time.
102
+ */
103
+ open func capacitorDidLoad() {
104
+ }
105
+
106
+ open func loadInitialContext(_ userContentController: WKUserContentController) {
107
+ CAPLog.print("in loadInitialContext base")
108
+ }
109
+
110
+ public func setServerBasePath(path: String) {
111
+ let url = URL(fileURLWithPath: path, isDirectory: true)
112
+ guard FileManager.default.fileExists(atPath: url.path) else { return }
113
+
114
+ capacitorBridge.config = capacitorBridge.config.updatingAppLocation(url)
115
+ capacitorBridge.webViewAssetHandler.setAssetPath(url.path)
116
+
117
+ DispatchQueue.main.async { [weak self] in
118
+ guard let self = self else { return }
119
+ _ = self.webView.load(URLRequest(url: self.capacitorBridge.config.serverURL))
120
+ }
121
+ }
122
+ }
123
+
124
+ extension CAPWebView {
125
+
126
+ open func webViewConfiguration(for instanceConfiguration: InstanceConfiguration) -> WKWebViewConfiguration {
127
+ let webViewConfiguration = WKWebViewConfiguration()
128
+ webViewConfiguration.allowsInlineMediaPlayback = true
129
+ webViewConfiguration.suppressesIncrementalRendering = false
130
+ webViewConfiguration.allowsAirPlayForMediaPlayback = true
131
+ webViewConfiguration.mediaTypesRequiringUserActionForPlayback = []
132
+ if let appendUserAgent = instanceConfiguration.appendedUserAgentString {
133
+ if let appName = webViewConfiguration.applicationNameForUserAgent {
134
+ webViewConfiguration.applicationNameForUserAgent = "\(appName) \(appendUserAgent)"
135
+ } else {
136
+ webViewConfiguration.applicationNameForUserAgent = appendUserAgent
137
+ }
138
+ }
139
+ return webViewConfiguration
140
+ }
141
+
142
+ private func createWebView(with configuration: InstanceConfiguration, assetHandler: WebViewAssetHandler, delegationHandler: WebViewDelegationHandler) -> WKWebView {
143
+ // set the cookie policy
144
+ HTTPCookieStorage.shared.cookieAcceptPolicy = HTTPCookie.AcceptPolicy.always
145
+ // setup the web view configuration
146
+ let webViewConfig = webViewConfiguration(for: configuration)
147
+ webViewConfig.setURLSchemeHandler(assetHandler, forURLScheme: configuration.localURL.scheme ?? InstanceDescriptorDefaults.scheme)
148
+ webViewConfig.userContentController = delegationHandler.contentController
149
+ // create the web view and set its properties
150
+ loadInitialContext(webViewConfig.userContentController)
151
+ let webView = WKWebView(frame: .zero, configuration: webViewConfig)
152
+ webView.scrollView.bounces = false
153
+ webView.scrollView.contentInsetAdjustmentBehavior = configuration.contentInsetAdjustmentBehavior
154
+ webView.allowsLinkPreview = configuration.allowLinkPreviews
155
+ webView.configuration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")
156
+ webView.scrollView.isScrollEnabled = configuration.scrollingEnabled
157
+
158
+ if let overrideUserAgent = configuration.overridenUserAgentString {
159
+ webView.customUserAgent = overrideUserAgent
160
+ }
161
+
162
+ if let backgroundColor = configuration.backgroundColor {
163
+ self.backgroundColor = backgroundColor
164
+ webView.backgroundColor = backgroundColor
165
+ webView.scrollView.backgroundColor = backgroundColor
166
+ } else if #available(iOS 13, *) {
167
+ // Use the system background colors if background is not set by user
168
+ self.backgroundColor = UIColor.systemBackground
169
+ webView.backgroundColor = UIColor.systemBackground
170
+ webView.scrollView.backgroundColor = UIColor.systemBackground
171
+ }
172
+
173
+ // set our delegates
174
+ webView.uiDelegate = delegationHandler
175
+ webView.navigationDelegate = delegationHandler
176
+ return webView
177
+ }
178
+
179
+ private func logWarnings(for descriptor: InstanceDescriptor) {
180
+ if descriptor.warnings.contains(.missingAppDir) {
181
+ CAPLog.print("⚡️ ERROR: Unable to find application directory at: \"\(descriptor.appLocation.absoluteString)\"!")
182
+ }
183
+ if descriptor.instanceType == .fixed {
184
+ if descriptor.warnings.contains(.missingFile) {
185
+ CAPLog.print("Unable to find capacitor.config.json, make sure it exists and run npx cap copy.")
186
+ }
187
+ if descriptor.warnings.contains(.invalidFile) {
188
+ CAPLog.print("Unable to parse capacitor.config.json. Make sure it's valid JSON.")
189
+ }
190
+ if descriptor.warnings.contains(.missingCordovaFile) {
191
+ CAPLog.print("Unable to find config.xml, make sure it exists and run npx cap copy.")
192
+ }
193
+ if descriptor.warnings.contains(.invalidCordovaFile) {
194
+ CAPLog.print("Unable to parse config.xml. Make sure it's valid XML.")
195
+ }
196
+ }
197
+ }
198
+
199
+ private func updateBinaryVersion() {
200
+ guard isNewBinary else {
201
+ return
202
+ }
203
+ guard let versionCode = Bundle.main.infoDictionary?["CFBundleVersion"] as? String,
204
+ let versionName = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else {
205
+ return
206
+ }
207
+ let prefs = UserDefaults.standard
208
+ prefs.set(versionCode, forKey: "lastBinaryVersionCode")
209
+ prefs.set(versionName, forKey: "lastBinaryVersionName")
210
+ prefs.set("", forKey: "serverBasePath")
211
+ prefs.synchronize()
212
+ }
213
+
214
+ private func fatalLoadError() -> Never {
215
+ printLoadError()
216
+ exit(1)
217
+ }
218
+
219
+ private func printLoadError() {
220
+ let fullStartPath = capacitorBridge.config.appStartFileURL.path
221
+
222
+ CAPLog.print("⚡️ ERROR: Unable to load \(fullStartPath)")
223
+ CAPLog.print("⚡️ This file is the root of your web app and must exist before")
224
+ CAPLog.print("⚡️ Capacitor can run. Ensure you've run capacitor copy at least")
225
+ CAPLog.print("⚡️ or, if embedding, that this directory exists as a resource directory.")
226
+ }
227
+ }
228
+
229
+ extension CAPWebView: CAPBridgeDelegate {
230
+ internal var bridgedWebView: WKWebView? {
231
+ return webView
232
+ }
233
+
234
+ internal var bridgedViewController: UIViewController? {
235
+ // search for the parent view controller
236
+ var object = self.next
237
+ while !(object is UIViewController) && object != nil {
238
+ object = object?.next
239
+ }
240
+ return object as? UIViewController
241
+ }
242
+ }
@@ -156,6 +156,13 @@ internal class CapacitorBridge: NSObject, CAPBridgeProtocol {
156
156
  statusBarAnimation = animation
157
157
  }
158
158
 
159
+ public func setServerBasePath(_ path: String) {
160
+ let url = URL(fileURLWithPath: path, isDirectory: true)
161
+ guard FileManager.default.fileExists(atPath: url.path) else { return }
162
+ config = config.updatingAppLocation(url)
163
+ webViewAssetHandler.setAssetPath(url.path)
164
+ }
165
+
159
166
  // MARK: - Static Methods
160
167
 
161
168
  /**
@@ -125,6 +125,15 @@ public protocol JSValueContainer: JSStringContainer, JSBoolContainer, JSIntConta
125
125
  }
126
126
 
127
127
  extension JSValueContainer {
128
+ public func getValue(_ key: String) -> JSValue? {
129
+ return jsObjectRepresentation[key]
130
+ }
131
+
132
+ @available(*, message: "All values returned conform to JSValue, use getValue(_:) instead.", renamed: "getValue(_:)")
133
+ public func getAny(_ key: String) -> Any? {
134
+ return getValue(key)
135
+ }
136
+
128
137
  public func getString(_ key: String) -> String? {
129
138
  return jsObjectRepresentation[key] as? String
130
139
  }
@@ -10,18 +10,20 @@ import Foundation
10
10
 
11
11
  public protocol Router {
12
12
  func route(for path: String) -> String
13
+ var basePath: String { get set }
13
14
  }
14
15
 
15
16
  // swiftlint:disable:next type_name
16
17
  internal struct _Router: Router {
18
+ var basePath: String = ""
17
19
  func route(for path: String) -> String {
18
- let pathUrl = URL(string: path)
20
+ let pathUrl = URL(fileURLWithPath: path)
19
21
 
20
- // if the pathUrl is null, then it is an invalid url (meaning it is empty or just plain invalid) then we want to route to /index.html
21
- if pathUrl?.pathExtension.isEmpty ?? true {
22
- return "/index.html"
22
+ // If there's no path extension it also means the path is empty or a SPA route
23
+ if pathUrl.pathExtension.isEmpty {
24
+ return basePath + "/index.html"
23
25
  }
24
26
 
25
- return path
27
+ return basePath + path
26
28
  }
27
29
  }
@@ -3,8 +3,7 @@ import MobileCoreServices
3
3
 
4
4
  @objc(CAPWebViewAssetHandler)
5
5
  internal class WebViewAssetHandler: NSObject, WKURLSchemeHandler {
6
- private let router: Router
7
- private var basePath: String = ""
6
+ private var router: Router
8
7
 
9
8
  init(router: Router) {
10
9
  self.router = router
@@ -12,19 +11,18 @@ internal class WebViewAssetHandler: NSObject, WKURLSchemeHandler {
12
11
  }
13
12
 
14
13
  func setAssetPath(_ assetPath: String) {
15
- self.basePath = assetPath
14
+ router.basePath = assetPath
16
15
  }
17
16
 
18
17
  func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
19
- var startPath = self.basePath
18
+ let startPath: String
20
19
  let url = urlSchemeTask.request.url!
21
20
  let stringToLoad = url.path
22
21
 
23
- let resolvedRoute = router.route(for: stringToLoad)
24
22
  if stringToLoad.starts(with: CapacitorBridge.fileStartIdentifier) {
25
23
  startPath = stringToLoad.replacingOccurrences(of: CapacitorBridge.fileStartIdentifier, with: "")
26
24
  } else {
27
- startPath.append(resolvedRoute)
25
+ startPath = router.route(for: stringToLoad)
28
26
  }
29
27
 
30
28
  let localUrl = URL.init(string: url.absoluteString)!
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capacitor/ios",
3
- "version": "3.5.1",
3
+ "version": "3.8.0",
4
4
  "description": "Capacitor: Cross-platform apps with JavaScript and the web",
5
5
  "homepage": "https://capacitorjs.com",
6
6
  "author": "Ionic Team <hi@ionic.io> (https://ionic.io)",
@@ -24,10 +24,10 @@
24
24
  "xc:build:CapacitorCordova": "cd CapacitorCordova && xcodebuild && cd .."
25
25
  },
26
26
  "peerDependencies": {
27
- "@capacitor/core": "^3.5.0"
27
+ "@capacitor/core": "^3.8.0"
28
28
  },
29
29
  "publishConfig": {
30
30
  "access": "public"
31
31
  },
32
- "gitHead": "009064dbf01cbc5900bbc16053b6d7535ad4c2b4"
32
+ "gitHead": "8ec9df5d4a8c5760c834c7706aaedea1b402108b"
33
33
  }