@legendapp/spark 0.0.1-next.2 → 0.0.1-next.4

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 (422) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -4
  3. package/ai-codex.ts +1 -0
  4. package/ai.ts +1 -0
  5. package/app-documents.ts +1 -0
  6. package/camera.ts +13 -0
  7. package/config-plugin.d.cts +2 -0
  8. package/contracts.ts +2 -0
  9. package/diagnostics.ts +1 -0
  10. package/expo-config.d.cts +1 -0
  11. package/metro.cjs +4 -1
  12. package/metro.d.cts +12 -0
  13. package/native.cjs +3 -1
  14. package/native.d.cts +6 -0
  15. package/package.json +93 -15
  16. package/patches/camera/react-native-nitro-image.patch +320 -0
  17. package/patches/camera/react-native-nitro-modules.patch +50 -0
  18. package/patches/camera/react-native-vision-camera.patch +2662 -0
  19. package/patches/camera/upstream.json +22 -0
  20. package/patches/dependencies.json +8 -0
  21. package/patches/react-native-enriched-markdown@1.0.2.patch +8684 -0
  22. package/postinstall.cjs +1 -0
  23. package/schema.json +276 -58
  24. package/settings-observable.ts +1 -0
  25. package/settings-window.ts +1 -0
  26. package/shortcuts-commands.ts +1 -0
  27. package/shortcuts-keyboard.ts +3 -0
  28. package/ui-classnames.ts +1 -0
  29. package/ui-glass.ts +1 -0
  30. package/ui-search.ts +1 -0
  31. package/ui-sidebar.ts +1 -0
  32. package/ui-split-view.ts +1 -0
  33. package/ui-swipe.ts +1 -0
  34. package/ui-symbol.ts +1 -0
  35. package/vendor/node_modules/@legendapp/spark-ai/package.json +19 -0
  36. package/vendor/node_modules/@legendapp/spark-ai/src/availability.ts +17 -0
  37. package/vendor/node_modules/@legendapp/spark-ai/src/index.ts +15 -0
  38. package/vendor/node_modules/@legendapp/spark-ai/src/invocations.ts +34 -0
  39. package/vendor/node_modules/@legendapp/spark-ai/src/output.ts +62 -0
  40. package/vendor/node_modules/@legendapp/spark-ai/src/run.ts +12 -0
  41. package/vendor/node_modules/@legendapp/spark-ai/src/types.ts +16 -0
  42. package/vendor/node_modules/@legendapp/spark-audio/macos/RNSparkAudio.h +2 -1
  43. package/vendor/node_modules/@legendapp/spark-audio/macos/RNSparkAudio.mm +126 -24
  44. package/vendor/node_modules/@legendapp/spark-audio/package.json +5 -2
  45. package/vendor/node_modules/@legendapp/spark-audio/src/NativeSparkAudio.ts +5 -1
  46. package/vendor/node_modules/@legendapp/spark-audio/src/desktop.ts +36 -32
  47. package/vendor/node_modules/@legendapp/spark-audio/src/events.ts +11 -0
  48. package/vendor/node_modules/@legendapp/spark-audio/src/hooks.ts +65 -0
  49. package/vendor/node_modules/@legendapp/spark-audio/src/index.web.ts +92 -33
  50. package/vendor/node_modules/@legendapp/spark-audio/src/media-session-unavailable.ts +6 -4
  51. package/vendor/node_modules/@legendapp/spark-audio/src/media-session.ts +52 -19
  52. package/vendor/node_modules/@legendapp/spark-audio/src/media-session.web.ts +18 -10
  53. package/vendor/node_modules/@legendapp/spark-audio/src/media-types.ts +40 -13
  54. package/vendor/node_modules/@legendapp/spark-audio/src/mobile.ts +40 -32
  55. package/vendor/node_modules/@legendapp/spark-audio/src/player.ts +97 -0
  56. package/vendor/node_modules/@legendapp/spark-audio/src/status-listeners.ts +21 -15
  57. package/vendor/node_modules/@legendapp/spark-audio/src/types.ts +9 -4
  58. package/vendor/node_modules/@legendapp/spark-audio/windows/SparkAudio/SparkAudio.h +67 -20
  59. package/vendor/node_modules/@legendapp/spark-auth-session/package.json +4 -3
  60. package/vendor/node_modules/@legendapp/spark-auth-session/src/core.ts +60 -42
  61. package/vendor/node_modules/@legendapp/spark-auth-session/src/index.ts +28 -12
  62. package/vendor/node_modules/@legendapp/spark-auth-session/src/mobile.ts +97 -41
  63. package/vendor/node_modules/@legendapp/spark-auth-session/src/types.ts +20 -9
  64. package/vendor/node_modules/@legendapp/spark-cli/dist/add-desktop.js +50 -7
  65. package/vendor/node_modules/@legendapp/spark-cli/dist/build-mode.js +1 -1
  66. package/vendor/node_modules/@legendapp/spark-cli/dist/build.js +145 -59
  67. package/vendor/node_modules/@legendapp/spark-cli/dist/cli-arguments.js +61 -0
  68. package/vendor/node_modules/@legendapp/spark-cli/dist/commands.js +10 -4
  69. package/vendor/node_modules/@legendapp/spark-cli/dist/create.js +37 -8
  70. package/vendor/node_modules/@legendapp/spark-cli/dist/credentials.js +86 -25
  71. package/vendor/node_modules/@legendapp/spark-cli/dist/dependency-pins.js +132 -0
  72. package/vendor/node_modules/@legendapp/spark-cli/dist/dev-command.js +13 -4
  73. package/vendor/node_modules/@legendapp/spark-cli/dist/dev.js +13 -10
  74. package/vendor/node_modules/@legendapp/spark-cli/dist/errors.js +100 -0
  75. package/vendor/node_modules/@legendapp/spark-cli/dist/examples.js +2 -2
  76. package/vendor/node_modules/@legendapp/spark-cli/dist/expo-dev-patch.cjs +4 -2
  77. package/vendor/node_modules/@legendapp/spark-cli/dist/expo-metro.cjs +1 -1
  78. package/vendor/node_modules/@legendapp/spark-cli/dist/expo-native.cjs +5 -1
  79. package/vendor/node_modules/@legendapp/spark-cli/dist/expo-node.js +3 -3
  80. package/vendor/node_modules/@legendapp/spark-cli/dist/helpers.js +1 -1
  81. package/vendor/node_modules/@legendapp/spark-cli/dist/index.js +52 -44
  82. package/vendor/node_modules/@legendapp/spark-cli/dist/json.js +16 -0
  83. package/vendor/node_modules/@legendapp/spark-cli/dist/local.js +1 -1
  84. package/vendor/node_modules/@legendapp/spark-cli/dist/metro.cjs +26 -5
  85. package/vendor/node_modules/@legendapp/spark-cli/dist/native-metadata.js +26 -0
  86. package/vendor/node_modules/@legendapp/spark-cli/dist/native-patch-preflight.js +86 -0
  87. package/vendor/node_modules/@legendapp/spark-cli/dist/native-preparation.js +88 -0
  88. package/vendor/node_modules/@legendapp/spark-cli/dist/notary-profiles.js +75 -0
  89. package/vendor/node_modules/@legendapp/spark-cli/dist/package.js +4 -2
  90. package/vendor/node_modules/@legendapp/spark-cli/dist/platform.js +16 -2
  91. package/vendor/node_modules/@legendapp/spark-cli/dist/process.js +7 -0
  92. package/vendor/node_modules/@legendapp/spark-cli/dist/project.js +90 -32
  93. package/vendor/node_modules/@legendapp/spark-cli/dist/release.js +86 -5
  94. package/vendor/node_modules/@legendapp/spark-cli/dist/release.json +23 -18
  95. package/vendor/node_modules/@legendapp/spark-cli/dist/runner-install.js +145 -31
  96. package/vendor/node_modules/@legendapp/spark-cli/dist/signing.js +4 -3
  97. package/vendor/node_modules/@legendapp/spark-cli/dist/universal.cjs +2 -1
  98. package/vendor/node_modules/@legendapp/spark-cli/dist/updates.js +41 -4
  99. package/vendor/node_modules/@legendapp/spark-cli/dist/windows.js +5 -9
  100. package/vendor/node_modules/@legendapp/spark-cli/package.json +7 -6
  101. package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/App.tsx +6 -12
  102. package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/desktop.config.json +5 -3
  103. package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/package.json +12 -10
  104. package/vendor/node_modules/@legendapp/spark-cli/templates/diff-lite/Git.macos.tsx +2 -2
  105. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/Integration.macos.tsx +23 -21
  106. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/Integration.windows.tsx +23 -21
  107. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/io.desktop.ts +11 -8
  108. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/io.macos.ts +3 -3
  109. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/Lifecycle.desktop.tsx +8 -14
  110. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/OpenFiles.desktop.tsx +5 -10
  111. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/io.desktop.ts +11 -8
  112. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/io.macos.ts +3 -3
  113. package/vendor/node_modules/@legendapp/spark-cli/templates/music-lite/assets.desktop.ts +2 -2
  114. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/Activation.desktop.tsx +2 -2
  115. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/Desktop.desktop.ts +37 -15
  116. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/session.ts +5 -6
  117. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/window-session.ts +5 -4
  118. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/desktop.config.json +5 -3
  119. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/metro.config.js +1 -1
  120. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/package.json +14 -12
  121. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/react-native.config.js +1 -1
  122. package/vendor/node_modules/@legendapp/spark-cli/templates/windows/package.json +14 -12
  123. package/vendor/node_modules/@legendapp/spark-cli/templates/windows/react-native.config.js +1 -1
  124. package/vendor/node_modules/@legendapp/spark-clipboard/macos/RNDesktopClipboard.mm +10 -16
  125. package/vendor/node_modules/@legendapp/spark-clipboard/package.json +2 -2
  126. package/vendor/node_modules/@legendapp/spark-clipboard/src/NativeDesktopClipboard.ts +1 -1
  127. package/vendor/node_modules/@legendapp/spark-clipboard/src/desktop.ts +55 -31
  128. package/vendor/node_modules/@legendapp/spark-clipboard/src/expo.ts +21 -11
  129. package/vendor/node_modules/@legendapp/spark-clipboard/src/options.ts +8 -0
  130. package/vendor/node_modules/@legendapp/spark-clipboard/src/types.ts +10 -0
  131. package/vendor/node_modules/@legendapp/spark-clipboard/windows/SparkClipboard/SparkClipboard.h +38 -15
  132. package/vendor/node_modules/@legendapp/spark-clipboard/windows/SparkClipboard/SparkClipboard.vcxproj +2 -0
  133. package/vendor/node_modules/@legendapp/spark-codex/RNCodex.podspec +22 -0
  134. package/vendor/node_modules/@legendapp/spark-codex/cpp/HybridCodexAppServer.hpp +23 -0
  135. package/vendor/node_modules/@legendapp/spark-codex/cpp/HybridCodexAppServer.mm +1529 -0
  136. package/vendor/node_modules/@legendapp/spark-codex/nitro.json +21 -0
  137. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/.gitattributes +1 -0
  138. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex+autolinking.rb +62 -0
  139. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Bridge.cpp +17 -0
  140. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Bridge.hpp +27 -0
  141. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Umbrella.hpp +38 -0
  142. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodexAutolinking.mm +35 -0
  143. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodexAutolinking.swift +16 -0
  144. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/CodexAvailability.hpp +95 -0
  145. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/CodexRunResult.hpp +99 -0
  146. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/HybridCodexAppServerSpec.cpp +24 -0
  147. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/HybridCodexAppServerSpec.hpp +71 -0
  148. package/vendor/node_modules/@legendapp/spark-codex/package.json +35 -0
  149. package/vendor/node_modules/@legendapp/spark-codex/react-native.config.js +11 -0
  150. package/vendor/node_modules/@legendapp/spark-codex/src/Codex.nitro.ts +30 -0
  151. package/vendor/node_modules/@legendapp/spark-codex/src/index.ts +65 -0
  152. package/vendor/node_modules/@legendapp/spark-codex/src/native.ts +6 -0
  153. package/vendor/node_modules/@legendapp/spark-commands/package.json +21 -0
  154. package/vendor/node_modules/@legendapp/spark-commands/src/bindings.ts +90 -0
  155. package/vendor/node_modules/@legendapp/spark-commands/src/index.tsx +400 -0
  156. package/vendor/node_modules/@legendapp/spark-commands/src/router.ts +138 -0
  157. package/vendor/node_modules/@legendapp/spark-commands/src/storage.ts +20 -0
  158. package/vendor/node_modules/@legendapp/spark-context-menu/macos/RNContextMenu.mm +7 -8
  159. package/vendor/node_modules/@legendapp/spark-context-menu/package.json +4 -3
  160. package/vendor/node_modules/@legendapp/spark-context-menu/src/NativeContextMenu.ts +1 -1
  161. package/vendor/node_modules/@legendapp/spark-context-menu/src/index.ts +26 -26
  162. package/vendor/node_modules/@legendapp/spark-context-menu/windows/SparkContextMenu/SparkContextMenu.h +3 -2
  163. package/vendor/node_modules/@legendapp/spark-desktop-app/RNDesktopApp.podspec +2 -1
  164. package/vendor/node_modules/@legendapp/spark-desktop-app/cpp/SparkBinaryJSI.hpp +89 -0
  165. package/vendor/node_modules/@legendapp/spark-desktop-app/cpp/SparkBinaryWindows.hpp +22 -0
  166. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/RNDesktopApp.mm +13 -7
  167. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkBinaryJSI.h +48 -0
  168. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktop.h +4 -0
  169. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktop.mm +33 -33
  170. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktopError.h +20 -0
  171. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkLifecycle.h +26 -0
  172. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkLifecycle.mm +49 -0
  173. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkQuitCoordinator.h +9 -0
  174. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkQuitCoordinator.mm +70 -0
  175. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkWindow.mm +9 -6
  176. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/recent-documents/RNRecentDocumentController.mm +55 -0
  177. package/vendor/node_modules/@legendapp/spark-desktop-app/package.json +5 -3
  178. package/vendor/node_modules/@legendapp/spark-desktop-app/src/NativeDesktopApp.ts +1 -1
  179. package/vendor/node_modules/@legendapp/spark-desktop-app/src/api.ts +86 -40
  180. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/accelerator.ts +40 -0
  181. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/index.ts +56 -0
  182. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/menu.ts +116 -0
  183. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/native-buffer.ts +22 -0
  184. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/path.ts +24 -0
  185. package/vendor/node_modules/@legendapp/spark-desktop-app/src/events.ts +75 -0
  186. package/vendor/node_modules/@legendapp/spark-desktop-app/src/index.ts +2 -0
  187. package/vendor/node_modules/@legendapp/spark-desktop-app/src/index.windows.ts +2 -0
  188. package/vendor/node_modules/@legendapp/spark-desktop-app/src/open-requests.ts +43 -0
  189. package/vendor/node_modules/@legendapp/spark-desktop-app/src/restoration.ts +11 -0
  190. package/vendor/node_modules/@legendapp/spark-desktop-app/src/transport.ts +10 -0
  191. package/vendor/node_modules/@legendapp/spark-desktop-config/app.plugin.cjs +10 -5
  192. package/vendor/node_modules/@legendapp/spark-desktop-config/app.plugin.d.cts +3 -0
  193. package/vendor/node_modules/@legendapp/spark-desktop-config/autolinking.cjs +35 -0
  194. package/vendor/node_modules/@legendapp/spark-desktop-config/config.cjs +18 -3
  195. package/vendor/node_modules/@legendapp/spark-desktop-config/config.d.cts +89 -9
  196. package/vendor/node_modules/@legendapp/spark-desktop-config/expo.cjs +1 -0
  197. package/vendor/node_modules/@legendapp/spark-desktop-config/expo.d.cts +3 -0
  198. package/vendor/node_modules/@legendapp/spark-desktop-config/identity.cjs +3 -2
  199. package/vendor/node_modules/@legendapp/spark-desktop-config/lifecycle.cjs +20 -0
  200. package/vendor/node_modules/@legendapp/spark-desktop-config/native-patches.cjs +106 -0
  201. package/vendor/node_modules/@legendapp/spark-desktop-config/native-patches.d.cts +1 -0
  202. package/vendor/node_modules/@legendapp/spark-desktop-config/package.json +7 -5
  203. package/vendor/node_modules/@legendapp/spark-desktop-config/schema.json +276 -58
  204. package/vendor/node_modules/@legendapp/spark-desktop-config/updates.cjs +1 -0
  205. package/vendor/node_modules/@legendapp/spark-desktop-config/validation.cjs +35 -0
  206. package/vendor/node_modules/@legendapp/spark-desktop-config/windows.plugin.cjs +2 -1
  207. package/vendor/node_modules/@legendapp/spark-desktop-host/AppDelegate.mm +129 -11
  208. package/vendor/node_modules/@legendapp/spark-desktop-host/package.json +2 -2
  209. package/vendor/node_modules/@legendapp/spark-desktop-host/windows/application.inc +277 -121
  210. package/vendor/node_modules/@legendapp/spark-desktop-links/common/AuthLoopback.h +5 -7
  211. package/vendor/node_modules/@legendapp/spark-desktop-links/macos/RNDesktopLinks.mm +10 -9
  212. package/vendor/node_modules/@legendapp/spark-desktop-links/package.json +2 -2
  213. package/vendor/node_modules/@legendapp/spark-desktop-links/src/NativeDesktopLinks.ts +1 -1
  214. package/vendor/node_modules/@legendapp/spark-desktop-links/src/api.ts +27 -33
  215. package/vendor/node_modules/@legendapp/spark-desktop-links/src/expo.ts +3 -4
  216. package/vendor/node_modules/@legendapp/spark-desktop-links/src/native.ts +9 -0
  217. package/vendor/node_modules/@legendapp/spark-desktop-links/windows/SparkLinks/SparkLinks.h +22 -22
  218. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/RNDesktopShortcuts.podspec +1 -0
  219. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/RNDesktopShortcuts.mm +1 -1
  220. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/keyboard-manager/RNKeyboardManager.h +9 -0
  221. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/keyboard-manager/RNKeyboardManager.mm +307 -0
  222. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/package.json +8 -5
  223. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/NativeDesktopShortcuts.ts +1 -1
  224. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/api.ts +39 -14
  225. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/NativeKeyboardManager.ts +12 -0
  226. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/codes.ts +108 -0
  227. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/index.ts +99 -0
  228. package/vendor/node_modules/@legendapp/spark-desktop-windows/RNDesktopWindows.podspec +2 -0
  229. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/RNDesktopWindows.mm +229 -103
  230. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/SparkWindowGeometry.h +63 -0
  231. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/LegendWindowRegistry.h +20 -0
  232. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/LegendWindowRegistry.mm +27 -0
  233. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManager.h +12 -0
  234. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManager.mm +3169 -0
  235. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManagerStartup.mm +31 -0
  236. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/SidebarStartupFallbacks.mm +14 -0
  237. package/vendor/node_modules/@legendapp/spark-desktop-windows/package.json +10 -6
  238. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/NativeDesktopWindowManager.ts +1 -1
  239. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/api.ts +146 -70
  240. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/diagnostics.ts +17 -0
  241. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/index.ts +3 -1
  242. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/index.windows.ts +3 -1
  243. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/macos-adapter.ts +53 -0
  244. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/macos.ts +51 -0
  245. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/transport.ts +41 -0
  246. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/types.ts +140 -0
  247. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/validation.ts +162 -0
  248. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/window-manager/NativeWindowManager.ts +28 -0
  249. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/WindowProvider.tsx +20 -0
  250. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/createWindowsNavigator.tsx +78 -0
  251. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/index.ts +6 -0
  252. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/primaryWindowLifecycle.ts +34 -0
  253. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/types.ts +11 -0
  254. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/usePrimaryWindowLifecycle.ts +18 -0
  255. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/useWindowFocusEffect.ts +17 -0
  256. package/vendor/node_modules/@legendapp/spark-documents/package.json +22 -0
  257. package/vendor/node_modules/@legendapp/spark-documents/src/controller.ts +97 -0
  258. package/vendor/node_modules/@legendapp/spark-documents/src/documentTransition.ts +13 -0
  259. package/vendor/node_modules/@legendapp/spark-documents/src/hooks.ts +139 -0
  260. package/vendor/node_modules/@legendapp/spark-documents/src/index.tsx +50 -0
  261. package/vendor/node_modules/@legendapp/spark-documents/src/reload.ts +39 -0
  262. package/vendor/node_modules/@legendapp/spark-documents/src/requests.ts +15 -0
  263. package/vendor/node_modules/@legendapp/spark-drag-drop/package.json +4 -3
  264. package/vendor/node_modules/@legendapp/spark-drag-drop/src/DesktopDragViewNativeComponent.ts +1 -1
  265. package/vendor/node_modules/@legendapp/spark-drag-drop/src/contracts.ts +73 -18
  266. package/vendor/node_modules/@legendapp/spark-drag-drop/src/index.tsx +58 -15
  267. package/vendor/node_modules/@legendapp/spark-drag-drop/windows/SparkDragDrop/SparkDragDrop.h +14 -8
  268. package/vendor/node_modules/@legendapp/spark-file-dialog/ios/RNFileDialog.mm +34 -121
  269. package/vendor/node_modules/@legendapp/spark-file-dialog/package.json +12 -5
  270. package/vendor/node_modules/@legendapp/spark-file-dialog/src/NativeFileDialog.ts +1 -5
  271. package/vendor/node_modules/@legendapp/spark-file-dialog/src/index.ts +19 -84
  272. package/vendor/node_modules/@legendapp/spark-file-dialog/src/native.ts +12 -0
  273. package/vendor/node_modules/@legendapp/spark-file-dialog/src/options.ts +34 -14
  274. package/vendor/node_modules/@legendapp/spark-file-dialog/src/types.ts +26 -0
  275. package/vendor/node_modules/@legendapp/spark-file-dialog/windows/SparkFileDialog/SparkFileDialog.h +42 -58
  276. package/vendor/node_modules/@legendapp/spark-file-system/RNDesktopFileSystem.podspec +1 -0
  277. package/vendor/node_modules/@legendapp/spark-file-system/macos/RNDesktopFileSystem.mm +32 -21
  278. package/vendor/node_modules/@legendapp/spark-file-system/macos/SparkFileMutations.h +24 -0
  279. package/vendor/node_modules/@legendapp/spark-file-system/macos/SparkFileMutations.mm +215 -0
  280. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScanner.h +9 -0
  281. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScanner.mm +82 -0
  282. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScannerCore.h +47 -0
  283. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScannerCore.mm +306 -0
  284. package/vendor/node_modules/@legendapp/spark-file-system/package.json +8 -6
  285. package/vendor/node_modules/@legendapp/spark-file-system/src/NativeDesktopFileSystem.ts +1 -1
  286. package/vendor/node_modules/@legendapp/spark-file-system/src/file-scanner/NativeFileScanner.ts +11 -0
  287. package/vendor/node_modules/@legendapp/spark-file-system/src/file-scanner/index.ts +93 -0
  288. package/vendor/node_modules/@legendapp/spark-file-system/src/handles.ts +48 -14
  289. package/vendor/node_modules/@legendapp/spark-file-system/src/index.ts +134 -45
  290. package/vendor/node_modules/@legendapp/spark-file-system/src/path.ts +1 -9
  291. package/vendor/node_modules/@legendapp/spark-file-system/windows/SparkFileSystem/SparkFileSystem.h +102 -26
  292. package/vendor/node_modules/@legendapp/spark-file-system/windows/SparkFileSystem/SparkFileSystem.vcxproj +2 -0
  293. package/vendor/node_modules/@legendapp/spark-global-shortcuts/RNDesktopGlobalShortcuts.podspec +1 -0
  294. package/vendor/node_modules/@legendapp/spark-global-shortcuts/macos/RNDesktopGlobalShortcuts.mm +7 -4
  295. package/vendor/node_modules/@legendapp/spark-global-shortcuts/package.json +4 -3
  296. package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/NativeDesktopGlobalShortcuts.ts +1 -1
  297. package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/index.ts +39 -10
  298. package/vendor/node_modules/@legendapp/spark-global-shortcuts/windows/SparkGlobalShortcuts/SparkGlobalShortcuts.h +5 -4
  299. package/vendor/node_modules/@legendapp/spark-message-dialog/macos/RNDesktopMessageDialog.mm +2 -2
  300. package/vendor/node_modules/@legendapp/spark-message-dialog/package.json +2 -2
  301. package/vendor/node_modules/@legendapp/spark-message-dialog/src/NativeDesktopMessageDialog.ts +1 -1
  302. package/vendor/node_modules/@legendapp/spark-message-dialog/src/index.ts +48 -11
  303. package/vendor/node_modules/@legendapp/spark-native-menu/ios/RNNativeMenu.mm +141 -609
  304. package/vendor/node_modules/@legendapp/spark-native-menu/package.json +9 -4
  305. package/vendor/node_modules/@legendapp/spark-native-menu/src/NativeMenu.ts +2 -7
  306. package/vendor/node_modules/@legendapp/spark-native-menu/src/api.ts +76 -121
  307. package/vendor/node_modules/@legendapp/spark-native-menu/src/hooks.ts +97 -0
  308. package/vendor/node_modules/@legendapp/spark-native-menu/src/windows-menus.ts +43 -37
  309. package/vendor/node_modules/@legendapp/spark-notifications/macos/RNDesktopNotifications.mm +75 -30
  310. package/vendor/node_modules/@legendapp/spark-notifications/package.json +2 -2
  311. package/vendor/node_modules/@legendapp/spark-notifications/src/NativeDesktopNotifications.ts +1 -1
  312. package/vendor/node_modules/@legendapp/spark-notifications/src/index.ts +130 -42
  313. package/vendor/node_modules/@legendapp/spark-notifications/windows/SparkNotifications/SparkNotifications.h +51 -8
  314. package/vendor/node_modules/@legendapp/spark-processes/macos/RNDesktopProcesses.mm +94 -21
  315. package/vendor/node_modules/@legendapp/spark-processes/package.json +4 -3
  316. package/vendor/node_modules/@legendapp/spark-processes/src/NativeDesktopProcesses.ts +1 -1
  317. package/vendor/node_modules/@legendapp/spark-processes/src/index.ts +124 -28
  318. package/vendor/node_modules/@legendapp/spark-processes/src/types.ts +34 -0
  319. package/vendor/node_modules/@legendapp/spark-processes/windows/SparkProcesses/SparkProcesses.h +83 -34
  320. package/vendor/node_modules/@legendapp/spark-processes/windows/SparkProcesses/SparkProcesses.vcxproj +2 -0
  321. package/vendor/node_modules/@legendapp/spark-secure-storage/macos/RNDesktopSecureStorage.mm +49 -40
  322. package/vendor/node_modules/@legendapp/spark-secure-storage/package.json +2 -2
  323. package/vendor/node_modules/@legendapp/spark-secure-storage/src/NativeDesktopSecureStorage.ts +1 -1
  324. package/vendor/node_modules/@legendapp/spark-secure-storage/src/desktop.ts +16 -14
  325. package/vendor/node_modules/@legendapp/spark-secure-storage/src/expo.ts +0 -1
  326. package/vendor/node_modules/@legendapp/spark-secure-storage/src/options.ts +3 -2
  327. package/vendor/node_modules/@legendapp/spark-secure-storage/src/unavailable.ts +6 -3
  328. package/vendor/node_modules/@legendapp/spark-settings/package.json +10 -3
  329. package/vendor/node_modules/@legendapp/spark-settings/src/fileStorage.ts +11 -0
  330. package/vendor/node_modules/@legendapp/spark-settings/src/index.ts +7 -9
  331. package/vendor/node_modules/@legendapp/spark-settings/src/storage/index.ts +142 -0
  332. package/vendor/node_modules/@legendapp/spark-settings/src/store.ts +63 -21
  333. package/vendor/node_modules/@legendapp/spark-settings-ui/package.json +20 -0
  334. package/vendor/node_modules/@legendapp/spark-settings-ui/src/index.tsx +436 -0
  335. package/vendor/node_modules/@legendapp/spark-settings-ui/src/options.ts +10 -0
  336. package/vendor/node_modules/@legendapp/spark-sqlite/package.json +3 -3
  337. package/vendor/node_modules/@legendapp/spark-sqlite/src/database.ts +182 -0
  338. package/vendor/node_modules/@legendapp/spark-sqlite/src/index.ts +19 -6
  339. package/vendor/node_modules/@legendapp/spark-sqlite/src/types.ts +22 -0
  340. package/vendor/node_modules/@legendapp/spark-system/macos/RNDesktopSystem.mm +36 -11
  341. package/vendor/node_modules/@legendapp/spark-system/package.json +2 -2
  342. package/vendor/node_modules/@legendapp/spark-system/src/NativeDesktopSystem.ts +1 -1
  343. package/vendor/node_modules/@legendapp/spark-system/src/index.ts +89 -32
  344. package/vendor/node_modules/@legendapp/spark-system/windows/SparkSystem/SparkSystem.h +8 -4
  345. package/vendor/node_modules/@legendapp/spark-tray/macos/RNDesktopTray.mm +20 -8
  346. package/vendor/node_modules/@legendapp/spark-tray/package.json +2 -2
  347. package/vendor/node_modules/@legendapp/spark-tray/src/NativeDesktopTray.ts +1 -1
  348. package/vendor/node_modules/@legendapp/spark-tray/src/index.ts +91 -34
  349. package/vendor/node_modules/@legendapp/spark-tray/windows/SparkTray/SparkTray.h +45 -15
  350. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkButton.mm +2 -1
  351. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkTextInput.mm +10 -2
  352. package/vendor/node_modules/@legendapp/spark-ui/macos/appkit-split-view/RNSidebarSplitViewComponent.h +15 -0
  353. package/vendor/node_modules/@legendapp/spark-ui/macos/appkit-split-view/RNSidebarSplitViewComponent.mm +1419 -0
  354. package/vendor/node_modules/@legendapp/spark-ui/macos/glass-effect-view/RNGlassEffectView.mm +72 -0
  355. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSegmentedControl.h +15 -0
  356. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSegmentedControl.mm +194 -0
  357. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSelect.h +15 -0
  358. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSelect.mm +180 -0
  359. package/vendor/node_modules/@legendapp/spark-ui/macos/sf-symbol/RNSFSymbol.h +15 -0
  360. package/vendor/node_modules/@legendapp/spark-ui/macos/sf-symbol/RNSFSymbol.mm +155 -0
  361. package/vendor/node_modules/@legendapp/spark-ui/macos/sidebar/RNSidebar.h +18 -0
  362. package/vendor/node_modules/@legendapp/spark-ui/macos/sidebar/RNSidebar.mm +645 -0
  363. package/vendor/node_modules/@legendapp/spark-ui/macos/swipe-actions/RNSwipeActions.h +10 -0
  364. package/vendor/node_modules/@legendapp/spark-ui/macos/swipe-actions/RNSwipeActions.mm +422 -0
  365. package/vendor/node_modules/@legendapp/spark-ui/macos/text-input-search/RNTextInputSearch.h +15 -0
  366. package/vendor/node_modules/@legendapp/spark-ui/macos/text-input-search/RNTextInputSearch.mm +164 -0
  367. package/vendor/node_modules/@legendapp/spark-ui/package.json +17 -4
  368. package/vendor/node_modules/@legendapp/spark-ui/src/SparkSelectNativeComponent.ts +3 -1
  369. package/vendor/node_modules/@legendapp/spark-ui/src/SparkTextInputNativeComponent.ts +6 -2
  370. package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/SidebarSplitViewNativeComponent.ts +37 -0
  371. package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/index.tsx +86 -0
  372. package/vendor/node_modules/@legendapp/spark-ui/src/availability.ts +10 -0
  373. package/vendor/node_modules/@legendapp/spark-ui/src/classnames/index.ts +8 -0
  374. package/vendor/node_modules/@legendapp/spark-ui/src/control.ts +42 -0
  375. package/vendor/node_modules/@legendapp/spark-ui/src/desktop.tsx +56 -0
  376. package/vendor/node_modules/@legendapp/spark-ui/src/glass-effect-view/index.tsx +15 -0
  377. package/vendor/node_modules/@legendapp/spark-ui/src/index.android.tsx +35 -21
  378. package/vendor/node_modules/@legendapp/spark-ui/src/index.ios.tsx +55 -26
  379. package/vendor/node_modules/@legendapp/spark-ui/src/index.tsx +1 -27
  380. package/vendor/node_modules/@legendapp/spark-ui/src/index.web.tsx +45 -14
  381. package/vendor/node_modules/@legendapp/spark-ui/src/index.windows.tsx +1 -42
  382. package/vendor/node_modules/@legendapp/spark-ui/src/mobile-input.tsx +11 -0
  383. package/vendor/node_modules/@legendapp/spark-ui/src/native-select/NativeSegmentedControlNativeComponent.ts +17 -0
  384. package/vendor/node_modules/@legendapp/spark-ui/src/native-select/NativeSelectNativeComponent.ts +17 -0
  385. package/vendor/node_modules/@legendapp/spark-ui/src/select.ts +5 -3
  386. package/vendor/node_modules/@legendapp/spark-ui/src/sf-symbol/SFSymbolNativeComponent.ts +13 -0
  387. package/vendor/node_modules/@legendapp/spark-ui/src/sf-symbol/index.tsx +27 -0
  388. package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/SidebarItemNativeComponent.ts +25 -0
  389. package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/SidebarNativeComponent.ts +24 -0
  390. package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/index.tsx +104 -0
  391. package/vendor/node_modules/@legendapp/spark-ui/src/specialized.ts +22 -0
  392. package/vendor/node_modules/@legendapp/spark-ui/src/swipe-actions/SwipeActionsNativeComponent.ts +11 -0
  393. package/vendor/node_modules/@legendapp/spark-ui/src/swipe-actions/index.tsx +41 -0
  394. package/vendor/node_modules/@legendapp/spark-ui/src/text-input-search/TextInputSearchNativeComponent.ts +33 -0
  395. package/vendor/node_modules/@legendapp/spark-ui/src/text-input-search/index.tsx +34 -0
  396. package/vendor/node_modules/@legendapp/spark-ui/src/types.ts +30 -19
  397. package/vendor/node_modules/@legendapp/spark-ui/uniwind.ts +3 -1
  398. package/vendor/node_modules/@legendapp/spark-ui/uniwind.web.ts +3 -1
  399. package/vendor/node_modules/@legendapp/spark-ui/windows/SparkUI/SparkUI.h +16 -11
  400. package/vendor/node_modules/@legendapp/spark-updates/macos/RNDesktopUpdates.mm +21 -8
  401. package/vendor/node_modules/@legendapp/spark-updates/package.json +2 -2
  402. package/vendor/node_modules/@legendapp/spark-updates/src/NativeDesktopUpdates.ts +3 -2
  403. package/vendor/node_modules/@legendapp/spark-updates/src/index.ts +71 -15
  404. package/vendor/node_modules/@legendapp/spark-webview/package.json +2 -2
  405. package/vendor/node_modules/@legendapp/spark-webview/src/WebView.tsx +11 -0
  406. package/vendor/node_modules/@legendapp/spark-webview/src/adapter.ts +55 -0
  407. package/vendor/node_modules/@legendapp/spark-webview/src/index.ts +2 -2
  408. package/vendor/node_modules/@legendapp/spark-webview/src/types.ts +32 -0
  409. package/vendor/node_modules/@legendapp/spark-webview/src/view-props.ts +28 -0
  410. package/vendor/node_modules/@legendapp/spark-window-options/index.cjs +51 -14
  411. package/vendor/node_modules/@legendapp/spark-window-options/index.d.ts +21 -8
  412. package/vendor/node_modules/@legendapp/spark-window-options/package.json +1 -1
  413. package/windows-macos.ts +1 -0
  414. package/expo-metro.cjs +0 -1
  415. package/message-dialog.ts +0 -1
  416. package/universal.cjs +0 -1
  417. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/accelerator.ts +0 -22
  418. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows-options.ts +0 -11
  419. package/vendor/node_modules/@legendapp/spark-file-dialog/src/index.windows.ts +0 -15
  420. package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/accelerator.ts +0 -22
  421. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkSelect.h +0 -4
  422. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkSelect.mm +0 -53
