@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,61 +1,150 @@
1
1
  import { NativeEventEmitter, Platform } from "react-native";
2
+ import { callBinary, nativeBytes } from "@legendapp/spark-desktop-app/src/contracts/native-buffer";
3
+ import { SparkError, invokeNative, asyncRegistration, type AsyncRegistration, type Availability } from "@legendapp/spark-desktop-app/src/contracts";
4
+ export type { AsyncRegistration } from "@legendapp/spark-desktop-app/src/contracts";
5
+ import { nativePath } from "@legendapp/spark-desktop-app/src/contracts/path";
2
6
  import Native from "./NativeDesktopFileSystem";
3
- import { absolutePath } from "./path";
4
- export type FileStat = { type: "file" | "directory" | "symlink"; size: number; modifiedAt: number };
7
+ import { createFileHandle, iterateFile, writeFileChunks, type FileMode, type ReadChunksOptions } from "./handles";
8
+ export { FileCleanupError } from "./handles";
9
+ export type { FileHandle, FileMode, ReadChunksOptions } from "./handles";
10
+ export interface FileInfo { type: "file" | "directory" | "symlink"; size: number; modifiedAt: number }
11
+ export interface DirectoryEntry { name: string; path: string }
12
+ export interface CreateDirectoryOptions { recursive?: boolean }
13
+ export interface RemoveOptions { recursive?: boolean }
14
+ export interface CopyMoveOptions { overwrite?: boolean }
15
+ export interface WatchOptions { recursive?: boolean }
16
+ export interface OpenFileOptions { mode?: FileMode }
17
+ export interface WriteChunksOptions { mode?: "write" | "createNew"; signal?: AbortSignal }
18
+ export function getFileSystemAvailability(): Availability {
19
+ if (Platform.OS !== "macos" && Platform.OS !== "windows") return { available: false, reason: "unsupported-platform" };
20
+ return Native ? { available: true } : { available: false, reason: "missing-module" };
21
+ }
22
+ function native() {
23
+ const availability = getFileSystemAvailability();
24
+ if (!availability.available) throw new SparkError(availability.reason === "unsupported-platform" ? "E_UNSUPPORTED_PLATFORM" : "E_MODULE_UNAVAILABLE", "File IO requires a desktop host with NativeDesktopFileSystem installed");
25
+ return Native!;
26
+ }
27
+ const absolute = (path: string) => nativePath(path, Platform.OS);
28
+ const isString = (value: unknown): value is string => typeof value === "string";
29
+ function validResponse(method: string, value: unknown): boolean {
30
+ if (["directory", "openFile"].includes(method)) return isString(value) && value.length > 0;
31
+ if (["readText"].includes(method)) return isString(value);
32
+ if (method === "readBytes" || method === "readChunk") return value instanceof ArrayBuffer;
33
+ if (method === "list") return Array.isArray(value) && value.every(name => isString(name) && name.length > 0 && ![".", ".."].includes(name) && !name.includes("/") && !name.includes("\0") && (Platform.OS !== "windows" || !name.includes("\\")));
34
+ if (method === "stat") {
35
+ if (!value || typeof value !== "object") return false;
36
+ const info = value as FileInfo;
37
+ return ["file", "directory", "symlink"].includes(info.type) && Number.isSafeInteger(info.size) && info.size >= 0 && typeof info.modifiedAt === "number" && Number.isFinite(info.modifiedAt);
38
+ }
39
+ if (method === "writeChunk") return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
40
+ if (method === "remove" || method === "writeTextIfUnchanged") return typeof value === "boolean";
41
+ return value === null;
42
+ }
5
43
  async function call<T = void>(method: string, args: object): Promise<T> {
6
- return JSON.parse(await Native.call(method, JSON.stringify(args))) as T;
7
- }
8
- function absolute(path: string) {
9
- return absolutePath(path, Platform.OS);
10
- }
11
- export const getDirectory = (kind: "data" | "cache" | "temp") => call<string>("directory", { kind });
12
- export const readText = (path: string) => call<string>("readText", { path: absolute(path) });
13
- export const writeText = (path: string, text: string) => call("writeText", { path: absolute(path), text });
14
- /** Binary data uses base64 across the bridge; it does not require Node or Buffer. */
15
- export const readBase64 = (path: string) => call<string>("readBytes", { path: absolute(path) });
16
- export const writeBase64 = (path: string, base64: string) => call("writeBytes", { path: absolute(path), base64 });
17
- export const stat = (path: string) => call<FileStat>("stat", { path: absolute(path) });
18
- export async function exists(path: string) {
44
+ const { bytes, ...metadata } = args as { bytes?: Uint8Array };
45
+ const value = await invokeNative(() => callBinary(native(), "__sparkFileSystemBinary", method, metadata, bytes));
46
+ if (!validResponse(method, value)) throw new SparkError("E_INVALID_DATA", "Invalid native file response");
47
+ return value as T;
48
+ }
49
+ /** Rejects unknown or extra option keys so a misspelled option can never silently no-op. */
50
+ function checkedOptions<T extends object>(options: T, allowed: readonly string[], label: string): T {
51
+ if (!options || typeof options !== "object" || Array.isArray(options)) throw new SparkError("E_INVALID_ARGUMENT", `Expected ${label} options`);
52
+ for (const key of Object.keys(options)) if (!allowed.includes(key)) throw new SparkError("E_UNSUPPORTED_OPTION", `Unknown ${label} option: ${key}`);
53
+ return options;
54
+ }
55
+ function recursiveOption(options: { recursive?: boolean }, fallback: boolean): boolean {
56
+ const value = checkedOptions(options, ["recursive"], "recursive");
57
+ if (value.recursive !== undefined && typeof value.recursive !== "boolean") throw new SparkError("E_INVALID_ARGUMENT", "recursive must be a boolean");
58
+ return value.recursive ?? fallback;
59
+ }
60
+ function overwriteOption(options: CopyMoveOptions): boolean {
61
+ const value = checkedOptions(options, ["overwrite"], "copy/move");
62
+ if (value.overwrite !== undefined && typeof value.overwrite !== "boolean") throw new SparkError("E_INVALID_ARGUMENT", "overwrite must be a boolean");
63
+ return value.overwrite ?? false;
64
+ }
65
+ export async function getDirectory(kind: "data" | "cache" | "temp"): Promise<string> {
66
+ if (!["data", "cache", "temp"].includes(kind)) throw new SparkError("E_INVALID_ARGUMENT", "Invalid directory kind");
67
+ const path = await call<string>("directory", { kind });
68
+ try { return absolute(path); } catch (cause) { throw new SparkError("E_INVALID_DATA", "Invalid native directory path", { cause }); }
69
+ }
70
+ /** UTF-8. Invalid text rejects instead of inserting replacement characters. */
71
+ export async function readText(path: string): Promise<string> { return call("readText", { path: absolute(path) }); }
72
+ export async function writeText(path: string, text: string): Promise<void> {
73
+ if (typeof text !== "string") throw new SparkError("E_INVALID_ARGUMENT", "Expected a text string");
74
+ await call("writeText", { path: absolute(path), text });
75
+ }
76
+ export async function readBytes(path: string): Promise<Uint8Array> { return nativeBytes(await call<ArrayBuffer>("readBytes", { path: absolute(path) })); }
77
+ export async function writeBytes(path: string, bytes: Uint8Array): Promise<void> {
78
+ if (!(bytes instanceof Uint8Array)) throw new SparkError("E_INVALID_ARGUMENT", "Expected Uint8Array");
79
+ await call("writeBytes", { path: absolute(path), bytes });
80
+ }
81
+ /** Describes the link itself, without following it. modifiedAt is Unix milliseconds. */
82
+ export async function stat(path: string): Promise<FileInfo> { return call("stat", { path: absolute(path) }); }
83
+ export async function exists(path: string): Promise<boolean> {
19
84
  try { await stat(path); return true; } catch (error) {
20
- if ((error as { code?: string }).code === "E_NOT_FOUND") return false;
85
+ if (error instanceof SparkError && error.code === "E_NOT_FOUND") return false;
21
86
  throw error;
22
87
  }
23
88
  }
