@thelacanians/vue-native-cli 0.4.14 → 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 (123) hide show
  1. package/dist/cli.js +789 -200
  2. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +156 -5
  3. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VListFactory.kt +33 -13
  4. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VScrollViewFactory.kt +27 -6
  5. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSliderFactory.kt +9 -2
  6. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VViewFactory.kt +178 -1
  7. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Helpers/EventThrottle.kt +57 -0
  8. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/GeneratedModuleRegistry.kt +28 -0
  9. package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModuleRegistry.kt +3 -0
  10. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ComponentFactoryTest.kt +674 -0
  11. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ErrorOverlayViewTest.kt +183 -0
  12. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/EventThrottleTest.kt +203 -0
  13. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/HotReloadManagerTest.kt +162 -0
  14. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/JSPolyfillsTest.kt +153 -0
  15. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeBridgeTest.kt +6 -3
  16. package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeModuleTest.kt +475 -0
  17. package/native/android/gradle.properties +1 -0
  18. package/native/android/gradlew +1 -1
  19. package/native/ios/VueNativeCore/Package.swift +1 -1
  20. package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/EventThrottle.swift +80 -0
  21. package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +244 -112
  22. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VListFactory.swift +19 -2
  23. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VScrollViewFactory.swift +9 -4
  24. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSliderFactory.swift +8 -3
  25. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VTextFactory.swift +43 -0
  26. package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VViewFactory.swift +116 -4
  27. package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/GestureWrapper.swift +100 -0
  28. package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/GeneratedModuleRegistry.swift +28 -0
  29. package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModuleRegistry.swift +3 -0
  30. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/CertificatePinningTests.swift +190 -0
  31. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/ComponentFactoryTests.swift +585 -0
  32. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/EventThrottleTests.swift +161 -0
  33. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/HotReloadManagerTests.swift +88 -0
  34. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/JSPolyfillsTests.swift +319 -0
  35. package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/NativeModuleTests.swift +400 -0
  36. package/native/macos/VueNativeMacOS/Package.swift +34 -0
  37. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/ErrorOverlayView.swift +112 -0
  38. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/EventThrottle.swift +58 -0
  39. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/HotReloadManager.swift +153 -0
  40. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSPolyfills.swift +696 -0
  41. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSRuntime.swift +347 -0
  42. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/NativeBridge.swift +877 -0
  43. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/VueNativeWindowController.swift +125 -0
  44. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/ComponentRegistry.swift +209 -0
  45. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActionSheetFactory.swift +155 -0
  46. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActivityIndicatorFactory.swift +85 -0
  47. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VAlertDialogFactory.swift +132 -0
  48. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VButtonFactory.swift +83 -0
  49. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VCheckboxFactory.swift +108 -0
  50. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VDropdownFactory.swift +155 -0
  51. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VImageFactory.swift +270 -0
  52. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VInputFactory.swift +257 -0
  53. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VKeyboardAvoidingFactory.swift +22 -0
  54. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VListFactory.swift +324 -0
  55. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VModalFactory.swift +231 -0
  56. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VOutlineViewFactory.swift +276 -0
  57. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPickerFactory.swift +134 -0
  58. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPressableFactory.swift +120 -0
  59. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VProgressBarFactory.swift +71 -0
  60. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRadioFactory.swift +193 -0
  61. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRefreshControlFactory.swift +25 -0
  62. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSafeAreaFactory.swift +46 -0
  63. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VScrollViewFactory.swift +190 -0
  64. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSectionListFactory.swift +374 -0
  65. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSegmentedControlFactory.swift +125 -0
  66. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSliderFactory.swift +131 -0
  67. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSplitViewFactory.swift +215 -0
  68. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VStatusBarFactory.swift +25 -0
  69. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSwitchFactory.swift +92 -0
  70. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VTextFactory.swift +336 -0
  71. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VToolbarFactory.swift +212 -0
  72. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VVideoFactory.swift +245 -0
  73. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VViewFactory.swift +314 -0
  74. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VWebViewFactory.swift +162 -0
  75. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/NativeComponentFactory.swift +54 -0
  76. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/ClickableView.swift +100 -0
  77. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/Extensions.swift +23 -0
  78. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/GestureWrapper.swift +183 -0
  79. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/NSColor+Hex.swift +78 -0
  80. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/FlippedView.swift +19 -0
  81. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/LayoutNode.swift +493 -0
  82. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AnimationModule.swift +354 -0
  83. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AppStateModule.swift +62 -0
  84. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/BiometryModule.swift +60 -0
  85. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/CameraModule.swift +167 -0
  86. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ClipboardModule.swift +34 -0
  87. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DeviceInfoModule.swift +49 -0
  88. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DragDropModule.swift +50 -0
  89. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/FileDialogModule.swift +86 -0
  90. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/HapticsModule.swift +42 -0
  91. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/KeyboardModule.swift +28 -0
  92. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/LinkingModule.swift +49 -0
  93. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/MenuModule.swift +95 -0
  94. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NativeModuleRegistry.swift +63 -0
  95. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NotificationsModule.swift +112 -0
  96. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/PermissionsModule.swift +149 -0
  97. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ShareModule.swift +37 -0
  98. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/WindowModule.swift +71 -0
  99. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Resources/vue-native-placeholder.js +2 -0
  100. package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Styling/StyleEngine.swift +885 -0
  101. package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/ComponentFactoryTests.swift +80 -0
  102. package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/VueNativeMacOSTests.swift +149 -0
  103. package/native/shared/VueNativeShared/AGENTS.md +129 -0
  104. package/native/shared/VueNativeShared/Package.swift +14 -0
  105. package/native/shared/VueNativeShared/Sources/VueNativeShared/CertificatePinning.swift +134 -0
  106. package/native/shared/VueNativeShared/Sources/VueNativeShared/EventThrottle.swift +78 -0
  107. package/native/shared/VueNativeShared/Sources/VueNativeShared/HotReloadManager.swift +162 -0
  108. package/native/shared/VueNativeShared/Sources/VueNativeShared/JSRuntime.swift +412 -0
  109. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AsyncStorageModule.swift +68 -0
  110. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AudioModule.swift +359 -0
  111. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/DatabaseModule.swift +259 -0
  112. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/FileSystemModule.swift +233 -0
  113. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/GeolocationModule.swift +156 -0
  114. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/NetworkModule.swift +59 -0
  115. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/PerformanceModule.swift +113 -0
  116. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/SecureStorageModule.swift +119 -0
  117. package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/WebSocketModule.swift +212 -0
  118. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeEventDispatcher.swift +6 -0
  119. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModule.swift +26 -0
  120. package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModuleRegistry.swift +37 -0
  121. package/native/shared/VueNativeShared/Sources/VueNativeShared/SharedJSPolyfills.swift +673 -0
  122. package/native/shared/VueNativeShared/Tests/VueNativeSharedTests/VueNativeSharedTests.swift +44 -0
  123. package/package.json +8 -2