@@ -1,39 +1,135 @@
1
1
  import { Platform } from "react-native";
2
+ import { callBinary, nativeBytes } from "@legendapp/spark-desktop-app/src/contracts/native-buffer";
2
3
  import Native from "./NativeDesktopProcesses";
3
- import { onDesktopEvent } from "@legendapp/spark-desktop-app";
4
- export type ProcessOptions = { executable: string; args?: string[]; cwd?: string; env?: Record<string, string>; timeoutMs?: number; input?: string };
5
- export type ProcessOutput = { stream: "stdout" | "stderr"; base64: string };
6
- export type ProcessResult = { exitCode: number; signal: boolean; stdout: string; stderr: string; stdoutBase64: string; stderrBase64: string; timedOut: boolean; outputTruncated: boolean };
4
+ import { onDesktopEvent } from "@legendapp/spark-desktop-app/src/events";
5
+ import { SparkError, invokeNative, nativeError, type Availability } from "@legendapp/spark-desktop-app/src/contracts";
6
+ import { nativePath } from "@legendapp/spark-desktop-app/src/contracts/path";
7
+ import type { ProcessOptions, RunCommandOptions, ProcessInput, ProcessResult, ProcessHandle } from "./types";
8
+ export type * from "./types";
9
+ export function getProcessAvailability(): Availability {
10
+ if (Platform.OS !== "macos" && Platform.OS !== "windows") return { available: false, reason: "unsupported-platform" };
11
+ return Native ? { available: true } : { available: false, reason: "missing-module" };
12
+ }
13
+ async function call<T>(method: string, args: object, validate: (value: unknown) => value is T): Promise<T> {
14
+ if (Platform.OS !== "macos" && Platform.OS !== "windows") throw new SparkError("E_UNSUPPORTED_PLATFORM", "Processes require a desktop host");
15
+ if (!Native) throw new SparkError("E_MODULE_UNAVAILABLE", "Process module is unavailable");
16
+ const { bytes, ...metadata } = args as { bytes?: Uint8Array };
17
+ const value = await invokeNative(() => callBinary(Native!, "__sparkProcessesBinary", method, metadata, bytes));
18
+ if (!validate(value)) throw new SparkError("E_INVALID_DATA", "Invalid native process response");
19
+ return value;
20
+ }
21
+ const command = (method: string, args: object) => call(method, args, (value): value is null => value === null).then(() => {});
22
+ function invalid(message: string): never { throw new SparkError("E_INVALID_ARGUMENT", message); }
23
+ function object(value: unknown, allowed: string[]): asserts value is Record<string, unknown> {
24
+ if (!value || typeof value !== "object" || Array.isArray(value)) invalid("Expected process options");
25
+ for (const key of Object.keys(value)) if (!allowed.includes(key)) throw new SparkError("E_UNSUPPORTED_OPTION", `Unknown process option: ${key}`);
26
+ }
27
+ function commandName(value: unknown): asserts value is string {
28
+ if (typeof value !== "string" || !value || /[\\/\0:]/.test(value) || value === "." || value === ".." || value.trim() !== value) invalid("Expected a command name without path components");
29
+ }
30
+ export async function resolveCommand(name: string): Promise<string | null> {
31
+ commandName(name);
32
+ const value = await call("resolveCommand", { command: name }, (value): value is string | null => value === null || typeof value === "string");
33
+ if (value === null) return null;
34
+ try { return nativePath(value, Platform.OS); } catch (cause) { throw new SparkError("E_INVALID_DATA", "Resolved command path is invalid", { cause }); }
35
+ }
36
+ function inputBytes(input: ProcessInput): Uint8Array {
37
+ if (typeof input === "string") return new TextEncoder().encode(input);
38
+ if (!(input instanceof Uint8Array)) invalid("Process input must be UTF-8 text or Uint8Array");
39
+ return input;
40
+ }
41
+ function result(value: unknown, aborted: boolean, limit: number): ProcessResult {
42
+ if (!value || typeof value !== "object") throw new SparkError("E_INVALID_DATA", "Invalid process exit result");
43
+ const data = value as Record<string, unknown>;
44
+ if (!Number.isInteger(data.exitCode) || (data.exitCode as number) < 0 || (data.exitCode as number) > 0xffffffff || typeof data.terminated !== "boolean" || !(data.terminationSignal === null || (Number.isInteger(data.terminationSignal) && (data.terminationSignal as number) > 0)) || (!data.terminated && data.terminationSignal !== null) || typeof data.timedOut !== "boolean" || typeof data.outputTruncated !== "boolean") throw new SparkError("E_INVALID_DATA", "Invalid process exit status");
45
+ return { exit: data.terminated ? { type: "terminated", signal: data.terminationSignal as number | null } : { type: "exited", code: data.exitCode as number }, stdout: nativeBytes(data.stdout, limit), stderr: nativeBytes(data.stderr, limit), timedOut: data.timedOut, outputTruncated: data.outputTruncated, aborted };
46
+ }
7
47
  let sequence = 0;
