@thelacanians/vue-native-cli 0.4.15 → 0.6.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.
Files changed (116) hide show
  1. package/dist/cli.js +329 -15
  2. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +118 -0
  3. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VViewFactory.kt +178 -1
  4. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/GeneratedModuleRegistry.kt +28 -0
  5. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModuleRegistry.kt +3 -0
  6. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ComponentFactoryTest.kt +674 -0
  7. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ErrorOverlayViewTest.kt +183 -0
  8. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/EventThrottleTest.kt +203 -0
  9. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/HotReloadManagerTest.kt +162 -0
  10. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/JSPolyfillsTest.kt +153 -0
  11. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeBridgeTest.kt +6 -3
  12. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeModuleTest.kt +475 -0
  13. package/native/android/gradle.properties +1 -0
  14. package/native/android/gradlew +1 -1
  15. package/native/ios/VueNativeCore/Package.swift +1 -1
  16. package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/EventThrottle.swift +1 -0
  17. package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +143 -5
  18. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VTextFactory.swift +43 -0
  19. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VViewFactory.swift +116 -4
  20. package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/GestureWrapper.swift +100 -0
  21. package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/GeneratedModuleRegistry.swift +28 -0
  22. package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModuleRegistry.swift +3 -0
  23. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/CertificatePinningTests.swift +190 -0
  24. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/ComponentFactoryTests.swift +585 -0
  25. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/EventThrottleTests.swift +161 -0
  26. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/HotReloadManagerTests.swift +88 -0
  27. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/JSPolyfillsTests.swift +319 -0
  28. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/NativeModuleTests.swift +400 -0
  29. package/native/macos/VueNativeMacOS/Package.swift +34 -0
  30. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/ErrorOverlayView.swift +112 -0
  31. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/EventThrottle.swift +58 -0
  32. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/HotReloadManager.swift +153 -0
  33. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSPolyfills.swift +696 -0
  34. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSRuntime.swift +347 -0
  35. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/NativeBridge.swift +877 -0
  36. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/VueNativeWindowController.swift +125 -0
  37. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/ComponentRegistry.swift +209 -0
  38. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActionSheetFactory.swift +155 -0
  39. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActivityIndicatorFactory.swift +85 -0
  40. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VAlertDialogFactory.swift +132 -0
  41. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VButtonFactory.swift +83 -0
  42. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VCheckboxFactory.swift +108 -0
  43. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VDropdownFactory.swift +155 -0
  44. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VImageFactory.swift +270 -0
  45. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VInputFactory.swift +257 -0
  46. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VKeyboardAvoidingFactory.swift +22 -0
  47. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VListFactory.swift +324 -0
  48. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VModalFactory.swift +231 -0
  49. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VOutlineViewFactory.swift +276 -0
  50. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPickerFactory.swift +134 -0
  51. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPressableFactory.swift +120 -0
  52. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VProgressBarFactory.swift +71 -0
  53. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRadioFactory.swift +193 -0
  54. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRefreshControlFactory.swift +25 -0
  55. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSafeAreaFactory.swift +46 -0
  56. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VScrollViewFactory.swift +190 -0
  57. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSectionListFactory.swift +374 -0
  58. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSegmentedControlFactory.swift +125 -0
  59. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSliderFactory.swift +131 -0
  60. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSplitViewFactory.swift +215 -0
  61. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VStatusBarFactory.swift +25 -0
  62. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSwitchFactory.swift +92 -0
  63. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VTextFactory.swift +336 -0
  64. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VToolbarFactory.swift +212 -0
  65. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VVideoFactory.swift +245 -0
  66. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VViewFactory.swift +314 -0
  67. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VWebViewFactory.swift +162 -0
  68. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/NativeComponentFactory.swift +54 -0
  69. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/ClickableView.swift +100 -0
  70. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/Extensions.swift +23 -0
  71. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/GestureWrapper.swift +183 -0
  72. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/NSColor+Hex.swift +78 -0
  73. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/FlippedView.swift +19 -0
  74. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/LayoutNode.swift +493 -0
  75. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AnimationModule.swift +354 -0
  76. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AppStateModule.swift +62 -0
  77. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/BiometryModule.swift +60 -0
  78. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/CameraModule.swift +167 -0
  79. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ClipboardModule.swift +34 -0
  80. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DeviceInfoModule.swift +49 -0
  81. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DragDropModule.swift +50 -0
  82. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/FileDialogModule.swift +86 -0
  83. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/HapticsModule.swift +42 -0
  84. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/KeyboardModule.swift +28 -0
  85. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/LinkingModule.swift +49 -0
  86. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/MenuModule.swift +95 -0
  87. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NativeModuleRegistry.swift +63 -0
  88. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NotificationsModule.swift +112 -0
  89. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/PermissionsModule.swift +149 -0
  90. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ShareModule.swift +37 -0
  91. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/WindowModule.swift +71 -0
  92. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Resources/vue-native-placeholder.js +2 -0
  93. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Styling/StyleEngine.swift +885 -0
  94. package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/ComponentFactoryTests.swift +80 -0
  95. package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/VueNativeMacOSTests.swift +149 -0
  96. package/native/shared/VueNativeShared/AGENTS.md +129 -0
  97. package/native/shared/VueNativeShared/Package.swift +14 -0
  98. package/native/shared/VueNativeShared/Sources/VueNativeShared/CertificatePinning.swift +134 -0
  99. package/native/shared/VueNativeShared/Sources/VueNativeShared/EventThrottle.swift +78 -0
  100. package/native/shared/VueNativeShared/Sources/VueNativeShared/HotReloadManager.swift +162 -0
  101. package/native/shared/VueNativeShared/Sources/VueNativeShared/JSRuntime.swift +412 -0
  102. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AsyncStorageModule.swift +68 -0
  103. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AudioModule.swift +359 -0
  104. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/DatabaseModule.swift +259 -0
  105. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/FileSystemModule.swift +233 -0
  106. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/GeolocationModule.swift +156 -0
  107. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/NetworkModule.swift +59 -0
  108. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/PerformanceModule.swift +113 -0
  109. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/SecureStorageModule.swift +119 -0
  110. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/WebSocketModule.swift +212 -0
  111. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeEventDispatcher.swift +6 -0
  112. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModule.swift +26 -0
  113. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModuleRegistry.swift +37 -0
  114. package/native/shared/VueNativeShared/Sources/VueNativeShared/SharedJSPolyfills.swift +673 -0
  115. package/native/shared/VueNativeShared/Tests/VueNativeSharedTests/VueNativeSharedTests.swift +44 -0
  116. package/package.json +8 -2