24
- export const list = (path: string) => call<string[]>("list", { path: absolute(path) });
25
- export const mkdir = (path: string, options: { recursive?: boolean } = {}) => call("mkdir", { path: absolute(path), recursive: options.recursive ?? true });
26
- export const remove = (path: string, options: { recursive?: boolean } = {}) => call<boolean>("remove", { path: absolute(path), recursive: options.recursive ?? false });
27
- export const copy = (path: string, to: string) => call("copy", { path: absolute(path), to: absolute(to) });
28
- export const move = (path: string, to: string) => call("move", { path: absolute(path), to: absolute(to) });
89
+ /** Entries are sorted by name. Metadata is obtained explicitly with stat(entry.path). */
90
+ export async function list(path: string): Promise<DirectoryEntry[]> {
91
+ const directory = absolute(path);
92
+ const names = await call<string[]>("list", { path: directory });
93
+ const separator = Platform.OS === "windows" ? "\\" : "/";
94
+ return names.sort().map(name => ({ name, path: directory.replace(/[\\/]+$/, "") + separator + name }));
95
+ }
96
+ export async function mkdir(path: string, options: CreateDirectoryOptions = {}): Promise<void> { await call("mkdir", { path: absolute(path), recursive: recursiveOption(options, true) }); }
97
+ /** Absence is success; deleting a nonempty directory requires recursive: true. */
98
+ export async function remove(path: string, options: RemoveOptions = {}): Promise<void> { await call("remove", { path: absolute(path), recursive: recursiveOption(options, false) }); }
99
+ /** Copies directories recursively and preserves symlinks. An existing destination rejects unless overwrite: true. Self-transfers and overlapping source/destination paths reject. Overwrite stages beside the destination and restores the previous destination if publication fails. If backup cleanup fails after commit, the native error reports the published destination and recoverable backup path. */
100
+ export async function copy(source: string, destination: string, options: CopyMoveOptions = {}): Promise<void> {
101
+ await call("copy", { path: absolute(source), to: absolute(destination), overwrite: overwriteOption(options) });
102
+ }
103
+ /** An existing destination rejects unless overwrite: true. Self-transfers and overlapping source/destination paths reject. Cross-volume moves copy beside the destination before deleting the source. If source cleanup fails, the destination has been published, the source may be partially removed, and the previous destination is retained at a recovery path reported by the native error. Backup cleanup failures also report the committed destination and retained backup. */
104
+ export async function move(source: string, destination: string, options: CopyMoveOptions = {}): Promise<void> {
105
+ await call("move", { path: absolute(source), to: absolute(destination), overwrite: overwriteOption(options) });
106
+ }
29
107
  let nextWatch = 0;
