@webspatial/platform-visionos 1.0.3 → 1.0.5

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.
Files changed (74) hide show
  1. package/package.json +2 -2
  2. package/web-spatial/EventEmitter.swift +56 -0
  3. package/web-spatial/JSBCommand.swift +348 -0
  4. package/web-spatial/SpatialObject.swift +108 -0
  5. package/web-spatial/WebMsgCommand.swift +119 -0
  6. package/web-spatial/WebSpatialApp.swift +111 -0
  7. package/web-spatial/{libs/uiKitDelegate/Window.swift → Window.swift} +1 -0
  8. package/web-spatial/manager/Dynamic3DManager.swift +114 -0
  9. package/web-spatial/manager/JSBManager.swift +148 -0
  10. package/web-spatial/manager/WKWebViewManager.swift +39 -0
  11. package/web-spatial/{libs/webView/manifest.swift → manifest.swift} +16 -5
  12. package/web-spatial/model/SpatialApp.swift +190 -0
  13. package/web-spatial/model/SpatialScene.swift +1042 -0
  14. package/web-spatial/model/Spatialized2DElement.swift +128 -0
  15. package/web-spatial/model/SpatializedDynamic3DElement.swift +34 -0
  16. package/web-spatial/model/SpatializedElement.swift +95 -0
  17. package/web-spatial/model/SpatializedStatic3DElement.swift +19 -0
  18. package/web-spatial/model/dynamic3d/Geometry.swift +95 -0
  19. package/web-spatial/model/dynamic3d/SpatialComponent.swift +72 -0
  20. package/web-spatial/model/dynamic3d/SpatialEntity.swift +211 -0
  21. package/web-spatial/model/dynamic3d/SpatialMaterial.swift +39 -0
  22. package/web-spatial/model/dynamic3d/SpatialModelEntity.swift +39 -0
  23. package/web-spatial/model/dynamic3d/SpatialModelResource.swift +38 -0
  24. package/web-spatial/model/dynamic3d/SpatialTextureResource.swift +18 -0
  25. package/web-spatial/protocol/ScrollAbleSpatialElementContainer.swift +1 -0
  26. package/web-spatial/protocol/SpatialScrollAble.swift +8 -0
  27. package/web-spatial/protocol/SpatializedElementContainer.swift +8 -0
  28. package/web-spatial/protocol/WebMsgSender.swift +5 -0
  29. package/web-spatial/types/Vec2.swift +12 -0
  30. package/web-spatial/types/Vec3.swift +7 -0
  31. package/web-spatial/view/SceneHandlerUIView.swift +92 -0
  32. package/web-spatial/{views/ui/NavView.swift → view/SpatialNavView.swift} +149 -77
  33. package/web-spatial/view/SpatialSceneContentView.swift +218 -0
  34. package/web-spatial/view/SpatialSceneView.swift +53 -0
  35. package/web-spatial/view/Spatialized2DElementView.swift +96 -0
  36. package/web-spatial/view/SpatializedDynamic3DView.swift +104 -0
  37. package/web-spatial/view/SpatializedElementView.swift +178 -0
  38. package/web-spatial/view/SpatializedStatic3DView.swift +70 -0
  39. package/web-spatial/{views → view/view-modifier}/MaterialWithBorderCornerModifier.swift +28 -8
  40. package/web-spatial/webview/SpatialWebController.swift +300 -0
  41. package/web-spatial/webview/SpatialWebView.swift +34 -0
  42. package/web-spatial/webview/SpatialWebViewModel.swift +307 -0
  43. package/web-spatial.xcodeproj/project.pbxproj +126 -207
  44. package/web-spatial/libs/EventEmitter.swift +0 -25
  45. package/web-spatial/libs/SpatialComponent.swift +0 -24
  46. package/web-spatial/libs/SpatialEntity.swift +0 -172
  47. package/web-spatial/libs/SpatialInputComponent.swift +0 -19
  48. package/web-spatial/libs/SpatialMeshResource.swift +0 -12
  49. package/web-spatial/libs/SpatialModel3DComponent.swift +0 -51
  50. package/web-spatial/libs/SpatialModelComponent.swift +0 -25
  51. package/web-spatial/libs/SpatialObject.swift +0 -140
  52. package/web-spatial/libs/SpatialPhysicallyBasedMaterial.swift +0 -19
  53. package/web-spatial/libs/SpatialViewComponent.swift +0 -8
  54. package/web-spatial/libs/SpatialWindowComponent.swift +0 -454
  55. package/web-spatial/libs/SpatialWindowContainer.swift +0 -153
  56. package/web-spatial/libs/Utils/CommandManager.swift +0 -823
  57. package/web-spatial/libs/Utils/PerfClock.swift +0 -43
  58. package/web-spatial/libs/Utils/SceneManager.swift +0 -101
  59. package/web-spatial/libs/Utils/WindowContainerMgr.swift +0 -122
  60. package/web-spatial/libs/json/JsonParser.swift +0 -45
  61. package/web-spatial/libs/webView/UpdateSystem.swift +0 -26
  62. package/web-spatial/libs/webView/backend/NativeWebView.swift +0 -350
  63. package/web-spatial/views/ImmersiveView.swift +0 -17
  64. package/web-spatial/views/OpenDismissHandlerUI.swift +0 -45
  65. package/web-spatial/views/PlainWindowContainerView.swift +0 -132
  66. package/web-spatial/views/SpatialModel3DView.swift +0 -187
  67. package/web-spatial/views/SpatialViewUI.swift +0 -168
  68. package/web-spatial/views/SpatialWebViewUI.swift +0 -179
  69. package/web-spatial/views/VolumetricWindowContainerView.swift +0 -30
  70. package/web-spatial/web_spatialApp.swift +0 -141
  71. /package/web-spatial/{libs/Utils → Utils}/ColorExtension.swift +0 -0
  72. /package/web-spatial/{libs/Utils → Utils}/Logger.swift +0 -0
  73. /package/web-spatial/{views → view}/LoadingView.swift +0 -0
  74. /package/web-spatial/{views → view/view-modifier}/HideViewModifier.swift +0 -0