8
- function absolute(value: string) { return Platform.OS === "windows" ? /^(?:[a-z]:[\\/]|\\\\[^\\/]+[\\/][^\\/]+)/i.test(value) : value.startsWith("/"); }
9
- export async function spawn(options: ProcessOptions, onOutput?: (chunk: ProcessOutput) => void) {
10
- if (typeof options.executable !== "string" || (options.executable.startsWith("helper:") && !/^helper:[A-Za-z0-9_-]+$/.test(options.executable))) throw new Error("Invalid helper name or executable");
11
- if (options.input !== undefined && typeof options.input !== "string") throw new Error("input must be a string");
12
- if (!absolute(options.executable) && !options.executable.startsWith("helper:")) throw new Error("executable must be an absolute path or helper:name");
13
- if (options.executable.includes("\0") || options.args?.some(arg => typeof arg !== "string" || arg.includes("\0"))) throw new Error("Invalid process arguments");
14
- if (options.cwd !== undefined && (!absolute(options.cwd) || options.cwd.includes("\0"))) throw new Error("cwd must be absolute");
15
- if (options.env && Object.entries(options.env).some(([key, value]) => !key || /[=\0]/.test(key) || typeof value !== "string" || value.includes("\0"))) throw new Error("Invalid process environment");
16
- if (options.timeoutMs !== undefined && (!Number.isFinite(options.timeoutMs) || options.timeoutMs < 1)) throw new Error("timeoutMs must be positive");
48
+ export async function spawn(options: ProcessOptions): Promise<ProcessHandle> {
49
+ object(options, ["target", "args", "cwd", "env", "input", "timeoutMs", "signal", "captureLimitBytes", "onOutput"]);
50
+ object(options.target, ["type", "path", "name"]);
51
+ const { target, signal, onOutput } = options;
52
+ let executable: string;
53
+ if (target.type === "executable") { object(target, ["type", "path"]); executable = nativePath(target.path, Platform.OS); }
54
+ else if (target.type === "helper") { object(target, ["type", "name"]); if (typeof target.name !== "string" || !/^[A-Za-z0-9_-]+$/.test(target.name)) invalid("Invalid helper name"); executable = `helper:${target.name}`; }
55
+ else if (target.type === "command") { object(target, ["type", "name"]); commandName(target.name); executable = target.name; }
56
+ else invalid("Unknown process target type");
57
+ if (options.args !== undefined && (!Array.isArray(options.args) || options.args.some(arg => typeof arg !== "string" || arg.includes("\0")))) invalid("Invalid process arguments");
58
+ const cwd = options.cwd === undefined ? undefined : nativePath(options.cwd, Platform.OS);
59
+ if (options.env !== undefined) {
60
+ if (!options.env || typeof options.env !== "object" || Array.isArray(options.env) || Object.entries(options.env).some(([key, value]) => !key || /[=\0]/.test(key) || typeof value !== "string" || value.includes("\0"))) invalid("Invalid process environment");
61
+ }
62
+ if (options.timeoutMs !== undefined && (!Number.isInteger(options.timeoutMs) || options.timeoutMs < 1 || options.timeoutMs > 0xfffffffe)) invalid("timeoutMs must be an integer from 1 to 4294967294");
63
+ const captureLimitBytes = options.captureLimitBytes ?? 8 * 1024 * 1024;
64
+ if (!Number.isInteger(captureLimitBytes) || captureLimitBytes < 0 || captureLimitBytes > 8 * 1024 * 1024) invalid("captureLimitBytes must be an integer from 0 to 8388608");
65
+ if (onOutput !== undefined && typeof onOutput !== "function") invalid("onOutput must be a function");
66
+ if (signal !== undefined && (!signal || typeof signal.aborted !== "boolean" || typeof signal.addEventListener !== "function" || typeof signal.removeEventListener !== "function")) invalid("Expected an AbortSignal");
67
+ const wire = { executable, command: target.type === "command", args: options.args ?? [], cwd, env: options.env ?? {}, bytes: options.input === undefined ? undefined : inputBytes(options.input), timeoutMs: options.timeoutMs, captureLimitBytes, streamOutput: !!onOutput };
68
+ if (signal?.aborted) throw new SparkError("E_ABORTED", "Process launch was aborted");
69
+ const availability = getProcessAvailability();
70
+ if (!availability.available) throw new SparkError(availability.reason === "missing-module" ? "E_MODULE_UNAVAILABLE" : "E_UNSUPPORTED_PLATFORM", "Process execution is unavailable");
17
71
  const id = `process-${Date.now()}-${++sequence}`;
18
- let finish!: (value: ProcessResult) => void;
19
- let ended = false;
20
- const exited = new Promise<ProcessResult>(resolve => { finish = resolve; });
72
+ let finish!: (value: ProcessResult) => void, fail!: (error: unknown) => void;
73
+ let ended = false, aborted = false, started = false, inputClosing = false, outputStopped = false;
74
+ let closingInput: Promise<void> | undefined, terminating: Promise<void> | undefined;
75
+ let settleExit!: () => void;
76
+ const exitObserved = new Promise<void>(resolve => { settleExit = resolve; });
77
+ const exited = new Promise<ProcessResult>((resolve, reject) => { finish = resolve; fail = reject; });
78
+ // Exit can precede the spawn acknowledgement, before callers receive the handle.
79
+ void exited.catch(() => {});
80
+ function abort() { if (!ended) { aborted = true; if (started) void terminate().catch(fail); } }
81
+ function terminate(): Promise<void> {
82
+ if (ended) return Promise.resolve();
83
+ return terminating ??= command("terminate", { id }).then(() => exitObserved).catch(error => { terminating = undefined; throw error; });
84
+ }
21
85
  const subscription = onDesktopEvent(event => {
22
- if (event.processId !== id) return;
23
- if (event.type === "processOutput") onOutput?.({ stream: event.stream as "stdout" | "stderr", base64: event.base64 as string });
24
- if (event.type === "processExit") { ended = true; subscription.remove(); finish(event.result as ProcessResult); }
25
- });
26
- const call = async (method: string, args: object = {}) => { await Native.call(method, JSON.stringify({ id, ...args })); };
27
- try { await call("spawn", { ...options, streamOutput: !!onOutput }); } catch (error) { subscription.remove(); throw error; }
86
+ if (event.processId !== id || ended) return;
87
+ if (event.type === "processExit") {
88
+ ended = true; subscription.remove(); signal?.removeEventListener("abort", abort); settleExit();
89
+ try { finish(result(event.result, aborted, captureLimitBytes)); } catch (error) { fail(error); }
90
+ } else if (event.type === "processOutput" && !outputStopped) {
91
+ try {
92
+ if (event.stream !== "stdout" && event.stream !== "stderr") throw new SparkError("E_INVALID_DATA", "Invalid process output stream");
93
+ const bytes = nativeBytes(event.bytes, 16384);
94
+ onOutput?.({ stream: event.stream, bytes });
95
+ } catch (error) { outputStopped = true; fail(nativeError(error)); void terminate().catch(fail); }
96
+ }
97
+ }, { types: ["processOutput", "processExit"], target: { field: "processId", value: id } });
98
+ signal?.addEventListener("abort", abort, { once: true });
99
+ try {
100
+ await command("spawn", { id, ...wire }); started = true;
101
+ if (signal?.aborted) abort();
102
+ if (aborted) void terminate().catch(fail);
103
+ } catch (error) {
104
+ subscription.remove(); signal?.removeEventListener("abort", abort);
105
+ // A malformed acknowledgement can follow successful allocation.
106
+ if (error instanceof SparkError && error.code === "E_INVALID_DATA") {
107
+ try { await command("terminate", { id }); }
108
+ catch (cleanup) { throw new SparkError("E_NATIVE", "Process launch and termination failed", { cause: new AggregateError([error, cleanup]) }); }
109
+ }
110
+ throw error;
111
+ }
28
112
  return {
29
113
  id, exited,
30
- async write(text: string) { if (typeof text !== "string") throw new Error("Process input must be a string"); if (ended) throw new Error("Process has exited"); await call("write", { text }); },
31
- async closeInput() { if (!ended) await call("closeInput"); },
32
- async terminate() { if (!ended) await call("terminate"); },
114
+ async write(input) {
115
+ const bytes = inputBytes(input);
116
+ if (ended || inputClosing) throw new SparkError("E_CLOSED", "Process input is closed");
117
+ return command("write", { id, bytes });
118
+ },
119
+ closeInput() {
120
+ inputClosing = true;
121
+ if (ended) return Promise.resolve();
122
+ return closingInput ??= command("closeInput", { id }).catch(error => { closingInput = undefined; throw error; });
123
+ },
124
+ terminate,
33
125
  };
