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

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 (399) 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/config-plugin.d.cts +2 -0
  7. package/contracts.ts +2 -0
  8. package/diagnostics.ts +1 -0
  9. package/expo-config.d.cts +1 -0
  10. package/metro.cjs +4 -1
  11. package/metro.d.cts +12 -0
  12. package/native.cjs +3 -1
  13. package/native.d.cts +6 -0
  14. package/package.json +82 -14
  15. package/schema.json +276 -58
  16. package/settings-observable.ts +1 -0
  17. package/settings-window.ts +1 -0
  18. package/shortcuts-commands.ts +1 -0
  19. package/shortcuts-keyboard.ts +3 -0
  20. package/ui-classnames.ts +1 -0
  21. package/ui-glass.ts +1 -0
  22. package/ui-search.ts +1 -0
  23. package/ui-sidebar.ts +1 -0
  24. package/ui-split-view.ts +1 -0
  25. package/ui-symbol.ts +1 -0
  26. package/vendor/node_modules/@legendapp/spark-ai/package.json +19 -0
  27. package/vendor/node_modules/@legendapp/spark-ai/src/availability.ts +17 -0
  28. package/vendor/node_modules/@legendapp/spark-ai/src/index.ts +15 -0
  29. package/vendor/node_modules/@legendapp/spark-ai/src/invocations.ts +34 -0
  30. package/vendor/node_modules/@legendapp/spark-ai/src/output.ts +62 -0
  31. package/vendor/node_modules/@legendapp/spark-ai/src/run.ts +12 -0
  32. package/vendor/node_modules/@legendapp/spark-ai/src/types.ts +16 -0
  33. package/vendor/node_modules/@legendapp/spark-audio/macos/RNSparkAudio.h +2 -1
  34. package/vendor/node_modules/@legendapp/spark-audio/macos/RNSparkAudio.mm +126 -24
  35. package/vendor/node_modules/@legendapp/spark-audio/package.json +5 -2
  36. package/vendor/node_modules/@legendapp/spark-audio/src/NativeSparkAudio.ts +5 -1
  37. package/vendor/node_modules/@legendapp/spark-audio/src/desktop.ts +36 -32
  38. package/vendor/node_modules/@legendapp/spark-audio/src/events.ts +11 -0
  39. package/vendor/node_modules/@legendapp/spark-audio/src/hooks.ts +65 -0
  40. package/vendor/node_modules/@legendapp/spark-audio/src/index.web.ts +92 -33
  41. package/vendor/node_modules/@legendapp/spark-audio/src/media-session-unavailable.ts +6 -4
  42. package/vendor/node_modules/@legendapp/spark-audio/src/media-session.ts +52 -19
  43. package/vendor/node_modules/@legendapp/spark-audio/src/media-session.web.ts +18 -10
  44. package/vendor/node_modules/@legendapp/spark-audio/src/media-types.ts +40 -13
  45. package/vendor/node_modules/@legendapp/spark-audio/src/mobile.ts +40 -32
  46. package/vendor/node_modules/@legendapp/spark-audio/src/player.ts +97 -0
  47. package/vendor/node_modules/@legendapp/spark-audio/src/status-listeners.ts +21 -15
  48. package/vendor/node_modules/@legendapp/spark-audio/src/types.ts +9 -4
  49. package/vendor/node_modules/@legendapp/spark-audio/windows/SparkAudio/SparkAudio.h +67 -20
  50. package/vendor/node_modules/@legendapp/spark-auth-session/package.json +4 -3
  51. package/vendor/node_modules/@legendapp/spark-auth-session/src/core.ts +60 -42
  52. package/vendor/node_modules/@legendapp/spark-auth-session/src/index.ts +28 -12
  53. package/vendor/node_modules/@legendapp/spark-auth-session/src/mobile.ts +97 -41
  54. package/vendor/node_modules/@legendapp/spark-auth-session/src/types.ts +20 -9
  55. package/vendor/node_modules/@legendapp/spark-cli/dist/add-desktop.js +50 -7
  56. package/vendor/node_modules/@legendapp/spark-cli/dist/build-mode.js +1 -1
  57. package/vendor/node_modules/@legendapp/spark-cli/dist/build.js +83 -48
  58. package/vendor/node_modules/@legendapp/spark-cli/dist/cli-arguments.js +60 -0
  59. package/vendor/node_modules/@legendapp/spark-cli/dist/commands.js +7 -2
  60. package/vendor/node_modules/@legendapp/spark-cli/dist/create.js +31 -5
  61. package/vendor/node_modules/@legendapp/spark-cli/dist/credentials.js +69 -23
  62. package/vendor/node_modules/@legendapp/spark-cli/dist/dev-command.js +12 -3
  63. package/vendor/node_modules/@legendapp/spark-cli/dist/dev.js +12 -9
  64. package/vendor/node_modules/@legendapp/spark-cli/dist/examples.js +1 -1
  65. package/vendor/node_modules/@legendapp/spark-cli/dist/expo-dev-patch.cjs +4 -2
  66. package/vendor/node_modules/@legendapp/spark-cli/dist/expo-metro.cjs +1 -1
  67. package/vendor/node_modules/@legendapp/spark-cli/dist/expo-node.js +3 -3
  68. package/vendor/node_modules/@legendapp/spark-cli/dist/index.js +10 -38
  69. package/vendor/node_modules/@legendapp/spark-cli/dist/local.js +1 -1
  70. package/vendor/node_modules/@legendapp/spark-cli/dist/metro.cjs +26 -5
  71. package/vendor/node_modules/@legendapp/spark-cli/dist/native-metadata.js +26 -0
  72. package/vendor/node_modules/@legendapp/spark-cli/dist/native-patch-preflight.js +84 -0
  73. package/vendor/node_modules/@legendapp/spark-cli/dist/native-preparation.js +88 -0
  74. package/vendor/node_modules/@legendapp/spark-cli/dist/notary-profiles.js +75 -0
  75. package/vendor/node_modules/@legendapp/spark-cli/dist/package.js +4 -2
  76. package/vendor/node_modules/@legendapp/spark-cli/dist/platform.js +16 -2
  77. package/vendor/node_modules/@legendapp/spark-cli/dist/project.js +66 -13
  78. package/vendor/node_modules/@legendapp/spark-cli/dist/release.js +84 -4
  79. package/vendor/node_modules/@legendapp/spark-cli/dist/release.json +23 -18
  80. package/vendor/node_modules/@legendapp/spark-cli/dist/runner-install.js +145 -31
  81. package/vendor/node_modules/@legendapp/spark-cli/dist/signing.js +4 -3
  82. package/vendor/node_modules/@legendapp/spark-cli/dist/updates.js +41 -4
  83. package/vendor/node_modules/@legendapp/spark-cli/dist/windows.js +4 -1
  84. package/vendor/node_modules/@legendapp/spark-cli/package.json +7 -6
  85. package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/App.tsx +6 -12
  86. package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/desktop.config.json +5 -3
  87. package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/package.json +11 -9
  88. package/vendor/node_modules/@legendapp/spark-cli/templates/diff-lite/Git.macos.tsx +2 -2
  89. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/Integration.macos.tsx +23 -21
  90. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/Integration.windows.tsx +23 -21
  91. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/io.desktop.ts +11 -8
  92. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/io.macos.ts +3 -3
  93. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/Lifecycle.desktop.tsx +8 -14
  94. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/OpenFiles.desktop.tsx +5 -10
  95. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/io.desktop.ts +11 -8
  96. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/io.macos.ts +3 -3
  97. package/vendor/node_modules/@legendapp/spark-cli/templates/music-lite/assets.desktop.ts +2 -2
  98. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/Activation.desktop.tsx +2 -2
  99. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/Desktop.desktop.ts +37 -15
  100. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/session.ts +5 -6
  101. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/window-session.ts +5 -4
  102. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/desktop.config.json +5 -3
  103. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/metro.config.js +1 -1
  104. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/package.json +13 -11
  105. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/react-native.config.js +1 -1
  106. package/vendor/node_modules/@legendapp/spark-cli/templates/windows/package.json +13 -11
  107. package/vendor/node_modules/@legendapp/spark-cli/templates/windows/react-native.config.js +1 -1
  108. package/vendor/node_modules/@legendapp/spark-clipboard/macos/RNDesktopClipboard.mm +10 -16
  109. package/vendor/node_modules/@legendapp/spark-clipboard/package.json +2 -2
  110. package/vendor/node_modules/@legendapp/spark-clipboard/src/NativeDesktopClipboard.ts +1 -1
  111. package/vendor/node_modules/@legendapp/spark-clipboard/src/desktop.ts +55 -31
  112. package/vendor/node_modules/@legendapp/spark-clipboard/src/expo.ts +21 -11
  113. package/vendor/node_modules/@legendapp/spark-clipboard/src/options.ts +8 -0
  114. package/vendor/node_modules/@legendapp/spark-clipboard/src/types.ts +10 -0
  115. package/vendor/node_modules/@legendapp/spark-clipboard/windows/SparkClipboard/SparkClipboard.h +38 -15
  116. package/vendor/node_modules/@legendapp/spark-clipboard/windows/SparkClipboard/SparkClipboard.vcxproj +2 -0
  117. package/vendor/node_modules/@legendapp/spark-codex/RNCodex.podspec +22 -0
  118. package/vendor/node_modules/@legendapp/spark-codex/cpp/HybridCodexAppServer.hpp +23 -0
  119. package/vendor/node_modules/@legendapp/spark-codex/cpp/HybridCodexAppServer.mm +1529 -0
  120. package/vendor/node_modules/@legendapp/spark-codex/nitro.json +21 -0
  121. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/.gitattributes +1 -0
  122. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex+autolinking.rb +62 -0
  123. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Bridge.cpp +17 -0
  124. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Bridge.hpp +27 -0
  125. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Umbrella.hpp +38 -0
  126. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodexAutolinking.mm +35 -0
  127. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodexAutolinking.swift +16 -0
  128. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/CodexAvailability.hpp +95 -0
  129. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/CodexRunResult.hpp +99 -0
  130. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/HybridCodexAppServerSpec.cpp +24 -0
  131. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/HybridCodexAppServerSpec.hpp +71 -0
  132. package/vendor/node_modules/@legendapp/spark-codex/package.json +35 -0
  133. package/vendor/node_modules/@legendapp/spark-codex/react-native.config.js +11 -0
  134. package/vendor/node_modules/@legendapp/spark-codex/src/Codex.nitro.ts +30 -0
  135. package/vendor/node_modules/@legendapp/spark-codex/src/index.ts +65 -0
  136. package/vendor/node_modules/@legendapp/spark-codex/src/native.ts +6 -0
  137. package/vendor/node_modules/@legendapp/spark-commands/package.json +21 -0
  138. package/vendor/node_modules/@legendapp/spark-commands/src/bindings.ts +90 -0
  139. package/vendor/node_modules/@legendapp/spark-commands/src/index.tsx +400 -0
  140. package/vendor/node_modules/@legendapp/spark-commands/src/router.ts +138 -0
  141. package/vendor/node_modules/@legendapp/spark-commands/src/storage.ts +20 -0
  142. package/vendor/node_modules/@legendapp/spark-context-menu/macos/RNContextMenu.mm +7 -8
  143. package/vendor/node_modules/@legendapp/spark-context-menu/package.json +4 -3
  144. package/vendor/node_modules/@legendapp/spark-context-menu/src/NativeContextMenu.ts +1 -1
  145. package/vendor/node_modules/@legendapp/spark-context-menu/src/index.ts +26 -26
  146. package/vendor/node_modules/@legendapp/spark-context-menu/windows/SparkContextMenu/SparkContextMenu.h +3 -2
  147. package/vendor/node_modules/@legendapp/spark-desktop-app/RNDesktopApp.podspec +2 -1
  148. package/vendor/node_modules/@legendapp/spark-desktop-app/cpp/SparkBinaryJSI.hpp +89 -0
  149. package/vendor/node_modules/@legendapp/spark-desktop-app/cpp/SparkBinaryWindows.hpp +22 -0
  150. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/RNDesktopApp.mm +13 -7
  151. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkBinaryJSI.h +48 -0
  152. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktop.h +4 -0
  153. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktop.mm +33 -33
  154. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktopError.h +20 -0
  155. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkLifecycle.h +26 -0
  156. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkLifecycle.mm +49 -0
  157. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkQuitCoordinator.h +9 -0
  158. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkQuitCoordinator.mm +70 -0
  159. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkWindow.mm +9 -6
  160. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/recent-documents/RNRecentDocumentController.mm +55 -0
  161. package/vendor/node_modules/@legendapp/spark-desktop-app/package.json +5 -3
  162. package/vendor/node_modules/@legendapp/spark-desktop-app/src/NativeDesktopApp.ts +1 -1
  163. package/vendor/node_modules/@legendapp/spark-desktop-app/src/api.ts +86 -40
  164. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/accelerator.ts +40 -0
  165. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/index.ts +56 -0
  166. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/menu.ts +114 -0
  167. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/native-buffer.ts +22 -0
  168. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/path.ts +24 -0
  169. package/vendor/node_modules/@legendapp/spark-desktop-app/src/events.ts +75 -0
  170. package/vendor/node_modules/@legendapp/spark-desktop-app/src/index.ts +2 -0
  171. package/vendor/node_modules/@legendapp/spark-desktop-app/src/index.windows.ts +2 -0
  172. package/vendor/node_modules/@legendapp/spark-desktop-app/src/open-requests.ts +43 -0
  173. package/vendor/node_modules/@legendapp/spark-desktop-app/src/restoration.ts +11 -0
  174. package/vendor/node_modules/@legendapp/spark-desktop-app/src/transport.ts +10 -0
  175. package/vendor/node_modules/@legendapp/spark-desktop-config/app.plugin.cjs +10 -5
  176. package/vendor/node_modules/@legendapp/spark-desktop-config/app.plugin.d.cts +3 -0
  177. package/vendor/node_modules/@legendapp/spark-desktop-config/autolinking.cjs +35 -0
  178. package/vendor/node_modules/@legendapp/spark-desktop-config/config.cjs +16 -3
  179. package/vendor/node_modules/@legendapp/spark-desktop-config/config.d.cts +89 -9
  180. package/vendor/node_modules/@legendapp/spark-desktop-config/expo.d.cts +3 -0
  181. package/vendor/node_modules/@legendapp/spark-desktop-config/identity.cjs +3 -2
  182. package/vendor/node_modules/@legendapp/spark-desktop-config/lifecycle.cjs +20 -0
  183. package/vendor/node_modules/@legendapp/spark-desktop-config/package.json +6 -5
  184. package/vendor/node_modules/@legendapp/spark-desktop-config/schema.json +276 -58
  185. package/vendor/node_modules/@legendapp/spark-desktop-config/updates.cjs +1 -0
  186. package/vendor/node_modules/@legendapp/spark-desktop-config/validation.cjs +35 -0
  187. package/vendor/node_modules/@legendapp/spark-desktop-config/windows.plugin.cjs +2 -1
  188. package/vendor/node_modules/@legendapp/spark-desktop-host/AppDelegate.mm +129 -11
  189. package/vendor/node_modules/@legendapp/spark-desktop-host/package.json +2 -2
  190. package/vendor/node_modules/@legendapp/spark-desktop-host/windows/application.inc +277 -121
  191. package/vendor/node_modules/@legendapp/spark-desktop-links/common/AuthLoopback.h +5 -7
  192. package/vendor/node_modules/@legendapp/spark-desktop-links/macos/RNDesktopLinks.mm +10 -9
  193. package/vendor/node_modules/@legendapp/spark-desktop-links/package.json +2 -2
  194. package/vendor/node_modules/@legendapp/spark-desktop-links/src/NativeDesktopLinks.ts +1 -1
  195. package/vendor/node_modules/@legendapp/spark-desktop-links/src/api.ts +27 -33
  196. package/vendor/node_modules/@legendapp/spark-desktop-links/src/expo.ts +3 -4
  197. package/vendor/node_modules/@legendapp/spark-desktop-links/src/native.ts +9 -0
  198. package/vendor/node_modules/@legendapp/spark-desktop-links/windows/SparkLinks/SparkLinks.h +22 -22
  199. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/RNDesktopShortcuts.podspec +1 -0
  200. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/RNDesktopShortcuts.mm +1 -1
  201. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/keyboard-manager/RNKeyboardManager.h +9 -0
  202. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/keyboard-manager/RNKeyboardManager.mm +307 -0
  203. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/package.json +8 -5
  204. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/NativeDesktopShortcuts.ts +1 -1
  205. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/api.ts +39 -14
  206. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/NativeKeyboardManager.ts +12 -0
  207. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/codes.ts +108 -0
  208. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/index.ts +99 -0
  209. package/vendor/node_modules/@legendapp/spark-desktop-windows/RNDesktopWindows.podspec +2 -0
  210. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/RNDesktopWindows.mm +229 -103
  211. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/SparkWindowGeometry.h +63 -0
  212. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/LegendWindowRegistry.h +20 -0
  213. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/LegendWindowRegistry.mm +27 -0
  214. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManager.h +12 -0
  215. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManager.mm +3130 -0
  216. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManagerStartup.mm +31 -0
  217. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/SidebarStartupFallbacks.mm +14 -0
  218. package/vendor/node_modules/@legendapp/spark-desktop-windows/package.json +10 -6
  219. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/NativeDesktopWindowManager.ts +1 -1
  220. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/api.ts +144 -70
  221. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/diagnostics.ts +17 -0
  222. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/index.ts +3 -1
  223. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/index.windows.ts +3 -1
  224. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/macos-adapter.ts +53 -0
  225. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/macos.ts +51 -0
  226. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/transport.ts +41 -0
  227. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/types.ts +140 -0
  228. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/validation.ts +162 -0
  229. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/window-manager/NativeWindowManager.ts +28 -0
  230. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/WindowProvider.tsx +20 -0
  231. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/createWindowsNavigator.tsx +78 -0
  232. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/index.ts +6 -0
  233. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/primaryWindowLifecycle.ts +34 -0
  234. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/types.ts +11 -0
  235. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/usePrimaryWindowLifecycle.ts +18 -0
  236. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/useWindowFocusEffect.ts +17 -0
  237. package/vendor/node_modules/@legendapp/spark-documents/package.json +22 -0
  238. package/vendor/node_modules/@legendapp/spark-documents/src/controller.ts +97 -0
  239. package/vendor/node_modules/@legendapp/spark-documents/src/documentTransition.ts +13 -0
  240. package/vendor/node_modules/@legendapp/spark-documents/src/hooks.ts +139 -0
  241. package/vendor/node_modules/@legendapp/spark-documents/src/index.tsx +50 -0
  242. package/vendor/node_modules/@legendapp/spark-documents/src/reload.ts +39 -0
  243. package/vendor/node_modules/@legendapp/spark-documents/src/requests.ts +15 -0
  244. package/vendor/node_modules/@legendapp/spark-drag-drop/package.json +4 -3
  245. package/vendor/node_modules/@legendapp/spark-drag-drop/src/DesktopDragViewNativeComponent.ts +1 -1
  246. package/vendor/node_modules/@legendapp/spark-drag-drop/src/contracts.ts +73 -18
  247. package/vendor/node_modules/@legendapp/spark-drag-drop/src/index.tsx +58 -15
  248. package/vendor/node_modules/@legendapp/spark-drag-drop/windows/SparkDragDrop/SparkDragDrop.h +14 -8
  249. package/vendor/node_modules/@legendapp/spark-file-dialog/ios/RNFileDialog.mm +34 -121
  250. package/vendor/node_modules/@legendapp/spark-file-dialog/package.json +12 -5
  251. package/vendor/node_modules/@legendapp/spark-file-dialog/src/NativeFileDialog.ts +1 -5
  252. package/vendor/node_modules/@legendapp/spark-file-dialog/src/index.ts +19 -84
  253. package/vendor/node_modules/@legendapp/spark-file-dialog/src/native.ts +12 -0
  254. package/vendor/node_modules/@legendapp/spark-file-dialog/src/options.ts +34 -14
  255. package/vendor/node_modules/@legendapp/spark-file-dialog/src/types.ts +26 -0
  256. package/vendor/node_modules/@legendapp/spark-file-dialog/windows/SparkFileDialog/SparkFileDialog.h +42 -58
  257. package/vendor/node_modules/@legendapp/spark-file-system/RNDesktopFileSystem.podspec +1 -0
  258. package/vendor/node_modules/@legendapp/spark-file-system/macos/RNDesktopFileSystem.mm +32 -21
  259. package/vendor/node_modules/@legendapp/spark-file-system/macos/SparkFileMutations.h +24 -0
  260. package/vendor/node_modules/@legendapp/spark-file-system/macos/SparkFileMutations.mm +215 -0
  261. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScanner.h +9 -0
  262. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScanner.mm +82 -0
  263. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScannerCore.h +47 -0
  264. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScannerCore.mm +306 -0
  265. package/vendor/node_modules/@legendapp/spark-file-system/package.json +8 -6
  266. package/vendor/node_modules/@legendapp/spark-file-system/src/NativeDesktopFileSystem.ts +1 -1
  267. package/vendor/node_modules/@legendapp/spark-file-system/src/file-scanner/NativeFileScanner.ts +11 -0
  268. package/vendor/node_modules/@legendapp/spark-file-system/src/file-scanner/index.ts +93 -0
  269. package/vendor/node_modules/@legendapp/spark-file-system/src/handles.ts +48 -14
  270. package/vendor/node_modules/@legendapp/spark-file-system/src/index.ts +134 -45
  271. package/vendor/node_modules/@legendapp/spark-file-system/src/path.ts +1 -9
  272. package/vendor/node_modules/@legendapp/spark-file-system/windows/SparkFileSystem/SparkFileSystem.h +102 -26
  273. package/vendor/node_modules/@legendapp/spark-file-system/windows/SparkFileSystem/SparkFileSystem.vcxproj +2 -0
  274. package/vendor/node_modules/@legendapp/spark-global-shortcuts/RNDesktopGlobalShortcuts.podspec +1 -0
  275. package/vendor/node_modules/@legendapp/spark-global-shortcuts/macos/RNDesktopGlobalShortcuts.mm +7 -4
  276. package/vendor/node_modules/@legendapp/spark-global-shortcuts/package.json +4 -3
  277. package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/NativeDesktopGlobalShortcuts.ts +1 -1
  278. package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/index.ts +39 -10
  279. package/vendor/node_modules/@legendapp/spark-global-shortcuts/windows/SparkGlobalShortcuts/SparkGlobalShortcuts.h +5 -4
  280. package/vendor/node_modules/@legendapp/spark-message-dialog/macos/RNDesktopMessageDialog.mm +2 -2
  281. package/vendor/node_modules/@legendapp/spark-message-dialog/package.json +2 -2
  282. package/vendor/node_modules/@legendapp/spark-message-dialog/src/NativeDesktopMessageDialog.ts +1 -1
  283. package/vendor/node_modules/@legendapp/spark-message-dialog/src/index.ts +48 -11
  284. package/vendor/node_modules/@legendapp/spark-native-menu/ios/RNNativeMenu.mm +141 -609
  285. package/vendor/node_modules/@legendapp/spark-native-menu/package.json +9 -4
  286. package/vendor/node_modules/@legendapp/spark-native-menu/src/NativeMenu.ts +2 -7
  287. package/vendor/node_modules/@legendapp/spark-native-menu/src/api.ts +76 -121
  288. package/vendor/node_modules/@legendapp/spark-native-menu/src/hooks.ts +97 -0
  289. package/vendor/node_modules/@legendapp/spark-native-menu/src/windows-menus.ts +43 -37
  290. package/vendor/node_modules/@legendapp/spark-notifications/macos/RNDesktopNotifications.mm +75 -30
  291. package/vendor/node_modules/@legendapp/spark-notifications/package.json +2 -2
  292. package/vendor/node_modules/@legendapp/spark-notifications/src/NativeDesktopNotifications.ts +1 -1
  293. package/vendor/node_modules/@legendapp/spark-notifications/src/index.ts +130 -42
  294. package/vendor/node_modules/@legendapp/spark-notifications/windows/SparkNotifications/SparkNotifications.h +46 -8
  295. package/vendor/node_modules/@legendapp/spark-processes/macos/RNDesktopProcesses.mm +94 -21
  296. package/vendor/node_modules/@legendapp/spark-processes/package.json +4 -3
  297. package/vendor/node_modules/@legendapp/spark-processes/src/NativeDesktopProcesses.ts +1 -1
  298. package/vendor/node_modules/@legendapp/spark-processes/src/index.ts +124 -28
  299. package/vendor/node_modules/@legendapp/spark-processes/src/types.ts +34 -0
  300. package/vendor/node_modules/@legendapp/spark-processes/windows/SparkProcesses/SparkProcesses.h +83 -34
  301. package/vendor/node_modules/@legendapp/spark-processes/windows/SparkProcesses/SparkProcesses.vcxproj +2 -0
  302. package/vendor/node_modules/@legendapp/spark-secure-storage/macos/RNDesktopSecureStorage.mm +49 -40
  303. package/vendor/node_modules/@legendapp/spark-secure-storage/package.json +2 -2
  304. package/vendor/node_modules/@legendapp/spark-secure-storage/src/NativeDesktopSecureStorage.ts +1 -1
  305. package/vendor/node_modules/@legendapp/spark-secure-storage/src/desktop.ts +16 -14
  306. package/vendor/node_modules/@legendapp/spark-secure-storage/src/expo.ts +0 -1
  307. package/vendor/node_modules/@legendapp/spark-secure-storage/src/options.ts +3 -2
  308. package/vendor/node_modules/@legendapp/spark-secure-storage/src/unavailable.ts +6 -3
  309. package/vendor/node_modules/@legendapp/spark-settings/package.json +10 -3
  310. package/vendor/node_modules/@legendapp/spark-settings/src/fileStorage.ts +11 -0
  311. package/vendor/node_modules/@legendapp/spark-settings/src/index.ts +7 -9
  312. package/vendor/node_modules/@legendapp/spark-settings/src/storage/index.ts +142 -0
  313. package/vendor/node_modules/@legendapp/spark-settings/src/store.ts +63 -21
  314. package/vendor/node_modules/@legendapp/spark-settings-ui/package.json +20 -0
  315. package/vendor/node_modules/@legendapp/spark-settings-ui/src/index.tsx +436 -0
  316. package/vendor/node_modules/@legendapp/spark-settings-ui/src/options.ts +10 -0
  317. package/vendor/node_modules/@legendapp/spark-sqlite/package.json +3 -3
  318. package/vendor/node_modules/@legendapp/spark-sqlite/src/database.ts +182 -0
  319. package/vendor/node_modules/@legendapp/spark-sqlite/src/index.ts +19 -6
  320. package/vendor/node_modules/@legendapp/spark-sqlite/src/types.ts +22 -0
  321. package/vendor/node_modules/@legendapp/spark-system/macos/RNDesktopSystem.mm +36 -11
  322. package/vendor/node_modules/@legendapp/spark-system/package.json +2 -2
  323. package/vendor/node_modules/@legendapp/spark-system/src/NativeDesktopSystem.ts +1 -1
  324. package/vendor/node_modules/@legendapp/spark-system/src/index.ts +89 -32
  325. package/vendor/node_modules/@legendapp/spark-system/windows/SparkSystem/SparkSystem.h +8 -4
  326. package/vendor/node_modules/@legendapp/spark-tray/macos/RNDesktopTray.mm +20 -8
  327. package/vendor/node_modules/@legendapp/spark-tray/package.json +2 -2
  328. package/vendor/node_modules/@legendapp/spark-tray/src/NativeDesktopTray.ts +1 -1
  329. package/vendor/node_modules/@legendapp/spark-tray/src/index.ts +91 -34
  330. package/vendor/node_modules/@legendapp/spark-tray/windows/SparkTray/SparkTray.h +45 -15
  331. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkButton.mm +2 -1
  332. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkTextInput.mm +10 -2
  333. package/vendor/node_modules/@legendapp/spark-ui/macos/appkit-split-view/RNSidebarSplitViewComponent.h +15 -0
  334. package/vendor/node_modules/@legendapp/spark-ui/macos/appkit-split-view/RNSidebarSplitViewComponent.mm +1182 -0
  335. package/vendor/node_modules/@legendapp/spark-ui/macos/glass-effect-view/RNGlassEffectView.mm +72 -0
  336. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSegmentedControl.h +15 -0
  337. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSegmentedControl.mm +194 -0
  338. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSelect.h +15 -0
  339. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSelect.mm +180 -0
  340. package/vendor/node_modules/@legendapp/spark-ui/macos/sf-symbol/RNSFSymbol.h +15 -0
  341. package/vendor/node_modules/@legendapp/spark-ui/macos/sf-symbol/RNSFSymbol.mm +155 -0
  342. package/vendor/node_modules/@legendapp/spark-ui/macos/sidebar/RNSidebar.h +18 -0
  343. package/vendor/node_modules/@legendapp/spark-ui/macos/sidebar/RNSidebar.mm +645 -0
  344. package/vendor/node_modules/@legendapp/spark-ui/macos/text-input-search/RNTextInputSearch.h +15 -0
  345. package/vendor/node_modules/@legendapp/spark-ui/macos/text-input-search/RNTextInputSearch.mm +164 -0
  346. package/vendor/node_modules/@legendapp/spark-ui/package.json +16 -4
  347. package/vendor/node_modules/@legendapp/spark-ui/src/SparkSelectNativeComponent.ts +3 -1
  348. package/vendor/node_modules/@legendapp/spark-ui/src/SparkTextInputNativeComponent.ts +6 -2
  349. package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/SidebarSplitViewNativeComponent.ts +31 -0
  350. package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/index.tsx +77 -0
  351. package/vendor/node_modules/@legendapp/spark-ui/src/availability.ts +10 -0
  352. package/vendor/node_modules/@legendapp/spark-ui/src/classnames/index.ts +8 -0
  353. package/vendor/node_modules/@legendapp/spark-ui/src/control.ts +42 -0
  354. package/vendor/node_modules/@legendapp/spark-ui/src/desktop.tsx +56 -0
  355. package/vendor/node_modules/@legendapp/spark-ui/src/glass-effect-view/index.tsx +15 -0
  356. package/vendor/node_modules/@legendapp/spark-ui/src/index.android.tsx +35 -21
  357. package/vendor/node_modules/@legendapp/spark-ui/src/index.ios.tsx +55 -26
  358. package/vendor/node_modules/@legendapp/spark-ui/src/index.tsx +1 -27
  359. package/vendor/node_modules/@legendapp/spark-ui/src/index.web.tsx +45 -14
  360. package/vendor/node_modules/@legendapp/spark-ui/src/index.windows.tsx +1 -42
  361. package/vendor/node_modules/@legendapp/spark-ui/src/mobile-input.tsx +11 -0
  362. package/vendor/node_modules/@legendapp/spark-ui/src/native-select/NativeSegmentedControlNativeComponent.ts +17 -0
  363. package/vendor/node_modules/@legendapp/spark-ui/src/native-select/NativeSelectNativeComponent.ts +17 -0
  364. package/vendor/node_modules/@legendapp/spark-ui/src/select.ts +5 -3
  365. package/vendor/node_modules/@legendapp/spark-ui/src/sf-symbol/SFSymbolNativeComponent.ts +13 -0
  366. package/vendor/node_modules/@legendapp/spark-ui/src/sf-symbol/index.tsx +27 -0
  367. package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/SidebarItemNativeComponent.ts +25 -0
  368. package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/SidebarNativeComponent.ts +24 -0
  369. package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/index.tsx +104 -0
  370. package/vendor/node_modules/@legendapp/spark-ui/src/specialized.ts +22 -0
  371. package/vendor/node_modules/@legendapp/spark-ui/src/text-input-search/TextInputSearchNativeComponent.ts +33 -0
  372. package/vendor/node_modules/@legendapp/spark-ui/src/text-input-search/index.tsx +34 -0
  373. package/vendor/node_modules/@legendapp/spark-ui/src/types.ts +30 -19
  374. package/vendor/node_modules/@legendapp/spark-ui/uniwind.ts +3 -1
  375. package/vendor/node_modules/@legendapp/spark-ui/uniwind.web.ts +3 -1
  376. package/vendor/node_modules/@legendapp/spark-ui/windows/SparkUI/SparkUI.h +16 -11
  377. package/vendor/node_modules/@legendapp/spark-updates/macos/RNDesktopUpdates.mm +21 -8
  378. package/vendor/node_modules/@legendapp/spark-updates/package.json +2 -2
  379. package/vendor/node_modules/@legendapp/spark-updates/src/NativeDesktopUpdates.ts +3 -2
  380. package/vendor/node_modules/@legendapp/spark-updates/src/index.ts +71 -15
  381. package/vendor/node_modules/@legendapp/spark-webview/package.json +2 -2
  382. package/vendor/node_modules/@legendapp/spark-webview/src/WebView.tsx +11 -0
  383. package/vendor/node_modules/@legendapp/spark-webview/src/adapter.ts +55 -0
  384. package/vendor/node_modules/@legendapp/spark-webview/src/index.ts +2 -2
  385. package/vendor/node_modules/@legendapp/spark-webview/src/types.ts +32 -0
  386. package/vendor/node_modules/@legendapp/spark-webview/src/view-props.ts +28 -0
  387. package/vendor/node_modules/@legendapp/spark-window-options/index.cjs +51 -14
  388. package/vendor/node_modules/@legendapp/spark-window-options/index.d.ts +21 -8
  389. package/vendor/node_modules/@legendapp/spark-window-options/package.json +1 -1
  390. package/windows-macos.ts +1 -0
  391. package/expo-metro.cjs +0 -1
  392. package/message-dialog.ts +0 -1
  393. package/universal.cjs +0 -1
  394. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/accelerator.ts +0 -22
  395. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows-options.ts +0 -11
  396. package/vendor/node_modules/@legendapp/spark-file-dialog/src/index.windows.ts +0 -15
  397. package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/accelerator.ts +0 -22
  398. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkSelect.h +0 -4
  399. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkSelect.mm +0 -53