@@ -0,0 +1,80 @@
1
+ import AppKit
2
+ import XCTest
3
+ @testable import VueNativeMacOS
4
+
5
+ @MainActor
6
+ final class ComponentFactoryTests: XCTestCase {
7
+
8
+ func testVListFactoryAppliesPublicProps() {
9
+ let factory = VListFactory()
10
+ guard let container = factory.createView() as? VListContainerView else {
11
+ return XCTFail("Expected VListContainerView")
12
+ }
13
+
14
+ factory.updateProp(view: container, key: "estimatedItemHeight", value: 72)
15
+ factory.updateProp(view: container, key: "showsScrollIndicator", value: false)
16
+ factory.updateProp(view: container, key: "bounces", value: false)
17
+
18
+ XCTAssertEqual(container.estimatedItemHeight, 72)
19
+ XCTAssertFalse(container.scrollView.hasVerticalScroller)
20
+ XCTAssertEqual(container.scrollView.verticalScrollElasticity, .none)
21
+ XCTAssertEqual(container.scrollView.horizontalScrollElasticity, .none)
22
+ }
23
+
24
+ func testVSectionListFactoryBuildsRowsFromInsertedChildren() {
25
+ let factory = VSectionListFactory()
26
+ guard let container = factory.createView() as? VSectionListContainerView else {
27
+ return XCTFail("Expected VSectionListContainerView")
28
+ }
29
+
30
+ let header = FlippedView(frame: NSRect(x: 0, y: 0, width: 200, height: 28))
31
+ header.ensureLayoutNode()
32
+ StyleEngine.setInternalPropDirect("__sectionHeader", value: true, on: header)
33
+
34
+ let item1 = FlippedView(frame: NSRect(x: 0, y: 0, width: 200, height: 44))
35
+ item1.ensureLayoutNode()
36
+ let item2 = FlippedView(frame: NSRect(x: 0, y: 0, width: 200, height: 44))
37
+ item2.ensureLayoutNode()
38
+
39
+ factory.insertChild(header, into: container, before: nil)
40
+ factory.insertChild(item1, into: container, before: nil)
41
+ factory.insertChild(item2, into: container, before: nil)
42
+
43
+ XCTAssertEqual(container.numberOfRows(in: container.tableView), 3)
44
+ XCTAssertTrue(container.tableView(container.tableView, isGroupRow: 0))
45
+ XCTAssertFalse(container.tableView(container.tableView, isGroupRow: 1))
46
+ }
47
+
48
+ func testVSectionListFactoryEmitsFlatScrollPayload() {
49
+ let factory = VSectionListFactory()
50
+ guard let container = factory.createView() as? VSectionListContainerView else {
51
+ return XCTFail("Expected VSectionListContainerView")
52
+ }
53
+
54
+ var payload: [String: Any]?
55
+ factory.addEventListener(view: container, event: "scroll") { eventPayload in
56
+ payload = eventPayload as? [String: Any]
57
+ }
58
+
59
+ container.scrollView.frame = NSRect(x: 0, y: 0, width: 120, height: 240)
60
+ container.tableView.frame = NSRect(x: 0, y: 0, width: 240, height: 960)
61
+ container.scrollView.contentView.scroll(to: NSPoint(x: 12, y: 34))
62
+ NotificationCenter.default.post(
63
+ name: NSView.boundsDidChangeNotification,
64
+ object: container.scrollView.contentView
65
+ )
66
+
67
+ guard let payload else {
68
+ return XCTFail("Expected scroll payload")
69
+ }
70
+
71
+ XCTAssertEqual(payload["x"] as? CGFloat, 12)
72
+ XCTAssertEqual(payload["y"] as? CGFloat, 34)
73
+ XCTAssertEqual(payload["contentWidth"] as? CGFloat, 240)
74
+ XCTAssertEqual(payload["contentHeight"] as? CGFloat, 960)
75
+ XCTAssertEqual(payload["layoutWidth"] as? CGFloat, 120)
76
+ XCTAssertEqual(payload["layoutHeight"] as? CGFloat, 240)
77
+ XCTAssertNil(payload["contentOffset"])
78
+ XCTAssertNil(payload["layoutMeasurement"])
79
+ }
80
+ }
@@ -0,0 +1,149 @@
1
+ import XCTest
2
+ @testable import VueNativeMacOS
3
+
4
+ @MainActor
5
+ final class VueNativeMacOSTests: XCTestCase {
6
+
7
+ // MARK: - FlippedView
8
+
9
+ func testFlippedViewIsFlipped() {
10
+ let view = FlippedView()
11
+ XCTAssertTrue(view.isFlipped, "FlippedView must have isFlipped = true")
12
+ XCTAssertTrue(view.wantsLayer, "FlippedView must be layer-backed")
13
+ }
14
+
15
+ // MARK: - LayoutNode
16
+
17
+ func testLayoutNodeDefaults() {
18
+ let node = LayoutNode()
19
+ XCTAssertEqual(node.flexDirection, .column)
20
+ XCTAssertEqual(node.justifyContent, .flexStart)
21
+ XCTAssertEqual(node.alignItems, .stretch)
22
+ XCTAssertEqual(node.flexGrow, 0)
23
+ XCTAssertEqual(node.flexShrink, 1)
24
+ XCTAssertEqual(node.positionType, .relative)
25
+ XCTAssertEqual(node.display, .flex)
26
+ }
27
+
28
+ func testLayoutValueResolvePoints() {
29
+ let value = LayoutValue.points(50)
30
+ XCTAssertEqual(value.resolve(relativeTo: 200), 50)
31
+ }
32
+
33
+ func testLayoutValueResolvePercent() {
34
+ let value = LayoutValue.percent(50)
35
+ XCTAssertEqual(value.resolve(relativeTo: 200), 100)
36
+ }
37
+
38
+ func testLayoutValueResolveAuto() {
39
+ let value = LayoutValue.auto
40
+ XCTAssertNil(value.resolve(relativeTo: 200))
41
+ }
42
+
43
+ func testLayoutValueResolveUndefined() {
44
+ let value = LayoutValue.undefined
45
+ XCTAssertNil(value.resolve(relativeTo: 200))
46
+ XCTAssertTrue(value.isUndefined)
47
+ }
48
+
49
+ func testEdgeInsetsZero() {
50
+ let insets = EdgeInsets.zero
51
+ XCTAssertEqual(insets.horizontal, 0)
52
+ XCTAssertEqual(insets.vertical, 0)
53
+ }
54
+
55
+ // MARK: - NSView LayoutNode Extension
56
+
57
+ func testNSViewLayoutNodeAssociation() {
58
+ let view = FlippedView()
59
+ XCTAssertNil(view.layoutNode)
60
+
61
+ let node = view.ensureLayoutNode()
62
+ XCTAssertNotNil(view.layoutNode)
63
+ XCTAssertTrue(node === view.layoutNode)
64
+ }
65
+
66
+ // MARK: - Percentage Operator
67
+
68
+ func testPercentageOperator() {
69
+ let value: LayoutValue = 50%
70
+ XCTAssertEqual(value, .percent(50))
71
+ }
72
+
73
+ // MARK: - Basic Layout
74
+
75
+ func testSimpleColumnLayout() {
76
+ let parent = FlippedView(frame: NSRect(x: 0, y: 0, width: 200, height: 400))
77
+ let parentNode = parent.ensureLayoutNode()
78
+ parentNode.flexDirection = .column
79
+ parentNode.width = .points(200)
80
+ parentNode.height = .points(400)
81
+
82
+ let child1 = FlippedView()
83
+ let child1Node = child1.ensureLayoutNode()
84
+ child1Node.height = .points(100)
85
+ child1Node.width = .points(200)
86
+ parent.addSubview(child1)
87
+
88
+ let child2 = FlippedView()
89
+ let child2Node = child2.ensureLayoutNode()
90
+ child2Node.height = .points(100)
91
+ child2Node.width = .points(200)
92
+ parent.addSubview(child2)
93
+
94
+ parentNode.layout(availableWidth: 200, availableHeight: 400)
95
+
96
+ XCTAssertEqual(child1.frame.origin.y, 0)
97
+ XCTAssertEqual(child2.frame.origin.y, 100)
98
+ }
99
+
100
+ func testFlexGrow() {
101
+ let parent = FlippedView(frame: NSRect(x: 0, y: 0, width: 200, height: 400))
102
+ let parentNode = parent.ensureLayoutNode()
103
+ parentNode.flexDirection = .column
104
+ parentNode.width = .points(200)
105
+ parentNode.height = .points(400)
106
+
107
+ let child1 = FlippedView()
108
+ let child1Node = child1.ensureLayoutNode()
109
+ child1Node.flexGrow = 1
110
+ parent.addSubview(child1)
111
+
112
+ let child2 = FlippedView()
113
+ let child2Node = child2.ensureLayoutNode()
114
+ child2Node.flexGrow = 1
115
+ parent.addSubview(child2)
116
+
117
+ parentNode.layout(availableWidth: 200, availableHeight: 400)
118
+
119
+ // Both children should share space equally
120
+ XCTAssertEqual(child1.frame.size.height, 200, accuracy: 1)
121
+ XCTAssertEqual(child2.frame.size.height, 200, accuracy: 1)
122
+ }
123
+
124
+ // MARK: - NSColor+Hex
125
+
126
+ func testColorFromHex6() {
127
+ let color = NSColor.fromHex("#FF0000")
128
+ XCTAssertNotNil(color)
129
+ // Should be red
130
+ var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
131
+ color.usingColorSpace(.sRGB)?.getRed(&r, green: &g, blue: &b, alpha: &a)
132
+ XCTAssertEqual(r, 1.0, accuracy: 0.01)
133
+ XCTAssertEqual(g, 0.0, accuracy: 0.01)
134
+ XCTAssertEqual(b, 0.0, accuracy: 0.01)
135
+ }
136
+
137
+ func testColorFromHexNamed() {
138
+ let transparent = NSColor.fromHex("transparent")
139
+ XCTAssertEqual(transparent, .clear)
140
+
141
+ let white = NSColor.fromHex("white")
142
+ XCTAssertEqual(white, .white)
143
+ }
144
+
145
+ func testColorFromHexInvalid() {
146
+ let color = NSColor.fromHex("notacolor")
147
+ XCTAssertEqual(color, .clear)
148
+ }
149
+ }
@@ -0,0 +1,129 @@
1
+ # AGENTS.md — VueNativeShared
2
+
3
+ Guidelines for AI agents working on the shared Swift package.
4
+
5
+ ---
6
+
7
+ ## What this package is
8
+
9
+ VueNativeShared is a cross-platform Swift package containing code shared between iOS (`VueNativeCore`) and macOS (`VueNativeMacOS`). It provides the JS engine interface, infrastructure protocols, and platform-agnostic native modules.
10
+
11
+ **Platforms:** iOS 16+, macOS 13+
12
+ **Swift:** 5.9+, no SwiftUI
13
+
14
+ ---
15
+
16
+ ## Package structure
17
+
18
+ ```
19
+ Sources/VueNativeShared/
20
+ JSRuntime.swift JSContext wrapper on serial DispatchQueue
21
+ SharedJSPolyfills.swift JSON encoding helpers for polyfill strings
22
+ EventThrottle.swift Throttle events to ~60fps (DispatchSourceTimer)
23
+ HotReloadManager.swift WebSocket-based hot reload (URLSessionWebSocketTask)
24
+ CertificatePinning.swift TLS certificate pinning (URLSession + Security)
25
+ NativeModule.swift Protocol — all native modules conform to this
26
+ NativeModuleRegistry.swift Singleton registry mapping module names → instances
27
+ NativeEventDispatcher.swift Protocol for cross-platform event dispatch
28
+ Modules/
29
+ AsyncStorageModule.swift UserDefaults key-value storage
30
+ NetworkModule.swift NWPathMonitor network connectivity
31
+ FileSystemModule.swift FileManager file operations
32
+ SecureStorageModule.swift Keychain secure storage
33
+ AudioModule.swift AVAudioPlayer audio playback
34
+ GeolocationModule.swift CLLocationManager location services
35
+ DatabaseModule.swift SQLite database operations
36
+ WebSocketModule.swift URLSession WebSocket client
37
+ PerformanceModule.swift ProcessInfo performance metrics
38
+ Tests/VueNativeSharedTests/
39
+ VueNativeSharedTests.swift XCTest suite (6 tests)
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Build & test
45
+
46
+ ```bash
47
+ swift build # Must compile without errors
48
+ swift test # All tests must pass (currently 6)
49
+ ```
50
+
51
+ Or from the monorepo root:
52
+ ```bash
53
+ bun run test:shared
54
+ ```
55
+
56
+ ---
57
+
58
+ ## Architecture rules
59
+
60
+ ### NativeModule protocol
61
+ - All native modules conform to `public protocol NativeModule: AnyObject`
62
+ - Required: `var moduleName: String` and `func invoke(method:args:callback:)`
63
+ - Optional: `func invokeSync(method:args:) -> Any?` (default returns nil)
64
+ - Modules are registered in `NativeModuleRegistry.shared`
65
+ - **Do NOT redefine this protocol in platform packages** — import VueNativeShared instead
66
+
67
+ ### JSRuntime
68
+ - JSContext runs on a dedicated serial `DispatchQueue` (`jsQueue`)
69
+ - **Never pass JSValue across threads** — extract primitives first
70
+ - Always use `[weak self]` in closures registered with JSContext
71
+ - JSC has native Promise support (iOS 13+ / macOS 10.15+)
72
+
73
+ ### EventThrottle
74
+ - Uses `CACurrentMediaTime()` for high-precision timing
75
+ - `DispatchSourceTimer` for deferred firing — NOT `Timer` or `RunLoop`
76
+ - Thread-safe: can be created and fired from any queue
77
+
78
+ ### Thread safety
79
+ - `NativeModuleRegistry` is **not** thread-safe — access from main thread only
80
+ - `CertificatePinning` uses a serial queue internally
81
+ - `HotReloadManager` runs WebSocket on a background queue, dispatches UI updates to main
82
+
83
+ ---
84
+
85
+ ## Coding conventions
86
+
87
+ - All public API types must be marked `public`
88
+ - Use `guard let` / `if let` — no force-unwraps (`!`) in production code
89
+ - Modules should handle errors gracefully: call `callback(nil, errorMessage)` on failure
90
+ - Keep modules platform-agnostic — use only Foundation, Security, CoreLocation, and similar frameworks available on both iOS and macOS
91
+ - Do NOT import UIKit or AppKit in this package
92
+
93
+ ---
94
+
95
+ ## How to add a shared module
96
+
97
+ 1. Create `Sources/VueNativeShared/Modules/<Name>Module.swift`
98
+ 2. Conform to `NativeModule` protocol
99
+ 3. Implement `moduleName`, `invoke(method:args:callback:)`, and optionally `invokeSync`
100
+ 4. Register in both platform packages' `NativeModuleRegistry.registerDefaults()`
101
+ 5. Add tests in `Tests/VueNativeSharedTests/`
102
+ 6. Only use frameworks available on both iOS 16+ and macOS 13+
103
+
104
+ ---
105
+
106
+ ## Common pitfalls
107
+
108
+ | Mistake | Why it breaks | Fix |
109
+ |---------|---------------|-----|
110
+ | Importing UIKit or AppKit | Package must be platform-agnostic | Use Foundation, CoreLocation, etc. |
111
+ | Force-unwrapping module args | Crashes if JS passes unexpected types | Use `as?` with guard/if-let |
112
+ | Accessing JSValue off jsQueue | JSContext is not thread-safe | Extract primitives on jsQueue first |
113
+ | Redefining NativeModule in platform package | Creates shadowing, type mismatches | Import VueNativeShared instead |
114
+ | Forgetting to register module | Module exists but is unreachable from JS | Add to registerDefaults() in both platforms |
115
+
116
+ ---
117
+
118
+ ## Key file locations
119
+
120
+ | What | Path |
121
+ |------|------|
122
+ | NativeModule protocol | `Sources/VueNativeShared/NativeModule.swift` |
123
+ | Module registry | `Sources/VueNativeShared/NativeModuleRegistry.swift` |
124
+ | JS runtime | `Sources/VueNativeShared/JSRuntime.swift` |
125
+ | Event throttle | `Sources/VueNativeShared/EventThrottle.swift` |
126
+ | Hot reload | `Sources/VueNativeShared/HotReloadManager.swift` |
127
+ | Certificate pinning | `Sources/VueNativeShared/CertificatePinning.swift` |
128
+ | Tests | `Tests/VueNativeSharedTests/VueNativeSharedTests.swift` |
129
+ | Package manifest | `Package.swift` |
@@ -0,0 +1,14 @@
1
+ // swift-tools-version: 5.9
2
+ import PackageDescription
3
+
4
+ let package = Package(
5
+ name: "VueNativeShared",
6
+ platforms: [.iOS(.v16), .macOS(.v13)],
7
+ products: [
8
+ .library(name: "VueNativeShared", targets: ["VueNativeShared"])
9
+ ],
10
+ targets: [
11
+ .target(name: "VueNativeShared", path: "Sources/VueNativeShared"),
12
+ .testTarget(name: "VueNativeSharedTests", dependencies: ["VueNativeShared"], path: "Tests/VueNativeSharedTests")
13
+ ]
14
+ )
@@ -0,0 +1,134 @@
1
+ import Foundation
2
+ import CommonCrypto
3
+
4
+ /// Manages certificate pinning for network requests.
5
+ /// Stores SHA-256 pin hashes per domain and provides a URLSession with
6
+ /// a delegate that validates server certificates against the pinned hashes.
7
+ ///
8
+ /// Usage from JS (via the bridge):
9
+ /// __VN_configurePins({ "api.example.com": ["sha256/BBBBBBB..."] })
10
+ ///
11
+ /// The fetch polyfill uses `CertificatePinning.shared.session` instead of
12
+ /// `URLSession.shared` when pins are configured for the request's host.
13
+ public final class CertificatePinning: NSObject, URLSessionDelegate {
14
+
15
+ public static let shared = CertificatePinning()
16
+
17
+ /// Maps lowercase domain to an array of base64-encoded SHA-256 hashes of the
18
+ /// Subject Public Key Info (SPKI).
19
+ private var pins: [String: [String]] = [:]
20
+
21
+ /// A URLSession configured with this object as its delegate for TLS validation.
22
+ /// Lazily created so we don't pay the cost if pinning is never configured.
23
+ public private(set) lazy var session: URLSession = {
24
+ let config = URLSessionConfiguration.default
25
+ config.timeoutIntervalForRequest = 30
26
+ config.timeoutIntervalForResource = 60
27
+ return URLSession(configuration: config, delegate: self, delegateQueue: nil)
28
+ }()
29
+
30
+ private override init() {
31
+ super.init()
32
+ }
33
+
34
+ // MARK: - Configuration
35
+
36
+ /// Configure certificate pins for one or more domains.
37
+ /// Each pin string must be in the format "sha256/<base64-encoded-hash>".
38
+ ///
39
+ /// - Parameter domainPins: Dictionary mapping domain names to arrays of pin strings.
40
+ public func configurePins(_ domainPins: [String: [String]]) {
41
+ for (domain, pinList) in domainPins {
42
+ let hashes = pinList.compactMap { pin -> String? in
43
+ // Accept "sha256/XXXXX" format — strip the prefix
44
+ if pin.hasPrefix("sha256/") {
45
+ return String(pin.dropFirst(7))
46
+ }
47
+ return nil
48
+ }
49
+ if !hashes.isEmpty {
50
+ pins[domain.lowercased()] = hashes
51
+ }
52
+ }
53
+ }
54
+
55
+ /// Returns true if there are pins configured for the given host.
56
+ public func hasPins(for host: String) -> Bool {
57
+ return pins[host.lowercased()] != nil
58
+ }
59
+
60
+ /// Remove all configured pins.
61
+ public func clearPins() {
62
+ pins.removeAll()
63
+ }
64
+
65
+ // MARK: - URLSessionDelegate
66
+
67
+ public func urlSession(
68
+ _ session: URLSession,
69
+ didReceive challenge: URLAuthenticationChallenge,
70
+ completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
71
+ ) {
72
+ guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
73
+ let serverTrust = challenge.protectionSpace.serverTrust else {
74
+ completionHandler(.performDefaultHandling, nil)
75
+ return
76
+ }
77
+
78
+ let host = challenge.protectionSpace.host.lowercased()
79
+
80
+ // If no pins configured for this host, use default handling
81
+ guard let expectedPins = pins[host] else {
82
+ completionHandler(.performDefaultHandling, nil)
83
+ return
84
+ }
85
+
86
+ // Evaluate the server trust
87
+ var error: CFError?
88
+ guard SecTrustEvaluateWithError(serverTrust, &error) else {
89
+ NSLog("[VueNative CertPin] Trust evaluation failed for %@: %@", host, error?.localizedDescription ?? "unknown")
90
+ completionHandler(.cancelAuthenticationChallenge, nil)
91
+ return
92
+ }
93
+
94
+ // Check each certificate in the chain against our pins
95
+ let certCount = SecTrustGetCertificateCount(serverTrust)
96
+ guard let certChain = SecTrustCopyCertificateChain(serverTrust) as? [SecCertificate] else {
97
+ completionHandler(.cancelAuthenticationChallenge, nil)
98
+ return
99
+ }
100
+ for i in 0..<certCount {
101
+ guard i < certChain.count else { continue }
102
+ let certificate = certChain[i]
103
+ let spkiHash = sha256OfSPKI(for: certificate)
104
+ if expectedPins.contains(spkiHash) {
105
+ completionHandler(.useCredential, URLCredential(trust: serverTrust))
106
+ return
107
+ }
108
+ }
109
+
110
+ // No pin matched — reject the connection
111
+ NSLog("[VueNative CertPin] Pin validation failed for %@. No matching pin found.", host)
112
+ completionHandler(.cancelAuthenticationChallenge, nil)
113
+ }
114
+
115
+ // MARK: - SPKI Hashing
116
+
117
+ /// Compute the base64-encoded SHA-256 hash of a certificate's Subject Public Key Info.
118
+ private func sha256OfSPKI(for certificate: SecCertificate) -> String {
119
+ guard let publicKey = SecCertificateCopyKey(certificate) else { return "" }
120
+
121
+ var error: Unmanaged<CFError>?
122
+ guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else {
123
+ return ""
124
+ }
125
+
126
+ // Hash the raw public key data with SHA-256
127
+ var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
128
+ publicKeyData.withUnsafeBytes { bytes in
129
+ _ = CC_SHA256(bytes.baseAddress, CC_LONG(publicKeyData.count), &hash)
130
+ }
131
+
132
+ return Data(hash).base64EncodedString()
133
+ }
134
+ }
@@ -0,0 +1,78 @@
1
+ import Foundation
2
+ import QuartzCore
3
+
4
+ /// Throttles high-frequency event handlers to avoid flooding the JS bridge.
5
+ ///
6
+ /// When a high-frequency event (scroll, slider drag, text input) fires many
7
+ /// times per frame, each invocation becomes a bridge round-trip. This utility
8
+ /// ensures at most one call per `interval` seconds, with a trailing call
9
+ /// to deliver the latest value.
10
+ ///
11
+ /// Default interval: 16ms (~60 FPS). Callers can customize via `interval`.
12
+ public final class EventThrottle {
13
+
14
+ /// Minimum time between handler invocations (seconds).
15
+ public let interval: TimeInterval
16
+
17
+ /// The throttled handler to call.
18
+ private let handler: (Any?) -> Void
19
+
20
+ /// Timestamp of the last invocation.
21
+ private var lastFireTime: CFTimeInterval = 0
22
+
23
+ /// Whether a trailing call is pending.
24
+ private var pendingTrailing = false
25
+
26
+ /// The most recent payload, used for the trailing call.
27
+ private var latestPayload: Any?
28
+
29
+ /// Timer for delivering the trailing call.
30
+ private var trailingTimer: DispatchSourceTimer?
31
+
32
+ /// Create a throttled event handler.
33
+ /// - Parameters:
34
+ /// - interval: Minimum seconds between invocations. Default 0.016 (~60fps).
35
+ /// - handler: The closure to invoke with the event payload.
36
+ public init(interval: TimeInterval = 0.016, handler: @escaping (Any?) -> Void) {
37
+ self.interval = interval
38
+ self.handler = handler
39
+ }
40
+
41
+ deinit {
42
+ trailingTimer?.cancel()
43
+ }
44
+
45
+ /// Call this from the native event callback instead of the original handler.
46
+ /// Fires immediately if enough time has elapsed, otherwise schedules a trailing call.
47
+ public func fire(_ payload: Any?) {
48
+ let now = CACurrentMediaTime()
49
+ let elapsed = now - lastFireTime
50
+
51
+ latestPayload = payload
52
+
53
+ if elapsed >= interval {
54
+ // Enough time has passed — fire immediately
55
+ lastFireTime = now
56
+ pendingTrailing = false
57
+ trailingTimer?.cancel()
58
+ trailingTimer = nil
59
+ handler(payload)
60
+ } else if !pendingTrailing {
61
+ // Schedule a trailing call for the remaining time
62
+ pendingTrailing = true
63
+ let remaining = interval - elapsed
64
+ let timer = DispatchSource.makeTimerSource(queue: .main)
65
+ timer.schedule(deadline: .now() + remaining)
66
+ timer.setEventHandler { [weak self] in
67
+ guard let self = self else { return }
68
+ self.lastFireTime = CACurrentMediaTime()
69
+ self.pendingTrailing = false
70
+ self.trailingTimer = nil
71
+ self.handler(self.latestPayload)
72
+ }
73
+ trailingTimer = timer
74
+ timer.resume()
75
+ }
76
+ // If a trailing call is already pending, just update latestPayload (done above)
77
+ }
78
+ }