34
126
  }
35
- export async function runCommand(options: ProcessOptions) {
127
+ export async function runCommand(options: RunCommandOptions): Promise<ProcessResult> {
36
128
  const child = await spawn(options);
37
- await child.closeInput();
38
- return child.exited;
129
+ try { await child.closeInput(); return await child.exited; }
130
+ catch (error) {
131
+ try { await child.terminate(); }
132
+ catch (cleanup) { throw new SparkError("E_NATIVE", "Command and termination failed", { cause: new AggregateError([error, cleanup]) }); }
133
+ throw error;
134
+ }
39
135
  }
@@ -0,0 +1,34 @@
1
+ export type ProcessTarget = { type: "executable"; path: string } | { type: "helper"; name: string } | { type: "command"; name: string };
2
+ export type ProcessInput = string | Uint8Array;
3
+ export type ProcessOutput = { stream: "stdout" | "stderr"; bytes: Uint8Array };
4
+ export type ProcessOptions = {
5
+ target: ProcessTarget;
6
+ args?: readonly string[];
7
+ cwd?: string;
8
+ env?: Readonly<Record<string, string>>;
9
+ input?: ProcessInput;
10
+ timeoutMs?: number;
11
+ signal?: AbortSignal;
12
+ /** Maximum captured bytes per stream, 0–8 MiB; streaming remains complete. */
13
+ captureLimitBytes?: number;
14
+ onOutput?: (output: ProcessOutput) => void;
15
+ };
16
+ export type RunCommandOptions = ProcessOptions;
17
+ export type ProcessExit = { type: "exited"; code: number } | { type: "terminated"; signal: number | null };
18
+ export type ProcessResult = {
19
+ exit: ProcessExit;
20
+ stdout: Uint8Array;
21
+ stderr: Uint8Array;
22
+ timedOut: boolean;
23
+ aborted: boolean;
24
+ outputTruncated: boolean;
25
+ };
26
+ export interface ProcessHandle {
27
+ readonly id: string;
28
+ readonly exited: Promise<ProcessResult>;
29
+ /** Strings use UTF-8; byte arrays pass through unchanged. Calls preserve order. */
30
+ write(input: ProcessInput): Promise<void>;
31
+ closeInput(): Promise<void>;
32
+ /** Resolve after the process tree exits and its output drains. Retry on failure. */
33
+ terminate(): Promise<void>;
34
+ }
@@ -1,11 +1,11 @@
1
1
  #pragma once
2
2
  #include "NativeModules.h"
3
+ #include <SparkBinaryWindows.hpp>
3
4
  #include <algorithm>
4
5
  #include <atomic>
5
6
  #include <condition_variable>
6
7
  #include <deque>
7
8
  #include <filesystem>
8
- #include <future>
9
9
  #include <fstream>
10
10
  #include <map>
11
11
  #include <memory>
@@ -13,9 +13,7 @@
13
13
  #include <optional>
14
14
  #include <thread>
15
15
  #include <vector>
16
- #include <wincrypt.h>
17
16
  #include <winrt/Windows.Data.Json.h>
18
- #pragma comment(lib, "Crypt32.lib")
19
17
  namespace winrt::SparkProcesses {
20
18
  namespace React = Microsoft::ReactNative;
21
19
  namespace Json = Windows::Data::Json;
@@ -27,47 +25,75 @@ inline std::wstring Quote(std::wstring const &argument) {
27
25
  }
28
26
  result.append(slashes * 2, L'\\'); result += L'"'; return result;
29
27
  }
30
- inline std::string Base64(std::string const &bytes) {
31
- if (bytes.empty()) return "";
32
- DWORD length = 0; if (!CryptBinaryToStringA(reinterpret_cast<BYTE const *>(bytes.data()), static_cast<DWORD>(bytes.size()), CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, nullptr, &length)) throw_last_error();
33
- std::string result(length, '\0'); if (!CryptBinaryToStringA(reinterpret_cast<BYTE const *>(bytes.data()), static_cast<DWORD>(bytes.size()), CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, result.data(), &length)) throw_last_error(); result.resize(length); return result;
28
+ inline std::wstring ResolveCommand(std::wstring const &name) {
29
+ if (name.empty() || name.find_first_of(L"/\\:") != std::wstring::npos) throw hresult_invalid_argument(L"Expected a command name");
30
+ DWORD size = GetEnvironmentVariableW(L"PATH", nullptr, 0);
31
+ if (!size) return {};
32
+ std::wstring paths(size, L'\0'); GetEnvironmentVariableW(L"PATH", paths.data(), size);
33
+ std::wstring absolutePaths; size_t start = 0;
34
+ while (start < paths.size()) {
35
+ auto end = paths.find(L';', start); auto directory = paths.substr(start, end == std::wstring::npos ? std::wstring::npos : end - start);
36
+ directory.resize(wcslen(directory.c_str()));
37
+ if (directory.size() >= 2 && directory.front() == L'"' && directory.back() == L'"') directory = directory.substr(1, directory.size() - 2);
38
+ if (std::filesystem::path(directory).is_absolute()) { if (!absolutePaths.empty()) absolutePaths += L';'; absolutePaths += directory; }
39
+ if (end == std::wstring::npos) break; start = end + 1;
40
+ }
41
+ if (absolutePaths.empty()) return {};
42
+ wchar_t result[32768]{};
43
+ DWORD count = SearchPathW(absolutePaths.c_str(), name.c_str(), L".exe", 32768, result, nullptr);
44
+ if (!count) return {};
45
+ if (count >= 32768) throw hresult_invalid_argument(L"Resolved command path is too long");
46
+ auto attributes = GetFileAttributesW(result);
47
+ if (attributes == INVALID_FILE_ATTRIBUTES || (attributes & FILE_ATTRIBUTE_DIRECTORY)) return {};
48
+ auto extension = std::filesystem::path(result).extension().wstring();
49
+ if (_wcsicmp(extension.c_str(), L".exe") && _wcsicmp(extension.c_str(), L".com")) return {};
50
+ return result;
34
51
  }
35
- inline std::string UTF8(std::string const &bytes) { return bytes.empty() || MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, bytes.data(), static_cast<int>(bytes.size()), nullptr, 0) ? bytes : ""; }
52
+ class CapturedBytes final : public spark::binary::jsi::MutableBuffer {
53
+ std::string bytes;
54
+ public:
55
+ explicit CapturedBytes(std::string value) : bytes(std::move(value)) {}
56
+ size_t size() const override { return bytes.size(); }
57
+ uint8_t *data() override { return reinterpret_cast<uint8_t *>(bytes.data()); }
58
+ };
36
59
  struct Process : std::enable_shared_from_this<Process> {
37
- struct Input { std::string text; bool close; std::optional<React::ReactPromise<std::string>> promise; };
60
+ struct Input { std::vector<uint8_t> text; bool close; std::optional<spark::binary::NativePromise> promise; };
38
61
  handle process, job, input, output, error;
39
62
  std::mutex mutex; std::condition_variable condition; std::deque<Input> queue;
40
63
  std::atomic<bool> done{false}, terminated{false}, timedOut{false}, truncated{false};
41
64
  bool inputClosed = false;
65
+ size_t captureLimit = 8 * 1024 * 1024;
42
66
  std::string id, stdoutBytes, stderrBytes;
43
67
  React::ReactContext context;
68
+ void StopChecked() { if (job && !TerminateJobObject(job.get(), 1)) throw_last_error(); terminated = true; }
44
69
  void Stop() noexcept { terminated = true; if (job) TerminateJobObject(job.get(), 1); }
45
- void Write(Input value) { std::lock_guard lock(mutex); if (done || inputClosed) { if (value.promise) { if (value.close) value.promise->Resolve("null"); else value.promise->Reject(React::ReactError{"E_PIPE", "Process input is closed"}); } return; } if (value.close) inputClosed = true; queue.push_back(std::move(value)); condition.notify_one(); }
70
+ void Write(Input value) { std::lock_guard lock(mutex); if (done || inputClosed) { if (value.promise) { if (value.close) value.promise->Resolve("null"); else value.promise->Reject(React::ReactError{"E_CLOSED", "Process input is closed"}); } return; } if (value.close) inputClosed = true; queue.push_back(std::move(value)); condition.notify_one(); }
46
71
  void InputLoop() noexcept {
47
72
  while (true) {
48
73
  Input value;
49
74
  { std::unique_lock lock(mutex); condition.wait(lock, [&]() { return done || !queue.empty(); }); if (queue.empty()) break; value = std::move(queue.front()); queue.pop_front(); }
50
- if (done || !input) { if (value.promise) value.promise->Reject(React::ReactError{"E_PIPE", "Process input is closed"}); continue; }
75
+ if (done || !input) { if (value.promise) value.promise->Reject(React::ReactError{"E_CLOSED", "Process input is closed"}); continue; }
51
76
  bool success = true;
52
77
  if (value.close) input.close();
53
78
  else for (size_t offset = 0; offset < value.text.size();) { DWORD written = 0; if (!WriteFile(input.get(), value.text.data() + offset, static_cast<DWORD>(std::min<size_t>(16384, value.text.size() - offset)), &written, nullptr) || !written) { success = false; break; } offset += written; }
54
- if (value.promise) { if (success) value.promise->Resolve("null"); else value.promise->Reject(React::ReactError{"E_PIPE", "Process input is closed"}); }
79
+ if (value.promise) { if (success) value.promise->Resolve("null"); else value.promise->Reject(React::ReactError{"E_CLOSED", "Process input is closed"}); }
55
80
  }
56
81
  input.close();
57
82
  }
58
83
  void Read(HANDLE pipe, std::string &buffer, std::string stream, bool streaming, std::shared_ptr<std::atomic<bool>> active) {
84
+ struct Credits { std::mutex mutex; std::condition_variable ready; size_t pending = 0; };
85
+ auto credits = std::make_shared<Credits>();
59
86
  char bytes[16384]; DWORD count;
60
87
  while (ReadFile(pipe, bytes, sizeof(bytes), &count, nullptr) && count) {
61
- const auto keep = std::min<size_t>(count, 8 * 1024 * 1024 - buffer.size()); buffer.append(bytes, keep); if (keep < count) truncated = true;
88
+ const auto keep = std::min<size_t>(count, captureLimit - buffer.size()); buffer.append(bytes, keep); if (keep < count) truncated = true;
62
89
  if (streaming && *active) {
63
- auto flushed = std::make_shared<std::promise<void>>(); auto wait = flushed->get_future();
64
- auto data = Base64(std::string(bytes, count)); auto key = id;
65
- context.UIDispatcher().Post([context = context, active, key, stream, data, flushed]() {
66
- if (*active) context.EmitJSEvent(L"RCTDeviceEventEmitter", L"desktop", React::JSValueObject{{"type", "processOutput"}, {"processId", key}, {"stream", stream}, {"base64", data}});
67
- flushed->set_value();
90
+ { std::unique_lock lock(credits->mutex); while (*active && credits->pending >= 64) credits->ready.wait_for(lock, std::chrono::milliseconds(100)); if (!*active) break; ++credits->pending; }
91
+ auto data = std::make_shared<spark::binary::Bytes>(std::vector<uint8_t>(bytes, bytes + count)); auto key = id;
92
+ auto delivered = [credits] { { std::lock_guard lock(credits->mutex); --credits->pending; } credits->ready.notify_one(); };
93
+ context.UIDispatcher().Post([context = context, active, key, stream, data, delivered]() {
94
+ if (*active) { Json::JsonObject event; event.SetNamedValue(L"type", Json::JsonValue::CreateStringValue(L"processOutput")); event.SetNamedValue(L"processId", Json::JsonValue::CreateStringValue(to_hstring(key))); event.SetNamedValue(L"stream", Json::JsonValue::CreateStringValue(to_hstring(stream))); spark::binary::Response response{to_string(event.Stringify()), {}, {{"bytes", data}}}; spark::binary::Emit(context.CallInvoker(), response.Build(), delivered); } else delivered();
68
95
  });
69
- // Backpressure bounds the JS queue. Runtime shutdown releases this wait.
70
- while (*active && wait.wait_for(std::chrono::milliseconds(100)) != std::future_status::ready) {}
96
+ // At most 1 MiB per stream awaits JS delivery; no per-chunk round trip.
71
97
  }
72
98
  }
73
99
  }
@@ -76,8 +102,12 @@ struct Process : std::enable_shared_from_this<Process> {
76
102
  if (!CreatePipe(read.put(), write.put(), &security, 0)) throw_last_error();
77
103
  if (!SetHandleInformation(parentReads ? read.get() : write.get(), HANDLE_FLAG_INHERIT, 0)) throw_last_error();
78
104
  }
79
- void Start(Json::JsonObject const &args) {
105
+ void Start(Json::JsonObject const &args, std::optional<std::vector<uint8_t>> inputBytes) {
106
+ const auto limit = args.GetNamedNumber(L"captureLimitBytes", 8 * 1024 * 1024);
107
+ if (limit < 0 || limit > 8 * 1024 * 1024) throw hresult_invalid_argument(L"Invalid capture limit");
108
+ captureLimit = static_cast<size_t>(limit);
80
109
  auto executable = std::wstring(args.GetNamedString(L"executable"));
110
+ if (args.GetNamedBoolean(L"command", false)) { executable = ResolveCommand(executable); if (executable.empty()) throw hresult_error(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), L"Command not found"); }
81
111
  if (executable.rfind(L"helper:", 0) == 0) {
82
112
  const auto name = executable.substr(7); if (name.empty() || name.find_first_not_of(L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-") != std::wstring::npos) throw hresult_invalid_argument(L"Invalid helper name");
83
113
  wchar_t module[32768]{}; GetModuleFileNameW(nullptr, module, 32768);
@@ -117,7 +147,7 @@ struct Process : std::enable_shared_from_this<Process> {
117
147
  process.attach(info.hProcess); handle thread{info.hThread};
118
148
  if (!AssignProcessToJobObject(job.get(), process.get())) { auto error = GetLastError(); TerminateProcess(process.get(), 1); throw hresult_error(HRESULT_FROM_WIN32(error)); }
119
149
  if (ResumeThread(thread.get()) == static_cast<DWORD>(-1)) { Stop(); throw_last_error(); }
120
- if (args.HasKey(L"input")) Write({to_string(args.GetNamedString(L"input")), false, std::nullopt});
150
+ if (inputBytes) Write({std::move(*inputBytes), false, std::nullopt});
121
151
  }
122
152
  };
123
153
  REACT_MODULE(SparkProcesses, L"NativeDesktopProcesses")
@@ -126,16 +156,30 @@ struct SparkProcesses {
126
156
  std::shared_ptr<State> state = std::make_shared<State>();
127
157
  REACT_INIT(Initialize) void Initialize(React::ReactContext const &context) noexcept { state->context = context; }
128
158
  ~SparkProcesses() { auto current = state; *current->active = false; if (current->context) current->context.UIDispatcher().Post([current]() { for (auto const &[id, process] : current->processes) process->Stop(); }); }
129
- REACT_METHOD(call) void call(std::string method, std::string encoded, React::ReactPromise<std::string> promise) noexcept {
130
- auto current = state;
131
- current->context.UIDispatcher().Post([current, method, encoded, promise]() {
159
+ REACT_SYNC_METHOD(installBinary) std::optional<std::string> installBinary() noexcept {
160
+ try {
161
+ spark::binary::Install(state->context, "__sparkProcessesBinary", [current = state](std::string method, std::string encoded, std::optional<std::vector<uint8_t>> bytes, spark::binary::Completion finish) { Call(current, std::move(method), std::move(encoded), spark::binary::NativePromise(finish), std::move(bytes)); });
162
+ return std::nullopt;
163
+ } catch (std::exception const &error) { return error.what(); }
164
+ catch (hresult_error const &error) { return to_string(error.message()); }
165
+ catch (...) { return "Could not install native binary transport"; }
166
+ }
167
+
168
+ REACT_METHOD(call) void call(std::string method, std::string encoded, React::ReactPromise<std::string> promise) noexcept { Call(state, std::move(method), std::move(encoded), promise); }
169
+ static void Call(std::shared_ptr<State> current, std::string method, std::string encoded, spark::binary::NativePromise promise, std::optional<std::vector<uint8_t>> bytes = {}) {
170
+ current->context.UIDispatcher().Post([current, method = std::move(method), encoded = std::move(encoded), promise, bytes = std::move(bytes)]() mutable {
132
171
  try {
133
172
  if (!*current->active) { promise.Reject(React::ReactError{"E_CLOSED", "Process module is closed"}); return; }
134
- auto args = Json::JsonObject::Parse(to_hstring(encoded)); auto id = to_string(args.GetNamedString(L"id"));
173
+ auto args = Json::JsonObject::Parse(to_hstring(encoded));
174
+ if (method == "resolveCommand") {
175
+ auto path = ResolveCommand(std::wstring(args.GetNamedString(L"command")));
176
+ promise.Resolve(to_string(path.empty() ? Json::JsonValue::CreateNullValue().Stringify() : Json::JsonValue::CreateStringValue(path).Stringify())); return;
177
+ }
178
+ auto id = to_string(args.GetNamedString(L"id"));
135
179
  auto found = current->processes.find(id);
136
180
  if (method == "spawn") {
137
181
  if (found != current->processes.end()) { promise.Reject(React::ReactError{"E_EXISTS", "Process id already exists"}); return; }
138
- auto child = std::make_shared<Process>(); child->context = current->context; child->id = id; child->Start(args);
182
+ auto child = std::make_shared<Process>(); child->context = current->context; child->id = id; child->Start(args, std::move(bytes));
139
183
  const auto timeout = args.HasKey(L"timeoutMs") ? static_cast<DWORD>(std::clamp(args.GetNamedNumber(L"timeoutMs"), 1.0, static_cast<double>(INFINITE - 1))) : INFINITE;
140
184
  const bool stream = args.GetNamedBoolean(L"streamOutput", false);
141
185
  std::thread([current, child, timeout, stream]() {
@@ -151,20 +195,25 @@ struct SparkProcesses {
151
195
  TerminateJobObject(child->job.get(), 1); child->done = true; child->condition.notify_all();
152
196
  if (input.joinable()) input.join(); if (output.joinable()) output.join(); if (error.joinable()) error.join();
153
197
  current->context.UIDispatcher().Post([current, child, exit]() {
154
- if (*current->active) current->context.EmitJSEvent(L"RCTDeviceEventEmitter", L"desktop", React::JSValueObject{{"type", "processExit"}, {"processId", child->id}, {"result", React::JSValueObject{
155
- {"exitCode", static_cast<int64_t>(exit)}, {"signal", child->terminated.load()}, {"timedOut", child->timedOut.load()}, {"outputTruncated", child->truncated.load()},
156
- {"stdout", UTF8(child->stdoutBytes)}, {"stderr", UTF8(child->stderrBytes)}, {"stdoutBase64", Base64(child->stdoutBytes)}, {"stderrBase64", Base64(child->stderrBytes)}}});
198
+ if (*current->active) {
199
+ Json::JsonObject result; result.SetNamedValue(L"exitCode", Json::JsonValue::CreateNumberValue(exit)); result.SetNamedValue(L"terminated", Json::JsonValue::CreateBooleanValue(child->terminated)); result.SetNamedValue(L"terminationSignal", Json::JsonValue::CreateNullValue()); result.SetNamedValue(L"timedOut", Json::JsonValue::CreateBooleanValue(child->timedOut)); result.SetNamedValue(L"outputTruncated", Json::JsonValue::CreateBooleanValue(child->truncated));
200
+ Json::JsonObject event; event.SetNamedValue(L"type", Json::JsonValue::CreateStringValue(L"processExit")); event.SetNamedValue(L"processId", Json::JsonValue::CreateStringValue(to_hstring(child->id))); event.SetNamedValue(L"result", result);
201
+ auto stdoutBytes = std::make_shared<CapturedBytes>(std::move(child->stdoutBytes)); auto stderrBytes = std::make_shared<CapturedBytes>(std::move(child->stderrBytes));
202
+ auto metadata = to_string(event.Stringify()); spark::binary::Emit(current->context.CallInvoker(), [metadata = std::move(metadata), stdoutBytes, stderrBytes](spark::binary::jsi::Runtime &rt) { auto value = spark::binary::JSON(metadata)(rt).asObject(rt); auto result = value.getPropertyAsObject(rt, "result"); result.setProperty(rt, "stdout", spark::binary::Buffer(rt, stdoutBytes)); result.setProperty(rt, "stderr", spark::binary::Buffer(rt, stderrBytes)); return spark::binary::jsi::Value(std::move(value)); });
203
+ }
157
204
  current->processes.erase(child->id);
158
205
  });
159
206
  }).detach();
160
207
  current->processes.emplace(id, child);
161
208
  } else if (found == current->processes.end() || found->second->done) { if (method == "terminate" || method == "closeInput") promise.Resolve("null"); else promise.Reject(React::ReactError{"E_CLOSED", "Process has exited"}); return; }
162
- else if (method == "terminate") found->second->Stop();
163
- else if (method == "write" || method == "closeInput") { found->second->Write({method == "write" ? to_string(args.GetNamedString(L"text")) : "", method == "closeInput", promise}); return; }
209
+ else if (method == "terminate") found->second->StopChecked();
210
+ else if (method == "write" || method == "closeInput") { found->second->Write({method == "write" && bytes ? std::move(*bytes) : std::vector<uint8_t>{}, method == "closeInput", promise}); return; }
164
211
  else throw hresult_invalid_argument(L"Unknown process operation");
165
212
  promise.Resolve("null");
166
- } catch (hresult_error const &error) { promise.Reject(React::ReactError{"E_PROCESS", to_string(error.message())}); }
167
- catch (std::exception const &error) { promise.Reject(React::ReactError{"E_PROCESS", error.what()}); }
213
+ } catch (hresult_error const &error) { const auto code = error.code();
214
+ const char *kind = code == E_INVALIDARG ? "E_INVALID_ARGUMENT" : code == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || code == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) ? "E_NOT_FOUND" : code == E_ACCESSDENIED ? "E_PERMISSION_DENIED" : "E_NATIVE";
215
+ promise.Reject(React::ReactError{kind, to_string(error.message())}); }
216
+ catch (std::exception const &error) { promise.Reject(React::ReactError{"E_NATIVE", error.what()}); }
168
217
  });
169
218
  }
170
219
  };
@@ -80,6 +80,8 @@
80
80
  <PreprocessorDefinitions>_WINRT_DLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
81
81
  <AdditionalUsingDirectories>$(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories)</AdditionalUsingDirectories>
82
82
  <AdditionalIncludeDirectories>
83
+ $(MSBuildThisFileDirectory)..\..\..\spark-desktop-app\cpp;
84
+ $(MSBuildThisFileDirectory)..\..\..\desktop-app\cpp;
83
85
  $(MSBuildThisFileDirectory);
84
86
  %(AdditionalIncludeDirectories)
85
87
  </AdditionalIncludeDirectories>
@@ -1,50 +1,59 @@
1
1
  #import "RNDesktopSecureStorage.h"
2
2
  #import <RNDesktopApp/SparkDesktop.h>
3
3
  #import <Security/Security.h>
4
+
5
+ static NSDictionary *Failure(NSString *code, NSString *message) {
6
+ return @{ @"error": @{ @"code": code, @"message": message } };
7
+ }
8
+ static NSDictionary *StorageOperation(NSString *method, NSDictionary *args) {
9
+ NSString *key = args[@"key"];
10
+ if (![key isKindOfClass:NSString.class] || !key.length || key.length > 200) return Failure(@"E_INVALID_ARGUMENT", @"Expected a Keychain key of 1–200 characters");
11
+ NSString *service = SparkNamespace();
12
+ NSMutableDictionary *query = [@{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
13
+ (__bridge id)kSecAttrService: service, (__bridge id)kSecAttrAccount: key } mutableCopy];
14
+ OSStatus status;
15
+ if ([method isEqual:@"get"]) {
16
+ query[(__bridge id)kSecReturnData] = @YES;
17
+ query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
18
+ CFTypeRef result = NULL;
19
+ status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
20
+ if (status == errSecItemNotFound) return @{ @"value": NSNull.null };
21
+ if (status == errSecSuccess) {
22
+ NSData *data = CFBridgingRelease(result);
23
+ NSString *value = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
24
+ return value ? @{ @"value": value } : Failure(@"E_KEYCHAIN", @"Keychain item is not valid UTF-8");
25
+ }
26
+ } else if ([method isEqual:@"set"]) {
27
+ if (![args[@"value"] isKindOfClass:NSString.class]) return Failure(@"E_INVALID_ARGUMENT", @"Expected a string value");
28
+ NSData *value = [args[@"value"] dataUsingEncoding:NSUTF8StringEncoding];
29
+ NSDictionary *attributes = @{ (__bridge id)kSecValueData: value };
30
+ status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)attributes);
31
+ if (status == errSecItemNotFound) {
32
+ [query addEntriesFromDictionary:attributes];
33
+ query[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
34
+ status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
35
+ if (status == errSecDuplicateItem) {
36
+ [query removeObjectForKey:(__bridge id)kSecValueData];
37
+ [query removeObjectForKey:(__bridge id)kSecAttrAccessible];
38
+ status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)attributes);
39
+ }
40
+ }
41
+ } else if ([method isEqual:@"remove"]) {
42
+ status = SecItemDelete((__bridge CFDictionaryRef)query);
43
+ if (status == errSecItemNotFound) status = errSecSuccess;
44
+ } else return Failure(@"E_INVALID_ARGUMENT", @"Unknown Keychain operation");
45
+ if (status == errSecSuccess) return @{ @"value": NSNull.null };
46
+ NSString *message = CFBridgingRelease(SecCopyErrorMessageString(status, NULL));
47
+ return Failure(@"E_KEYCHAIN", [NSString stringWithFormat:@"Keychain error %d: %@", (int)status, message ?: @"Unknown error"]);
48
+ }
4
49
  @implementation RNDesktopSecureStorage
5
50
  RCT_EXPORT_MODULE(NativeDesktopSecureStorage)
6
51
  - (void)call:(NSString *)method args:(NSString *)json resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
7
52
  dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
8
- NSDictionary *args = SparkArgs(json);
9
- NSString *key = args[@"key"];
10
- if (![key isKindOfClass:NSString.class] || !key.length || key.length > 200) { SparkInvalid(reject, @"Expected a Keychain key of 1–200 characters"); return; }
11
- // Callers cannot override service identity and accidentally collide in Go.
12
- NSMutableDictionary *query = [@{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
13
- (__bridge id)kSecAttrService: SparkNamespace(), (__bridge id)kSecAttrAccount: key } mutableCopy];
14
- OSStatus status;
15
- if ([method isEqual:@"get"]) {
16
- query[(__bridge id)kSecReturnData] = @YES;
17
- query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
18
- CFTypeRef value = NULL;
19
- status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &value);
20
- if (status == errSecItemNotFound) { resolve(@"null"); return; }
21
- NSData *data = CFBridgingRelease(value);
22
- if (status == errSecSuccess) {
23
- NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
24
- if (!text) { reject(@"E_ENCODING", @"Keychain value is not UTF-8", nil); return; }
25
- resolve(SparkJSON(text)); return;
26
- }
27
- } else if ([method isEqual:@"set"]) {
28
- if (![args[@"value"] isKindOfClass:NSString.class]) { SparkInvalid(reject, @"Expected a string value"); return; }
29
- NSData *value = [args[@"value"] dataUsingEncoding:NSUTF8StringEncoding];
30
- NSDictionary *attributes = @{ (__bridge id)kSecValueData: value };
31
- status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)attributes);
32
- if (status == errSecItemNotFound) {
33
- [query addEntriesFromDictionary:attributes];
34
- query[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
35
- status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
36
- // Another caller may have added it after our update lookup.
37
- if (status == errSecDuplicateItem) { [query removeObjectForKey:(__bridge id)kSecValueData]; [query removeObjectForKey:(__bridge id)kSecAttrAccessible]; status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)attributes); }
38
- }
39
- } else if ([method isEqual:@"remove"]) {
40
- status = SecItemDelete((__bridge CFDictionaryRef)query);
41
- if (status == errSecItemNotFound) status = errSecSuccess;
42
- } else { SparkInvalid(reject, @"Unknown Keychain operation"); return; }
43
- if (status == errSecSuccess) resolve(@"null");
44
- else {
45
- NSString *message = CFBridgingRelease(SecCopyErrorMessageString(status, NULL));
46
- reject(@"E_KEYCHAIN", [NSString stringWithFormat:@"Keychain error %d: %@", (int)status, message ?: @"Unknown error"], nil);
47
- }
53
+ NSDictionary *result = StorageOperation(method, SparkArgs(json));
54
+ NSDictionary *error = result[@"error"];
55
+ if (error) reject(error[@"code"], error[@"message"], nil);
56
+ else resolve(SparkJSON(result[@"value"]));
48
57
  });
49
58
  }