@@ -7,7 +7,6 @@
7
7
  #pragma comment(lib, "Shlwapi.lib")
8
8
  #include <winrt/Windows.Data.Json.h>
9
9
  #include <filesystem>
10
- #include <fstream>
11
10
  #include <vector>
12
11
  #pragma comment(lib, "Ole32.lib")
13
12
  namespace winrt::SparkFileDialog {
@@ -18,29 +17,6 @@ inline std::filesystem::path FilePath(std::string const &value) {
18
17
  if (!result.is_absolute() || value.find('\0') != std::string::npos) throw hresult_invalid_argument(L"Expected an absolute file path");
19
18
  return result;
20
19
  }
21
- inline std::string Read(std::string const &value) {
22
- auto file = FilePath(value);
23
- if (std::filesystem::file_size(file) > 8 * 1024 * 1024) throw hresult_invalid_argument(L"This text editor supports files up to 8 MB");
24
- std::ifstream input(file, std::ios::binary);
25
- if (!input) throw hresult_error(E_FAIL, L"Could not read file");
26
- std::string text((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>());
27
- if (input.bad()) throw hresult_error(E_FAIL, L"File read failed");
28
- if (text.starts_with("\xef\xbb\xbf")) text.erase(0, 3);
29
- // Reject non-UTF-8 input instead of silently destroying its encoding on save.
30
- (void)to_hstring(text);
31
- return text;
32
- }
33
- inline void Write(std::string const &value, std::string const &contents) {
34
- auto file = FilePath(value);
35
- GUID guid{}; check_hresult(CoCreateGuid(&guid));
36
- wchar_t id[40]{}; StringFromGUID2(guid, id, 40);
37
- auto temporary = file; temporary += std::wstring(L".spark-") + id;
38
- try {
39
- { std::ofstream output(temporary, std::ios::binary | std::ios::trunc);
40
- output.exceptions(std::ios::badbit | std::ios::failbit); output.write(contents.data(), contents.size()); output.close(); }
41
- if (!MoveFileExW(temporary.c_str(), file.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) throw_last_error();
42
- } catch (...) { std::error_code ignored; std::filesystem::remove(temporary, ignored); throw; }
43
- }
44
20
  REACT_MODULE(SparkFileDialog, L"NativeFileDialog")
45
21
  struct SparkFileDialog {
46
22
  React::ReactContext context;
@@ -54,6 +30,18 @@ struct SparkFileDialog {
54
30
  context.UIDispatcher().Post([save, options, promise]() {
55
31
  try {
56
32
  auto args = Json::JsonObject::Parse(to_hstring(options));
33
+ HWND parent = nullptr;
34
+ if (args.HasKey(L"windowId")) {
35
+ struct Search { std::wstring property; HWND found = nullptr; } search{L"Spark.Window." + std::wstring(args.GetNamedString(L"windowId"))};
36
+ EnumWindows([](HWND hwnd, LPARAM value) -> BOOL {
37
+ auto search = reinterpret_cast<Search *>(value); DWORD process = 0; GetWindowThreadProcessId(hwnd, &process);
38
+ if (process == GetCurrentProcessId() && GetPropW(hwnd, search->property.c_str())) { search->found = hwnd; return FALSE; }
39
+ return TRUE;
40
+ }, reinterpret_cast<LPARAM>(&search));
41
+ parent = search.found;
42
+ if (!parent) { promise.Reject(React::ReactError{"E_NOT_FOUND", "Dialog owner does not exist"}); return; }
43
+ if (!IsWindowEnabled(parent)) { promise.Reject(React::ReactError{"E_BUSY", "Dialog owner already has a modal operation"}); return; }
44
+ }
57
45
  com_ptr<IFileDialog> dialog;
58
46
  check_hresult(CoCreateInstance(save ? CLSID_FileSaveDialog : CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(dialog.put())));
59
47
  DWORD flags{}; check_hresult(dialog->GetOptions(&flags));
@@ -67,21 +55,43 @@ struct SparkFileDialog {
67
55
  const auto message = args.GetNamedString(L"message", L"");
68
56
  if (!message.empty()) { auto custom = dialog.as<IFileDialogCustomize>(); check_hresult(custom->AddText(100, message.c_str())); }
69
57
  auto directory = args.GetNamedString(save ? L"directory" : L"directoryURL", L"");
58
+ std::wstring suggestedName = args.GetNamedString(L"defaultName", L"");
70
59
  if (!directory.empty()) {
71
60
  std::wstring value(directory);
72
61
  if (value.rfind(L"file:", 0) == 0) { wchar_t buffer[32768]; DWORD length = 32768; check_hresult(PathCreateFromUrlW(value.c_str(), buffer, &length, 0)); value = buffer; }
62
+ // defaultPath may name a file for save dialogs: a live directory starts the
63
+ // panel; otherwise the last component suggests defaultName and the parent starts it.
64
+ if (save && GetFileAttributesW(value.c_str()) == INVALID_FILE_ATTRIBUTES) {
65
+ auto parsed = std::filesystem::path(value);
66
+ auto parent = parsed.parent_path();
67
+ if (suggestedName.empty()) suggestedName = parsed.filename().wstring();
68
+ if (!parent.empty()) value = parent.wstring();
69
+ }
73
70
  auto folder = FilePath(to_string(value)); com_ptr<IShellItem> item;
74
71
  check_hresult(SHCreateItemFromParsingName(folder.c_str(), nullptr, IID_PPV_ARGS(item.put())));
75
72
  check_hresult(dialog->SetFolder(item.get()));
76
73
  }
77
- const auto name = args.GetNamedString(L"defaultName", L"Untitled.txt"); if (save) check_hresult(dialog->SetFileName(name.c_str()));
78
- std::vector<std::wstring> patterns;
79
- if (args.HasKey(L"allowedFileTypes")) for (auto const &item : args.GetNamedArray(L"allowedFileTypes")) patterns.push_back(L"*." + std::wstring(item.GetString()));
80
- std::wstring pattern;
81
- for (auto const &item : patterns) { if (!pattern.empty()) pattern += L";"; pattern += item; }
82
- COMDLG_FILTERSPEC filter{L"Supported files", pattern.c_str()};
83
- if (!pattern.empty()) check_hresult(dialog->SetFileTypes(1, &filter));
84
- auto status = dialog->Show(GetActiveWindow());
74
+ if (save) check_hresult(dialog->SetFileName(suggestedName.empty() ? L"Untitled.txt" : suggestedName.c_str()));
75
+ // Windows presents one labeled filter group per filter, like Electron;
76
+ // hosts without per-filter labels read the flattened allowedFileTypes.
77
+ std::vector<std::wstring> labels, patterns;
78
+ if (args.HasKey(L"filters")) for (auto const &item : args.GetNamedArray(L"filters")) {
79
+ auto filter = item.GetObject();
80
+ std::wstring pattern;
81
+ for (auto const &extension : filter.GetNamedArray(L"extensions")) { if (!pattern.empty()) pattern += L";"; pattern += L"*." + std::wstring(extension.GetString()); }
82
+ if (pattern.empty()) continue;
83
+ labels.push_back(filter.HasKey(L"name") ? std::wstring(filter.GetNamedString(L"name")) : std::wstring(L"Supported files"));
84
+ patterns.push_back(pattern);
85
+ }
86
+ else if (args.HasKey(L"allowedFileTypes")) {
87
+ std::wstring pattern;
88
+ for (auto const &item : args.GetNamedArray(L"allowedFileTypes")) { if (!pattern.empty()) pattern += L";"; pattern += L"*." + std::wstring(item.GetString()); }
89
+ if (!pattern.empty()) { labels.push_back(L"Supported files"); patterns.push_back(pattern); }
90
+ }
91
+ std::vector<COMDLG_FILTERSPEC> filterSpecs;
92
+ for (size_t index = 0; index < patterns.size(); ++index) filterSpecs.push_back(COMDLG_FILTERSPEC{labels[index].c_str(), patterns[index].c_str()});
93
+ if (!filterSpecs.empty()) check_hresult(dialog->SetFileTypes(static_cast<UINT>(filterSpecs.size()), filterSpecs.data()));
94
+ auto status = dialog->Show(parent);
85
95
  if (status == HRESULT_FROM_WIN32(ERROR_CANCELLED)) { promise.Resolve("null"); return; }
86
96
  check_hresult(status);
87
97
  auto pathFor = [](IShellItem *item) {
@@ -98,31 +108,5 @@ struct SparkFileDialog {
98
108
  } catch (hresult_error const &error) { promise.Reject(React::ReactError{"E_DIALOG", to_string(error.message())}); }
99
109
  });
100
110
  }
101
- REACT_METHOD(readTextFile)
102
- void readTextFile(std::string file, React::ReactPromise<std::string> promise) noexcept {
103
- try { promise.Resolve(Read(file)); } catch (std::exception const &error) { promise.Reject(error.what()); } catch (hresult_error const &error) { promise.Reject(to_string(error.message()).c_str()); }
104
- }
105
- REACT_METHOD(writeTextFile)
106
- void writeTextFile(std::string file, std::string contents, React::ReactPromise<void> promise) noexcept {
107
- try { Write(file, contents); promise.Resolve(); } catch (std::exception const &error) { promise.Reject(error.what()); } catch (hresult_error const &error) { promise.Reject(to_string(error.message()).c_str()); }
108
- }
109
- REACT_METHOD(writeTextFileIfUnchanged)
110
- void writeTextFileIfUnchanged(std::string file, std::string expected, std::string contents, React::ReactPromise<bool> promise) noexcept {
111
- try { if (Read(file) != expected) { promise.Resolve(false); return; } Write(file, contents); promise.Resolve(true); }
112
- catch (std::exception const &error) { promise.Reject(error.what()); } catch (hresult_error const &error) { promise.Reject(to_string(error.message()).c_str()); }
113
- }
114
- REACT_METHOD(revealInFinder)
115
- void revealInFinder(std::string file, React::ReactPromise<bool> promise) noexcept {
116
- context.UIDispatcher().Post([file, promise] {
117
- PIDLIST_ABSOLUTE item = nullptr;
118
- try {
119
- auto path = FilePath(file);
120
- check_hresult(SHParseDisplayName(path.c_str(), nullptr, &item, 0, nullptr));
121
- auto result = SHOpenFolderAndSelectItems(item, 0, nullptr, 0);
122
- CoTaskMemFree(item); item = nullptr; check_hresult(result); promise.Resolve(true);
123
- } catch (hresult_error const &error) { if (item) CoTaskMemFree(item); promise.Reject(React::ReactError{"E_REVEAL", to_string(error.message())}); }
124
- catch (std::exception const &error) { if (item) CoTaskMemFree(item); promise.Reject(error.what()); }
125
- });
126
- }
127
111
  };
128
112
  }
@@ -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 = "CoreServices"
17
18
  end
@@ -1,4 +1,7 @@
1
1
  #import "RNDesktopFileSystem.h"
2
+ #import <RNDesktopApp/SparkBinaryJSI.h>
3
+ #import <AppKit/AppKit.h>
4
+ #import "SparkFileMutations.h"
2
5
  #import <RNDesktopApp/SparkDesktop.h>
3
6
  #import <fcntl.h>
4
7
  #import <unistd.h>
@@ -51,13 +54,15 @@ RCT_EXPORT_MODULE(NativeDesktopFileSystem)
51
54
  + (BOOL)requiresMainQueueSetup { return NO; }
52
55
  - (instancetype)init { if (self = [super init]) { _ioQueue = dispatch_queue_create("spark.files", DISPATCH_QUEUE_SERIAL); _files = [NSMutableDictionary new]; _watches = [NSMutableDictionary new]; _recursiveWatches = [NSMutableDictionary new]; } return self; }
53
56
  - (NSArray<NSString *> *)supportedEvents { return @[@"change"]; }
54
- - (void)call:(NSString *)method args:(NSString *)json resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
57
+ - (void)call:(NSString *)method args:(NSString *)json resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { [self perform:method args:SparkArgs(json) binary:NO resolve:resolve reject:reject]; }
58
+ - (void)binaryCall:(NSString *)method args:(NSString *)json bytes:(NSData *)bytes resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { NSMutableDictionary *args = [SparkArgs(json) mutableCopy]; if (bytes) args[@"bytes"] = bytes; [self perform:method args:args binary:YES resolve:resolve reject:reject]; }
59
+ - (void)perform:(NSString *)method args:(NSDictionary *)args binary:(BOOL)binary resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
55
60
  dispatch_async(self.ioQueue, ^{
56
- NSDictionary *args = SparkArgs(json); NSFileManager *fm = NSFileManager.defaultManager;
61
+ NSFileManager *fm = NSFileManager.defaultManager;
57
62
  NSError *error = nil; id result = NSNull.null;
58
63
  if ([@[@"readChunk", @"writeChunk", @"flushFile", @"closeFile"] containsObject:method]) {
59
64
  NSString *identifier = args[@"id"]; SparkOpenFile *file = self.files[identifier];
60
- if ([method isEqual:@"closeFile"]) { [self.files removeObjectForKey:identifier]; resolve(@"null"); return; }
65
+ if ([method isEqual:@"closeFile"]) { [self.files removeObjectForKey:identifier]; resolve(binary ? NSNull.null : @"null"); return; }
61
66
  if (!file) { reject(@"E_CLOSED", @"Unknown or closed file handle", nil); return; }
62
67
  if ([method isEqual:@"flushFile"]) { if (fsync(file.descriptor)) error = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]; }
63
68
  else {
@@ -69,11 +74,9 @@ RCT_EXPORT_MODULE(NativeDesktopFileSystem)
69
74
  NSMutableData *data = [NSMutableData dataWithLength:(NSUInteger)size]; ssize_t count;
70
75
  do { count = pread(file.descriptor, data.mutableBytes, data.length, (off_t)position); } while (count < 0 && errno == EINTR);
71
76
  if (count < 0) error = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil];
72
- else { data.length = count; result = [data base64EncodedStringWithOptions:0]; }
77
+ else { data.length = count; result = data; }
73
78
  } else {
74
- NSString *base64 = args[@"base64"];
75
- if (![base64 isKindOfClass:NSString.class] || base64.length > 1398104) { SparkInvalid(reject, @"Chunk exceeds 1 MiB"); return; }
76
- NSData *data = [[NSData alloc] initWithBase64EncodedString:base64 options:0];
79
+ NSData *data = args[@"bytes"];
77
80
  if (!data || data.length > 1048576 || position + data.length > 9007199254740991.0) { SparkInvalid(reject, @"Invalid chunk"); return; }
78
81
  NSUInteger written = 0;
79
82
  while (written < data.length) {
@@ -122,29 +125,37 @@ RCT_EXPORT_MODULE(NativeDesktopFileSystem)
122
125
  } else if ([method isEqual:@"trash"]) { [fm trashItemAtURL:url resultingItemURL:nil error:&error]; }
123
126
  else if ([method isEqual:@"readText"]) result = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
124
127
  else if ([method isEqual:@"readBytes"]) {
125
- NSData *data = [NSData dataWithContentsOfURL:url options:0 error:&error];
126
- result = [data base64EncodedStringWithOptions:0];
128
+ NSMutableData *data = [NSMutableData dataWithContentsOfURL:url options:0 error:&error];
129
+ result = data;
130
+ }
131
+ else if ([method isEqual:@"reveal"]) {
132
+ if (![fm attributesOfItemAtPath:url.path error:&error]) { SparkReject(reject, error); return; }
133
+ dispatch_async(dispatch_get_main_queue(), ^{
134
+ [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:@[url]];
135
+ resolve(binary ? NSNull.null : @"null");
136
+ });
137
+ return;
138
+ }
139
+ else if ([method isEqual:@"writeTextIfUnchanged"]) {
140
+ result = @(SparkWriteTextIfUnchanged(url, args[@"expected"], args[@"text"], &error));
127
141
  }
128
142
  else if ([method isEqual:@"writeText"]) [args[@"text"] writeToURL:url atomically:YES encoding:NSUTF8StringEncoding error:&error];
129
143
  else if ([method isEqual:@"writeBytes"]) {
130
- NSData *data = [[NSData alloc] initWithBase64EncodedString:args[@"base64"] options:0];
131
- if (!data) { SparkInvalid(reject, @"Invalid base64 data"); return; }
144
+ NSData *data = args[@"bytes"];
145
+ if (!data) { SparkInvalid(reject, @"Expected binary file data"); return; }
132
146
  [data writeToURL:url options:NSDataWritingAtomic error:&error];
133
147
  }
134
148
  else if ([method isEqual:@"mkdir"]) [fm createDirectoryAtURL:url withIntermediateDirectories:[args[@"recursive"] boolValue] attributes:nil error:&error];
135
149
  else if ([method isEqual:@"remove"]) {
136
- BOOL directory = NO;
137
- if (![fm fileExistsAtPath:url.path isDirectory:&directory]) { resolve(@"false"); return; }
138
- if (directory && ![args[@"recursive"] boolValue] && [fm contentsOfDirectoryAtPath:url.path error:&error].count) {
139
- reject(@"E_NOT_EMPTY", @"Directory is not empty; pass recursive: true", nil); return;
150
+ result = @(SparkRemoveFile(url, [args[@"recursive"] boolValue], &error));
151
+ if ([error.domain isEqual:NSPOSIXErrorDomain] && error.code == ENOTEMPTY) {
152
+ reject(@"E_NOT_EMPTY", @"Directory is not empty; pass recursive: true", error); return;
140
153
  }
141
- if (!error) result = @([fm removeItemAtURL:url error:&error]);
142
154
  }
143
155
  else if ([method isEqual:@"copy"] || [method isEqual:@"move"]) {
144
156
  NSURL *to = FileURL(args[@"to"]);
145
157
  if (!to || !to.isFileURL || (to.host.length && ![to.host isEqual:@"localhost"])) { SparkInvalid(reject, @"Expected an absolute destination"); return; }
146
- if ([method isEqual:@"copy"]) [fm copyItemAtURL:url toURL:to error:&error];
147
- else [fm moveItemAtURL:url toURL:to error:&error];
158
+ SparkTransferPath(url, to, [method isEqual:@"copy"] ? SparkTransferCopy : SparkTransferMove, [args[@"overwrite"] boolValue], &error);
148
159
  }
149
160
  else if ([method isEqual:@"stat"]) {
150
161
  NSDictionary *attrs = [fm attributesOfItemAtPath:url.path error:&error];
@@ -177,7 +188,7 @@ RCT_EXPORT_MODULE(NativeDesktopFileSystem)
177
188
  if (!watch.stream) { reject(@"E_WATCH", @"Could not create directory watcher", nil); return; }
178
189
  FSEventStreamSetDispatchQueue(watch.stream, self.ioQueue);
179
190
  if (!FSEventStreamStart(watch.stream)) { [watch stop]; reject(@"E_WATCH", @"Could not start directory watcher", nil); return; }
180
- self.recursiveWatches[watchID] = watch; resolve(@"null"); return;
191
+ self.recursiveWatches[watchID] = watch; resolve(binary ? NSNull.null : @"null"); return;
181
192
  }
182
193
  // Observe the parent so replacing a file atomically does not lose its watch.
183
194
  BOOL isDirectory = NO;
@@ -198,7 +209,7 @@ RCT_EXPORT_MODULE(NativeDesktopFileSystem)
198
209
  }
199
210
  else { SparkInvalid(reject, @"Unknown filesystem operation"); return; }
200
211
  }
201
- if (error) SparkReject(reject, error); else resolve(SparkJSON(result));
212
+ if (error) SparkReject(reject, error); else resolve(binary ? result : SparkJSON(result));
202
213
  });
203
214
  }
204
215
  - (void)invalidate {
@@ -206,6 +217,6 @@ RCT_EXPORT_MODULE(NativeDesktopFileSystem)
206
217
  [super invalidate];
207
218
  }
208
219
  - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
209
- return std::make_shared<facebook::react::NativeDesktopFileSystemSpecJSI>(params);
220
+ return std::make_shared<spark::binary::Module<facebook::react::NativeDesktopFileSystemSpecJSI>>(params);
210
221
  }
211
222
  @end
@@ -0,0 +1,24 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ enum SparkTransferKind {
4
+ SparkTransferCopy,
5
+ SparkTransferMove,
6
+ };
7
+
8
+ // A false result with no error means conflict/absence respectively.
9
+ BOOL SparkWriteTextIfUnchanged(NSURL *url, NSString *expected, NSString *text, NSError **error);
10
+ BOOL SparkRemoveFile(NSURL *url, BOOL recursive, NSError **error);
11
+ #if defined(SPARK_FILE_MUTATIONS_TESTING)
12
+ enum SparkTransferTestFailure {
13
+ SparkFailStageCopy = 1 << 0,
14
+ SparkFailPublish = 1 << 1,
15
+ SparkFailRestoreDestination = 1 << 2,
16
+ SparkFailRestoreSource = 1 << 3,
17
+ SparkFailSourceRemoval = 1 << 4,
18
+ SparkFailBackupRemoval = 1 << 5,
19
+ SparkFailStageCleanup = 1 << 6,
20
+ };
21
+ void SparkSetTransferTestFailures(unsigned failures, BOOL forceCrossVolumeMove);
22
+ #endif
23
+ /** Copies or moves, replacing the destination only when overwrite is set. An existing destination otherwise conflicts with NSFileWriteFileExistsError, including a dangling link. Self-transfers and overlapping paths reject. Transfers stage beside the destination before publication; cross-volume moves copy then delete. A cross-volume source-cleanup failure can leave a partial source; source-cleanup and backup-cleanup errors report committed and retained paths. */
24
+ BOOL SparkTransferPath(NSURL *from, NSURL *to, SparkTransferKind kind, BOOL overwrite, NSError **error);
@@ -0,0 +1,215 @@
1
+ #import "SparkFileMutations.h"
2
+ #import <sys/stat.h>
3
+ #import <errno.h>
4
+ #import <unistd.h>
5
+ #import <limits.h>
6
+ #import <stdio.h>
7
+
8
+ #if defined(SPARK_FILE_MUTATIONS_TESTING)
9
+ static unsigned SparkTransferTestFailures;
10
+ static BOOL SparkForceCrossVolumeMove;
11
+ void SparkSetTransferTestFailures(unsigned failures, BOOL forceCrossVolumeMove) {
12
+ SparkTransferTestFailures = failures;
13
+ SparkForceCrossVolumeMove = forceCrossVolumeMove;
14
+ }
15
+ static BOOL SparkTakeTransferTestFailure(unsigned failure) {
16
+ if (!(SparkTransferTestFailures & failure)) return NO;
17
+ SparkTransferTestFailures &= ~failure;
18
+ errno = EIO;
19
+ return YES;
20
+ }
21
+ #define SPARK_TAKE_FAILURE(failure) SparkTakeTransferTestFailure(failure)
22
+ #else
23
+ #define SPARK_TAKE_FAILURE(failure) NO
24
+ enum { SparkFailStageCopy = 0, SparkFailPublish = 0, SparkFailRestoreDestination = 0, SparkFailRestoreSource = 0,
25
+ SparkFailSourceRemoval = 0, SparkFailBackupRemoval = 0, SparkFailStageCleanup = 0 };
26
+ #endif
27
+
28
+ static int SparkPublishRename(NSString *from, NSString *to, unsigned failure) {
29
+ #if !defined(SPARK_FILE_MUTATIONS_TESTING)
30
+ (void)failure;
31
+ #endif
32
+ return SPARK_TAKE_FAILURE(failure) ? -1 : renamex_np(from.fileSystemRepresentation, to.fileSystemRepresentation, RENAME_EXCL);
33
+ }
34
+
35
+ static NSString *SparkSiblingPath(NSURL *url, NSString *label) {
36
+ return [url.URLByDeletingLastPathComponent.path stringByAppendingPathComponent:
37
+ [NSString stringWithFormat:@".spark-%@-%@", label, NSUUID.UUID.UUIDString]];
38
+ }
39
+
40
+ static NSString *SparkResolvedPath(NSURL *url) {
41
+ char resolved[PATH_MAX];
42
+ if (realpath(url.fileSystemRepresentation, resolved)) return @(resolved);
43
+ NSString *parent = url.URLByDeletingLastPathComponent.path;
44
+ if (realpath(parent.fileSystemRepresentation, resolved))
45
+ return [[@(resolved) stringByAppendingPathComponent:url.lastPathComponent] stringByStandardizingPath];
46
+ return url.URLByStandardizingPath.path;
47
+ }
48
+
49
+ static BOOL SparkPathContains(NSString *parent, NSString *child) {
50
+ if ([parent isEqualToString:child]) return YES;
51
+ NSString *prefix = [parent hasSuffix:@"/"] ? parent : [parent stringByAppendingString:@"/"];
52
+ return [child hasPrefix:prefix];
53
+ }
54
+
55
+ static BOOL SparkRejectUnsafeRelationship(NSURL *from, NSURL *to, NSError **error) {
56
+ struct stat sourceInfo, destinationInfo;
57
+ BOOL sourceExists = lstat(from.fileSystemRepresentation, &sourceInfo) == 0;
58
+ BOOL destinationExists = lstat(to.fileSystemRepresentation, &destinationInfo) == 0;
59
+ BOOL sameInode = sourceExists && destinationExists && sourceInfo.st_dev == destinationInfo.st_dev && sourceInfo.st_ino == destinationInfo.st_ino;
60
+ NSString *sourcePath = SparkResolvedPath(from), *destinationPath = SparkResolvedPath(to);
61
+ BOOL overlap = SparkPathContains(sourcePath, destinationPath) || SparkPathContains(destinationPath, sourcePath);
62
+ if (!sameInode && !overlap) return NO;
63
+ if (error) *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteInvalidFileNameError userInfo:@{NSLocalizedDescriptionKey: @"Source and destination identify or contain one another"}];
64
+ return YES;
65
+ }
66
+
67
+ BOOL SparkWriteTextIfUnchanged(NSURL *url, NSString *expected, NSString *text, NSError **error) {
68
+ NSString *current = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:error];
69
+ if (!current || ![current isEqualToString:expected]) return NO;
70
+ return [text writeToURL:url atomically:YES encoding:NSUTF8StringEncoding error:error];
71
+ }
72
+
73
+ BOOL SparkTransferPath(NSURL *from, NSURL *to, SparkTransferKind kind, BOOL overwrite, NSError **error) {
74
+ NSFileManager *fm = NSFileManager.defaultManager;
75
+ if (SparkRejectUnsafeRelationship(from, to, error)) return NO;
76
+ struct stat existing;
77
+ BOOL destinationExists = NO;
78
+ if (lstat(to.fileSystemRepresentation, &existing) == 0) {
79
+ destinationExists = YES;
80
+ if (!overwrite) {
81
+ if (error) *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteFileExistsError userInfo:nil];
82
+ return NO;
83
+ }
84
+ } else if (errno != ENOENT && errno != ENOTDIR) {
85
+ if (error) *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil];
86
+ return NO;
87
+ }
88
+ NSString *stagePath = SparkSiblingPath(to, @"stage");
89
+ NSURL *stage = [NSURL fileURLWithPath:stagePath];
90
+ struct stat sourceStat, parentStat;
91
+ BOOL sameVolumeMove = kind == SparkTransferMove && lstat(from.fileSystemRepresentation, &sourceStat) == 0 &&
92
+ stat(to.URLByDeletingLastPathComponent.fileSystemRepresentation, &parentStat) == 0 && sourceStat.st_dev == parentStat.st_dev;
93
+ #if defined(SPARK_FILE_MUTATIONS_TESTING)
94
+ if (SparkForceCrossVolumeMove) sameVolumeMove = NO;
95
+ #endif
96
+ BOOL stagedByRename = NO;
97
+ if (sameVolumeMove) {
98
+ if (rename(from.fileSystemRepresentation, stage.fileSystemRepresentation) != 0) {
99
+ int code = errno;
100
+ if (error) *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:nil];
101
+ return NO;
102
+ }
103
+ stagedByRename = YES;
104
+ } else {
105
+ NSError *copyError = nil;
106
+ BOOL staged = YES;
107
+ if (SPARK_TAKE_FAILURE(SparkFailStageCopy)) {
108
+ #if defined(SPARK_FILE_MUTATIONS_TESTING)
109
+ [@"partial stage" writeToURL:stage atomically:YES encoding:NSUTF8StringEncoding error:nil];
110
+ #endif
111
+ copyError = [NSError errorWithDomain:NSPOSIXErrorDomain code:EIO userInfo:nil];
112
+ staged = NO;
113
+ } else {
114
+ staged = [fm copyItemAtURL:from toURL:stage error:&copyError];
115
+ }
116
+ if (!staged) {
117
+ NSMutableDictionary *info = [copyError.userInfo mutableCopy] ?: [NSMutableDictionary dictionary];
118
+ struct stat partial;
119
+ if (lstat(stage.fileSystemRepresentation, &partial) == 0) {
120
+ NSError *cleanupError = nil;
121
+ BOOL failCleanup = SPARK_TAKE_FAILURE(SparkFailStageCleanup);
122
+ if (failCleanup) cleanupError = [NSError errorWithDomain:NSPOSIXErrorDomain code:EIO userInfo:nil];
123
+ if (failCleanup || ![fm removeItemAtURL:stage error:&cleanupError]) {
124
+ info[@"SparkRecoverableStagePath"] = stagePath;
125
+ info[NSLocalizedDescriptionKey] = [NSString stringWithFormat:@"Transfer staging failed and partial stage cleanup failed; recoverable stage: %@", stagePath];
126
+ if (cleanupError) info[@"SparkStageCleanupError"] = cleanupError;
127
+ }
128
+ }
129
+ if (error) *error = [NSError errorWithDomain:(copyError.domain ?: NSPOSIXErrorDomain)
130
+ code:(copyError ? copyError.code : EIO) userInfo:info];
131
+ return NO;
132
+ }
133
+ }
134
+ NSString *backupPath = nil;
135
+ if (destinationExists) {
136
+ backupPath = SparkSiblingPath(to, @"backup");
137
+ if (rename(to.fileSystemRepresentation, backupPath.fileSystemRepresentation) != 0) {
138
+ int code = errno;
139
+ int rollbackCode = 0;
140
+ if (stagedByRename && SparkPublishRename(stagePath, from.path, SparkFailRestoreSource) != 0) rollbackCode = errno;
141
+ else if (!stagedByRename && ![fm removeItemAtURL:stage error:nil]) rollbackCode = errno ?: EIO;
142
+ if (error) *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:rollbackCode ? @{
143
+ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Destination staging failed and source rollback failed (%d); recoverable source: %@", rollbackCode, stagePath],
144
+ NSUnderlyingErrorKey: [NSError errorWithDomain:NSPOSIXErrorDomain code:rollbackCode userInfo:nil],
145
+ @"SparkRecoverableStagePath": stagePath,
146
+ } : nil];
147
+ return NO;
148
+ }
149
+ }
150
+ int publishResult = SparkPublishRename(stagePath, to.path, SparkFailPublish);
151
+ if (publishResult != 0) {
152
+ int code = errno;
153
+ int rollbackCode = 0;
154
+ if (backupPath && SparkPublishRename(backupPath, to.path, SparkFailRestoreDestination) != 0) rollbackCode = errno;
155
+ if (stagedByRename && SparkPublishRename(stagePath, from.path, SparkFailRestoreSource) != 0) rollbackCode = errno;
156
+ if (!stagedByRename && ![fm removeItemAtURL:stage error:nil]) rollbackCode = errno ?: EIO;
157
+ if (error) *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:rollbackCode ? @{
158
+ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Transfer publication failed and rollback failed (%d); recoverable stage: %@; destination backup: %@", rollbackCode, stagePath, backupPath ?: @"none"],
159
+ NSUnderlyingErrorKey: [NSError errorWithDomain:NSPOSIXErrorDomain code:rollbackCode userInfo:nil],
160
+ @"SparkRecoverableStagePath": stagePath,
161
+ @"SparkRecoverableBackupPath": backupPath ?: @"",
162
+ } : nil];
163
+ return NO;
164
+ }
165
+ BOOL failSourceRemoval = SPARK_TAKE_FAILURE(SparkFailSourceRemoval);
166
+ if (kind == SparkTransferMove && !stagedByRename && (failSourceRemoval || ![fm removeItemAtURL:from error:error])) {
167
+ if (failSourceRemoval && error) *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:EIO userInfo:nil];
168
+ if (error && *error) {
169
+ NSMutableDictionary *info = [(*error).userInfo mutableCopy] ?: [NSMutableDictionary dictionary];
170
+ info[NSLocalizedDescriptionKey] = [NSString stringWithFormat:@"Source cleanup failed after publishing %@; source may be partially removed%@", to.path, backupPath ? [NSString stringWithFormat:@"; previous destination is retained at %@", backupPath] : @""];
171
+ info[@"SparkPublishedDestinationPath"] = to.path;
172
+ if (backupPath) info[@"SparkPreviousDestinationBackupPath"] = backupPath;
173
+ *error = [NSError errorWithDomain:(*error).domain code:(*error).code userInfo:info];
174
+ }
175
+ return NO;
176
+ }
177
+ if (backupPath) {
178
+ NSError *cleanupError = nil;
179
+ BOOL failCleanup = SPARK_TAKE_FAILURE(SparkFailBackupRemoval);
180
+ BOOL removed = !failCleanup && [fm removeItemAtPath:backupPath error:&cleanupError];
181
+ if (!removed) {
182
+ if (failCleanup) cleanupError = [NSError errorWithDomain:NSPOSIXErrorDomain code:EIO userInfo:nil];
183
+ if (error) *error = [NSError errorWithDomain:(cleanupError.domain ?: NSPOSIXErrorDomain) code:cleanupError.code userInfo:@{
184
+ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Transfer committed at %@, but previous destination cleanup failed; recoverable backup: %@", to.path, backupPath],
185
+ NSUnderlyingErrorKey: cleanupError ?: [NSError errorWithDomain:NSPOSIXErrorDomain code:EIO userInfo:nil],
186
+ @"SparkPublishedDestinationPath": to.path,
187
+ @"SparkRecoverableBackupPath": backupPath,
188
+ }];
189
+ return NO;
190
+ }
191
+ }
192
+ return YES;
193
+ }
194
+
195
+ BOOL SparkRemoveFile(NSURL *url, BOOL recursive, NSError **error) {
196
+ struct stat info;
197
+ if (lstat(url.fileSystemRepresentation, &info) != 0) {
198
+ int code = errno;
199
+ if (code == ENOENT || code == ENOTDIR) return NO;
200
+ if (error) *error = [NSError errorWithDomain:NSCocoaErrorDomain
201
+ code:(code == EACCES || code == EPERM) ? NSFileReadNoPermissionError : NSFileReadUnknownError
202
+ userInfo:@{NSUnderlyingErrorKey: [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:nil]}];
203
+ return NO;
204
+ }
205
+ NSFileManager *fm = NSFileManager.defaultManager;
206
+ if (S_ISDIR(info.st_mode) && !recursive) {
207
+ NSArray *children = [fm contentsOfDirectoryAtPath:url.path error:error];
208
+ if (!children) return NO;
209
+ if (children.count) {
210
+ if (error) *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:ENOTEMPTY userInfo:nil];
211
+ return NO;
212
+ }
213
+ }
214
+ return [fm removeItemAtURL:url error:error];
215
+ }
@@ -0,0 +1,9 @@
1
+ #import <React/RCTEventEmitter.h>
2
+ #import <RNDesktopFileSystemSpec/RNDesktopFileSystemSpec.h>
3
+
4
+ NS_ASSUME_NONNULL_BEGIN
5
+
6
+ @interface RNFileScanner : RCTEventEmitter <NativeFileScannerSpec>
7
+ @end
8
+
9
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,82 @@
1
+ #import "RNFileScanner.h"
2
+ #import "RNFileScannerCore.h"
3
+ #import <React/RCTBridgeModule.h>
4
+
5
+ @interface SparkScanTask : NSObject
6
+ @property (atomic) BOOL cancelled;
7
+ @end
8
+ @implementation SparkScanTask
9
+ @end
10
+
11
+ @implementation RNFileScanner {
12
+ BOOL _hasListeners;
13
+ BOOL _invalidated;
14
+ NSMutableDictionary<NSString *, SparkScanTask *> *_tasks;
15
+ }
16
+ RCT_EXPORT_MODULE(NativeFileScanner)
17
+ - (instancetype)init { if (self = [super init]) _tasks = [NSMutableDictionary new]; return self; }
18
+ - (NSArray<NSString *> *)supportedEvents { return @[@"onFileScanBatch", @"onFileScanProgress"]; }
19
+ - (void)startObserving { @synchronized(self) { _hasListeners = YES; } }
20
+ - (void)stopObserving { @synchronized(self) { _hasListeners = NO; } }
21
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
22
+ return std::make_shared<facebook::react::NativeFileScannerSpecJSI>(params);
23
+ }
24
+ - (RNFileScannerOptions *)scanOptionsFromJSON:(NSString *)optionsJson
25
+ {
26
+ id rawValue = RNFileScannerJSONObjectFromString(optionsJson);
27
+ NSDictionary *rawOptions = [rawValue isKindOfClass:NSDictionary.class] ? rawValue : @{};
28
+
29
+ RNFileScannerOptions *options = [RNFileScannerOptions new];
30
+ options.allowedExtensions = RNFileScannerExtensionsFromArray(rawOptions[@"allowedExtensions"], @[]);
31
+ options.batchSize = [rawOptions[@"batchSize"] unsignedIntegerValue] ?: 64;
32
+ options.includeHidden = [rawOptions[@"includeHidden"] boolValue];
33
+ options.includeStats = [rawOptions[@"includeStats"] boolValue];
34
+ options.skipLookup = RNFileScannerSkipLookupFromArray(rawOptions[@"skip"]);
35
+ return options;
36
+ }
37
+
38
+
39
+ - (BOOL)canEmitForTask:(SparkScanTask *)task {
40
+ @synchronized(self) { return _hasListeners && !_invalidated && !task.cancelled; }
41
+ }
42
+
43
+ - (void)scanFiles:(NSString *)identifier pathsJson:(NSString *)pathsJson optionsJson:(NSString *)optionsJson
44
+ resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
45
+ SparkScanTask *task = [SparkScanTask new];
46
+ @synchronized(self) {
47
+ if (_invalidated) { reject(@"E_CLOSED", @"Scanner has shut down", nil); return; }
48
+ if (_tasks[identifier]) { reject(@"E_BUSY", @"Duplicate scan ID", nil); return; }
49
+ _tasks[identifier] = task;
50
+ }
51
+ NSArray *paths = RNFileScannerJSONObjectFromString(pathsJson);
52
+ RNFileScannerOptions *options = [self scanOptionsFromJSON:optionsJson];
53
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
54
+ NSDictionary *result = RNFileScannerRun(paths, options, nil,
55
+ ^(NSArray<NSDictionary *> *items, NSUInteger rootIndex, NSUInteger completedRoots, NSUInteger totalRoots) {
56
+ dispatch_async(dispatch_get_main_queue(), ^{
57
+ if ([self canEmitForTask:task]) [self sendEventWithName:@"onFileScanBatch" body:@{
58
+ @"id": identifier, @"files": items, @"rootIndex": @(rootIndex), @"completedRoots": @(completedRoots), @"totalRoots": @(totalRoots)}];
59
+ });
60
+ },
61
+ ^(NSUInteger rootIndex, NSUInteger completedRoots, NSUInteger totalRoots) {
62
+ dispatch_async(dispatch_get_main_queue(), ^{
63
+ if ([self canEmitForTask:task]) [self sendEventWithName:@"onFileScanProgress" body:@{
64
+ @"id": identifier, @"rootIndex": @(rootIndex), @"completedRoots": @(completedRoots), @"totalRoots": @(totalRoots)}];
65
+ });
66
+ }, ^BOOL { return task.cancelled; });
67
+ dispatch_async(dispatch_get_main_queue(), ^{
68
+ @synchronized(self) { [self->_tasks removeObjectForKey:identifier]; }
69
+ if (task.cancelled) reject(@"E_ABORTED", @"File scan cancelled", nil);
70
+ else resolve(RNFileScannerJSONString(result));
71
+ });
72
+ });
73
+ }
74
+ - (void)cancelScan:(NSString *)identifier resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
75
+ @synchronized(self) { _tasks[identifier].cancelled = YES; }
76
+ resolve(nil);
77
+ }
78
+ - (void)invalidate {
79
+ @synchronized(self) { _invalidated = YES; for (SparkScanTask *task in _tasks.allValues) task.cancelled = YES; }
80
+ [super invalidate];
81
+ }
82
+ @end
@@ -0,0 +1,47 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ @interface RNFileScannerOptions : NSObject
6
+ @property (nonatomic, copy) NSSet<NSString *> *allowedExtensions;
7
+ @property (nonatomic, assign) NSUInteger batchSize;
8
+ @property (nonatomic, assign) BOOL includeHidden;
9
+ @property (nonatomic, assign) BOOL includeStats;
10
+ @property (nonatomic, copy) NSDictionary<NSNumber *, NSSet<NSString *> *> *skipLookup;
11
+ @end
12
+
13
+ typedef NSDictionary *_Nullable (^RNFileScannerMapFileBlock)(NSURL *fileURL,
14
+ NSString *rootPath,
15
+ NSUInteger rootIndex,
16
+ NSString *relativePath,
17
+ BOOL skipped);
18
+ typedef void (^RNFileScannerEmitBatchBlock)(NSArray<NSDictionary *> *items,
19
+ NSUInteger rootIndex,
20
+ NSUInteger completedRoots,
21
+ NSUInteger totalRoots);
22
+ typedef void (^RNFileScannerProgressBlock)(NSUInteger rootIndex,
23
+ NSUInteger completedRoots,
24
+ NSUInteger totalRoots);
25
+
26
+ NSString *RNFileScannerNormalizePath(NSString *path);
27
+ NSString *RNFileScannerRelativePath(NSString *fullPath, NSString *rootPath);
28
+ NSArray<NSString *> *RNFileScannerNormalizeRoots(NSArray<NSString *> *paths);
29
+ NSSet<NSString *> *RNFileScannerExtensionsFromArray(NSArray<NSString *> *_Nullable extensions,
30
+ NSArray<NSString *> *defaultExtensions);
31
+ NSDictionary<NSNumber *, NSSet<NSString *> *> *RNFileScannerSkipLookupFromArray(NSArray *_Nullable skipEntries);
32
+ NSDictionary *RNFileScannerDefaultFileItem(NSURL *fileURL,
33
+ NSString *rootPath,
34
+ NSUInteger rootIndex,
35
+ NSString *relativePath,
36
+ BOOL skipped,
37
+ BOOL includeStats);
38
+ NSDictionary *RNFileScannerRun(NSArray<NSString *> *paths,
39
+ RNFileScannerOptions *options,
40
+ RNFileScannerMapFileBlock _Nullable mapFile,
41
+ RNFileScannerEmitBatchBlock _Nullable emitBatch,
42
+ RNFileScannerProgressBlock _Nullable emitProgress,
43
+ BOOL (^ _Nullable isCancelled)(void));
44
+ NSString *RNFileScannerJSONString(id object);
45
+ id RNFileScannerJSONObjectFromString(NSString *json);
46
+
47
+ NS_ASSUME_NONNULL_END