30
- const emitter = new NativeEventEmitter(Native);
31
- export type WatchOptions = { recursive?: boolean };
32
- /** Invalidation, not an exact change log. Recursive watches require a directory.
33
- * Re-read the watched path after a callback; events may be coalesced. */
34
- export async function watch(path: string, listener: (path: string) => void, options: WatchOptions = {}) {
35
- if (options.recursive !== undefined && typeof options.recursive !== "boolean") throw new TypeError("recursive must be a boolean");
108
+ /** Invalidation, not an exact change log. Recursive watches require a directory. */
109
+ export async function watch(path: string, listener: (path: string) => void, options: WatchOptions = {}): Promise<AsyncRegistration> {
110
+ const location = absolute(path), recursive = recursiveOption(options, false);
111
+ if (typeof listener !== "function") throw new SparkError("E_INVALID_ARGUMENT", "Expected a watch listener");
36
112
  const id = `watch-${Date.now()}-${++nextWatch}`;
37
113
  let removed = false;
114
+ const emitter = new NativeEventEmitter(native());
38
115
  const subscription = emitter.addListener("change", (event: { id: string; path: string }) => {
39
- if (!removed && event.id === id) listener(event.path);
116
+ if (!removed && event?.id === id && typeof event.path === "string") listener(event.path);
40
117
  });
41
- try { await call("watch", { path: absolute(path), id, recursive: options.recursive ?? false }); }
118
+ try { await call("watch", { path: location, id, recursive }); }
42
119
  catch (error) { subscription.remove(); throw error; }
43
- return { async remove() {
44
- if (removed) return;
45
- removed = true; subscription.remove(); await call("unwatch", { id });
46
- } };
120
+ return asyncRegistration(() => { removed = true; subscription.remove(); }, async () => { await call("unwatch", { id }); });
47
121
  }
48
-
49
- import { createFileHandle, iterateFile, writeFileChunks, type FileMode, type ReadChunksOptions } from "./handles";
50
- export type { FileHandle, FileMode, ReadChunksOptions } from "./handles";
51
- /** read/readWrite require an existing regular file. write truncates; createNew fails if it exists. */
52
- export const openFile = (path: string, options: { mode?: FileMode } = {}) => createFileHandle(call, absolute(path), options.mode);
53
- /** Pull-based binary stream; closes the handle on EOF, error, abort, or early loop exit. */
54
- export const readChunks = (path: string, options: ReadChunksOptions = {}) => iterateFile(() => openFile(path), options);
55
- /** Writes sequentially, splitting large chunks. Failure leaves a partial file; use a temporary file + move for publication. */
56
- export const writeChunks = (path: string, chunks: AsyncIterable<Uint8Array> | Iterable<Uint8Array>, options: { mode?: "write" | "createNew"; signal?: AbortSignal } = {}) => {
57
- if (options.mode !== undefined && options.mode !== "write" && options.mode !== "createNew") throw new TypeError("Streaming writes require write or createNew mode");
122
+ /** read/readWrite require an existing regular file. write truncates; createNew rejects if it exists. */
123
+ export async function openFile(path: string, options: OpenFileOptions = {}) {
124
+ checkedOptions(options, ["mode"], "openFile");
125
+ return createFileHandle(call, absolute(path), options.mode);
126
+ }
127
+ /** Returns an iterator synchronously; option names validate now, while path/range validation occurs on first iteration. Closes on EOF, error, abort, or early loop exit. */
128
+ export function readChunks(path: string, options: ReadChunksOptions = {}): AsyncGenerator<Uint8Array> {
129
+ checkedOptions(options, ["offset", "chunkSize", "signal"], "readChunks");
130
+ return iterateFile(() => openFile(path), options);
131
+ }
132
+ /** Promise-based validation and IO; failure leaves a partial file, so use a temporary file + move for publication. */
133
+ export async function writeChunks(path: string, chunks: AsyncIterable<Uint8Array> | Iterable<Uint8Array>, options: WriteChunksOptions = {}): Promise<number> {
134
+ checkedOptions(options, ["mode", "signal"], "writeChunks");
135
+ if (options.mode !== undefined && options.mode !== "write" && options.mode !== "createNew") throw new SparkError("E_INVALID_ARGUMENT", "Streaming writes require write or createNew mode");
58
136
  return writeFileChunks(() => openFile(path, { mode: options.mode ?? "write" }), chunks, options.signal);
59
- };
137
+ }
60
138
  /** Move to the OS Trash/Recycle Bin. Never falls back to permanent deletion. */
61
- export const trash = (path: string) => call<void>("trash", { path: absolute(path) });
139
+ export async function trash(path: string): Promise<void> { await call("trash", { path: absolute(path) }); }
140
+
141
+ /** Reveal an existing path in Finder or Explorer. */
142
+ export async function revealInFileManager(path: string): Promise<void> { await call("reveal", { path: absolute(path) }); }
143
+ /** Compare before replacing. Detects observed changes; not an OS-wide atomic compare-and-swap. */
144
+ export async function writeTextIfUnchanged(path: string, expected: string, contents: string): Promise<{ written: boolean }> {
145
+ if (typeof expected !== "string" || typeof contents !== "string") throw new SparkError("E_INVALID_ARGUMENT", "Expected text strings");
146
+ return { written: await call<boolean>("writeTextIfUnchanged", { path: absolute(path), expected, text: contents }) };
147
+ }
148
+
149
+ export { scanFiles, getFileScanAvailability } from "./file-scanner";
150
+ export type { FileScanOptions, FileScanSkipEntry, ScannedFile, FileScanProgress, FileScanBatch, FileScanResult } from "./file-scanner";
@@ -1,9 +1 @@
1
- /** Drive-qualified and UNC paths are absolute on Windows; C:foo and \foo are not. */
2
- export function absolutePath(path: string, platform: string) {
3
- const windows = /^(?:[a-z]:[\\/]|\\\\[^\\/]+[\\/][^\\/]+)/i.test(path);
4
- if (typeof path !== "string" || path.includes("\0") ||
5
- !(path.startsWith("file://") || (platform === "windows" ? windows : path.startsWith("/")))) {
6
- throw new Error("Expected an absolute path or file URL");
7
- }
8
- return path;
9
- }
1
+ export { absolutePath } from "@legendapp/spark-desktop-app/src/contracts/path";
@@ -1,9 +1,9 @@
1
1
  #pragma once
2
2
  #include "NativeModules.h"
3
+ #include <SparkBinaryWindows.hpp>
3
4
  #include <shlobj.h>
4
5
  #include <shlwapi.h>
5
6
  #include <winrt/Windows.Data.Json.h>
6
- #include <winrt/Windows.Security.Cryptography.h>
7
7
  #include <winrt/Windows.Storage.Streams.h>
8
8
  #include <filesystem>
9
9
  #include <algorithm>
@@ -26,7 +26,6 @@ namespace winrt::SparkFileSystem {
26
26
  namespace React = Microsoft::ReactNative;
27
27
  namespace Json = Windows::Data::Json;
28
28
  namespace fs = std::filesystem;
29
- using Crypto = Windows::Security::Cryptography::CryptographicBuffer;
30
29
 
31
30
  inline fs::path FilePath(hstring const &input) {
32
31
  std::wstring value(input);
@@ -58,7 +57,7 @@ inline std::string ErrorCode(DWORD error) {
58
57
  default: return "E_IO";
59
58
  }
60
59
  }
61
- inline void Reject(React::ReactPromise<std::string> const &promise) noexcept {
60
+ template <class Promise> inline void Reject(Promise const &promise) noexcept {
62
61
  try { throw; }
63
62
  catch (fs::filesystem_error const &e) {
64
63
  auto code = e.code(); std::string kind = "E_IO";
@@ -100,6 +99,58 @@ inline void Write(fs::path const &path, std::vector<uint8_t> const &data) {
100
99
  if (!MoveFileExW(temporary.c_str(), path.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) throw_last_error();
101
100
  } catch (...) { std::error_code ignored; fs::remove(temporary, ignored); throw; }
102
101
  }
102
+ inline bool PathContains(fs::path const &parent, fs::path const &child) {
103
+ auto p = parent.begin(), c = child.begin();
104
+ for (; p != parent.end() && c != child.end() && CompareStringOrdinal(p->c_str(), -1, c->c_str(), -1, TRUE) == CSTR_EQUAL; ++p, ++c) {}
105
+ return p == parent.end();
106
+ }
107
+ inline void Transfer(fs::path const &from, fs::path const &to, bool move, bool overwrite) {
108
+ auto source = fs::weakly_canonical(from), destination = fs::weakly_canonical(to);
109
+ std::error_code identityError;
110
+ bool sameFile = fs::equivalent(from, to, identityError);
111
+ if (identityError && identityError != std::errc::no_such_file_or_directory) throw fs::filesystem_error("Could not compare transfer paths", from, to, identityError);
112
+ if (sameFile || PathContains(source, destination) || PathContains(destination, source))
113
+ throw hresult_invalid_argument(L"Source and destination identify or contain one another");
114
+ bool destinationExists = fs::symlink_status(to).type() != fs::file_type::not_found;
115
+ if (destinationExists && !overwrite) throw fs::filesystem_error("Destination already exists", to, std::make_error_code(std::errc::file_exists));
116
+ GUID guid{}; check_hresult(CoCreateGuid(&guid)); wchar_t identifier[40]{}; StringFromGUID2(guid, identifier, 40);
117
+ auto stage = to.parent_path() / (std::wstring(L".spark-stage-") + identifier);
118
+ auto backup = to.parent_path() / (std::wstring(L".spark-backup-") + identifier);
119
+ bool stagedByRename = false;
120
+ try {
121
+ wchar_t sourceVolume[32768]{}, destinationVolume[32768]{};
122
+ bool sameVolumeMove = move && GetVolumePathNameW(from.parent_path().c_str(), sourceVolume, ARRAYSIZE(sourceVolume)) &&
123
+ GetVolumePathNameW(to.parent_path().c_str(), destinationVolume, ARRAYSIZE(destinationVolume)) &&
124
+ _wcsicmp(sourceVolume, destinationVolume) == 0;
125
+ if (sameVolumeMove) { fs::rename(from, stage); stagedByRename = true; }
126
+ else fs::copy(from, stage, fs::copy_options::recursive | fs::copy_options::copy_symlinks);
127
+ if (destinationExists && !MoveFileExW(to.c_str(), backup.c_str(), MOVEFILE_WRITE_THROUGH)) {
128
+ auto failure = GetLastError();
129
+ bool restoredSource = !stagedByRename || MoveFileExW(stage.c_str(), from.c_str(), MOVEFILE_WRITE_THROUGH);
130
+ if (!restoredSource) throw hresult_error(HRESULT_FROM_WIN32(ERROR_WRITE_FAULT), L"Destination staging failed and source rollback failed; recoverable source: " + stage.wstring());
131
+ throw hresult_error(HRESULT_FROM_WIN32(failure));
132
+ }
133
+ if (!MoveFileExW(stage.c_str(), to.c_str(), MOVEFILE_WRITE_THROUGH)) {
134
+ auto failure = GetLastError();
135
+ bool restoredDestination = !destinationExists || MoveFileExW(backup.c_str(), to.c_str(), MOVEFILE_WRITE_THROUGH);
136
+ bool restoredSource = !stagedByRename || MoveFileExW(stage.c_str(), from.c_str(), MOVEFILE_WRITE_THROUGH);
137
+ if (!restoredDestination || !restoredSource)
138
+ throw hresult_error(HRESULT_FROM_WIN32(ERROR_WRITE_FAULT), L"Publication and rollback failed; recoverable paths: " + stage.wstring() + L"; " + backup.wstring());
139
+ throw hresult_error(HRESULT_FROM_WIN32(failure));
140
+ }
141
+ if (move && !stagedByRename) {
142
+ std::error_code sourceError; fs::remove_all(from, sourceError);
143
+ if (sourceError) throw fs::filesystem_error("Source cleanup failed after publishing " + PathString(to) + "; source may be partially removed; previous destination is retained at " + PathString(backup), from, sourceError);
144
+ }
145
+ if (destinationExists) {
146
+ std::error_code cleanupError; fs::remove_all(backup, cleanupError);
147
+ if (cleanupError) throw fs::filesystem_error("Transfer committed at " + PathString(to) + "; previous destination cleanup failed; recoverable backup: " + PathString(backup), backup, cleanupError);
148
+ }
149
+ } catch (...) {
150
+ if (!stagedByRename) { std::error_code ignored; fs::remove_all(stage, ignored); }
151
+ throw;
152
+ }
153
+ }
103
154
  inline fs::path Directory(hstring const &kind) {
104
155
  wchar_t identity[201]{}; auto count = GetEnvironmentVariableW(L"SPARK_PROJECT_ID", identity, 201);
105
156
  if (!count || count >= 201) throw hresult_invalid_argument(L"Launch through Spark to establish project storage identity");
@@ -182,7 +233,7 @@ struct FileQueue {
182
233
  }) {}
183
234
  ~FileQueue() { { std::lock_guard lock(mutex); stopping = true; } ready.notify_one(); worker.join(); }
184
235
  void Post(std::function<void()> action) { { std::lock_guard lock(mutex); pending.push_back(std::move(action)); } ready.notify_one(); }
185
- Json::IJsonValue Call(std::string const &method, Json::JsonObject const &args) {
236
+ Json::IJsonValue Call(std::string const &method, Json::JsonObject const &args, std::optional<std::vector<uint8_t>> const &input = {}, std::vector<uint8_t> *output = nullptr) {
186
237
  if (method == "directory") return Json::JsonValue::CreateStringValue(Directory(args.GetNamedString(L"kind")).wstring());
187
238
  if (method == "unwatch") { watches.erase(to_string(args.GetNamedString(L"id"))); return Json::JsonValue::Parse(L"null"); }
188
239
  if (method == "closeFile") { files.erase(to_string(args.GetNamedString(L"id"))); return Json::JsonValue::Parse(L"null"); }
@@ -200,13 +251,10 @@ struct FileQueue {
200
251
  if (!std::isfinite(length) || length < 1 || length > 1048576 || std::floor(length) != length || offset + length > 9007199254740991.0) throw hresult_invalid_argument(L"Invalid chunk length");
201
252
  std::vector<uint8_t> data(static_cast<size_t>(length)); DWORD count = 0;
202
253
  if (!ReadFile(file, data.data(), static_cast<DWORD>(data.size()), &count, nullptr)) throw_last_error();
203
- data.resize(count); return Json::JsonValue::CreateStringValue(Crypto::EncodeToBase64String(Crypto::CreateFromByteArray(data)));
254
+ data.resize(count); if (!output) throw hresult_invalid_argument(L"Expected binary transport"); *output = std::move(data); return Json::JsonValue::CreateNullValue();
204
255
  }
205
- auto base64 = args.GetNamedString(L"base64");
206
- if (base64.size() > 1398104) throw hresult_invalid_argument(L"Chunk exceeds 1 MiB");
207
- auto buffer = Crypto::DecodeFromBase64String(base64);
208
- if (Crypto::EncodeToBase64String(buffer) != base64 || buffer.Length() > 1048576 || offset + buffer.Length() > 9007199254740991.0) throw hresult_invalid_argument(L"Invalid chunk");
209
- com_array<uint8_t> data; Crypto::CopyToByteArray(buffer, data);
256
+ if (!input || input->size() > 1048576 || offset + input->size() > 9007199254740991.0) throw hresult_invalid_argument(L"Invalid chunk");
257
+ auto const &data = *input;
210
258
  size_t written = 0;
211
259
  while (written < data.size()) { DWORD count = 0; if (!WriteFile(file, data.data() + written, static_cast<DWORD>(data.size() - written), &count, nullptr)) throw_last_error(); if (!count) throw hresult_error(E_FAIL, L"File write made no progress"); written += count; }
212
260
  return Json::JsonValue::CreateNumberValue(static_cast<double>(written));
@@ -234,13 +282,15 @@ struct FileQueue {
234
282
  if (text.size() > INT_MAX) throw hresult_error(E_FAIL, L"Text exceeds the UTF-8 conversion limit");
235
283
  if (!text.empty() && !MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.data(), static_cast<int>(text.size()), nullptr, 0)) throw_last_error();
236
284
  return Json::JsonValue::CreateStringValue(to_hstring(text));
237
- } else if (method == "readBytes") return Json::JsonValue::CreateStringValue(Crypto::EncodeToBase64String(Crypto::CreateFromByteArray(Read(path))));
285
+ } else if (method == "readBytes") { if (!output) throw hresult_invalid_argument(L"Expected binary transport"); *output = Read(path); return Json::JsonValue::CreateNullValue(); }
286
+ else if (method == "writeTextIfUnchanged") {
287
+ if (Call("readText", args).GetString() != args.GetNamedString(L"expected")) return Json::JsonValue::CreateBooleanValue(false);
288
+ auto text = to_string(args.GetNamedString(L"text")); Write(path, std::vector<uint8_t>(text.begin(), text.end()));
289
+ return Json::JsonValue::CreateBooleanValue(true);
290
+ }
238
291
  else if (method == "writeText") { auto text = to_string(args.GetNamedString(L"text")); Write(path, std::vector<uint8_t>(text.begin(), text.end())); }
239
292
  else if (method == "writeBytes") {
240
- auto base64 = args.GetNamedString(L"base64"); Windows::Storage::Streams::IBuffer buffer{nullptr};
241
- try { buffer = base64.empty() ? Crypto::CreateFromByteArray(array_view<uint8_t const>{}) : Crypto::DecodeFromBase64String(base64); } catch (hresult_error const &) { throw hresult_invalid_argument(L"Invalid base64 data"); }
242
- if (Crypto::EncodeToBase64String(buffer) != base64) throw hresult_invalid_argument(L"Invalid base64 data");
243
- com_array<uint8_t> data; Crypto::CopyToByteArray(buffer, data); Write(path, std::vector<uint8_t>(data.begin(), data.end()));
293
+ if (!input) throw hresult_invalid_argument(L"Expected binary file data"); Write(path, *input);
244
294
  } else if (method == "mkdir") {
245
295
  if (args.GetNamedBoolean(L"recursive", true)) fs::create_directories(path);
246
296
  else if (!fs::create_directory(path)) throw hresult_error(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS));
@@ -249,15 +299,7 @@ struct FileQueue {
249
299
  return Json::JsonValue::CreateBooleanValue(fs::remove(path));
250
300
  } else if (method == "copy" || method == "move") {
251
301
  auto to = FilePath(args.GetNamedString(L"to"));
252
- auto destination = fs::symlink_status(to);
253
- if (destination.type() != fs::file_type::not_found) throw hresult_error(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS));
254
- if (method == "copy") fs::copy(path, to, fs::copy_options::recursive | fs::copy_options::copy_symlinks);
255
- else if (!MoveFileExW(path.c_str(), to.c_str(), MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH)) {
256
- if (GetLastError() != ERROR_NOT_SAME_DEVICE || !fs::is_directory(path)) throw_last_error();
257
- try { fs::copy(path, to, fs::copy_options::recursive | fs::copy_options::copy_symlinks); }
258
- catch (...) { std::error_code ignored; fs::remove_all(to, ignored); throw; }
259
- fs::remove_all(path);
260
- }
302
+ Transfer(path, to, method == "move", args.GetNamedBoolean(L"overwrite", false));
261
303
  } else if (method == "stat") {
262
304
  // OPEN_REPARSE_POINT describes a link itself, including dangling links.
263
305
  auto file = Open(path, FILE_READ_ATTRIBUTES, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT);
@@ -281,9 +323,43 @@ struct FileQueue {
281
323
  };
282
324
  REACT_MODULE(SparkFileSystem, L"NativeDesktopFileSystem")
283
325
  struct SparkFileSystem {
284
- std::unique_ptr<FileQueue> queue;
285
- REACT_INIT(Initialize) void Initialize(React::ReactContext const &context) noexcept { queue = std::make_unique<FileQueue>(context); }
326
+ React::ReactContext context;
327
+ std::shared_ptr<FileQueue> queue;
328
+ REACT_INIT(Initialize) void Initialize(React::ReactContext const &value) noexcept { context = value; queue = std::make_shared<FileQueue>(context); }
329
+ REACT_SYNC_METHOD(installBinary) std::optional<std::string> installBinary() noexcept {
330
+ try {
331
+ spark::binary::Install(context, "__sparkFileSystemBinary", [queue = queue](std::string method, std::string encoded, std::optional<std::vector<uint8_t>> bytes, spark::binary::Completion finish) {
332
+ if (method == "reveal") {
333
+ queue->context.UIDispatcher().Post([encoded, promise = spark::binary::NativePromise(finish)] {
334
+ PIDLIST_ABSOLUTE item = nullptr;
335
+ try { auto path = FilePath(Json::JsonObject::Parse(to_hstring(encoded)).GetNamedString(L"path")); check_hresult(SHParseDisplayName(path.c_str(), nullptr, &item, 0, nullptr)); auto result = SHOpenFolderAndSelectItems(item, 0, nullptr, 0); CoTaskMemFree(item); item = nullptr; check_hresult(result); promise.Resolve("null"); }
336
+ catch (...) { if (item) CoTaskMemFree(item); Reject(promise); }
337
+ }); return;
338
+ }
339
+ queue->Post([queue, method = std::move(method), encoded = std::move(encoded), bytes = std::move(bytes), promise = spark::binary::NativePromise(finish)] {
340
+ try { std::vector<uint8_t> output; auto result = queue->Call(method, Json::JsonObject::Parse(to_hstring(encoded)), bytes, &output); spark::binary::Response response{to_string(result.Stringify()), {}, {}}; if (method == "readBytes" || method == "readChunk") response.bytes = std::make_shared<spark::binary::Bytes>(std::move(output)); promise.Resolve(std::move(response)); }
341
+ catch (...) { Reject(promise); }
342
+ });
343
+ });
344
+ return std::nullopt;
345
+ } catch (std::exception const &error) { return error.what(); }
346
+ catch (hresult_error const &error) { return to_string(error.message()); }
347
+ catch (...) { return "Could not install native binary transport"; }
348
+ }
349
+
286
350
  REACT_METHOD(call) void call(std::string method, std::string args, React::ReactPromise<std::string> promise) noexcept {
351
+ if (method == "reveal") {
352
+ context.UIDispatcher().Post([args, promise] {
353
+ PIDLIST_ABSOLUTE item = nullptr;
354
+ try {
355
+ auto path = FilePath(Json::JsonObject::Parse(to_hstring(args)).GetNamedString(L"path"));
356
+ check_hresult(SHParseDisplayName(path.c_str(), nullptr, &item, 0, nullptr));
357
+ auto result = SHOpenFolderAndSelectItems(item, 0, nullptr, 0);
358
+ CoTaskMemFree(item); item = nullptr; check_hresult(result); promise.Resolve("null");
359
+ } catch (...) { if (item) CoTaskMemFree(item); Reject(promise); }
360
+ });
361
+ return;
362
+ }
287
363
  queue->Post([state = queue.get(), method, args, promise] {
288
364
  try { promise.Resolve(to_string(state->Call(method, Json::JsonObject::Parse(to_hstring(args))).Stringify())); }
289
365
  catch (...) { Reject(promise); }
@@ -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>
@@ -14,4 +14,5 @@ Pod::Spec.new do |s|
14
14
  s.dependency "React-Core"
15
15
  s.dependency "ReactCodegen"
16
16
  s.dependency "RNDesktopApp"
17
+ s.osx.frameworks = "Carbon"
17
18
  end
@@ -36,6 +36,7 @@ RCT_EXPORT_MODULE(NativeDesktopGlobalShortcuts)
36
36
  dispatch_async(dispatch_get_main_queue(), ^{
37
37
  NSDictionary *args = SparkArgs(json); NSString *key = args[@"id"];
38
38
  if ([method isEqual:@"register"]) {
39
+ if (![args[@"key"] isKindOfClass:NSString.class] || ![args[@"key"] length]) { SparkInvalid(reject, @"Expected a shortcut key"); return; }
39
40
  NSInteger code = KeyCode(args[@"key"]);
40
41
  if (code < 0) { reject(@"E_KEY", @"Key is unavailable in the current keyboard layout", nil); return; }
41
42
  NSUInteger flags = [args[@"modifiers"] unsignedIntegerValue]; UInt32 modifiers = 0;
@@ -43,15 +44,17 @@ RCT_EXPORT_MODULE(NativeDesktopGlobalShortcuts)
43
44
  if (flags & NSEventModifierFlagControl) modifiers |= controlKey;
44
45
  if (flags & NSEventModifierFlagShift) modifiers |= shiftKey;
45
46
  if (flags & NSEventModifierFlagOption) modifiers |= optionKey;
46
- for (NSDictionary *existing in self.registrations.allValues) if ([existing[@"code"] integerValue] == code && [existing[@"modifiers"] unsignedIntValue] == modifiers) { reject(@"E_SHORTCUT_CONFLICT", @"Shortcut already registered", nil); return; }
47
- if (self.registrations[key]) { reject(@"E_SHORTCUT_CONFLICT", @"Shortcut id already exists", nil); return; }
47
+ for (NSDictionary *existing in self.registrations.allValues) if ([existing[@"code"] integerValue] == code && [existing[@"modifiers"] unsignedIntValue] == modifiers) { reject(@"E_BUSY", @"Shortcut already registered", nil); return; }
48
+ if (self.registrations[key]) { reject(@"E_BUSY", @"Shortcut id already exists", nil); return; }
48
49
  if (!self.handler) { EventTypeSpec spec = { kEventClassKeyboard, kEventHotKeyPressed }; EventHandlerRef handler;
49
50
  if (InstallEventHandler(GetEventDispatcherTarget(), Hotkey, 1, &spec, (__bridge void *)self, &handler) != noErr) { reject(@"E_SHORTCUT", @"Could not install hotkey handler", nil); return; } self.handler = handler; }
50
51
  EventHotKeyID identity = { 'LGDS', ++self.sequence }; EventHotKeyRef reference;
51
- if (RegisterEventHotKey((UInt32)code, modifiers, identity, GetEventDispatcherTarget(), 0, &reference) != noErr) { reject(@"E_SHORTCUT_CONFLICT", @"Shortcut is unavailable or reserved by another app", nil); return; }
52
+ if (RegisterEventHotKey((UInt32)code, modifiers, identity, GetEventDispatcherTarget(), 0, &reference) != noErr) { reject(@"E_BUSY", @"Shortcut is unavailable or reserved by another app", nil); return; }
52
53
  self.registrations[key] = @{ @"code": @(code), @"modifiers": @(modifiers), @"number": @(identity.id), @"reference": [NSValue valueWithPointer:reference] };
53
54
  } else if ([method isEqual:@"remove"]) {
54
- NSDictionary *value = self.registrations[key]; if (value) UnregisterEventHotKey((EventHotKeyRef)[value[@"reference"] pointerValue]); [self.registrations removeObjectForKey:key];
55
+ NSDictionary *value = self.registrations[key];
56
+ if (value && UnregisterEventHotKey((EventHotKeyRef)[value[@"reference"] pointerValue]) != noErr) { reject(@"E_NATIVE", @"Could not unregister global shortcut", nil); return; }
57
+ [self.registrations removeObjectForKey:key];
55
58
  } else { SparkInvalid(reject, @"Unknown global shortcut operation"); return; }
56
59
  resolve(@"null");
57
60
  });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/spark-global-shortcuts",
3
- "version": "0.0.1-next.2",
3
+ "version": "0.0.1-next.4",
4
4
  "license": "MIT",
5
5
  "main": "src/index.ts",
6
6
  "react-native": "src/index.ts",
@@ -22,7 +22,8 @@
22
22
  "ios": {
23
23
  "modulesProvider": {
24
24
  "NativeDesktopGlobalShortcuts": "RNDesktopGlobalShortcuts"
25
- }
25
+ },
26
+ "componentProvider": {}
26
27
  }
27
28
  },
28
29
  "spark": {
@@ -40,7 +41,7 @@
40
41
  ]
41
42
  },
42
43
  "dependencies": {
43
- "@legendapp/spark-desktop-app": "0.0.1-next.2"
44
+ "@legendapp/spark-desktop-app": "0.0.1-next.4"
44
45
  },
45
46
  "private": true
46
47
  }
@@ -1,3 +1,3 @@
1
1
  import { TurboModuleRegistry, type TurboModule } from "react-native";
2
2
  export interface Spec extends TurboModule { call(method: string, args: string): Promise<string>; }
3
- export default TurboModuleRegistry.getEnforcing<Spec>("NativeDesktopGlobalShortcuts");
3
+ export default TurboModuleRegistry.get<Spec>("NativeDesktopGlobalShortcuts");
@@ -1,16 +1,45 @@
1
1
  import { Platform } from "react-native";
2
2
  import Native from "./NativeDesktopGlobalShortcuts";
3
- import { onDesktopEvent } from "@legendapp/spark-desktop-app";
4
- import { parseAccelerator } from "./accelerator";
3
+ import { onDesktopEvent } from "@legendapp/spark-desktop-app/src/events";
4
+ import { parseAccelerator } from "@legendapp/spark-desktop-app/src/contracts/accelerator";
5
+ import { SparkError, invokeNative, parseNativeResult, asyncRegistration, type AsyncRegistration, type Availability } from "@legendapp/spark-desktop-app/src/contracts";
6
+ export type { AsyncRegistration } from "@legendapp/spark-desktop-app/src/contracts";
7
+ export interface GlobalShortcutOptions {
8
+ /** Fire on keyboard auto-repeat. Windows registers without MOD_NOREPEAT; macOS hotkeys never deliver repeats, so true rejects there. */
9
+ repeat?: boolean;
10
+ }
5
11
  let sequence = 0;
6
- export async function registerGlobalShortcut(accelerator: string, handler: () => void) {
12
+ export function getGlobalShortcutAvailability(): Availability {
13
+ if (Platform.OS !== "macos" && Platform.OS !== "windows") return { available: false, reason: "unsupported-platform" };
14
+ return Native ? { available: true } : { available: false, reason: "missing-module" };
15
+ }
16
+ function native() {
17
+ const availability = getGlobalShortcutAvailability();
18
+ if (!availability.available) throw new SparkError(availability.reason === "unsupported-platform" ? "E_UNSUPPORTED_PLATFORM" : "E_MODULE_UNAVAILABLE", "Global shortcuts are unavailable");
19
+ return Native!;
20
+ }
21
+ async function call(method: string, args: object) {
22
+ parseNativeResult(await invokeNative(() => native().call(method, JSON.stringify(args))), (value): value is null => value === null);
23
+ }
24
+ /** Conflicts/reserved shortcuts reject E_BUSY. Layout is resolved at registration. */
25
+ export async function registerGlobalShortcut(accelerator: string, handler: () => void, options: GlobalShortcutOptions = {}): Promise<AsyncRegistration> {
26
+ if (typeof handler !== "function" || !options || typeof options !== "object" || Array.isArray(options)) throw new SparkError("E_INVALID_ARGUMENT", "Expected global shortcut handler and options");
27
+ for (const key of Object.keys(options)) if (!["repeat"].includes(key)) throw new SparkError("E_UNSUPPORTED_OPTION", `Unsupported global shortcut option: ${key}`);
28
+ if (options.repeat !== undefined && typeof options.repeat !== "boolean") throw new SparkError("E_INVALID_ARGUMENT", "Global shortcut repeat must be boolean");
29
+ if (options.repeat === true && Platform.OS === "macos") throw new SparkError("E_UNSUPPORTED_OPTION", "macOS global shortcuts never deliver keyboard repeats");
7
30
  const parsed = parseAccelerator(accelerator, Platform.OS === "windows" ? "windows" : "macos"), id = `global-${Date.now()}-${++sequence}`;
31
+ native();
8
32
  let removed = false;
9
- const subscription = onDesktopEvent(event => { if (!removed && event.type === "globalShortcut" && event.id === id) handler(); });
10
- try { await Native.call("register", JSON.stringify({ id, ...parsed })); }
11
- catch (error) { subscription.remove(); throw error; }
12
- return { async remove() {
13
- if (removed) return;
14
- await Native.call("remove", JSON.stringify({ id })); removed = true; subscription.remove();
15
- } };
33
+ const subscription = onDesktopEvent(event => { if (!removed && event.type === "globalShortcut" && event.id === id) {
34
+ try { handler(); }
35
+ catch (cause) { console.error(new SparkError("E_NATIVE", "Global shortcut handler failed", { cause })); }
36
+ } }, { types: ["globalShortcut"], target: { field: "id", value: id } });
37
+ try { await call("register", { id, repeat: options.repeat ?? false, ...parsed }); }
38
+ catch (cause) {
39
+ removed = true; subscription.remove();
40
+ try { await call("remove", { id }); }
41
+ catch (cleanup) { throw new SparkError("E_NATIVE", "Shortcut registration and cleanup failed", { cause: new AggregateError([cause, cleanup]) }); }
42
+ throw cause;
43
+ }
44
+ return asyncRegistration(() => { removed = true; subscription.remove(); }, async () => { await call("remove", { id }); });
16
45
  }
@@ -58,18 +58,19 @@ struct SparkGlobalShortcuts {
58
58
  auto args = Json::JsonObject::Parse(to_hstring(encoded)); auto id = to_string(args.GetNamedString(L"id"));
59
59
  current->EnsureWindow();
60
60
  if (method == "remove") {
61
- for (auto item = current->keys.begin(); item != current->keys.end(); ++item) if (item->second == id) { UnregisterHotKey(current->hwnd, item->first); current->keys.erase(item); break; }
61
+ for (auto item = current->keys.begin(); item != current->keys.end(); ++item) if (item->second == id) { if (!UnregisterHotKey(current->hwnd, item->first)) winrt::throw_last_error(); current->keys.erase(item); break; }
62
62
  } else if (method == "register") {
63
- for (auto const &[key, existing] : current->keys) if (existing == id) { promise.Reject(React::ReactError{"E_SHORTCUT_CONFLICT", "Shortcut id already exists"}); return; }
63
+ for (auto const &[key, existing] : current->keys) if (existing == id) { promise.Reject(React::ReactError{"E_BUSY", "Shortcut id already exists"}); return; }
64
64
  auto flags = static_cast<int>(args.GetNamedNumber(L"modifiers"));
65
- UINT modifiers = MOD_NOREPEAT;
65
+ // MOD_NOREPEAT suppresses OS auto-repeat; repeat:true registers with repeats.
66
+ UINT modifiers = args.GetNamedBoolean(L"repeat", false) ? 0 : MOD_NOREPEAT;
66
67
  if (flags & (1 << 17)) modifiers |= MOD_SHIFT; if (flags & (1 << 18)) modifiers |= MOD_CONTROL;
67
68
  if (flags & (1 << 19)) modifiers |= MOD_ALT; if (flags & (1 << 20)) modifiers |= MOD_WIN;
68
69
  const auto key = Hotkeys::Key(args.GetNamedString(L"key"), modifiers);
69
70
  // Window-local registration IDs must remain within the documented range.
70
71
  int identifier = 0;
71
72
  for (int i = 0; i < 0xbfff; ++i) { int candidate = current->sequence = current->sequence % 0xbfff + 1; if (!current->keys.count(candidate)) { identifier = candidate; break; } }
72
- if (!identifier || !RegisterHotKey(current->hwnd, identifier, modifiers, key)) { promise.Reject(React::ReactError{"E_SHORTCUT_CONFLICT", "Shortcut is unavailable or reserved by another app"}); return; }
73
+ if (!identifier || !RegisterHotKey(current->hwnd, identifier, modifiers, key)) { promise.Reject(React::ReactError{"E_BUSY", "Shortcut is unavailable or reserved by another app"}); return; }
73
74
  current->keys.emplace(identifier, id);
74
75
  } else throw hresult_invalid_argument(L"Unknown global shortcut operation");
75
76
  promise.Resolve("null");
@@ -9,9 +9,9 @@ RCT_EXPORT_MODULE(NativeDesktopMessageDialog)
9
9
  [NSRunLoop.mainRunLoop performBlock:^{
10
10
  if (![method isEqual:@"show"]) { SparkInvalid(reject, @"Unknown dialog operation"); return; }
11
11
  if (self.alert) { reject(@"E_BUSY", @"A message dialog is already open", nil); return; }
12
- NSDictionary *args = SparkArgs(json); NSWindow *parent;
12
+ NSDictionary *args = SparkArgs(json); NSWindow *parent = nil;
13
13
  if (args[@"windowId"]) {
14
- for (NSWindow *window in NSApp.windows) if ([window.identifier isEqual:[@"spark." stringByAppendingString:args[@"windowId"]]]) parent = window;
14
+ for (NSWindow *window in NSApp.windows) if ([window.identifier isEqual:args[@"windowId"]] || [window.identifier isEqual:[@"spark." stringByAppendingString:args[@"windowId"]]]) { parent = window; break; }
15
15
  if (!parent) { reject(@"E_NOT_FOUND", @"Dialog parent does not exist", nil); return; }
16
16
  if (parent.attachedSheet) { reject(@"E_BUSY", @"Parent already has a sheet", nil); return; }
17
17
  }