50
59
  - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/spark-secure-storage",
3
- "version": "0.0.1-next.2",
3
+ "version": "0.0.1-next.4",
4
4
  "license": "MIT",
5
5
  "main": "src/index",
6
6
  "react-native": "src/index",
@@ -41,7 +41,7 @@
41
41
  ]
42
42
  },
43
43
  "dependencies": {
44
- "@legendapp/spark-desktop-app": "0.0.1-next.2"
44
+ "@legendapp/spark-desktop-app": "0.0.1-next.4"
45
45
  },
46
46
  "peerDependenciesMeta": {
47
47
  "expo-secure-store": {
@@ -3,4 +3,4 @@ import { TurboModuleRegistry } from "react-native";
3
3
  export interface Spec extends TurboModule {
4
4
  call(method: string, args: string): Promise<string>;
5
5
  }
6
- export default TurboModuleRegistry.getEnforcing<Spec>("NativeDesktopSecureStorage");
6
+ export default TurboModuleRegistry.get<Spec>("NativeDesktopSecureStorage");
@@ -1,24 +1,26 @@
1
+ import { Platform } from "react-native";
2
+ import { SparkError, parseNativeResult, invokeNative } from "@legendapp/spark-desktop-app/src/contracts";
1
3
  import Native from "./NativeDesktopSecureStorage";
2
- function key(value: string) { if (!value.length || value.length > 200) throw new Error("Keychain keys must contain 1–200 characters"); return value; }
3
- async function call<T = void>(method: string, args: object): Promise<T> { return JSON.parse(await Native.call(method, JSON.stringify(args))) as T; }
4
- export const secureStorage = {
5
- get: (name: string) => call<string | null>("get", { key: key(name) }),
6
- set: (name: string, value: string) => call("set", { key: key(name), value }),
7
- remove: (name: string) => call("remove", { key: key(name) }),
8
- };
9
-
10
4
  import { validateItem, type SecureStoreOptions } from "./options";
11
5
  export type { SecureStoreOptions } from "./options";
12
- /** Basic Expo SecureStore subset; desktop service identity stays project-scoped. */
6
+ function native() {
7
+ if (Platform.OS !== "macos" && Platform.OS !== "windows") throw new SparkError("E_UNSUPPORTED_PLATFORM", "Desktop secure storage is unavailable on this target");
8
+ if (!Native) throw new SparkError("E_MODULE_UNAVAILABLE", "NativeDesktopSecureStorage is not installed");
9
+ return Native;
10
+ }
11
+ async function call<T>(method: string, args: object, validate: (value: unknown) => value is T): Promise<T> {
12
+ return parseNativeResult(await invokeNative(() => native().call(method, JSON.stringify(args))), validate);
13
+ }
14
+ /** Selected Expo subset; service identity is project-scoped. Missing is null, including on desktop. */
13
15
  export async function getItemAsync(key: string, options: SecureStoreOptions = {}): Promise<string | null> {
14
- validateItem(key, options); return secureStorage.get(key);
16
+ validateItem(key, options); return call("get", { key }, (value): value is string | null => value === null || typeof value === "string");
15
17
  }
16
18
  export async function setItemAsync(key: string, value: string, options: SecureStoreOptions = {}): Promise<void> {
17
19
  validateItem(key, options);
18
- if (typeof value !== "string") throw new TypeError("SecureStore values must be strings");
19
- await secureStorage.set(key, value);
20
+ if (typeof value !== "string") throw new SparkError("E_INVALID_ARGUMENT", "SecureStore values must be strings");
21
+ await call("set", { key, value }, (value): value is null => value === null);
20
22
  }
21
23
  export async function deleteItemAsync(key: string, options: SecureStoreOptions = {}): Promise<void> {
22
- validateItem(key, options); await secureStorage.remove(key);
24
+ validateItem(key, options); await call("remove", { key }, (value): value is null => value === null);
23
25
  }
24
- export async function isAvailableAsync(): Promise<boolean> { return true; }
26
+ export async function isAvailableAsync(): Promise<boolean> { return (Platform.OS === "macos" || Platform.OS === "windows") && !!Native; }
@@ -11,4 +11,3 @@ export async function setItemAsync(key: string, value: string, options: SecureSt
11
11
  export async function deleteItemAsync(key: string, options: SecureStoreOptions = {}): Promise<void> {
12
12
  validateItem(key, options); await SecureStore.deleteItemAsync(key);
13
13
  }
14
- export const secureStorage = { get: getItemAsync, set: setItemAsync, remove: deleteItemAsync };