@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,673 @@
1
+ import JavaScriptCore
2
+ import Security
3
+ import Foundation
4
+
5
+ /// Registers browser-like APIs in JSContext that the Vue runtime and application code expect.
6
+ /// All callbacks execute on the JS queue (not the main thread) unless noted otherwise.
7
+ ///
8
+ /// This shared version registers everything EXCEPT requestAnimationFrame/cancelAnimationFrame,
9
+ /// which are platform-specific (CADisplayLink on iOS, CVDisplayLink/CADisplayLink on macOS).
10
+ /// Each platform adds its own RAF registration after calling `SharedJSPolyfills.register(in:)`.
11
+ public enum SharedJSPolyfills {
12
+
13
+ // MARK: - Timer storage
14
+
15
+ /// Holds a scheduled Timer alongside its JSManagedValue so both can be cleaned up together.
16
+ private struct TimerEntry {
17
+ let timer: Timer
18
+ let callbackRef: JSManagedValue?
19
+ }
20
+
21
+ /// Serial queue that guards all mutable timer state.
22
+ /// Timer callbacks fire on the main thread and clear/read `timers`;
23
+ /// JS queue closures also mutate `timers` (via setTimeout/clearTimeout).
24
+ /// This queue serializes all access to prevent data races.
25
+ private static let stateQueue = DispatchQueue(label: "com.vuenative.polyfills.state")
26
+
27
+ /// Active timers, keyed by string ID. Access ONLY via `stateQueue`.
28
+ private static var timers: [String: TimerEntry] = [:]
29
+ /// Next timer ID counter. Access ONLY via `stateQueue`.
30
+ private static var nextTimerId: Int = 1
31
+
32
+ // MARK: - RAF storage (managed by platform-specific code)
33
+
34
+ /// Pending requestAnimationFrame callbacks. Access ONLY via `stateQueue`.
35
+ public private(set) static var rafCallbacks: [String: JSValue] = [:]
36
+ /// Next RAF ID counter. Access ONLY via `stateQueue`.
37
+ public private(set) static var nextRafId: Int = 1
38
+
39
+ /// Atomically allocate a RAF ID and store the callback. Returns the ID string.
40
+ /// Platform-specific RAF registration uses this to store callbacks.
41
+ public static func storeRAFCallback(_ callback: JSValue) -> String {
42
+ return stateQueue.sync {
43
+ let id = String(nextRafId)
44
+ nextRafId += 1
45
+ rafCallbacks[id] = callback
46
+ return id
47
+ }
48
+ }
49
+
50
+ /// Remove a RAF callback by ID. Returns true if it was present.
51
+ @discardableResult
52
+ public static func removeRAFCallback(_ id: String) -> Bool {
53
+ return stateQueue.sync {
54
+ return rafCallbacks.removeValue(forKey: id) != nil
55
+ }
56
+ }
57
+
58
+ /// Snapshot and clear all pending RAF callbacks (one-shot semantics).
59
+ /// Platform-specific display link handlers call this each frame.
60
+ public static func drainRAFCallbacks() -> [String: JSValue] {
61
+ return stateQueue.sync {
62
+ let snapshot = rafCallbacks
63
+ rafCallbacks.removeAll()
64
+ return snapshot
65
+ }
66
+ }
67
+
68
+ /// Returns true if there are pending RAF callbacks.
69
+ public static var hasRAFCallbacks: Bool {
70
+ return stateQueue.sync { !rafCallbacks.isEmpty }
71
+ }
72
+
73
+ // MARK: - Reset
74
+
75
+ /// Reset all polyfill state. Call before creating a fresh JSContext on hot reload.
76
+ /// Safe to call from any thread — synchronizes internally via `stateQueue`.
77
+ /// NOTE: Does NOT touch display links — those are platform-specific.
78
+ /// Each platform must invalidate its own display link separately.
79
+ public static func reset() {
80
+ // Snapshot and clear timer/RAF state under the lock
81
+ let oldTimers: [String: TimerEntry] = stateQueue.sync {
82
+ let snapshot = timers
83
+ timers.removeAll()
84
+ rafCallbacks.removeAll()
85
+ nextTimerId = 1
86
+ nextRafId = 1
87
+ return snapshot
88
+ }
89
+
90
+ // Invalidate timers on the main thread (where they were scheduled)
91
+ DispatchQueue.main.async {
92
+ for (_, entry) in oldTimers {
93
+ entry.timer.invalidate()
94
+ }
95
+ }
96
+ }
97
+
98
+ // MARK: - Registration
99
+
100
+ /// Register all shared polyfills into the given JSRuntime's context.
101
+ /// MUST be called on the JS queue.
102
+ /// Does NOT register requestAnimationFrame — each platform adds that separately.
103
+ public static func register(in runtime: JSRuntime) {
104
+ guard let context = runtime.context else { return }
105
+
106
+ registerConsole(in: context)
107
+ registerTimers(in: context, runtime: runtime)
108
+ registerMicrotask(in: context)
109
+ registerPerformance(in: context, runtime: runtime)
110
+ registerGlobalThis(in: context)
111
+ registerFetch(in: context, runtime: runtime)
112
+ registerBase64(in: context)
113
+ registerTextEncoding(in: context)
114
+ registerURL(in: context)
115
+ registerCrypto(in: context)
116
+ registerBridgeStubs(in: context)
117
+ }
118
+
119
+ // MARK: - Bridge callback stubs
120
+
121
+ /// Register no-op stubs for global functions that the bundle will overwrite.
122
+ /// Native modules may call dispatchGlobalEvent() before the JS bundle has
123
+ /// loaded and registered the real handlers — these stubs prevent the
124
+ /// "JS function 'X' not found" warnings during that window.
125
+ private static func registerBridgeStubs(in context: JSContext) {
126
+ context.evaluateScript("""
127
+ if (typeof __VN_handleGlobalEvent === 'undefined') {
128
+ globalThis.__VN_handleGlobalEvent = function() {};
129
+ }
130
+ if (typeof __VN_handleEvent === 'undefined') {
131
+ globalThis.__VN_handleEvent = function() {};
132
+ }
133
+ if (typeof __VN_resolveCallback === 'undefined') {
134
+ globalThis.__VN_resolveCallback = function() {};
135
+ }
136
+ """)
137
+ }
138
+
139
+ // MARK: - console.log / warn / error
140
+
141
+ private static func registerConsole(in context: JSContext) {
142
+ // Create a console object
143
+ context.evaluateScript("var console = {};")
144
+
145
+ /// Helper: format all arguments passed from JS into a single space-separated string.
146
+ func formatArgs() -> String {
147
+ guard let args = JSContext.currentArguments() as? [JSValue], !args.isEmpty else {
148
+ return ""
149
+ }
150
+ return args.map { value in
151
+ value.isUndefined ? "undefined" : (value.toString() ?? "null")
152
+ }.joined(separator: " ")
153
+ }
154
+
155
+ let consoleLog: @convention(block) () -> Void = {
156
+ NSLog("[VueNative LOG] %@", formatArgs())
157
+ }
158
+
159
+ let consoleWarn: @convention(block) () -> Void = {
160
+ NSLog("[VueNative WARN] %@", formatArgs())
161
+ }
162
+
163
+ let consoleError: @convention(block) () -> Void = {
164
+ NSLog("[VueNative ERROR] %@", formatArgs())
165
+ }
166
+
167
+ let consoleDebug: @convention(block) () -> Void = {
168
+ NSLog("[VueNative DEBUG] %@", formatArgs())
169
+ }
170
+
171
+ let consoleInfo: @convention(block) () -> Void = {
172
+ NSLog("[VueNative INFO] %@", formatArgs())
173
+ }
174
+
175
+ guard let consoleObj = context.objectForKeyedSubscript("console") else {
176
+ NSLog("[VueNative] Warning: failed to get console object")
177
+ return
178
+ }
179
+ consoleObj.setObject(consoleLog, forKeyedSubscript: "log" as NSString)
180
+ consoleObj.setObject(consoleWarn, forKeyedSubscript: "warn" as NSString)
181
+ consoleObj.setObject(consoleError, forKeyedSubscript: "error" as NSString)
182
+ consoleObj.setObject(consoleDebug, forKeyedSubscript: "debug" as NSString)
183
+ consoleObj.setObject(consoleInfo, forKeyedSubscript: "info" as NSString)
184
+ }
185
+
186
+ // MARK: - setTimeout / clearTimeout / setInterval / clearInterval
187
+
188
+ private static func registerTimers(in context: JSContext, runtime: JSRuntime) {
189
+
190
+ // setTimeout(callback, delay) -> timerId (String)
191
+ let setTimeout: @convention(block) (JSValue, JSValue) -> JSValue = { [weak runtime] callback, delay in
192
+ guard let runtime = runtime, let context = runtime.context else {
193
+ return JSValue(nullIn: JSContext.current())
194
+ }
195
+
196
+ let delayMs = delay.isUndefined ? 0.0 : delay.toDouble()
197
+ let timerId: String = stateQueue.sync {
198
+ let id = String(nextTimerId)
199
+ nextTimerId += 1
200
+ return id
201
+ }
202
+
203
+ // Protect callback from GC by storing in the context
204
+ let callbackRef = JSManagedValue(value: callback)
205
+ context.virtualMachine.addManagedReference(callbackRef, withOwner: context)
206
+
207
+ // Schedule timer on the main thread RunLoop, then dispatch callback to JS queue
208
+ DispatchQueue.main.async {
209
+ let timer = Timer.scheduledTimer(withTimeInterval: max(delayMs / 1000.0, 0.001), repeats: false) { [weak runtime] _ in
210
+ guard let runtime = runtime else { return }
211
+ runtime.jsQueue.async { [weak runtime] in
212
+ guard let runtime = runtime, let context = runtime.context else { return }
213
+ // Timer already fired — only invoke if not cleared
214
+ let stillActive: Bool = stateQueue.sync {
215
+ guard timers[timerId] != nil else { return false }
216
+ timers.removeValue(forKey: timerId)
217
+ return true
218
+ }
219
+ guard stillActive else { return }
220
+ if let cb = callbackRef?.value, !cb.isUndefined {
221
+ cb.call(withArguments: [])
222
+ // Drain microtasks after timer callback
223
+ context.evaluateScript("void 0;")
224
+ }
225
+ context.virtualMachine.removeManagedReference(callbackRef, withOwner: context)
226
+ }
227
+ }
228
+ RunLoop.main.add(timer, forMode: .common)
229
+ stateQueue.async {
230
+ // Only store if not already cleared before the timer was created
231
+ if timers[timerId] == nil {
232
+ timers[timerId] = TimerEntry(timer: timer, callbackRef: callbackRef)
233
+ }
234
+ }
235
+ }
236
+
237
+ return JSValue(object: timerId, in: context)
238
+ }
239
+
240
+ // clearTimeout(timerId)
241
+ let clearTimeout: @convention(block) (JSValue) -> Void = { [weak runtime] timerId in
242
+ guard let runtime = runtime, let context = runtime.context else { return }
243
+ guard let id = timerId.toString() else { return }
244
+ let entry: TimerEntry? = stateQueue.sync {
245
+ return timers.removeValue(forKey: id)
246
+ }
247
+ if let entry = entry {
248
+ // Remove the managed reference so the JSValue can be GC'd
249
+ context.virtualMachine.removeManagedReference(entry.callbackRef, withOwner: context)
250
+ // Timer invalidation must happen on the main thread where it was created
251
+ DispatchQueue.main.async {
252
+ entry.timer.invalidate()
253
+ }
254
+ }
255
+ }
256
+
257
+ // setInterval(callback, delay) -> timerId (String)
258
+ let setInterval: @convention(block) (JSValue, JSValue) -> JSValue = { [weak runtime] callback, delay in
259
+ guard let runtime = runtime, let context = runtime.context else {
260
+ return JSValue(nullIn: JSContext.current())
261
+ }
262
+
263
+ let delayMs = delay.isUndefined ? 0.0 : delay.toDouble()
264
+ let timerId: String = stateQueue.sync {
265
+ let id = String(nextTimerId)
266
+ nextTimerId += 1
267
+ return id
268
+ }
269
+
270
+ let callbackRef = JSManagedValue(value: callback)
271
+ context.virtualMachine.addManagedReference(callbackRef, withOwner: context)
272
+
273
+ DispatchQueue.main.async {
274
+ let timer = Timer.scheduledTimer(withTimeInterval: max(delayMs / 1000.0, 0.001), repeats: true) { [weak runtime] _ in
275
+ guard let runtime = runtime else { return }
276
+ runtime.jsQueue.async { [weak runtime] in
277
+ guard let runtime = runtime, let context = runtime.context else { return }
278
+ // If the interval was cleared, do not invoke the callback
279
+ let stillActive: Bool = stateQueue.sync { timers[timerId] != nil }
280
+ guard stillActive else { return }
281
+ if let cb = callbackRef?.value, !cb.isUndefined {
282
+ cb.call(withArguments: [])
283
+ context.evaluateScript("void 0;")
284
+ }
285
+ }
286
+ }
287
+ RunLoop.main.add(timer, forMode: .common)
288
+ stateQueue.async {
289
+ timers[timerId] = TimerEntry(timer: timer, callbackRef: callbackRef)
290
+ }
291
+ }
292
+
293
+ return JSValue(object: timerId, in: context)
294
+ }
295
+
296
+ // clearInterval(timerId)
297
+ let clearInterval: @convention(block) (JSValue) -> Void = { [weak runtime] timerId in
298
+ guard let runtime = runtime, let context = runtime.context else { return }
299
+ guard let id = timerId.toString() else { return }
300
+ let entry: TimerEntry? = stateQueue.sync {
301
+ return timers.removeValue(forKey: id)
302
+ }
303
+ if let entry = entry {
304
+ // Remove the managed reference so the JSValue can be GC'd
305
+ context.virtualMachine.removeManagedReference(entry.callbackRef, withOwner: context)
306
+ // Invalidate the timer on the main thread where it was scheduled
307
+ DispatchQueue.main.async {
308
+ entry.timer.invalidate()
309
+ }
310
+ }
311
+ }
312
+
313
+ context.setObject(setTimeout, forKeyedSubscript: "setTimeout" as NSString)
314
+ context.setObject(clearTimeout, forKeyedSubscript: "clearTimeout" as NSString)
315
+ context.setObject(setInterval, forKeyedSubscript: "setInterval" as NSString)
316
+ context.setObject(clearInterval, forKeyedSubscript: "clearInterval" as NSString)
317
+ }
318
+
319
+ // MARK: - queueMicrotask
320
+
321
+ private static func registerMicrotask(in context: JSContext) {
322
+ // queueMicrotask uses Promise.resolve().then() since JSC has native Promise support.
323
+ // This is exactly what Vue's scheduler uses internally.
324
+ context.evaluateScript("""
325
+ function queueMicrotask(callback) {
326
+ Promise.resolve().then(callback);
327
+ }
328
+ """)
329
+ }
330
+
331
+ // MARK: - performance.now()
332
+
333
+ private static func registerPerformance(in context: JSContext, runtime: JSRuntime) {
334
+ context.evaluateScript("var performance = {};")
335
+
336
+ let performanceNow: @convention(block) () -> Double = { [weak runtime] in
337
+ guard let runtime = runtime else { return 0 }
338
+ // Return milliseconds since runtime start
339
+ return (CFAbsoluteTimeGetCurrent() - runtime.startTime) * 1000.0
340
+ }
341
+
342
+ guard let perfObj = context.objectForKeyedSubscript("performance") else {
343
+ NSLog("[VueNative] Warning: failed to get performance object")
344
+ return
345
+ }
346
+ perfObj.setObject(performanceNow, forKeyedSubscript: "now" as NSString)
347
+ }
348
+
349
+ // MARK: - globalThis
350
+
351
+ private static func registerGlobalThis(in context: JSContext) {
352
+ // Ensure globalThis points to the global object (may already be set)
353
+ context.evaluateScript("""
354
+ if (typeof globalThis === 'undefined') {
355
+ var globalThis = this;
356
+ }
357
+ """)
358
+ }
359
+
360
+ // MARK: - fetch
361
+
362
+ private static func registerFetch(in context: JSContext, runtime: JSRuntime) {
363
+ // Register __VN_configurePins(pinsJSON) for certificate pinning from JS.
364
+ // pinsJSON is a JSON string: { "domain": ["sha256/hash1", "sha256/hash2"] }
365
+ let configurePins: @convention(block) (JSValue) -> Void = { pinsValue in
366
+ guard let jsonString = pinsValue.toString(),
367
+ let data = jsonString.data(using: .utf8),
368
+ let pinsDict = try? JSONSerialization.jsonObject(with: data) as? [String: [String]] else {
369
+ NSLog("[VueNative CertPin] Invalid pins configuration")
370
+ return
371
+ }
372
+ CertificatePinning.shared.configurePins(pinsDict)
373
+ NSLog("[VueNative CertPin] Configured pins for %d domains", pinsDict.count)
374
+ }
375
+ context.setObject(configurePins, forKeyedSubscript: "__VN_configurePins" as NSString)
376
+
377
+ // fetch(url, options?) -> Promise<Response>
378
+ let fetch: @convention(block) (JSValue, JSValue) -> JSValue = { [weak runtime] urlValue, optionsValue in
379
+ guard let runtime = runtime, let context = runtime.context else {
380
+ return JSValue(undefinedIn: JSContext.current())
381
+ }
382
+
383
+ let urlString = urlValue.toString() ?? ""
384
+ guard let url = URL(string: urlString) else {
385
+ return context.evaluateScript("Promise.reject(new TypeError('Invalid URL'))") ?? JSValue(undefinedIn: context)
386
+ }
387
+
388
+ // Build URLRequest
389
+ var request = URLRequest(url: url)
390
+
391
+ if !optionsValue.isUndefined && !optionsValue.isNull {
392
+ if let method = optionsValue.objectForKeyedSubscript("method")?.toString() {
393
+ request.httpMethod = method.uppercased()
394
+ } else {
395
+ request.httpMethod = "GET"
396
+ }
397
+ if let headersObj = optionsValue.objectForKeyedSubscript("headers"),
398
+ !headersObj.isUndefined, !headersObj.isNull,
399
+ let headers = headersObj.toDictionary() as? [String: String] {
400
+ for (key, val) in headers {
401
+ request.setValue(val, forHTTPHeaderField: key)
402
+ }
403
+ }
404
+ if let body = optionsValue.objectForKeyedSubscript("body"),
405
+ !body.isUndefined, !body.isNull,
406
+ let bodyStr = body.toString() {
407
+ request.httpBody = bodyStr.data(using: .utf8)
408
+ }
409
+ } else {
410
+ request.httpMethod = "GET"
411
+ }
412
+
413
+ // Create a Promise via JS with captured resolve/reject
414
+ var resolveRef: JSValue?
415
+ var rejectRef: JSValue?
416
+
417
+ let captureExecutor: @convention(block) (JSValue, JSValue) -> Void = { resolve, reject in
418
+ resolveRef = resolve
419
+ rejectRef = reject
420
+ }
421
+
422
+ let promiseCtor = context.evaluateScript("""
423
+ (function(executor) {
424
+ return new Promise(executor);
425
+ })
426
+ """)
427
+ let captureBlock = JSValue(object: captureExecutor as AnyObject, in: context)
428
+ let promise = promiseCtor?.call(withArguments: [captureBlock as Any])
429
+
430
+ // Use the pinning session when pins are configured for this host,
431
+ // otherwise fall back to URLSession.shared for zero overhead.
432
+ let host = url.host ?? ""
433
+ let urlSession = CertificatePinning.shared.hasPins(for: host)
434
+ ? CertificatePinning.shared.session
435
+ : URLSession.shared
436
+
437
+ let task = urlSession.dataTask(with: request) { [weak runtime] data, response, error in
438
+ guard let runtime = runtime else { return }
439
+ runtime.jsQueue.async { [weak runtime] in
440
+ guard runtime != nil, let context = runtime?.context else { return }
441
+
442
+ if let error = error {
443
+ let errMsg = error.localizedDescription
444
+ if let reject = rejectRef, !reject.isUndefined {
445
+ let errObj = context.evaluateScript("new Error(\(SharedJSPolyfillsJSON.encode(errMsg)))")
446
+ reject.call(withArguments: [errObj as Any])
447
+ }
448
+ return
449
+ }
450
+
451
+ let httpResponse = response as? HTTPURLResponse
452
+ let status = httpResponse?.statusCode ?? 200
453
+ let ok = (200...299).contains(status)
454
+ let bodyData = data ?? Data()
455
+ let bodyString = String(data: bodyData, encoding: .utf8) ?? ""
456
+
457
+ // Build headers dictionary
458
+ var headersDict: [String: String] = [:]
459
+ if let httpResp = httpResponse {
460
+ for (k, v) in httpResp.allHeaderFields {
461
+ headersDict["\(k)"] = "\(v)"
462
+ }
463
+ }
464
+
465
+ // Create response object in JS
466
+ if let resolve = resolveRef, !resolve.isUndefined {
467
+ guard let responseObj = JSValue(newObjectIn: context) else {
468
+ NSLog("[VueNative] Warning: failed to create response object")
469
+ return
470
+ }
471
+ responseObj.setObject(status, forKeyedSubscript: "status" as NSString)
472
+ responseObj.setObject(ok, forKeyedSubscript: "ok" as NSString)
473
+ responseObj.setObject(bodyString, forKeyedSubscript: "_body" as NSString)
474
+
475
+ // headers object
476
+ if let headersObj = JSValue(newObjectIn: context) {
477
+ for (k, v) in headersDict {
478
+ headersObj.setObject(v, forKeyedSubscript: k as NSString)
479
+ }
480
+ responseObj.setObject(headersObj, forKeyedSubscript: "headers" as NSString)
481
+ }
482
+
483
+ // .text() method
484
+ let bodyStringCopy = bodyString
485
+ let textMethod: @convention(block) () -> JSValue = {
486
+ return context.evaluateScript("Promise.resolve(\(SharedJSPolyfillsJSON.encode(bodyStringCopy)))") ?? JSValue(undefinedIn: context)
487
+ }
488
+ responseObj.setObject(textMethod, forKeyedSubscript: "text" as NSString)
489
+
490
+ // .json() method
491
+ let jsonMethod: @convention(block) () -> JSValue = {
492
+ return context.evaluateScript("(function(s){ try { return Promise.resolve(JSON.parse(s)); } catch(e) { return Promise.reject(e); } })(\(SharedJSPolyfillsJSON.encode(bodyStringCopy)))") ?? JSValue(undefinedIn: context)
493
+ }
494
+ responseObj.setObject(jsonMethod, forKeyedSubscript: "json" as NSString)
495
+
496
+ resolve.call(withArguments: [responseObj])
497
+ }
498
+ }
499
+ }
500
+ task.resume()
501
+
502
+ return promise ?? JSValue(undefinedIn: context)
503
+ }
504
+
505
+ context.setObject(fetch, forKeyedSubscript: "fetch" as NSString)
506
+ }
507
+
508
+ // MARK: - atob / btoa (Base64)
509
+
510
+ private static func registerBase64(in context: JSContext) {
511
+ // atob — decode a base64-encoded string
512
+ let atob: @convention(block) (String) -> String = { encoded in
513
+ guard let data = Data(base64Encoded: encoded) else { return "" }
514
+ return String(data: data, encoding: .utf8) ?? ""
515
+ }
516
+ context.setObject(atob, forKeyedSubscript: "atob" as NSString)
517
+
518
+ // btoa — encode a string to base64
519
+ let btoa: @convention(block) (String) -> String = { str in
520
+ guard let data = str.data(using: .utf8) else { return "" }
521
+ return data.base64EncodedString()
522
+ }
523
+ context.setObject(btoa, forKeyedSubscript: "btoa" as NSString)
524
+ }
525
+
526
+ // MARK: - TextEncoder / TextDecoder
527
+
528
+ private static func registerTextEncoding(in context: JSContext) {
529
+ context.evaluateScript("""
530
+ class TextEncoder {
531
+ constructor(encoding = 'utf-8') { this.encoding = encoding; }
532
+ encode(str) {
533
+ const arr = [];
534
+ for (let i = 0; i < str.length; i++) {
535
+ let c = str.charCodeAt(i);
536
+ if (c < 0x80) { arr.push(c); }
537
+ else if (c < 0x800) { arr.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f)); }
538
+ else if (c < 0xd800 || c >= 0xe000) { arr.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)); }
539
+ 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)); }
540
+ }
541
+ return new Uint8Array(arr);
542
+ }
543
+ }
544
+ class TextDecoder {
545
+ constructor(encoding = 'utf-8') { this.encoding = encoding; }
546
+ decode(buffer) {
547
+ const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
548
+ let result = '';
549
+ for (let i = 0; i < bytes.length;) {
550
+ let c = bytes[i++];
551
+ if (c < 0x80) { result += String.fromCharCode(c); }
552
+ else if (c < 0xe0) { result += String.fromCharCode(((c & 0x1f) << 6) | (bytes[i++] & 0x3f)); }
553
+ else if (c < 0xf0) { result += String.fromCharCode(((c & 0x0f) << 12) | ((bytes[i++] & 0x3f) << 6) | (bytes[i++] & 0x3f)); }
554
+ else { const cp = ((c & 0x07) << 18) | ((bytes[i++] & 0x3f) << 12) | ((bytes[i++] & 0x3f) << 6) | (bytes[i++] & 0x3f); result += String.fromCodePoint(cp); }
555
+ }
556
+ return result;
557
+ }
558
+ }
559
+ globalThis.TextEncoder = TextEncoder;
560
+ globalThis.TextDecoder = TextDecoder;
561
+ """)
562
+ }
563
+
564
+ // MARK: - URL / URLSearchParams
565
+
566
+ private static func registerURL(in context: JSContext) {
567
+ context.evaluateScript("""
568
+ if (typeof URL === 'undefined') {
569
+ class URL {
570
+ constructor(url, base) {
571
+ if (base) {
572
+ if (!url.match(/^[a-zA-Z]+:/)) {
573
+ const b = new URL(base);
574
+ url = b.origin + (url.startsWith('/') ? '' : '/') + url;
575
+ }
576
+ }
577
+ const match = url.match(/^([a-zA-Z]+:)\\/\\/([^/:]+)(:\\d+)?(\\/[^?#]*)?(\\?[^#]*)?(#.*)?$/);
578
+ 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; }
579
+ this.protocol = match[1] || '';
580
+ this.hostname = match[2] || '';
581
+ this.port = (match[3] || '').slice(1);
582
+ this.host = this.hostname + (this.port ? ':' + this.port : '');
583
+ this.pathname = match[4] || '/';
584
+ this.search = match[5] || '';
585
+ this.hash = match[6] || '';
586
+ this.origin = this.protocol + '//' + this.host;
587
+ this.href = url;
588
+ this.searchParams = new URLSearchParams(this.search);
589
+ }
590
+ toString() { return this.href; }
591
+ }
592
+ globalThis.URL = URL;
593
+ }
594
+ if (typeof URLSearchParams === 'undefined') {
595
+ class URLSearchParams {
596
+ constructor(init) {
597
+ this._params = [];
598
+ if (typeof init === 'string') {
599
+ init.replace(/^\\?/, '').split('&').filter(Boolean).forEach(p => {
600
+ const [k, ...v] = p.split('=');
601
+ this._params.push([decodeURIComponent(k), decodeURIComponent(v.join('='))]);
602
+ });
603
+ }
604
+ }
605
+ get(name) { const p = this._params.find(([k]) => k === name); return p ? p[1] : null; }
606
+ getAll(name) { return this._params.filter(([k]) => k === name).map(([,v]) => v); }
607
+ has(name) { return this._params.some(([k]) => k === name); }
608
+ set(name, value) { this.delete(name); this._params.push([name, String(value)]); }
609
+ append(name, value) { this._params.push([name, String(value)]); }
610
+ delete(name) { this._params = this._params.filter(([k]) => k !== name); }
611
+ toString() { return this._params.map(([k, v]) => encodeURIComponent(k) + '=' + encodeURIComponent(v)).join('&'); }
612
+ forEach(cb) { this._params.forEach(([k, v]) => cb(v, k, this)); }
613
+ entries() { return this._params[Symbol.iterator](); }
614
+ keys() { return this._params.map(([k]) => k)[Symbol.iterator](); }
615
+ values() { return this._params.map(([,v]) => v)[Symbol.iterator](); }
616
+ [Symbol.iterator]() { return this.entries(); }
617
+ }
618
+ globalThis.URLSearchParams = URLSearchParams;
619
+ }
620
+ """)
621
+ }
622
+
623
+ // MARK: - crypto.getRandomValues
624
+
625
+ private static func registerCrypto(in context: JSContext) {
626
+ // Native callback using SecRandomCopyBytes for cryptographic randomness
627
+ let cryptoGetRandomValues: @convention(block) (JSValue) -> JSValue = { typedArray in
628
+ let length = typedArray.forProperty("length").toInt32()
629
+ if length > 0 {
630
+ var bytes = [UInt8](repeating: 0, count: Int(length))
631
+ _ = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)
632
+ for i in 0..<Int(length) {
633
+ typedArray.setValue(bytes[i], at: i)
634
+ }
635
+ }
636
+ return typedArray
637
+ }
638
+
639
+ // Ensure the crypto global object exists
640
+ context.evaluateScript("""
641
+ if (typeof crypto === 'undefined') {
642
+ globalThis.crypto = {};
643
+ }
644
+ """)
645
+
646
+ if let cryptoObj = context.objectForKeyedSubscript("crypto") {
647
+ cryptoObj.setObject(cryptoGetRandomValues, forKeyedSubscript: "getRandomValues" as NSString)
648
+ }
649
+ }
650
+ }
651
+
652
+ // MARK: - JSON encode helper
653
+
654
+ /// Produce a JSON-safe string literal (with quotes) for embedding in JS eval strings.
655
+ public enum SharedJSPolyfillsJSON {
656
+ public static func encode(_ str: String) -> String {
657
+ // Wrap in array so JSONSerialization gets a valid top-level type.
658
+ // String alone causes an NSException that try? cannot catch.
659
+ if let data = try? JSONSerialization.data(withJSONObject: [str]),
660
+ let json = String(data: data, encoding: .utf8),
661
+ json.count >= 2 {
662
+ return String(json.dropFirst().dropLast())
663
+ }
664
+ // Fallback: manual escaping
665
+ let escaped = str
666
+ .replacingOccurrences(of: "\\", with: "\\\\")
667
+ .replacingOccurrences(of: "\"", with: "\\\"")
668
+ .replacingOccurrences(of: "\n", with: "\\n")
669
+ .replacingOccurrences(of: "\r", with: "\\r")
670
+ .replacingOccurrences(of: "\t", with: "\\t")
671
+ return "\"\(escaped)\""
672
+ }
673
+ }