@@ -0,0 +1,162 @@
1
+ import Foundation
2
+
3
+ /// Manages a WebSocket connection to the Vue Native dev server for hot reload.
4
+ /// When a new bundle is broadcast, calls `onBundleReceived` so the platform-specific
5
+ /// bridge can perform the reload.
6
+ ///
7
+ /// Usage in your app (debug builds only):
8
+ /// ```swift
9
+ /// #if DEBUG
10
+ /// HotReloadManager.shared.onBundleReceived = { bundle in
11
+ /// NativeBridge.shared.reloadWithBundle(bundle)
12
+ /// }
13
+ /// HotReloadManager.shared.connect(to: URL(string: "ws://localhost:8174")!)
14
+ /// #endif
15
+ /// ```
16
+ public final class HotReloadManager: NSObject, URLSessionWebSocketDelegate {
17
+
18
+ public static let shared = HotReloadManager()
19
+
20
+ private var webSocketTask: URLSessionWebSocketTask?
21
+ private var session: URLSession?
22
+ private var serverURL: URL?
23
+ private var isConnecting = false
24
+ private var reconnectAttempts = 0
25
+ private let maxReconnectAttempts = 10
26
+
27
+ /// Called when a new bundle is received from the dev server.
28
+ /// Platform-specific code sets this to trigger a full app reload.
29
+ public var onBundleReceived: ((String) -> Void)?
30
+
31
+ private override init() {
32
+ super.init()
33
+ }
34
+
35
+ // MARK: - Public API
36
+
37
+ /// Connect to the dev server. Safe to call multiple times.
38
+ public func connect(to url: URL) {
39
+ serverURL = url
40
+ reconnectAttempts = 0
41
+ scheduleConnect(delay: 0)
42
+ }
43
+
44
+ /// Disconnect and stop reconnecting.
45
+ public func disconnect() {
46
+ serverURL = nil
47
+ webSocketTask?.cancel(with: .goingAway, reason: nil)
48
+ webSocketTask = nil
49
+ session?.invalidateAndCancel()
50
+ session = nil
51
+ NSLog("[VueNative HotReload] Disconnected")
52
+ }
53
+
54
+ // MARK: - Connection
55
+
56
+ private func scheduleConnect(delay: TimeInterval) {
57
+ guard serverURL != nil, !isConnecting else { return }
58
+ isConnecting = true
59
+ DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
60
+ self?.openConnection()
61
+ }
62
+ }
63
+
64
+ private func openConnection() {
65
+ guard let url = serverURL else {
66
+ isConnecting = false
67
+ return
68
+ }
69
+
70
+ // Create a fresh session each time to avoid stale state
71
+ session?.invalidateAndCancel()
72
+ let config = URLSessionConfiguration.default
73
+ config.timeoutIntervalForRequest = 5
74
+ session = URLSession(configuration: config, delegate: self, delegateQueue: .main)
75
+
76
+ webSocketTask = session?.webSocketTask(with: url)
77
+ webSocketTask?.resume()
78
+ NSLog("[VueNative HotReload] Connecting to \(url)...")
79
+ receiveNextMessage()
80
+ }
81
+
82
+ // MARK: - Message Handling
83
+
84
+ private func receiveNextMessage() {
85
+ webSocketTask?.receive { [weak self] result in
86
+ guard let self = self else { return }
87
+ switch result {
88
+ case .success(let message):
89
+ self.handleMessage(message)
90
+ self.receiveNextMessage()
91
+ case .failure(let error):
92
+ NSLog("[VueNative HotReload] Receive error: \(error.localizedDescription)")
93
+ self.scheduleReconnect()
94
+ }
95
+ }
96
+ }
97
+
98
+ private func handleMessage(_ message: URLSessionWebSocketTask.Message) {
99
+ let text: String
100
+ switch message {
101
+ case .string(let s): text = s
102
+ case .data(let d): text = String(data: d, encoding: .utf8) ?? ""
103
+ @unknown default: return
104
+ }
105
+
106
+ guard let data = text.data(using: .utf8),
107
+ let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
108
+ let type = json["type"] as? String else {
109
+ return
110
+ }
111
+
112
+ switch type {
113
+ case "connected":
114
+ isConnecting = false
115
+ reconnectAttempts = 0
116
+ NSLog("[VueNative HotReload] Connected — hot reload active")
117
+
118
+ case "bundle":
119
+ guard let bundle = json["bundle"] as? String else { return }
120
+ NSLog("[VueNative HotReload] Received bundle (\(bundle.count) bytes) — reloading...")
121
+ DispatchQueue.main.async { [weak self] in
122
+ self?.onBundleReceived?(bundle)
123
+ }
124
+
125
+ case "ping":
126
+ // Respond to keep-alive pings
127
+ let pong = "{\"type\":\"pong\"}"
128
+ webSocketTask?.send(.string(pong)) { _ in }
129
+
130
+ default:
131
+ break
132
+ }
133
+ }
134
+
135
+ // MARK: - Reconnection
136
+
137
+ private func scheduleReconnect() {
138
+ guard serverURL != nil else { return }
139
+ reconnectAttempts += 1
140
+ if reconnectAttempts > maxReconnectAttempts {
141
+ NSLog("[VueNative HotReload] Giving up after %d attempts — start `bun run dev` and relaunch the app", maxReconnectAttempts)
142
+ disconnect()
143
+ return
144
+ }
145
+ isConnecting = false
146
+ NSLog("[VueNative HotReload] Reconnecting in 2s... (attempt %d/%d)", reconnectAttempts, maxReconnectAttempts)
147
+ scheduleConnect(delay: 2.0)
148
+ }
149
+
150
+ // MARK: - URLSessionWebSocketDelegate
151
+
152
+ public func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask,
153
+ didOpenWithProtocol protocol: String?) {
154
+ NSLog("[VueNative HotReload] WebSocket opened")
155
+ }
156
+
157
+ public func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask,
158
+ didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
159
+ NSLog("[VueNative HotReload] WebSocket closed (code: \(closeCode.rawValue))")
160
+ scheduleReconnect()
161
+ }
162
+ }
@@ -0,0 +1,412 @@
1
+ import JavaScriptCore
2
+ import Foundation
3
+
4
+ // MARK: - Bundle Source
5
+
6
+ /// Describes where to load the JS application bundle from.
7
+ public enum BundleSource {
8
+ /// Load from an embedded resource in the app bundle.
9
+ case embedded(name: String)
10
+ /// Load from a development server URL (for live reload).
11
+ case devServer(url: URL)
12
+ }
13
+
14
+ // MARK: - JSRuntime
15
+
16
+ /// Core JavaScript runtime manager. Wraps JSContext on a dedicated serial DispatchQueue.
17
+ /// All JS operations are guaranteed to execute on the JS queue. UI operations
18
+ /// are never performed on this queue.
19
+ ///
20
+ /// Thread safety contract:
21
+ /// - All JSContext access happens exclusively on `jsQueue`
22
+ /// - Never pass JSValue across threads — extract primitives first
23
+ /// - All closures registered with JSContext use [weak self]
24
+ public final class JSRuntime: @unchecked Sendable {
25
+
26
+ // MARK: - Singleton
27
+
28
+ public static let shared = JSRuntime()
29
+
30
+ // MARK: - Properties
31
+
32
+ /// Dedicated serial queue for all JavaScript execution.
33
+ /// QoS is userInteractive because JS drives the UI pipeline.
34
+ public let jsQueue = DispatchQueue(label: "com.vuenative.js", qos: .userInteractive)
35
+
36
+ /// The underlying JavaScriptCore context. Only access on jsQueue.
37
+ public private(set) var context: JSContext!
38
+
39
+ /// Whether the runtime has been initialized.
40
+ public private(set) var isInitialized = false
41
+
42
+ /// Startup time reference for performance.now()
43
+ public let startTime: CFAbsoluteTime = CFAbsoluteTimeGetCurrent()
44
+
45
+ /// Callback invoked in DEBUG builds when JS throws an exception.
46
+ /// Platform-specific layers set this to show an error overlay.
47
+ public var onError: ((String) -> Void)?
48
+
49
+ // MARK: - Initialization
50
+
51
+ private init() {}
52
+
53
+ /// Initialize the JS runtime. Creates the JSContext on the JS queue,
54
+ /// configures exception handling, and registers polyfills.
55
+ /// Must be called before any other method.
56
+ public func initialize(completion: (() -> Void)? = nil) {
57
+ jsQueue.async { [weak self] in
58
+ guard let self = self else { return }
59
+ guard !self.isInitialized else {
60
+ completion?()
61
+ return
62
+ }
63
+
64
+ // Create the JSContext
65
+ self.context = JSContext()
66
+
67
+ // Configure exception handler
68
+ self.context.exceptionHandler = { [weak self] context, exception in
69
+ guard let exception = exception else { return }
70
+ let message = exception.toString() ?? "Unknown JS error"
71
+ let line = exception.objectForKeyedSubscript("line")?.toInt32() ?? 0
72
+ let column = exception.objectForKeyedSubscript("column")?.toInt32() ?? 0
73
+ let stack = exception.objectForKeyedSubscript("stack")?.toString() ?? ""
74
+ NSLog("[VueNative JS Error] \(message) at line \(line):\(column)")
75
+ if !stack.isEmpty {
76
+ NSLog("[VueNative JS Stack] \(stack)")
77
+ }
78
+ #if DEBUG
79
+ let fullMessage = stack.isEmpty ? message : "\(message)\n\n\(stack)"
80
+ self?.onError?(fullMessage)
81
+ #endif
82
+ _ = self // prevent unused warning
83
+ }
84
+
85
+ // Set globalThis = global object
86
+ self.context.evaluateScript("var globalThis = this;")
87
+
88
+ // Register shared polyfills (everything except platform-specific RAF)
89
+ SharedJSPolyfills.register(in: self)
90
+
91
+ self.isInitialized = true
92
+ completion?()
93
+ }
94
+ }
95
+
96
+ // MARK: - Script Evaluation
97
+
98
+ /// Evaluate a JavaScript string on the JS queue.
99
+ /// The completion handler is called on the JS queue with the result.
100
+ public func evaluateScript(_ script: String, sourceURL: String? = nil, completion: ((JSValue?) -> Void)? = nil) {
101
+ jsQueue.async { [weak self] in
102
+ guard let self = self, let context = self.context else {
103
+ completion?(nil)
104
+ return
105
+ }
106
+
107
+ let result: JSValue?
108
+ if let sourceURL = sourceURL, let url = URL(string: sourceURL) {
109
+ result = context.evaluateScript(script, withSourceURL: url)
110
+ } else {
111
+ result = context.evaluateScript(script)
112
+ }
113
+
114
+ // Force microtask drain after evaluation.
115
+ // JSC may not automatically drain microtasks after evaluateScript returns.
116
+ // This ensures Vue's Promise-based scheduler flushes.
117
+ context.evaluateScript("void 0;")
118
+
119
+ completion?(result)
120
+ }
121
+ }
122
+
123
+ /// Evaluate a script synchronously. MUST only be called from the JS queue.
124
+ /// Returns the JSValue result directly.
125
+ @discardableResult
126
+ public func evaluateScriptSync(_ script: String, sourceURL: String? = nil) -> JSValue? {
127
+ dispatchPrecondition(condition: .onQueue(jsQueue))
128
+ guard let context = context else { return nil }
129
+
130
+ let result: JSValue?
131
+ if let sourceURL = sourceURL, let url = URL(string: sourceURL) {
132
+ result = context.evaluateScript(script, withSourceURL: url)
133
+ } else {
134
+ result = context.evaluateScript(script)
135
+ }
136
+
137
+ // Force microtask drain
138
+ context.evaluateScript("void 0;")
139
+ return result
140
+ }
141
+
142
+ // MARK: - Function Calls
143
+
144
+ /// Call a global JS function by name with arguments. Runs on the JS queue.
145
+ /// Uses objectForKeyedSubscript().call() for performance (avoids re-parsing).
146
+ public func callFunction(_ name: String, withArguments args: [Any], completion: ((JSValue?) -> Void)? = nil) {
147
+ jsQueue.async { [weak self] in
148
+ guard let self = self, let context = self.context else {
149
+ completion?(nil)
150
+ return
151
+ }
152
+
153
+ let function = context.objectForKeyedSubscript(name)
154
+ guard let fn = function, !fn.isUndefined else {
155
+ NSLog("[VueNative] Warning: JS function '\(name)' not found")
156
+ completion?(nil)
157
+ return
158
+ }
159
+
160
+ let result = fn.call(withArguments: args)
161
+
162
+ // Force microtask drain
163
+ context.evaluateScript("void 0;")
164
+
165
+ completion?(result)
166
+ }
167
+ }
168
+
169
+ /// Call a global JS function synchronously. MUST only be called from the JS queue.
170
+ @discardableResult
171
+ public func callFunctionSync(_ name: String, withArguments args: [Any]) -> JSValue? {
172
+ dispatchPrecondition(condition: .onQueue(jsQueue))
173
+ guard let context = context else { return nil }
174
+
175
+ let function = context.objectForKeyedSubscript(name)
176
+ guard let fn = function, !fn.isUndefined else {
177
+ NSLog("[VueNative] Warning: JS function '\(name)' not found")
178
+ return nil
179
+ }
180
+
181
+ let result = fn.call(withArguments: args)
182
+
183
+ // Force microtask drain
184
+ context.evaluateScript("void 0;")
185
+ return result
186
+ }
187
+
188
+ // MARK: - Function Registration
189
+
190
+ /// Register a Swift function as a global JS function.
191
+ /// The block receives a single JSValue argument (use .toArray() etc. to extract args).
192
+ /// IMPORTANT: The block executes on the JS queue.
193
+ public func registerFunction(_ name: String, block: @escaping @convention(block) (JSValue) -> Void) {
194
+ jsQueue.async { [weak self] in
195
+ guard let self = self, let context = self.context else { return }
196
+ let wrappedBlock: @convention(block) (JSValue) -> Void = { [weak self] value in
197
+ _ = self // prevent retain cycle through closure capture
198
+ block(value)
199
+ }
200
+ context.setObject(wrappedBlock, forKeyedSubscript: name as NSString)
201
+ }
202
+ }
203
+
204
+ /// Register a Swift function with multiple arguments.
205
+ public func registerFunctionMultiArg(_ name: String, argCount: Int, block: @escaping ([JSValue]) -> Void) {
206
+ jsQueue.async { [weak self] in
207
+ guard let self = self, let context = self.context else { return }
208
+
209
+ switch argCount {
210
+ case 0:
211
+ let wrapped: @convention(block) () -> Void = { [weak self] in
212
+ _ = self
213
+ block([])
214
+ }
215
+ context.setObject(wrapped, forKeyedSubscript: name as NSString)
216
+ case 1:
217
+ let wrapped: @convention(block) (JSValue) -> Void = { [weak self] a in
218
+ _ = self
219
+ block([a])
220
+ }
221
+ context.setObject(wrapped, forKeyedSubscript: name as NSString)
222
+ case 2:
223
+ let wrapped: @convention(block) (JSValue, JSValue) -> Void = { [weak self] a, b in
224
+ _ = self
225
+ block([a, b])
226
+ }
227
+ context.setObject(wrapped, forKeyedSubscript: name as NSString)
228
+ case 3:
229
+ let wrapped: @convention(block) (JSValue, JSValue, JSValue) -> Void = { [weak self] a, b, c in
230
+ _ = self
231
+ block([a, b, c])
232
+ }
233
+ context.setObject(wrapped, forKeyedSubscript: name as NSString)
234
+ default:
235
+ // For 4+ args, use the single-arg version and extract from JSValue
236
+ let wrapped: @convention(block) (JSValue) -> Void = { [weak self] value in
237
+ _ = self
238
+ block([value])
239
+ }
240
+ context.setObject(wrapped, forKeyedSubscript: name as NSString)
241
+ }
242
+ }
243
+ }
244
+
245
+ // MARK: - Bundle Loading
246
+
247
+ /// Load a JS application bundle from the given source.
248
+ /// Polyfills are already registered during initialize().
249
+ public func loadBundle(source: BundleSource, completion: ((Bool) -> Void)? = nil) {
250
+ jsQueue.async { [weak self] in
251
+ guard let self = self, self.context != nil else {
252
+ completion?(false)
253
+ return
254
+ }
255
+
256
+ switch source {
257
+ case .embedded(let name):
258
+ self.loadEmbeddedBundle(name: name, completion: completion)
259
+
260
+ case .devServer(let url):
261
+ self.loadDevServerBundle(url: url, completion: completion)
262
+ }
263
+ }
264
+ }
265
+
266
+ // MARK: - Private: Bundle Loading
267
+
268
+ private func loadEmbeddedBundle(name: String, completion: ((Bool) -> Void)?) {
269
+ // Try to find the bundle in the main app bundle
270
+ let bundleName: String
271
+ let bundleExtension: String
272
+
273
+ if name.contains(".") {
274
+ let parts = name.split(separator: ".", maxSplits: 1)
275
+ bundleName = String(parts[0])
276
+ bundleExtension = String(parts[1])
277
+ } else {
278
+ bundleName = name
279
+ bundleExtension = "js"
280
+ }
281
+
282
+ // Search in main bundle
283
+ let scriptURL = Bundle.main.url(forResource: bundleName, withExtension: bundleExtension)
284
+
285
+ guard let url = scriptURL else {
286
+ NSLog("[VueNative] Error: Bundle '\(name)' not found in app resources")
287
+ completion?(false)
288
+ return
289
+ }
290
+
291
+ do {
292
+ let script = try String(contentsOf: url, encoding: .utf8)
293
+ NSLog("[VueNative] Loading bundle: \(name) (\(script.count) bytes)")
294
+ self.context.evaluateScript(script, withSourceURL: url)
295
+ // Force microtask drain after bundle load
296
+ self.context.evaluateScript("void 0;")
297
+ NSLog("[VueNative] Bundle loaded successfully")
298
+ completion?(true)
299
+ } catch {
300
+ NSLog("[VueNative] Error loading bundle '\(name)': \(error.localizedDescription)")
301
+ completion?(false)
302
+ }
303
+ }
304
+
305
+ private func loadDevServerBundle(url: URL, completion: ((Bool) -> Void)?) {
306
+ // Fetch from dev server on a background queue, then evaluate on JS queue
307
+ let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
308
+ guard let self = self else {
309
+ completion?(false)
310
+ return
311
+ }
312
+
313
+ self.jsQueue.async { [weak self] in
314
+ guard let self = self, self.context != nil else {
315
+ completion?(false)
316
+ return
317
+ }
318
+
319
+ if let error = error {
320
+ NSLog("[VueNative] Dev server error: \(error.localizedDescription)")
321
+ completion?(false)
322
+ return
323
+ }
324
+
325
+ guard let data = data, let script = String(data: data, encoding: .utf8) else {
326
+ NSLog("[VueNative] Dev server returned invalid data")
327
+ completion?(false)
328
+ return
329
+ }
330
+
331
+ NSLog("[VueNative] Loading dev bundle from \(url) (\(script.count) bytes)")
332
+ self.context.evaluateScript(script, withSourceURL: url)
333
+ // Force microtask drain
334
+ self.context.evaluateScript("void 0;")
335
+ NSLog("[VueNative] Dev bundle loaded successfully")
336
+ completion?(true)
337
+ }
338
+ }
339
+ task.resume()
340
+ }
341
+
342
+ // MARK: - Hot Reload
343
+
344
+ /// Reload the runtime with a new JavaScript bundle string.
345
+ /// Creates a fresh JSContext, re-registers polyfills, and evaluates the bundle.
346
+ /// Calls completion on the JS queue with success/failure.
347
+ public func reload(bundle: String, completion: ((Bool) -> Void)? = nil) {
348
+ jsQueue.async { [weak self] in
349
+ guard let self = self else {
350
+ completion?(false)
351
+ return
352
+ }
353
+
354
+ NSLog("[VueNative] Hot reload: tearing down old context...")
355
+
356
+ // Tear down old Vue app gracefully
357
+ if let teardown = self.context?.objectForKeyedSubscript("__VN_teardown"),
358
+ !teardown.isUndefined {
359
+ teardown.call(withArguments: [])
360
+ self.context?.evaluateScript("void 0;")
361
+ }
362
+
363
+ // Reset polyfill state (timers) before creating new context
364
+ SharedJSPolyfills.reset()
365
+
366
+ // Create fresh context
367
+ self.context = JSContext()
368
+ self.context.exceptionHandler = { [weak self] _, exception in
369
+ guard let exception = exception else { return }
370
+ let message = exception.toString() ?? "Unknown JS error"
371
+ let line = exception.objectForKeyedSubscript("line")?.toInt32() ?? 0
372
+ let stack = exception.objectForKeyedSubscript("stack")?.toString() ?? ""
373
+ NSLog("[VueNative JS Error] \(message) at line \(line)")
374
+ #if DEBUG
375
+ let fullMessage = stack.isEmpty ? message : "\(message)\n\n\(stack)"
376
+ self?.onError?(fullMessage)
377
+ #endif
378
+ _ = self
379
+ }
380
+
381
+ // Re-register globalThis and polyfills
382
+ self.context.evaluateScript("var globalThis = this;")
383
+ SharedJSPolyfills.register(in: self)
384
+
385
+ NSLog("[VueNative] Hot reload: evaluating new bundle (\(bundle.count) bytes)...")
386
+ self.context.evaluateScript(bundle)
387
+ if let exception = self.context.exception {
388
+ NSLog("[VueNative] Hot reload bundle error: %@", exception.toString() ?? "unknown")
389
+ self.context.exception = nil
390
+ completion?(false)
391
+ return
392
+ }
393
+ self.context.evaluateScript("void 0;")
394
+ NSLog("[VueNative] Hot reload: complete")
395
+
396
+ completion?(true)
397
+ }
398
+ }
399
+
400
+ // MARK: - Teardown
401
+
402
+ /// Invalidate the runtime and release resources.
403
+ /// After calling this, the runtime cannot be used again.
404
+ public func invalidate() {
405
+ jsQueue.async { [weak self] in
406
+ guard let self = self else { return }
407
+ self.context = nil
408
+ self.isInitialized = false
409
+ NSLog("[VueNative] JS runtime invalidated")
410
+ }
411
+ }
412
+ }
@@ -0,0 +1,68 @@
1
+ import Foundation
2
+
3
+ /// Native module providing async key-value storage backed by UserDefaults.
4
+ ///
5
+ /// Methods:
6
+ /// - getItem(key: String) -> String?
7
+ /// - setItem(key: String, value: String)
8
+ /// - removeItem(key: String)
9
+ /// - getAllKeys() -> [String]
10
+ /// - clear()
11
+ public final class AsyncStorageModule: NativeModule {
12
+ public let moduleName = "AsyncStorage"
13
+
14
+ private let defaults = UserDefaults.standard
15
+ private let keyPrefix = "VueNative.AsyncStorage."
16
+
17
+ public init() {}
18
+
19
+ public func invoke(method: String, args: [Any], callback: @escaping (Any?, String?) -> Void) {
20
+ DispatchQueue.global(qos: .userInitiated).async { [weak self] in
21
+ guard let self = self else { return }
22
+ switch method {
23
+ case "getItem":
24
+ guard let key = args.first as? String else {
25
+ callback(nil, "getItem: missing key")
26
+ return
27
+ }
28
+ let result = self.defaults.string(forKey: self.keyPrefix + key)
29
+ callback(result, nil)
30
+
31
+ case "setItem":
32
+ guard args.count >= 2,
33
+ let key = args[0] as? String,
34
+ let val = args[1] as? String else {
35
+ callback(nil, "setItem: missing key or value")
36
+ return
37
+ }
38
+ self.defaults.set(val, forKey: self.keyPrefix + key)
39
+ callback(nil, nil)
40
+
41
+ case "removeItem":
42
+ guard let key = args.first as? String else {
43
+ callback(nil, "removeItem: missing key")
44
+ return
45
+ }
46
+ self.defaults.removeObject(forKey: self.keyPrefix + key)
47
+ callback(nil, nil)
48
+
49
+ case "getAllKeys":
50
+ let allKeys = self.defaults.dictionaryRepresentation().keys
51
+ .filter { $0.hasPrefix(self.keyPrefix) }
52
+ .map { String($0.dropFirst(self.keyPrefix.count)) }
53
+ callback(allKeys, nil)
54
+
55
+ case "clear":
56
+ let allKeys = self.defaults.dictionaryRepresentation().keys
57
+ .filter { $0.hasPrefix(self.keyPrefix) }
58
+ for key in allKeys {
59
+ self.defaults.removeObject(forKey: key)
60
+ }
61
+ callback(nil, nil)
62
+
63
+ default:
64
+ callback(nil, "AsyncStorageModule: Unknown method '\(method)'")
65
+ }
66
+ }
67
+ }
68
+ }