@thelacanians/vue-native-cli 0.4.15 → 0.6.2

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,696 @@
1
+ import JavaScriptCore
2
+ import AppKit
3
+ import Security
4
+
5
+ /// Registers browser-like APIs in JSContext that the Vue runtime and application code expect.
6
+ /// macOS port of the iOS JSPolyfills. All callbacks execute on the JS queue unless noted.
7
+ enum JSPolyfills {
8
+
9
+ // MARK: - Timer storage
10
+
11
+ private struct TimerEntry {
12
+ let timer: Timer
13
+ let callbackRef: JSManagedValue?
14
+ }
15
+
16
+ private static let stateQueue = DispatchQueue(label: "com.vuenative.macos.polyfills.state")
17
+ private static var timers: [String: TimerEntry] = [:]
18
+ private static var nextTimerId: Int = 1
19
+
20
+ // MARK: - RAF storage
21
+
22
+ private static var displayLink: CVDisplayLink?
23
+ private static var displayLinkSource: DispatchSourceUserDataAdd?
24
+ private static var displayLinkSourcePtr: UnsafeMutableRawPointer?
25
+ private static var rafCallbacks: [String: JSValue] = [:]
26
+ private static var nextRafId: Int = 1
27
+ private static weak var rafRuntime: JSRuntime?
28
+
29
+ // MARK: - Reset
30
+
31
+ static func reset() {
32
+ let oldTimers: [String: TimerEntry] = stateQueue.sync {
33
+ let snapshot = timers
34
+ timers.removeAll()
35
+ rafCallbacks.removeAll()
36
+ nextTimerId = 1
37
+ nextRafId = 1
38
+ return snapshot
39
+ }
40
+
41
+ DispatchQueue.main.async {
42
+ for (_, entry) in oldTimers {
43
+ entry.timer.invalidate()
44
+ }
45
+ if let link = displayLink {
46
+ CVDisplayLinkStop(link)
47
+ }
48
+ displayLink = nil
49
+ displayLinkSource?.cancel()
50
+ displayLinkSource = nil
51
+ if let ptr = displayLinkSourcePtr {
52
+ Unmanaged<AnyObject>.fromOpaque(ptr).release()
53
+ displayLinkSourcePtr = nil
54
+ }
55
+ rafRuntime = nil
56
+ }
57
+ }
58
+
59
+ // MARK: - Registration
60
+
61
+ static func register(in runtime: JSRuntime) {
62
+ guard let context = runtime.context else { return }
63
+
64
+ registerConsole(in: context)
65
+ registerTimers(in: context, runtime: runtime)
66
+ registerMicrotask(in: context)
67
+ registerRAF(in: context, runtime: runtime)
68
+ registerPerformance(in: context, runtime: runtime)
69
+ registerGlobalThis(in: context)
70
+ registerFetch(in: context, runtime: runtime)
71
+ registerBase64(in: context)
72
+ registerTextEncoding(in: context)
73
+ registerURL(in: context)
74
+ registerCrypto(in: context)
75
+ registerBridgeStubs(in: context)
76
+ }
77
+
78
+ // MARK: - Bridge callback stubs
79
+
80
+ private static func registerBridgeStubs(in context: JSContext) {
81
+ context.evaluateScript("""
82
+ if (typeof __VN_handleGlobalEvent === 'undefined') {
83
+ globalThis.__VN_handleGlobalEvent = function() {};
84
+ }
85
+ if (typeof __VN_handleEvent === 'undefined') {
86
+ globalThis.__VN_handleEvent = function() {};
87
+ }
88
+ if (typeof __VN_resolveCallback === 'undefined') {
89
+ globalThis.__VN_resolveCallback = function() {};
90
+ }
91
+ """)
92
+ }
93
+
94
+ // MARK: - console.log / warn / error
95
+
96
+ private static func registerConsole(in context: JSContext) {
97
+ context.evaluateScript("var console = {};")
98
+
99
+ func formatArgs() -> String {
100
+ guard let args = JSContext.currentArguments() as? [JSValue], !args.isEmpty else {
101
+ return ""
102
+ }
103
+ return args.map { value in
104
+ value.isUndefined ? "undefined" : (value.toString() ?? "null")
105
+ }.joined(separator: " ")
106
+ }
107
+
108
+ let consoleLog: @convention(block) () -> Void = {
109
+ NSLog("[VueNative LOG] %@", formatArgs())
110
+ }
111
+
112
+ let consoleWarn: @convention(block) () -> Void = {
113
+ NSLog("[VueNative WARN] %@", formatArgs())
114
+ }
115
+
116
+ let consoleError: @convention(block) () -> Void = {
117
+ NSLog("[VueNative ERROR] %@", formatArgs())
118
+ }
119
+
120
+ let consoleDebug: @convention(block) () -> Void = {
121
+ NSLog("[VueNative DEBUG] %@", formatArgs())
122
+ }
123
+
124
+ let consoleInfo: @convention(block) () -> Void = {
125
+ NSLog("[VueNative INFO] %@", formatArgs())
126
+ }
127
+
128
+ guard let consoleObj = context.objectForKeyedSubscript("console") else {
129
+ NSLog("[VueNative macOS] Warning: failed to get console object")
130
+ return
131
+ }
132
+ consoleObj.setObject(consoleLog, forKeyedSubscript: "log" as NSString)
133
+ consoleObj.setObject(consoleWarn, forKeyedSubscript: "warn" as NSString)
134
+ consoleObj.setObject(consoleError, forKeyedSubscript: "error" as NSString)
135
+ consoleObj.setObject(consoleDebug, forKeyedSubscript: "debug" as NSString)
136
+ consoleObj.setObject(consoleInfo, forKeyedSubscript: "info" as NSString)
137
+ }
138
+
139
+ // MARK: - setTimeout / clearTimeout / setInterval / clearInterval
140
+
141
+ private static func registerTimers(in context: JSContext, runtime: JSRuntime) {
142
+
143
+ let setTimeout: @convention(block) (JSValue, JSValue) -> JSValue = { [weak runtime] callback, delay in
144
+ guard let runtime = runtime, let context = runtime.context else {
145
+ return JSValue(nullIn: JSContext.current())
146
+ }
147
+
148
+ let delayMs = delay.isUndefined ? 0.0 : delay.toDouble()
149
+ let timerId: String = stateQueue.sync {
150
+ let id = String(nextTimerId)
151
+ nextTimerId += 1
152
+ return id
153
+ }
154
+
155
+ let callbackRef = JSManagedValue(value: callback)
156
+ context.virtualMachine.addManagedReference(callbackRef, withOwner: context)
157
+
158
+ DispatchQueue.main.async {
159
+ let timer = Timer.scheduledTimer(withTimeInterval: max(delayMs / 1000.0, 0.001), repeats: false) { [weak runtime] _ in
160
+ guard let runtime = runtime else { return }
161
+ runtime.jsQueue.async { [weak runtime] in
162
+ guard let runtime = runtime, let context = runtime.context else { return }
163
+ let stillActive: Bool = stateQueue.sync {
164
+ guard timers[timerId] != nil else { return false }
165
+ timers.removeValue(forKey: timerId)
166
+ return true
167
+ }
168
+ guard stillActive else { return }
169
+ if let cb = callbackRef?.value, !cb.isUndefined {
170
+ cb.call(withArguments: [])
171
+ context.evaluateScript("void 0;")
172
+ }
173
+ context.virtualMachine.removeManagedReference(callbackRef, withOwner: context)
174
+ }
175
+ }
176
+ RunLoop.main.add(timer, forMode: .common)
177
+ stateQueue.async {
178
+ if timers[timerId] == nil {
179
+ timers[timerId] = TimerEntry(timer: timer, callbackRef: callbackRef)
180
+ }
181
+ }
182
+ }
183
+
184
+ return JSValue(object: timerId, in: context)
185
+ }
186
+
187
+ let clearTimeout: @convention(block) (JSValue) -> Void = { [weak runtime] timerId in
188
+ guard let runtime = runtime, let context = runtime.context else { return }
189
+ guard let id = timerId.toString() else { return }
190
+ let entry: TimerEntry? = stateQueue.sync {
191
+ return timers.removeValue(forKey: id)
192
+ }
193
+ if let entry = entry {
194
+ context.virtualMachine.removeManagedReference(entry.callbackRef, withOwner: context)
195
+ DispatchQueue.main.async {
196
+ entry.timer.invalidate()
197
+ }
198
+ }
199
+ }
200
+
201
+ let setInterval: @convention(block) (JSValue, JSValue) -> JSValue = { [weak runtime] callback, delay in
202
+ guard let runtime = runtime, let context = runtime.context else {
203
+ return JSValue(nullIn: JSContext.current())
204
+ }
205
+
206
+ let delayMs = delay.isUndefined ? 0.0 : delay.toDouble()
207
+ let timerId: String = stateQueue.sync {
208
+ let id = String(nextTimerId)
209
+ nextTimerId += 1
210
+ return id
211
+ }
212
+
213
+ let callbackRef = JSManagedValue(value: callback)
214
+ context.virtualMachine.addManagedReference(callbackRef, withOwner: context)
215
+
216
+ DispatchQueue.main.async {
217
+ let timer = Timer.scheduledTimer(withTimeInterval: max(delayMs / 1000.0, 0.001), repeats: true) { [weak runtime] _ in
218
+ guard let runtime = runtime else { return }
219
+ runtime.jsQueue.async { [weak runtime] in
220
+ guard let runtime = runtime, let context = runtime.context else { return }
221
+ let stillActive: Bool = stateQueue.sync { timers[timerId] != nil }
222
+ guard stillActive else { return }
223
+ if let cb = callbackRef?.value, !cb.isUndefined {
224
+ cb.call(withArguments: [])
225
+ context.evaluateScript("void 0;")
226
+ }
227
+ }
228
+ }
229
+ RunLoop.main.add(timer, forMode: .common)
230
+ stateQueue.async {
231
+ timers[timerId] = TimerEntry(timer: timer, callbackRef: callbackRef)
232
+ }
233
+ }
234
+
235
+ return JSValue(object: timerId, in: context)
236
+ }
237
+
238
+ let clearInterval: @convention(block) (JSValue) -> Void = { [weak runtime] timerId in
239
+ guard let runtime = runtime, let context = runtime.context else { return }
240
+ guard let id = timerId.toString() else { return }
241
+ let entry: TimerEntry? = stateQueue.sync {
242
+ return timers.removeValue(forKey: id)
243
+ }
244
+ if let entry = entry {
245
+ context.virtualMachine.removeManagedReference(entry.callbackRef, withOwner: context)
246
+ DispatchQueue.main.async {
247
+ entry.timer.invalidate()
248
+ }
249
+ }
250
+ }
251
+
252
+ context.setObject(setTimeout, forKeyedSubscript: "setTimeout" as NSString)
253
+ context.setObject(clearTimeout, forKeyedSubscript: "clearTimeout" as NSString)
254
+ context.setObject(setInterval, forKeyedSubscript: "setInterval" as NSString)
255
+ context.setObject(clearInterval, forKeyedSubscript: "clearInterval" as NSString)
256
+ }
257
+
258
+ // MARK: - queueMicrotask
259
+
260
+ private static func registerMicrotask(in context: JSContext) {
261
+ context.evaluateScript("""
262
+ function queueMicrotask(callback) {
263
+ Promise.resolve().then(callback);
264
+ }
265
+ """)
266
+ }
267
+
268
+ // MARK: - requestAnimationFrame / cancelAnimationFrame
269
+
270
+ private static func registerRAF(in context: JSContext, runtime: JSRuntime) {
271
+
272
+ let requestAnimationFrame: @convention(block) (JSValue) -> JSValue = { [weak runtime] callback in
273
+ guard let runtime = runtime, let context = runtime.context else {
274
+ return JSValue(nullIn: JSContext.current())
275
+ }
276
+
277
+ let rafId: String = stateQueue.sync {
278
+ let id = String(nextRafId)
279
+ nextRafId += 1
280
+ rafCallbacks[id] = callback
281
+ return id
282
+ }
283
+
284
+ // Ensure CVDisplayLink is running
285
+ DispatchQueue.main.async {
286
+ if displayLink == nil {
287
+ rafRuntime = runtime
288
+ startDisplayLink(runtime: runtime)
289
+ }
290
+ }
291
+
292
+ return JSValue(object: rafId, in: context)
293
+ }
294
+
295
+ let cancelAnimationFrame: @convention(block) (JSValue) -> Void = { [weak runtime] rafId in
296
+ _ = runtime
297
+ guard let id = rafId.toString() else { return }
298
+ stateQueue.async { rafCallbacks.removeValue(forKey: id) }
299
+ }
300
+
301
+ context.setObject(requestAnimationFrame, forKeyedSubscript: "requestAnimationFrame" as NSString)
302
+ context.setObject(cancelAnimationFrame, forKeyedSubscript: "cancelAnimationFrame" as NSString)
303
+ }
304
+
305
+ /// Start a CVDisplayLink for requestAnimationFrame on macOS.
306
+ /// CVDisplayLink fires on a background thread, so we use a DispatchSource
307
+ /// to coalesce signals and dispatch to the JS queue.
308
+ private static func startDisplayLink(runtime: JSRuntime) {
309
+ var link: CVDisplayLink?
310
+ CVDisplayLinkCreateWithActiveCGDisplays(&link)
311
+ guard let link = link else { return }
312
+
313
+ let source = DispatchSourceMakeUserDataAdd()
314
+ displayLinkSource = source
315
+
316
+ source.setEventHandler { [weak runtime] in
317
+ guard let runtime = runtime else { return }
318
+ let timestamp = CACurrentMediaTime() * 1000.0
319
+ fireRAFCallbacks(runtime: runtime, timestamp: timestamp)
320
+ }
321
+ source.resume()
322
+
323
+ // Wrap the DispatchSource in an Unmanaged pointer so the C callback
324
+ // can access it without capturing context (C function pointers cannot
325
+ // capture Swift closures).
326
+ let sourcePtr = Unmanaged.passRetained(source as AnyObject).toOpaque()
327
+ displayLinkSourcePtr = sourcePtr
328
+
329
+ CVDisplayLinkSetOutputCallback(link, { _, _, _, _, _, userInfo -> CVReturn in
330
+ guard let userInfo = userInfo else { return kCVReturnSuccess }
331
+ let src = Unmanaged<AnyObject>.fromOpaque(userInfo).takeUnretainedValue()
332
+ (src as? DispatchSourceUserDataAdd)?.add(data: 1)
333
+ return kCVReturnSuccess
334
+ }, sourcePtr)
335
+
336
+ CVDisplayLinkStart(link)
337
+ displayLink = link
338
+ }
339
+
340
+ /// Fire all pending RAF callbacks. RAF is one-shot.
341
+ fileprivate static func fireRAFCallbacks(runtime: JSRuntime, timestamp: Double) {
342
+ let callbacks: [String: JSValue] = stateQueue.sync {
343
+ let snapshot = rafCallbacks
344
+ rafCallbacks.removeAll()
345
+ return snapshot
346
+ }
347
+
348
+ guard !callbacks.isEmpty else {
349
+ // No pending callbacks -- stop the display link
350
+ DispatchQueue.main.async {
351
+ if let link = displayLink {
352
+ CVDisplayLinkStop(link)
353
+ }
354
+ displayLink = nil
355
+ displayLinkSource?.cancel()
356
+ displayLinkSource = nil
357
+ }
358
+ return
359
+ }
360
+
361
+ runtime.jsQueue.async { [weak runtime] in
362
+ guard let runtime = runtime, let context = runtime.context else { return }
363
+
364
+ for (_, callback) in callbacks {
365
+ if !callback.isUndefined {
366
+ callback.call(withArguments: [timestamp])
367
+ }
368
+ }
369
+
370
+ context.evaluateScript("void 0;")
371
+
372
+ let isEmpty: Bool = stateQueue.sync { rafCallbacks.isEmpty }
373
+ if isEmpty {
374
+ DispatchQueue.main.async {
375
+ if let link = displayLink {
376
+ CVDisplayLinkStop(link)
377
+ }
378
+ displayLink = nil
379
+ displayLinkSource?.cancel()
380
+ displayLinkSource = nil
381
+ }
382
+ }
383
+ }
384
+ }
385
+
386
+ // MARK: - performance.now()
387
+
388
+ private static func registerPerformance(in context: JSContext, runtime: JSRuntime) {
389
+ context.evaluateScript("var performance = {};")
390
+
391
+ let performanceNow: @convention(block) () -> Double = { [weak runtime] in
392
+ guard let runtime = runtime else { return 0 }
393
+ return (CFAbsoluteTimeGetCurrent() - runtime.startTime) * 1000.0
394
+ }
395
+
396
+ guard let perfObj = context.objectForKeyedSubscript("performance") else {
397
+ NSLog("[VueNative macOS] Warning: failed to get performance object")
398
+ return
399
+ }
400
+ perfObj.setObject(performanceNow, forKeyedSubscript: "now" as NSString)
401
+ }
402
+
403
+ // MARK: - globalThis
404
+
405
+ private static func registerGlobalThis(in context: JSContext) {
406
+ context.evaluateScript("""
407
+ if (typeof globalThis === 'undefined') {
408
+ var globalThis = this;
409
+ }
410
+ """)
411
+ }
412
+
413
+ // MARK: - fetch
414
+
415
+ private static func registerFetch(in context: JSContext, runtime: JSRuntime) {
416
+ let fetch: @convention(block) (JSValue, JSValue) -> JSValue = { [weak runtime] urlValue, optionsValue in
417
+ guard let runtime = runtime, let context = runtime.context else {
418
+ return JSValue(undefinedIn: JSContext.current())
419
+ }
420
+
421
+ let urlString = urlValue.toString() ?? ""
422
+ guard let url = URL(string: urlString) else {
423
+ return context.evaluateScript("Promise.reject(new TypeError('Invalid URL'))") ?? JSValue(undefinedIn: context)
424
+ }
425
+
426
+ var request = URLRequest(url: url)
427
+
428
+ if !optionsValue.isUndefined && !optionsValue.isNull {
429
+ if let method = optionsValue.objectForKeyedSubscript("method")?.toString() {
430
+ request.httpMethod = method.uppercased()
431
+ } else {
432
+ request.httpMethod = "GET"
433
+ }
434
+ if let headersObj = optionsValue.objectForKeyedSubscript("headers"),
435
+ !headersObj.isUndefined, !headersObj.isNull,
436
+ let headers = headersObj.toDictionary() as? [String: String] {
437
+ for (key, val) in headers {
438
+ request.setValue(val, forHTTPHeaderField: key)
439
+ }
440
+ }
441
+ if let body = optionsValue.objectForKeyedSubscript("body"),
442
+ !body.isUndefined, !body.isNull,
443
+ let bodyStr = body.toString() {
444
+ request.httpBody = bodyStr.data(using: .utf8)
445
+ }
446
+ } else {
447
+ request.httpMethod = "GET"
448
+ }
449
+
450
+ var resolveRef: JSValue?
451
+ var rejectRef: JSValue?
452
+
453
+ let captureExecutor: @convention(block) (JSValue, JSValue) -> Void = { resolve, reject in
454
+ resolveRef = resolve
455
+ rejectRef = reject
456
+ }
457
+
458
+ let promiseCtor = context.evaluateScript("""
459
+ (function(executor) {
460
+ return new Promise(executor);
461
+ })
462
+ """)
463
+ let captureBlock = JSValue(object: captureExecutor as AnyObject, in: context)
464
+ let promise = promiseCtor?.call(withArguments: [captureBlock as Any])
465
+
466
+ let task = URLSession.shared.dataTask(with: request) { [weak runtime] data, response, error in
467
+ guard let runtime = runtime else { return }
468
+ runtime.jsQueue.async { [weak runtime] in
469
+ guard runtime != nil, let context = runtime?.context else { return }
470
+
471
+ if let error = error {
472
+ let errMsg = error.localizedDescription
473
+ if let reject = rejectRef, !reject.isUndefined {
474
+ let errObj = context.evaluateScript("new Error(\(JSPolyfillsJSON.encode(errMsg)))")
475
+ reject.call(withArguments: [errObj as Any])
476
+ }
477
+ return
478
+ }
479
+
480
+ let httpResponse = response as? HTTPURLResponse
481
+ let status = httpResponse?.statusCode ?? 200
482
+ let ok = (200...299).contains(status)
483
+ let bodyData = data ?? Data()
484
+ let bodyString = String(data: bodyData, encoding: .utf8) ?? ""
485
+
486
+ var headersDict: [String: String] = [:]
487
+ if let httpResp = httpResponse {
488
+ for (k, v) in httpResp.allHeaderFields {
489
+ headersDict["\(k)"] = "\(v)"
490
+ }
491
+ }
492
+
493
+ if let resolve = resolveRef, !resolve.isUndefined {
494
+ guard let responseObj = JSValue(newObjectIn: context) else {
495
+ NSLog("[VueNative macOS] Warning: failed to create response object")
496
+ return
497
+ }
498
+ responseObj.setObject(status, forKeyedSubscript: "status" as NSString)
499
+ responseObj.setObject(ok, forKeyedSubscript: "ok" as NSString)
500
+ responseObj.setObject(bodyString, forKeyedSubscript: "_body" as NSString)
501
+
502
+ if let headersObj = JSValue(newObjectIn: context) {
503
+ for (k, v) in headersDict {
504
+ headersObj.setObject(v, forKeyedSubscript: k as NSString)
505
+ }
506
+ responseObj.setObject(headersObj, forKeyedSubscript: "headers" as NSString)
507
+ }
508
+
509
+ let bodyStringCopy = bodyString
510
+ let textMethod: @convention(block) () -> JSValue = {
511
+ return context.evaluateScript("Promise.resolve(\(JSPolyfillsJSON.encode(bodyStringCopy)))") ?? JSValue(undefinedIn: context)
512
+ }
513
+ responseObj.setObject(textMethod, forKeyedSubscript: "text" as NSString)
514
+
515
+ let jsonMethod: @convention(block) () -> JSValue = {
516
+ return context.evaluateScript("(function(s){ try { return Promise.resolve(JSON.parse(s)); } catch(e) { return Promise.reject(e); } })(\(JSPolyfillsJSON.encode(bodyStringCopy)))") ?? JSValue(undefinedIn: context)
517
+ }
518
+ responseObj.setObject(jsonMethod, forKeyedSubscript: "json" as NSString)
519
+
520
+ resolve.call(withArguments: [responseObj])
521
+ }
522
+ }
523
+ }
524
+ task.resume()
525
+
526
+ return promise ?? JSValue(undefinedIn: context)
527
+ }
528
+
529
+ context.setObject(fetch, forKeyedSubscript: "fetch" as NSString)
530
+ }
531
+
532
+ // MARK: - atob / btoa (Base64)
533
+
534
+ private static func registerBase64(in context: JSContext) {
535
+ let atob: @convention(block) (String) -> String = { encoded in
536
+ guard let data = Data(base64Encoded: encoded) else { return "" }
537
+ return String(data: data, encoding: .utf8) ?? ""
538
+ }
539
+ context.setObject(atob, forKeyedSubscript: "atob" as NSString)
540
+
541
+ let btoa: @convention(block) (String) -> String = { str in
542
+ guard let data = str.data(using: .utf8) else { return "" }
543
+ return data.base64EncodedString()
544
+ }
545
+ context.setObject(btoa, forKeyedSubscript: "btoa" as NSString)
546
+ }
547
+
548
+ // MARK: - TextEncoder / TextDecoder
549
+
550
+ private static func registerTextEncoding(in context: JSContext) {
551
+ context.evaluateScript("""
552
+ class TextEncoder {
553
+ constructor(encoding = 'utf-8') { this.encoding = encoding; }
554
+ encode(str) {
555
+ const arr = [];
556
+ for (let i = 0; i < str.length; i++) {
557
+ let c = str.charCodeAt(i);
558
+ if (c < 0x80) { arr.push(c); }
559
+ else if (c < 0x800) { arr.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f)); }
560
+ else if (c < 0xd800 || c >= 0xe000) { arr.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)); }
561
+ else { i++; c = 0x10000 + (((c & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff)); arr.push(0xf0 | (c >> 18), 0x80 | ((c >> 12) & 0x3f), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)); }
562
+ }
563
+ return new Uint8Array(arr);
564
+ }
565
+ }
566
+ class TextDecoder {
567
+ constructor(encoding = 'utf-8') { this.encoding = encoding; }
568
+ decode(buffer) {
569
+ const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
570
+ let result = '';
571
+ for (let i = 0; i < bytes.length;) {
572
+ let c = bytes[i++];
573
+ if (c < 0x80) { result += String.fromCharCode(c); }
574
+ else if (c < 0xe0) { result += String.fromCharCode(((c & 0x1f) << 6) | (bytes[i++] & 0x3f)); }
575
+ else if (c < 0xf0) { result += String.fromCharCode(((c & 0x0f) << 12) | ((bytes[i++] & 0x3f) << 6) | (bytes[i++] & 0x3f)); }
576
+ else { const cp = ((c & 0x07) << 18) | ((bytes[i++] & 0x3f) << 12) | ((bytes[i++] & 0x3f) << 6) | (bytes[i++] & 0x3f); result += String.fromCodePoint(cp); }
577
+ }
578
+ return result;
579
+ }
580
+ }
581
+ globalThis.TextEncoder = TextEncoder;
582
+ globalThis.TextDecoder = TextDecoder;
583
+ """)
584
+ }
585
+
586
+ // MARK: - URL / URLSearchParams
587
+
588
+ private static func registerURL(in context: JSContext) {
589
+ context.evaluateScript("""
590
+ if (typeof URL === 'undefined') {
591
+ class URL {
592
+ constructor(url, base) {
593
+ if (base) {
594
+ if (!url.match(/^[a-zA-Z]+:/)) {
595
+ const b = new URL(base);
596
+ url = b.origin + (url.startsWith('/') ? '' : '/') + url;
597
+ }
598
+ }
599
+ const match = url.match(/^([a-zA-Z]+:)\\/\\/([^/:]+)(:\\d+)?(\\/[^?#]*)?(\\?[^#]*)?(#.*)?$/);
600
+ if (!match) { this.href = url; this.protocol = ''; this.host = ''; this.hostname = ''; this.port = ''; this.pathname = '/'; this.search = ''; this.hash = ''; this.origin = ''; this.searchParams = new URLSearchParams(''); return; }
601
+ this.protocol = match[1] || '';
602
+ this.hostname = match[2] || '';
603
+ this.port = (match[3] || '').slice(1);
604
+ this.host = this.hostname + (this.port ? ':' + this.port : '');
605
+ this.pathname = match[4] || '/';
606
+ this.search = match[5] || '';
607
+ this.hash = match[6] || '';
608
+ this.origin = this.protocol + '//' + this.host;
609
+ this.href = url;
610
+ this.searchParams = new URLSearchParams(this.search);
611
+ }
612
+ toString() { return this.href; }
613
+ }
614
+ globalThis.URL = URL;
615
+ }
616
+ if (typeof URLSearchParams === 'undefined') {
617
+ class URLSearchParams {
618
+ constructor(init) {
619
+ this._params = [];
620
+ if (typeof init === 'string') {
621
+ init.replace(/^\\?/, '').split('&').filter(Boolean).forEach(p => {
622
+ const [k, ...v] = p.split('=');
623
+ this._params.push([decodeURIComponent(k), decodeURIComponent(v.join('='))]);
624
+ });
625
+ }
626
+ }
627
+ get(name) { const p = this._params.find(([k]) => k === name); return p ? p[1] : null; }
628
+ getAll(name) { return this._params.filter(([k]) => k === name).map(([,v]) => v); }
629
+ has(name) { return this._params.some(([k]) => k === name); }
630
+ set(name, value) { this.delete(name); this._params.push([name, String(value)]); }
631
+ append(name, value) { this._params.push([name, String(value)]); }
632
+ delete(name) { this._params = this._params.filter(([k]) => k !== name); }
633
+ toString() { return this._params.map(([k, v]) => encodeURIComponent(k) + '=' + encodeURIComponent(v)).join('&'); }
634
+ forEach(cb) { this._params.forEach(([k, v]) => cb(v, k, this)); }
635
+ entries() { return this._params[Symbol.iterator](); }
636
+ keys() { return this._params.map(([k]) => k)[Symbol.iterator](); }
637
+ values() { return this._params.map(([,v]) => v)[Symbol.iterator](); }
638
+ [Symbol.iterator]() { return this.entries(); }
639
+ }
640
+ globalThis.URLSearchParams = URLSearchParams;
641
+ }
642
+ """)
643
+ }
644
+
645
+ // MARK: - crypto.getRandomValues
646
+
647
+ private static func registerCrypto(in context: JSContext) {
648
+ let cryptoGetRandomValues: @convention(block) (JSValue) -> JSValue = { typedArray in
649
+ let length = typedArray.forProperty("length").toInt32()
650
+ if length > 0 {
651
+ var bytes = [UInt8](repeating: 0, count: Int(length))
652
+ _ = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)
653
+ for i in 0..<Int(length) {
654
+ typedArray.setValue(bytes[i], at: i)
655
+ }
656
+ }
657
+ return typedArray
658
+ }
659
+
660
+ context.evaluateScript("""
661
+ if (typeof crypto === 'undefined') {
662
+ globalThis.crypto = {};
663
+ }
664
+ """)
665
+
666
+ if let cryptoObj = context.objectForKeyedSubscript("crypto") {
667
+ cryptoObj.setObject(cryptoGetRandomValues, forKeyedSubscript: "getRandomValues" as NSString)
668
+ }
669
+ }
670
+ }
671
+
672
+ // MARK: - JSON encode helper
673
+
674
+ private enum JSPolyfillsJSON {
675
+ static func encode(_ str: String) -> String {
676
+ if let data = try? JSONSerialization.data(withJSONObject: [str]),
677
+ let json = String(data: data, encoding: .utf8),
678
+ json.count >= 2 {
679
+ return String(json.dropFirst().dropLast())
680
+ }
681
+ let escaped = str
682
+ .replacingOccurrences(of: "\\", with: "\\\\")
683
+ .replacingOccurrences(of: "\"", with: "\\\"")
684
+ .replacingOccurrences(of: "\n", with: "\\n")
685
+ .replacingOccurrences(of: "\r", with: "\\r")
686
+ .replacingOccurrences(of: "\t", with: "\\t")
687
+ return "\"\(escaped)\""
688
+ }
689
+ }
690
+
691
+ // MARK: - DispatchSource helper
692
+
693
+ /// Creates a DispatchSourceUserDataAdd on the main queue for coalescing CVDisplayLink signals.
694
+ private func DispatchSourceMakeUserDataAdd() -> DispatchSourceUserDataAdd {
695
+ return DispatchSource.makeUserDataAddSource(queue: .main)
696
+ }