@webspatial/platform-visionos 1.0.4 → 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.
- package/package.json +2 -2
- package/web-spatial/EventEmitter.swift +56 -0
- package/web-spatial/JSBCommand.swift +348 -0
- package/web-spatial/SpatialObject.swift +108 -0
- package/web-spatial/WebMsgCommand.swift +119 -0
- package/web-spatial/WebSpatialApp.swift +111 -0
- package/web-spatial/{libs/uiKitDelegate/Window.swift → Window.swift} +1 -0
- package/web-spatial/manager/Dynamic3DManager.swift +114 -0
- package/web-spatial/manager/JSBManager.swift +148 -0
- package/web-spatial/manager/WKWebViewManager.swift +39 -0
- package/web-spatial/{libs/webView/manifest.swift → manifest.swift} +16 -5
- package/web-spatial/model/SpatialApp.swift +190 -0
- package/web-spatial/model/SpatialScene.swift +1042 -0
- package/web-spatial/model/Spatialized2DElement.swift +128 -0
- package/web-spatial/model/SpatializedDynamic3DElement.swift +34 -0
- package/web-spatial/model/SpatializedElement.swift +95 -0
- package/web-spatial/model/SpatializedStatic3DElement.swift +19 -0
- package/web-spatial/model/dynamic3d/Geometry.swift +95 -0
- package/web-spatial/model/dynamic3d/SpatialComponent.swift +72 -0
- package/web-spatial/model/dynamic3d/SpatialEntity.swift +211 -0
- package/web-spatial/model/dynamic3d/SpatialMaterial.swift +39 -0
- package/web-spatial/model/dynamic3d/SpatialModelEntity.swift +39 -0
- package/web-spatial/model/dynamic3d/SpatialModelResource.swift +38 -0
- package/web-spatial/model/dynamic3d/SpatialTextureResource.swift +18 -0
- package/web-spatial/protocol/ScrollAbleSpatialElementContainer.swift +1 -0
- package/web-spatial/protocol/SpatialScrollAble.swift +8 -0
- package/web-spatial/protocol/SpatializedElementContainer.swift +8 -0
- package/web-spatial/protocol/WebMsgSender.swift +5 -0
- package/web-spatial/types/Vec2.swift +12 -0
- package/web-spatial/types/Vec3.swift +7 -0
- package/web-spatial/view/SceneHandlerUIView.swift +92 -0
- package/web-spatial/{views/ui/NavView.swift → view/SpatialNavView.swift} +149 -77
- package/web-spatial/view/SpatialSceneContentView.swift +218 -0
- package/web-spatial/view/SpatialSceneView.swift +53 -0
- package/web-spatial/view/Spatialized2DElementView.swift +96 -0
- package/web-spatial/view/SpatializedDynamic3DView.swift +104 -0
- package/web-spatial/view/SpatializedElementView.swift +178 -0
- package/web-spatial/view/SpatializedStatic3DView.swift +70 -0
- package/web-spatial/{views → view/view-modifier}/MaterialWithBorderCornerModifier.swift +28 -8
- package/web-spatial/webview/SpatialWebController.swift +300 -0
- package/web-spatial/webview/SpatialWebView.swift +34 -0
- package/web-spatial/webview/SpatialWebViewModel.swift +307 -0
- package/web-spatial.xcodeproj/project.pbxproj +126 -207
- package/web-spatial/libs/EventEmitter.swift +0 -25
- package/web-spatial/libs/SpatialComponent.swift +0 -24
- package/web-spatial/libs/SpatialEntity.swift +0 -172
- package/web-spatial/libs/SpatialInputComponent.swift +0 -19
- package/web-spatial/libs/SpatialMeshResource.swift +0 -12
- package/web-spatial/libs/SpatialModel3DComponent.swift +0 -51
- package/web-spatial/libs/SpatialModelComponent.swift +0 -25
- package/web-spatial/libs/SpatialObject.swift +0 -140
- package/web-spatial/libs/SpatialPhysicallyBasedMaterial.swift +0 -19
- package/web-spatial/libs/SpatialViewComponent.swift +0 -8
- package/web-spatial/libs/SpatialWindowComponent.swift +0 -454
- package/web-spatial/libs/SpatialWindowContainer.swift +0 -153
- package/web-spatial/libs/Utils/CommandManager.swift +0 -823
- package/web-spatial/libs/Utils/PerfClock.swift +0 -43
- package/web-spatial/libs/Utils/SceneManager.swift +0 -101
- package/web-spatial/libs/Utils/WindowContainerMgr.swift +0 -122
- package/web-spatial/libs/json/JsonParser.swift +0 -45
- package/web-spatial/libs/webView/UpdateSystem.swift +0 -26
- package/web-spatial/libs/webView/backend/NativeWebView.swift +0 -350
- package/web-spatial/views/ImmersiveView.swift +0 -17
- package/web-spatial/views/OpenDismissHandlerUI.swift +0 -45
- package/web-spatial/views/PlainWindowContainerView.swift +0 -132
- package/web-spatial/views/SpatialModel3DView.swift +0 -187
- package/web-spatial/views/SpatialViewUI.swift +0 -168
- package/web-spatial/views/SpatialWebViewUI.swift +0 -179
- package/web-spatial/views/VolumetricWindowContainerView.swift +0 -30
- package/web-spatial/web_spatialApp.swift +0 -141
- /package/web-spatial/{libs/Utils → Utils}/ColorExtension.swift +0 -0
- /package/web-spatial/{libs/Utils → Utils}/Logger.swift +0 -0
- /package/web-spatial/{views → view}/LoadingView.swift +0 -0
- /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
|
-
}
|