@@ -1,43 +0,0 @@
1
- import SwiftUI
2
-
3
- struct PerfStats: Codable {
4
- // Time from app start until the first call to setMaterial. In milliseconds
5
- var firstBackgroundSet: Int = 0
6
-
7
- // Attempts to track the number of commands handled over the last second
8
- var commandCounter: Int = 0
9
- var commandCounterStartTime: Int = 0
10
- var commandsPerSecond = 0.0
11
- }
12
-
13
- class PerfClock {
14
- var perfStats = PerfStats()
15
-
16
- let createTimeMS: Int
17
-
18
- init() {
19
- createTimeMS = PerfClock.getCurrentTimeMS()
20
- perfStats.commandCounterStartTime = createTimeMS
21
- }
22
-
23
- func backgroundSet() {
24
- if perfStats.firstBackgroundSet == 0 {
25
- perfStats.firstBackgroundSet = PerfClock.getCurrentTimeMS() - createTimeMS
26
- }
27
- }
28
-
29
- func onMessage() {
30
- perfStats.commandCounter += 1
31
- let dt = PerfClock.getCurrentTimeMS() - createTimeMS
32
- if dt > 1000 {
33
- perfStats.commandsPerSecond = Double(perfStats.commandCounter) / (Double(1000.0) / Double(dt))
34
-
35
- perfStats.commandCounter = 0
36
- perfStats.commandCounterStartTime = PerfClock.getCurrentTimeMS()
37
- }
38
- }
39
-
40
- static func getCurrentTimeMS() -> Int {
41
- return Int(Date().timeIntervalSince1970 * 1000)
42
- }
43
- }
@@ -1,101 +0,0 @@
1
- import Foundation
2
-
3
- class SceneManager {
4
- static let Instance = SceneManager()
5
-
6
- private init() {}
7
-
8
- // create scene
9
- // if config is provided, it show immediately
10
- // else it won't show until showRoot is called
11
- func createRoot(target: SpatialWindowComponent, windowID: String, config: WindowContainerOptions? = nil) {
12
- let windowContainerID = UUID().uuidString
13
- // open window
14
- let wgd = WindowContainerData(
15
- windowStyle: "Plain",
16
- windowContainerID: windowContainerID
17
- )
18
- let ent = SpatialEntity()
19
- ent.coordinateSpace = CoordinateSpaceMode.ROOT
20
- let windowComponent = SpatialWindowComponent(
21
- parentWindowContainerID: windowContainerID
22
- )
23
-
24
- if let spawnedWebView = target.spawnedNativeWebviews.removeValue(forKey: windowID) {
25
- windowComponent.getView()!.destroy()
26
- windowComponent.setView(wv: spawnedWebView)
27
- windowComponent.getView()!.webViewHolder.webViewCoordinator!.webViewRef = windowComponent
28
- // focusRoot need the windowContainerID
29
- windowComponent.evaluateJS(js: "window._webSpatialGroupID='\(windowContainerID)';")
30
- // tell new webview parentWindowContainerID to open loadingview
31
- windowComponent.evaluateJS(js: "window._webSpatialParentGroupID='\(target.parentWindowContainerID)';")
32
-
33
- if config != nil {
34
- // signal off hook
35
- windowComponent.evaluateJS(js: "window._SceneHookOff=true;")
36
- }
37
- } else {
38
- logger.warning("Unable to find spawned webview")
39
- }
40
-
41
- ent.addComponent(windowComponent)
42
-
43
- let wg = SpatialWindowContainer.getOrCreateSpatialWindowContainer(windowContainerID, wgd)
44
- ent.setParentWindowContainer(wg: wg)
45
-
46
- if let config = config {
47
- showRoot(
48
- target: windowComponent,
49
- config: config,
50
- parentWindowContainerID: target.parentWindowContainerID
51
- )
52
- }
53
- }
54
-
55
- // set defaultvalues for the scene and show it
56
- func showRoot(target: SpatialWindowComponent, config: WindowContainerOptions, parentWindowContainerID: String) {
57
- let plainDV = WindowContainerPlainDefaultValues(
58
- config
59
- )
60
-
61
- if let pwg = SpatialWindowContainer.getSpatialWindowContainer(parentWindowContainerID),
62
- let wg = SpatialWindowContainer.getSpatialWindowContainer(
63
- target.parentWindowContainerID
64
- )
65
- {
66
- WindowContainerMgr.Instance
67
- .updateWindowContainerPlainDefaultValues(
68
- plainDV
69
- ) // set default values
70
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
71
- pwg.openWindowData.send(wg.wgd) // openwindow
72
- }
73
- }
74
- }
75
-
76
- // bring the scene to focus
77
- func focusRoot(target: SpatialWindowComponent, windowContainerID: String) {
78
- if let wg = SpatialWindowContainer.getSpatialWindowContainer(windowContainerID) {
79
- wg.openWindowData.send(wg.wgd)
80
- }
81
- }
82
-
83
- // show LodingView when window.xrCurrentSceneDefaults is executing
84
- func setLoading(_ method: LoadingMethod, windowContainerID: String) {
85
- // trigger open loading view by parent windowContainer due to current windowContainer isn't visible yet
86
- if let wg = SpatialWindowContainer.getSpatialWindowContainer(windowContainerID) {
87
- let lwgdata = LoadingWindowContainerData(
88
- method: method,
89
- windowStyle: nil
90
- )
91
- wg.setLoadingWindowData.send(lwgdata)
92
- }
93
- }
94
-
95
- // dismiss the scene content when webview closed
96
- func closeRoot(_ target: SpatialWindowComponent) {
97
- if let wg = SpatialWindowContainer.getSpatialWindowContainer(target.parentWindowContainerID) {
98
- wg.closeWindowData.send(wg.wgd)
99
- }
100
- }
101
- }
@@ -1,122 +0,0 @@
1
- import Combine
2
- import SwiftUI
3
- import UIKit
4
-
5
- // TODO: maybe get input from pwa manifest
6
- let defaultWindowContainerConfig = WindowContainerOptions(
7
- defaultSize: WindowContainerOptions.Size(
8
- width: DefaultPlainWindowContainerSize.width,
9
- height: DefaultPlainWindowContainerSize.height
10
- ),
11
- resizability: nil
12
- )
13
-
14
- struct WindowContainerData: Decodable, Hashable, Encodable {
15
- let windowStyle: String
16
- let windowContainerID: String
17
- }
18
-
19
- struct WindowContainerResizability: Decodable, Encodable {
20
- let resizeRange: ResizeRange?
21
- }
22
-
23
- enum LoadingMethod: String, Decodable, Encodable, Hashable {
24
- case show
25
- case hide
26
- }
27
-
28
- struct LoadingWindowContainerData: Decodable, Hashable, Encodable {
29
- let method: LoadingMethod
30
- let windowStyle: String?
31
- }
32
-
33
- struct WindowContainerPlainDefaultValues {
34
- var defaultSize: CGSize?
35
- var windowResizability: WindowResizability?
36
- var resizeRange: ResizeRange?
37
- }
38
-
39
- // support WindowContainerOptions => WindowContainerPlainDefaultValues
40
- extension WindowContainerPlainDefaultValues {
41
- init(_ options: WindowContainerOptions) {
42
- defaultSize = CGSize(
43
- width: options.defaultSize?.width ?? DefaultPlainWindowContainerSize.width,
44
- height: options.defaultSize?.height ?? DefaultPlainWindowContainerSize.height
45
- )
46
- windowResizability = getWindowResizability(nil)
47
- resizeRange = options.resizability
48
- }
49
- }
50
-
51
- struct ResizeRange: Codable {
52
- var minWidth: Double?
53
- var minHeight: Double?
54
- var maxWidth: Double?
55
- var maxHeight: Double?
56
- }
57
-
58
- // incomming JSB data
59
- struct WindowContainerOptions: Codable {
60
- // windowContainer
61
- let defaultSize: Size?
62
- struct Size: Codable {
63
- var width: Double
64
- var height: Double
65
- }
66
-
67
- let resizability: ResizeRange?
68
- }
69
-
70
- func getWindowResizability(_ windowResizability: String?) -> WindowResizability {
71
- switch windowResizability {
72
- case "automatic":
73
- return .automatic
74
- case "contentSize":
75
- return .contentSize
76
- case "contentMinSize":
77
- return .contentMinSize
78
- default:
79
- return .automatic
80
- }
81
- }
82
-
83
- @Observable
84
- class WindowContainerMgr: ObservableObject {
85
- static let Instance = WindowContainerMgr()
86
-
87
- // cache for dynamic loading scene reopen
88
- var memorizedMainSceneConfig: WindowContainerPlainDefaultValues? = nil
89
-
90
- private init() {
91
- setToMainSceneCfg()
92
- }
93
-
94
- private var wgSetting: WindowContainerPlainDefaultValues = .init(
95
- defaultSize: CGSize(width: 1080, height: 720 + (pwaManager.display != .fullscreen ? NavView.navHeight : 0)),
96
- windowResizability: .automatic,
97
- resizeRange: nil
98
- )
99
-
100
- func getValue() -> WindowContainerPlainDefaultValues {
101
- return wgSetting
102
- }
103
-
104
- func setToMainSceneCfg() {
105
- if let cfg = memorizedMainSceneConfig != nil ? memorizedMainSceneConfig : WindowContainerPlainDefaultValues(pwaManager.mainScene) {
106
- updateWindowContainerPlainDefaultValues(cfg)
107
- }
108
- }
109
-
110
- func updateWindowContainerPlainDefaultValues(_ data: WindowContainerPlainDefaultValues) {
111
- if var newSize = data.defaultSize {
112
- newSize.height += (pwaManager.display != .fullscreen ? NavView.navHeight : 0)
113
- wgSetting.defaultSize = newSize
114
- }
115
- if let newResizability = data.windowResizability {
116
- wgSetting.windowResizability = newResizability
117
- }
118
- if let newResizeRange = data.resizeRange {
119
- wgSetting.resizeRange = newResizeRange
120
- }
121
- }
122
- }
@@ -1,45 +0,0 @@
1
- import Foundation
2
-
3
- class JsonParser {
4
- var json: [String: AnyObject]?
5
- init(str: String?) {
6
- if let toParse = str {
7
- if let data = toParse.data(using: .utf8) {
8
- do {
9
- json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: AnyObject]
10
- } catch {}
11
- }
12
- }
13
- }
14
-
15
- func getValue<T>(lookup: [String]) -> T? {
16
- if var anyObj = json as? AnyObject {
17
- for (index, str) in lookup.enumerated() {
18
- if index == lookup.count - 1 {
19
- return anyObj[str] as? T
20
- }
21
- if let o = (anyObj as? [String: AnyObject]),
22
- let x = o[str]
23
- {
24
- anyObj = x
25
- } else {
26
- return nil
27
- }
28
- }
29
- }
30
- return nil
31
- }
32
-
33
- // Convert the instance to a JSON string
34
- static func serialize<T: Encodable>(_ data: T) -> String? {
35
- let encoder = JSONEncoder()
36
- encoder.outputFormatting = .prettyPrinted // Makes the JSON output more readable
37
- do {
38
- let jsonData = try encoder.encode(data)
39
- return String(data: jsonData, encoding: .utf8) // Convert Data to String
40
- } catch {
41
- logger.error("Failed to encode WindowContainerOptions to JSON: \(error)")
42
- return nil
43
- }
44
- }
45
- }
@@ -1,26 +0,0 @@
1
- import Foundation
2
- import RealityKit
3
-
4
- struct UpdateWebViewComponent: Component {
5
- var webView: SpatialWindowComponent?
6
- init() {}
7
- }
8
-
9
- class UpdateWebViewSystem: System {
10
- static let query = EntityQuery(where: .has(UpdateWebViewComponent.self))
11
- required init(scene: RealityKit.Scene) {
12
- // Perform required initialization or setup.
13
- }
14
-
15
- var pos = 0.0
16
- func update(context: SceneUpdateContext) {
17
- for entity in context.entities(matching: Self.query, updatingSystemWhen: .rendering) {
18
- pos += context.deltaTime
19
-
20
- var x = Transform()
21
- x.translation.x = Float(sin(pos)) * 0.3
22
- x.translation.z = 0.2
23
- entity.move(to: x, relativeTo: nil)
24
- }
25
- }
26
- }
@@ -1,350 +0,0 @@
1
- import Combine
2
- import Foundation
3
- import RealityKit
4
- import RealityKitContent
5
- import SwiftUI
6
- @preconcurrency import WebKit
7
-
8
- class WebViewHolder {
9
- var needsUpdate = false
10
- var appleWebView: WKWebView?
11
- var webViewCoordinator: Coordinator?
12
- deinit {
13
- appleWebView = nil
14
- }
15
- }
16
-
17
- struct PreloadStyleSettings: Codable {
18
- var cornerRadius: CornerRadius? = .init()
19
- var backgroundMaterial: BackgroundMaterial? = .None
20
- }
21
-
22
- struct WebviewEarlyStyle {
23
- let webview: WKWebView
24
- let style: PreloadStyleSettings
25
- }
26
-
27
- // event of forcestyle handler
28
- var webviewGetEarlyStyleData = PassthroughSubject<WebviewEarlyStyle, Never>()
29
-
30
- class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler, WKUIDelegate, UIScrollViewDelegate, WKURLSchemeHandler {
31
- let decoder = JSONDecoder()
32
- override public init() {
33
- WKWebView.enableFileScheme() // ensure the handler is usable
34
- }
35
-
36
- func webView(_ webView: WKWebView, start urlSchemeTask: any WKURLSchemeTask) {
37
- // Parse the style json string from url
38
- let url = urlSchemeTask.request.url
39
-
40
- // Local web projects accessing resources through relative paths will default to using the file protocol
41
- if url!.absoluteString.starts(with: "file://") {
42
- let resource: String = pwaManager.getLocalResourceURL(url: url!.absoluteString)
43
- var urlRequest = urlSchemeTask.request
44
-
45
- if resource != "" {
46
- urlRequest = URLRequest(url: URL(string: resource)!)
47
- } else {
48
- return
49
- }
50
- let session = URLSession(configuration: URLSessionConfiguration.default)
51
- let dataTask = session.dataTask(with: urlRequest) { [task = urlSchemeTask as AnyObject] data, response, _ in
52
- guard let task = task as? WKURLSchemeTask else { return }
53
-
54
- task.didReceive(response!)
55
- task.didReceive(data!)
56
- task.didFinish()
57
- }
58
- dataTask.resume()
59
- return
60
- }
61
- }
62
-
63
- func webView(_ webView: WKWebView, stop urlSchemeTask: any WKURLSchemeTask) {}
64
-
65
- @Environment(\.openWindow) private var openWindow
66
- @Environment(\.dismissWindow) private var dismissWindow
67
- @Environment(\.dismiss) private var dismiss
68
- @Environment(\.openImmersiveSpace) private var openImmersiveSpace
69
-
70
- deinit {}
71
-
72
- weak var webViewRef: SpatialWindowComponent? = nil
73
-
74
- func webView(_ webView: WKWebView, didStartProvisionalNavigation: WKNavigation!) {
75
- webViewRef?.didStartLoadPage()
76
- }
77
-
78
- func webView(
79
- _ webView: WKWebView,
80
- didCommit navigation: WKNavigation!
81
- ) {
82
- webViewRef?.didStartReceivePageContent()
83
- }
84
-
85
- func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
86
- webViewRef?.loadRequestWV?.didLoadChild(loadRequestID: webViewRef!.loadRequestID, resourceID: webViewRef!.id)
87
- webViewRef?.loadRequestID = -1
88
- webViewRef?.didFinishLoadPage()
89
- }
90
-
91
- func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Swift.Void) {
92
- if let url = navigationAction.request.url,
93
- url.absoluteString == "webspatial://createWindowContext"
94
- {
95
- decisionHandler(.cancel)
96
- return
97
- }
98
- if let url = navigationAction.request.url,
99
- url.absoluteString.starts(with: "forcestyle://")
100
- {
101
- var styleJsonString: String? = URLComponents(string: url.absoluteString)?.queryItems?.first(where: { $0.name == "style" })?.value
102
- do {
103
- if styleJsonString?.contains("?") != nil {
104
- // remove invalid query string
105
- // before "{\"glassEffect\":true,\"cornerRadius\":50}?uniqueURL=0.0010192470591506853"
106
- // after "{\"glassEffect\":true,\"cornerRadius\":50}"
107
- styleJsonString = styleJsonString?
108
- .components(separatedBy: "?").first
109
- }
110
- let styleToSet = try decoder.decode(PreloadStyleSettings.self, from: styleJsonString!.data(using: .utf8)!)
111
-
112
- webviewGetEarlyStyleData.send(WebviewEarlyStyle(webview: webView, style: styleToSet))
113
- } catch {
114
- logger.warning("Style url parse failure " + error.localizedDescription)
115
- }
116
- decisionHandler(.cancel)
117
- return
118
- }
119
- var resource = navigationAction.request.url!.absoluteString
120
- if pwaManager.isLocal {
121
- resource = pwaManager.getLocalResourceURL(url: resource)
122
- }
123
- if pwaManager.checkInScope(url: navigationAction.request.url!.absoluteString) {
124
- if navigationAction.navigationType == .backForward {
125
- // backward/forward
126
- webViewRef?.didNavBackForward()
127
- }
128
- decisionHandler(.allow)
129
- } else {
130
- decisionHandler(.cancel)
131
- UIApplication.shared.open(navigationAction.request.url!, options: [:], completionHandler: nil)
132
- }
133
- }
134
-
135
- func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Swift.Void) {
136
- decisionHandler(.allow)
137
- }
138
-
139
- func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
140
- logger.warning("Navigation failed!!! " + error.localizedDescription)
141
- if let urlError = (error as? URLError) {
142
- logger.warning("URL ERROR: " + (urlError.failingURL != nil ? (urlError.failingURL!.absoluteString) : "no URL found"))
143
- if urlError.code == .cannotConnectToHost {
144
- webViewRef?.didFailLoadPage()
145
- }
146
- }
147
- }
148
-
149
- // Warning this should likeley be removed. There seems to be a bug with SSL loading on simulator https://stackoverflow.com/questions/27100540/allow-unverified-ssl-certificates-in-wkwebview
150
- // NSAllowsArbitraryLoads should also be removed from Info.plist if shipping an app
151
- // this is the workaround
152
- func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
153
- guard let serverTrust = challenge.protectionSpace.serverTrust else { return completionHandler(.useCredential, nil) }
154
- let exceptions = SecTrustCopyExceptions(serverTrust)
155
- SecTrustSetExceptions(serverTrust, exceptions)
156
- completionHandler(.useCredential, URLCredential(trust: serverTrust))
157
- }
158
-
159
- func webView(
160
- _ webView: WKWebView,
161
- createWebViewWith configuration: WKWebViewConfiguration,
162
- for navigationAction: WKNavigationAction,
163
- windowFeatures: WKWindowFeatures
164
- ) -> WKWebView? {
165
- var resource = navigationAction.request.url!.absoluteString
166
- if pwaManager.isLocal {
167
- resource = pwaManager.getLocalResourceURL(url: resource)
168
- }
169
- if resource != "webspatial://createWindowContext",
170
- !pwaManager.checkInScope(url: resource)
171
- {
172
- // open in safari
173
- UIApplication.shared.open(navigationAction.request.url!, options: [:], completionHandler: nil)
174
- return nil
175
- }
176
-
177
- var wvNative = WebViewNative()
178
- var needsUpdate = false
179
- if resource.starts(with: "file://") {
180
- wvNative.url = URL(string: resource)!
181
- needsUpdate = true
182
- }
183
- _ = wvNative.createResources(configuration: configuration, needsUpdate: needsUpdate)
184
-
185
- webViewRef!.didSpawnWebView(wv: wvNative)
186
-
187
- return wvNative.webViewHolder.appleWebView
188
- }
189
-
190
- // handle close
191
- func webViewDidClose(_ webView: WKWebView) {
192
- webViewRef!.didCloseWebView()
193
- }
194
-
195
- // receive message from wkwebview
196
- func userContentController(
197
- _ userContentController: WKUserContentController,
198
- didReceive message: WKScriptMessage
199
- ) {
200
- let command = CommandManager.Instance.decode(jsonData: message.body as! String)
201
- if let wv = webViewRef {
202
- CommandManager.Instance.doCommand(target: wv, jsb: command)
203
- }
204
- }
205
-
206
- func scrollViewDidScroll(_ scrollView: UIScrollView) {
207
- webViewRef?.scrollOffset = scrollView.contentOffset
208
- if webViewRef != nil {
209
- let wg = SpatialWindowContainer.getSpatialWindowContainer(webViewRef!.parentWindowContainerID)!
210
- wg.updateFrame = !(wg.updateFrame)
211
- }
212
- }
213
-
214
- private var isObserving = false
215
- func startObserving(webView: WKWebView) {
216
- guard !isObserving else { return }
217
- webView.addObserver(self, forKeyPath: #keyPath(WKWebView.url), options: .new, context: nil)
218
- isObserving = true
219
- }
220
-
221
- func stopObserving(webView: WKWebView) {
222
- guard isObserving else { return }
223
- webView.removeObserver(self, forKeyPath: #keyPath(WKWebView.url))
224
- isObserving = false
225
- }
226
-
227
- override func observeValue(
228
- forKeyPath keyPath: String?,
229
- of object: Any?,
230
- change: [NSKeyValueChangeKey: Any]?,
231
- context: UnsafeMutableRawPointer?
232
- ) {
233
- if keyPath == #keyPath(WKWebView.url),
234
- let url = (object as? WKWebView)?.url?.absoluteString
235
- {
236
- DispatchQueue.main.async {
237
- self.webViewRef?.navInfo.url = url
238
- }
239
- }
240
- }
241
- }
242
-
243
- struct WebViewNative: UIViewRepresentable {
244
- weak var webViewRef: SpatialWindowComponent? = nil
245
- var url: URL = .init(filePath: "/")
246
- var webViewHolder = WebViewHolder()
247
-
248
- func destroy() {
249
- // Remove references to Coordinator so that it gets cleaned up by arc
250
- webViewHolder.appleWebView?.configuration.userContentController.removeScriptMessageHandler(forName: "bridge")
251
- webViewHolder.appleWebView?.uiDelegate = nil
252
- webViewHolder.appleWebView?.navigationDelegate = nil
253
- webViewHolder.appleWebView?.scrollView.delegate = nil
254
- webViewHolder.appleWebView = nil
255
- }
256
-
257
- func makeCoordinator() -> Coordinator {
258
- let c = Coordinator()
259
- c.webViewRef = webViewRef
260
- return c
261
- }
262
-
263
- func createResources(configuration: WKWebViewConfiguration? = nil, needsUpdate: Bool = false) -> WKWebView {
264
- if webViewHolder.appleWebView == nil {
265
- webViewHolder.webViewCoordinator = makeCoordinator()
266
- let userContentController = WKUserContentController()
267
-
268
- let userScript = WKUserScript(source: "window.WebSpatailEnabled = true; window.WebSpatailNativeVersion = '" + nativeAPIVersion + "';", injectionTime: .atDocumentStart, forMainFrameOnly: false)
269
- userContentController.addUserScript(userScript)
270
- userContentController.add(webViewHolder.webViewCoordinator!, name: "bridge")
271
-
272
- let myConfig = (configuration != nil) ? configuration! : WKWebViewConfiguration()
273
- myConfig.userContentController = userContentController
274
- myConfig.preferences.javaScriptCanOpenWindowsAutomatically = true
275
- myConfig.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")
276
- if myConfig.urlSchemeHandler(forURLScheme: "file") == nil {
277
- myConfig.setURLSchemeHandler(webViewHolder.webViewCoordinator, forURLScheme: "file")
278
- }
279
- webViewHolder.appleWebView = WKWebView(frame: .zero, configuration: myConfig)
280
- webViewHolder.webViewCoordinator!.startObserving(webView: webViewHolder.appleWebView!)
281
- let configUA = myConfig.applicationNameForUserAgent as? String ?? ""
282
-
283
- // change webview ua
284
- let ua = webViewHolder.appleWebView?.value(forKey: "userAgent") as? String ?? ""
285
- let webviewVersion = ua.split(separator: configUA)[0].split(separator: "AppleWebKit")[1]
286
- webViewHolder.appleWebView!.customUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7; wv) AppleWebKit\(webviewVersion)WebSpatial/\(nativeAPIVersion)"
287
-
288
- webViewHolder.appleWebView!.uiDelegate = webViewHolder.webViewCoordinator
289
- webViewHolder.appleWebView!.allowsBackForwardNavigationGestures = true
290
- webViewHolder.appleWebView!.isInspectable = true
291
- webViewHolder.appleWebView!.allowsLinkPreview = true
292
- webViewHolder.appleWebView!.navigationDelegate = webViewHolder.webViewCoordinator
293
- webViewHolder.appleWebView!.scrollView.delegate = webViewHolder.webViewCoordinator
294
- webViewHolder.needsUpdate = (configuration != nil && !needsUpdate) ? false : true
295
- }
296
-
297
- return webViewHolder.appleWebView!
298
- }
299
-
300
- func initialLoad() {
301
- if webViewHolder.needsUpdate {
302
- let request = URLRequest(url: url)
303
- webViewHolder.appleWebView!.load(request)
304
- webViewHolder.needsUpdate = false
305
- }
306
- }
307
-
308
- func makeUIView(context: Context) -> WKWebView {
309
- return createResources()
310
- }
311
-
312
- func updateUIView(_ webView: WKWebView, context: Context) {
313
- initialLoad()
314
- }
315
-
316
- static func dismantleUIView(_ uiView: WKWebView, coordinator: Coordinator) {
317
- coordinator.stopObserving(webView: uiView)
318
- }
319
- }
320
-
321
- // extend webview to support file://
322
- @available(iOS 11.0, *)
323
- extension WKWebView {
324
- /// WKWebView, Support setting file scheme in configuration
325
- public private(set) static var isEnableFileSupport = false
326
- public static func enableFileScheme() {
327
- /// This method supports adapting supported files through Configuration, but cannot be cancelled (Configuration is immutable).
328
- if !isEnableFileSupport {
329
- switchHandlesURLScheme()
330
- }
331
- }
332
-
333
- private static func switchHandlesURLScheme() {
334
- if
335
- case let cls = WKWebView.self,
336
- let m1 = class_getClassMethod(cls, NSSelectorFromString("handlesURLScheme:")),
337
- let m2 = class_getClassMethod(cls, #selector(WKWebView.wrapHandles(urlScheme:)))
338
- {
339
- method_exchangeImplementations(m1, m2)
340
- isEnableFileSupport = !isEnableFileSupport
341
- }
342
- }
343
-
344
- /// Return true if WKWebview supports handling this protocol, but WKWebview supports HTTP by default, so return false to support using custom HTTP Handler
345
- @objc private dynamic
346
- static func wrapHandles(urlScheme: String) -> Bool {
347
- if urlScheme == "file" { return false }
348
- return wrapHandles(urlScheme: urlScheme)
349
- }
350
- }