@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/spark-notifications",
3
- "version": "0.0.1-next.2",
3
+ "version": "0.0.1-next.3",
4
4
  "license": "MIT",
5
5
  "main": "src/index.ts",
6
6
  "react-native": "src/index.ts",
@@ -40,7 +40,7 @@
40
40
  ]
41
41
  },
42
42
  "dependencies": {
43
- "@legendapp/spark-desktop-app": "0.0.1-next.2"
43
+ "@legendapp/spark-desktop-app": "0.0.1-next.3"
44
44
  },
45
45
  "private": true
46
46
  }
@@ -1,4 +1,4 @@
1
1
  import type { TurboModule } from "react-native";
2
2
  import { TurboModuleRegistry } from "react-native";
3
3
  export interface Spec extends TurboModule { call(method: string, args: string): Promise<string>; }
4
- export default TurboModuleRegistry.getEnforcing<Spec>("NativeDesktopNotifications");
4
+ export default TurboModuleRegistry.get<Spec>("NativeDesktopNotifications");
@@ -1,53 +1,141 @@
1
1
  import Native from "./NativeDesktopNotifications";
2
- import { Platform } from "react-native";
3
- import { onDesktopEvent, callApp } from "@legendapp/spark-desktop-app";
2
+ import { Platform, TurboModuleRegistry } from "react-native";
3
+ import type { Spec as AppSpec } from "@legendapp/spark-desktop-app/src/NativeDesktopApp";
4
+ import { onDesktopEvent } from "@legendapp/spark-desktop-app/src/events";
5
+ import { SparkError, invokeNative, parseNativeResult, type Availability, type Subscription } from "@legendapp/spark-desktop-app/src/contracts";
6
+ export type { Subscription } from "@legendapp/spark-desktop-app/src/contracts";
4
7
 
5
- export type NotificationPermission = "notDetermined" | "denied" | "authorized" | "provisional" | "unknown";
6
- export type DesktopNotification = {
7
- id: string;
8
+ export interface NotificationPermission {
9
+ status: "undetermined" | "denied" | "granted" | "unknown";
10
+ granted: boolean;
11
+ /** null when the host cannot determine this; Windows has no in-app prompt. */
12
+ canAskAgain: boolean | null;
13
+ macos?: { authorization: "notDetermined" | "denied" | "authorized" | "provisional" | "unknown" };
14
+ }
15
+ export interface NotificationAction { id: string; label: string }
16
+ /** Portable tone names; each target maps them to its own system sounds (documented in the native bridges). */
17
+ export type NotificationTone = "message" | "mail" | "reminder" | "call" | "error";
18
+ export interface NotificationContent {
8
19
  title: string;
9
20
  body?: string;
10
21
  subtitle?: string;
11
- sound?: boolean;
12
- /** Deliver after this many seconds, or immediately when omitted. */
13
- delay?: number;
22
+ /** false/omitted is silent; true or "default" plays the platform default; a tone selects a system sound. */
23
+ sound?: boolean | "default" | NotificationTone;
24
+ /** Custom buttons: 1–4 entries (the portable intersection of macOS categories and Windows toasts). macOS banners show the first two; the rest appear in Notification Center. Tapping the body reports "open", dismissing reports "dismiss". */
25
+ actions?: readonly NotificationAction[];
14
26
  data?: Record<string, string>;
15
- };
16
- export type NotificationResponse = { type: "notificationResponse"; id: string; notificationId: string; action: "open" | "dismiss"; data: Record<string, string> };
17
- async function call<T = void>(method: string, args: object = {}): Promise<T> {
18
- return JSON.parse(await Native.call(method, JSON.stringify(args))) as T;
19
- }
20
- function validId(id: string) {
21
- if (!/^[a-zA-Z0-9_.-]{1,100}$/.test(id)) throw new Error("Notification id must contain 1–100 letters, numbers, dots, underscores or hyphens");
22
- return id;
23
- }
24
- export const getNotificationPermission = () => call<NotificationPermission>("permission");
25
- /** Call from an explicit user action. Go shares its host's OS permission. */
26
- export const requestNotificationPermission = () => call<NotificationPermission>("requestPermission");
27
- export function showNotification(notification: DesktopNotification) {
28
- validId(notification.id);
29
- if (!notification.title?.trim()) throw new Error("Notification title is required");
30
- if (notification.delay !== undefined && (!Number.isFinite(notification.delay) || notification.delay < 1)) throw new Error("Notification delay must be at least one second");
31
- if (notification.data && Object.values(notification.data).some(value => typeof value !== "string")) throw new Error("Notification data values must be strings");
32
- return call("show", notification);
33
- }
34
- /** Removes pending and delivered notifications with this project-scoped id. */
35
- export const cancelNotification = (id: string) => call("cancel", { id: validId(id) });
36
- export const clearNotifications = () => call("clear");
37
- export const getPendingNotifications = () => call<string[]>("pending");
38
- export const getDeliveredNotifications = () => call<string[]>("delivered");
39
- /** Subscribe before draining cold-launch responses; each subscription deduplicates overlap. */
40
- export async function onNotificationResponse(listener: (response: NotificationResponse) => void) {
27
+ }
28
+ export interface ShowNotificationOptions { id: string; content: NotificationContent }
29
+ export type NotificationTrigger = { type: "delay"; delaySeconds: number };
30
+ export interface ScheduleNotificationOptions extends ShowNotificationOptions { trigger: NotificationTrigger }
31
+ /** action is "open", "dismiss", or the id of a tapped notification action. */
32
+ export interface NotificationResponse { type: "notificationResponse"; id: string; notificationId: string; action: "open" | "dismiss" | (string & {}); data: Record<string, string> }
33
+ export function getNotificationAvailability(): Availability {
34
+ if (Platform.OS !== "macos" && Platform.OS !== "windows") return { available: false, reason: "unsupported-platform" };
35
+ return Native ? { available: true } : { available: false, reason: "missing-module" };
36
+ }
37
+ function native() {
38
+ const availability = getNotificationAvailability();
39
+ if (!availability.available) throw new SparkError(availability.reason === "unsupported-platform" ? "E_UNSUPPORTED_PLATFORM" : "E_MODULE_UNAVAILABLE", "Desktop notifications are unavailable");
40
+ return Native!;
41
+ }
42
+ async function call<T>(method: string, args: object, validate: (value: unknown) => value is T): Promise<T> {
43
+ return parseNativeResult(await invokeNative(() => native().call(method, JSON.stringify(args))), validate);
44
+ }
45
+ async function command(method: string, args: object = {}): Promise<void> { await call(method, args, (value): value is null => value === null); }
46
+ function validId(id: unknown): id is string { return typeof id === "string" && /^[a-zA-Z0-9_.-]{1,100}$/.test(id); }
47
+ function id(value: string): string {
48
+ if (!validId(value)) throw new SparkError("E_INVALID_ARGUMENT", "Notification id must contain 1–100 letters, numbers, dots, underscores or hyphens");
49
+ return value;
50
+ }
51
+ function options(value: unknown, keys: string[]) {
52
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new SparkError("E_INVALID_ARGUMENT", "Expected notification options");
53
+ for (const key of Object.keys(value)) if (!keys.includes(key)) throw new SparkError("E_UNSUPPORTED_OPTION", `Unsupported notification option: ${key}`);
54
+ }
55
+ function stringRecord(value: unknown): value is Record<string, string> {
56
+ return !!value && typeof value === "object" && !Array.isArray(value) && Object.values(value).every(item => typeof item === "string");
57
+ }
58
+ const tones = ["message", "mail", "reminder", "call", "error"] as const;
59
+ function sound(value: unknown): "none" | "default" | NotificationTone {
60
+ if (value === undefined || value === false) return "none";
61
+ if (value === true || value === "default") return "default";
62
+ if (typeof value === "string" && (tones as readonly string[]).includes(value)) return value as NotificationTone;
63
+ throw new SparkError("E_INVALID_ARGUMENT", "Notification sound must be false, true, \"default\", or a tone name");
64
+ }
65
+ function actions(value: unknown): NotificationAction[] | undefined {
66
+ if (value === undefined) return undefined;
67
+ if (!Array.isArray(value) || !value.length || value.length > 4) throw new SparkError("E_INVALID_ARGUMENT", "Notification actions must contain 1–4 entries");
68
+ return value.map(action => {
69
+ if (!action || typeof action !== "object" || Array.isArray(action) || Object.keys(action).some(key => key !== "id" && key !== "label") || !validId((action as NotificationAction).id) || ["open", "dismiss"].includes((action as NotificationAction).id) || typeof (action as NotificationAction).label !== "string" || !(action as NotificationAction).label.trim() || (action as NotificationAction).label.includes("\0")) throw new SparkError("E_INVALID_ARGUMENT", "Notification actions need an id outside open/dismiss and a label");
70
+ return { id: action.id, label: action.label };
71
+ });
72
+ }
73
+ function notification(value: ShowNotificationOptions, scheduled: boolean) {
74
+ options(value, scheduled ? ["id", "content", "trigger"] : ["id", "content"]);
75
+ id(value.id); options(value.content, ["title", "body", "subtitle", "sound", "actions", "data"]);
76
+ const content = value.content;
77
+ if (typeof content.title !== "string" || !content.title.trim()) throw new SparkError("E_INVALID_ARGUMENT", "Notification title is required");
78
+ if ([content.body, content.subtitle].some(text => text !== undefined && typeof text !== "string") || (content.data !== undefined && !stringRecord(content.data))) throw new SparkError("E_INVALID_ARGUMENT", "Invalid notification content");
79
+ const result = { id: value.id, title: content.title, body: content.body, subtitle: content.subtitle, data: content.data, sound: sound(content.sound) };
80
+ const actionList = actions(content.actions);
81
+ return actionList === undefined ? result : { ...result, actions: actionList };
82
+ }
83
+ async function permission(method: string): Promise<NotificationPermission> {
84
+ const value = await call(method, {}, (value): value is NonNullable<NotificationPermission["macos"]>["authorization"] => typeof value === "string" && ["notDetermined", "denied", "authorized", "provisional", "unknown"].includes(value));
85
+ const granted = value === "authorized" || value === "provisional";
86
+ return { status: granted ? "granted" : value === "notDetermined" ? "undetermined" : value as "denied" | "unknown", granted,
87
+ canAskAgain: Platform.OS === "windows" ? false : value === "unknown" ? null : value !== "denied",
88
+ ...(Platform.OS === "macos" ? { macos: { authorization: value } } : {}) };
89
+ }
90
+ export async function getNotificationPermission(): Promise<NotificationPermission> { return permission("permission"); }
91
+ /** Call from an explicit user action. Windows reads OS settings without a prompt. */
92
+ export async function requestNotificationPermission(): Promise<NotificationPermission> { return permission("requestPermission"); }
93
+ /** Submission accepted by the OS; it does not guarantee presentation. */
94
+ export async function showNotification(value: ShowNotificationOptions): Promise<void> { await command("show", notification(value, false)); }
95
+ export async function scheduleNotification(value: ScheduleNotificationOptions): Promise<void> {
96
+ const content = notification(value, true);
97
+ options(value.trigger, ["type", "delaySeconds"]);
98
+ if (value.trigger.type !== "delay" || !Number.isFinite(value.trigger.delaySeconds) || value.trigger.delaySeconds < 1 || value.trigger.delaySeconds > 315360000) throw new SparkError("E_INVALID_ARGUMENT", "Notification delay must be 1 second to ten years");
99
+ await command("show", { ...content, delay: value.trigger.delaySeconds });
100
+ }
101
+ /** Cancels pending delivery only. Missing IDs succeed. */
102
+ export async function cancelNotification(value: string): Promise<void> { await command("cancel", { id: id(value) }); }
103
+ export async function cancelAllNotifications(): Promise<void> { await command("cancelAll"); }
104
+ /** Removes delivered notifications only. Missing IDs succeed. */
105
+ export async function dismissNotification(value: string): Promise<void> { await command("dismiss", { id: id(value) }); }
106
+ export async function dismissAllNotifications(): Promise<void> { await command("dismissAll"); }
107
+ const ids = (value: unknown): value is string[] => Array.isArray(value) && value.every(validId);
108
+ export async function getPendingNotifications(): Promise<string[]> { return call("pending", {}, ids); }
109
+ export async function getDeliveredNotifications(): Promise<string[]> { return call("delivered", {}, ids); }
110
+ function response(value: unknown): value is NotificationResponse {
111
+ if (!value || typeof value !== "object") return false;
112
+ const event = value as NotificationResponse;
113
+ return event.type === "notificationResponse" && typeof event.id === "string" && event.id.length > 0 && validId(event.notificationId) && validId(event.action) && stringRecord(event.data);
114
+ }
115
+ /** Subscribe before reading retained cold-launch responses; deduplicate queued/live overlap.
116
+ * Each new subscription replays retained responses (up to 100). Cleanup is synchronous.
117
+ */
118
+ export async function onNotificationResponse(listener: (event: NotificationResponse) => void): Promise<Subscription> {
119
+ if (typeof listener !== "function") throw new SparkError("E_INVALID_ARGUMENT", "Expected notification response listener");
120
+ native();
41
121
  let removed = false;
42
122
  const seen = new Set<string>();
43
- function receive(response: NotificationResponse) {
44
- if (removed || seen.has(response.id)) return;
45
- seen.add(response.id);
123
+ function receive(event: NotificationResponse) {
124
+ if (removed || seen.has(event.id)) return;
125
+ seen.add(event.id);
46
126
  if (seen.size > 256) seen.delete(seen.values().next().value!);
47
- listener(response);
127
+ listener(event);
48
128
  }
49
- const sub = onDesktopEvent(event => { if (event.type === "notificationResponse") receive(event as NotificationResponse); });
50
- try { for (const response of (Platform.OS === "windows" ? await callApp<NotificationResponse[]>("notificationResponses") : await call<NotificationResponse[]>("responses"))) receive(response); }
51
- catch (error) { sub.remove(); throw error; }
52
- return { remove() { removed = true; sub.remove(); } };
129
+ const sub = onDesktopEvent(event => { if (response(event)) receive(event); }, { types: ["notificationResponse"] });
130
+ try {
131
+ const responses = Platform.OS === "windows"
132
+ ? parseNativeResult(await invokeNative(() => {
133
+ const app = TurboModuleRegistry.get<AppSpec>("NativeDesktopApp");
134
+ if (!app) throw new SparkError("E_MODULE_UNAVAILABLE", "Notification activation host is unavailable");
135
+ return app.call("notificationResponses", "{}");
136
+ }), (value): value is NotificationResponse[] => Array.isArray(value) && value.every(response))
137
+ : await call("responses", {}, (value): value is NotificationResponse[] => Array.isArray(value) && value.every(response));
138
+ responses.forEach(receive);
139
+ } catch (error) { removed = true; sub.remove(); throw error; }
140
+ return { remove() { if (!removed) { removed = true; sub.remove(); } } };
53
141
  }
@@ -7,6 +7,7 @@
7
7
  #include <filesystem>
8
8
  #include <map>
9
9
  #include <memory>
10
+ #include <cstdint>
10
11
  #include <cmath>
11
12
  #include <winrt/Windows.Data.Json.h>
12
13
  #include <winrt/Windows.Data.Xml.Dom.h>
@@ -38,8 +39,9 @@ struct NotificationsState : std::enable_shared_from_this<NotificationsState> {
38
39
  React::ReactContext context;
39
40
  std::wstring project, appId;
40
41
  Toast::ToastNotifier notifier{nullptr};
41
- struct Live { Toast::ToastNotification toast{nullptr}; event_token dismissed; };
42
+ struct Live { Toast::ToastNotification toast{nullptr}; event_token dismissed; uint64_t generation; };
42
43
  std::map<std::wstring, Live> live;
44
+ uint64_t nextGeneration = 0;
43
45
  static constexpr wchar_t const *group = L"Spark";
44
46
  void Ensure() {
45
47
  if (notifier) return;
@@ -75,6 +77,12 @@ struct NotificationsState : std::enable_shared_from_this<NotificationsState> {
75
77
  notifier = candidate;
76
78
  }
77
79
  void Forget(std::wstring const &tag) { auto entry = live.find(tag); if (entry != live.end()) { entry->second.toast.Dismissed(entry->second.dismissed); live.erase(entry); } }
80
+ void Retire(std::wstring const &tag, uint64_t generation, bool userDismissed, std::wstring const &encoded) {
81
+ auto entry = live.find(tag);
82
+ if (entry == live.end() || entry->second.generation != generation) return;
83
+ Forget(tag);
84
+ if (userDismissed) Dismiss(hstring(encoded));
85
+ }
78
86
  void Close() noexcept { for (auto &[tag, item] : live) try { item.toast.Dismissed(item.dismissed); } catch (...) {} live.clear(); }
79
87
  void RemovePending(std::wstring const &tag) { for (auto const &item : notifier.GetScheduledToastNotifications()) if (item.Group() == group && (tag.empty() || item.Tag() == tag)) notifier.RemoveFromSchedule(item); }
80
88
  static hstring Id(Xml::XmlDocument const &xml) { return Json::JsonObject::Parse(xml.DocumentElement().GetAttribute(L"launch")).GetNamedString(L"notificationId"); }
@@ -92,15 +100,43 @@ struct NotificationsState : std::enable_shared_from_this<NotificationsState> {
92
100
  Xml::XmlDocument xml; xml.LoadXml(L"<toast><visual><binding template='ToastGeneric'/></visual></toast>"); xml.DocumentElement().SetAttribute(L"launch", response.Stringify());
93
101
  auto binding = xml.SelectSingleNode(L"/toast/visual/binding");
94
102
  for (auto field : {L"title", L"subtitle", L"body"}) { auto text = args.GetNamedString(field, L""); if (!text.empty()) { auto node = xml.CreateElement(L"text"); node.AppendChild(xml.CreateTextNode(text)); binding.AppendChild(node); } }
95
- if (!args.GetNamedBoolean(L"sound", true)) { auto audio = xml.CreateElement(L"audio"); audio.SetAttribute(L"silent", L"true"); xml.DocumentElement().AppendChild(audio); }
103
+ // JS normalizes sound to "none", "default", or a tone name mapped onto the
104
+ // system Notification.* catalog; tones without a Windows counterpart use Default.
105
+ auto sound = args.GetNamedString(L"sound", L"none");
106
+ if (sound == L"none") { auto audio = xml.CreateElement(L"audio"); audio.SetAttribute(L"silent", L"true"); xml.DocumentElement().AppendChild(audio); }
107
+ else {
108
+ auto audio = xml.CreateElement(L"audio");
109
+ auto tone = std::wstring(L"ms-winsoundevent:Notification.");
110
+ if (sound == L"mail") tone += L"Mail"; else if (sound == L"message") tone += L"IM"; else if (sound == L"reminder") tone += L"Reminder";
111
+ else if (sound == L"call") { tone += L"Looping.Call"; audio.SetAttribute(L"duration", L"long"); }
112
+ else tone += L"Default";
113
+ audio.SetAttribute(L"src", tone.c_str()); xml.DocumentElement().AppendChild(audio);
114
+ }
115
+ // Action buttons activate through the same CustomActivator as the toast body,
116
+ // carrying their own action id in the activation arguments.
117
+ for (auto const &entry : args.GetNamedArray(L"actions", Json::JsonArray())) {
118
+ auto action = entry.GetObject();
119
+ auto button = xml.CreateElement(L"action");
120
+ // JsonObject is a reference type; parse a copy so each action carries its
121
+ // own action id without mutating the body/dismiss payload.
122
+ auto arguments = Json::JsonObject::Parse(response.Stringify());
123
+ arguments.SetNamedValue(L"action", Json::JsonValue::CreateStringValue(action.GetNamedString(L"id")));
124
+ button.SetAttribute(L"content", action.GetNamedString(L"label").c_str());
125
+ button.SetAttribute(L"arguments", arguments.Stringify().c_str());
126
+ xml.DocumentElement().AppendChild(button);
127
+ }
96
128
  auto tag = Hash(id); RemovePending(tag); Forget(tag);
97
129
  if (delay > 0) {
98
130
  Toast::ScheduledToastNotification toast(xml, clock::now() + std::chrono::milliseconds(static_cast<int64_t>(delay * 1000))); toast.Tag(tag); toast.Group(group); notifier.AddToSchedule(toast);
99
131
  } else {
100
132
  Toast::ToastNotification toast(xml); toast.Tag(tag); toast.Group(group);
101
133
  response.SetNamedValue(L"action", Json::JsonValue::CreateStringValue(L"dismiss")); auto encoded = response.Stringify(); auto weak = weak_from_this(); auto dispatcher = context.UIDispatcher();
102
- auto token = toast.Dismissed([weak, dispatcher, encoded](auto const &, Toast::ToastDismissedEventArgs const &event) { if (event.Reason() == Toast::ToastDismissalReason::UserCanceled) dispatcher.Post([weak, encoded]() { if (auto state = weak.lock()) state->Dismiss(encoded); }); });
103
- try { notifier.Show(toast); live.emplace(tag, Live{toast, token}); } catch (...) { toast.Dismissed(token); throw; }
134
+ const auto generation = ++nextGeneration;
135
+ auto token = toast.Dismissed([weak, dispatcher, tag, generation, encoded](auto const &, Toast::ToastDismissedEventArgs const &event) {
136
+ const bool userDismissed = event.Reason() == Toast::ToastDismissalReason::UserCanceled;
137
+ dispatcher.Post([weak, tag, generation, userDismissed, encoded]() { if (auto state = weak.lock()) state->Retire(tag, generation, userDismissed, encoded); });
138
+ });
139
+ try { notifier.Show(toast); live.emplace(tag, Live{toast, token, generation}); } catch (...) { toast.Dismissed(token); throw; }
104
140
  }
105
141
  }
106
142
  };
@@ -115,8 +151,10 @@ struct SparkNotifications {
115
151
  value->Ensure(); auto args = Json::JsonObject::Parse(to_hstring(encoded));
116
152
  if (method == "permission" || method == "requestPermission") { promise.Resolve(value->notifier.Setting() == Toast::NotificationSetting::Enabled ? "\"authorized\"" : "\"denied\""); return; }
117
153
  if (method == "show") value->Show(args);
118
- else if (method == "cancel") { auto tag = Hash(args.GetNamedString(L"id")); value->RemovePending(tag); value->Forget(tag); Toast::ToastNotificationManager::History().Remove(tag, value->group, value->appId); }
119
- else if (method == "clear") { value->RemovePending(L""); value->Close(); Toast::ToastNotificationManager::History().RemoveGroup(value->group, value->appId); }
154
+ else if (method == "cancel") value->RemovePending(Hash(args.GetNamedString(L"id")));
155
+ else if (method == "dismiss") { auto tag = Hash(args.GetNamedString(L"id")); value->Forget(tag); Toast::ToastNotificationManager::History().Remove(tag, value->group, value->appId); }
156
+ else if (method == "cancelAll") value->RemovePending(L"");
157
+ else if (method == "dismissAll") { value->Close(); Toast::ToastNotificationManager::History().RemoveGroup(value->group, value->appId); }
120
158
  else if (method == "pending" || method == "delivered") {
121
159
  Json::JsonArray result;
122
160
  if (method == "pending") { for (auto const &item : value->notifier.GetScheduledToastNotifications()) if (item.Group() == value->group) result.Append(Json::JsonValue::CreateStringValue(value->Id(item.Content()))); }
@@ -124,8 +162,8 @@ struct SparkNotifications {
124
162
  promise.Resolve(to_string(result.Stringify())); return;
125
163
  } else throw hresult_invalid_argument(L"Unsupported notification operation");
126
164
  promise.Resolve("null");
127
- } catch (hresult_error const &error) { promise.Reject(React::ReactError{error.code() == E_ACCESSDENIED ? "E_NOTIFICATION_PERMISSION" : "E_NOTIFICATION", to_string(error.message())}); }
128
- catch (std::exception const &error) { promise.Reject(React::ReactError{"E_NOTIFICATION", error.what()}); }
165
+ } catch (hresult_error const &error) { promise.Reject(React::ReactError{error.code() == E_ACCESSDENIED ? "E_PERMISSION_DENIED" : "E_NATIVE", to_string(error.message())}); }
166
+ catch (std::exception const &error) { promise.Reject(React::ReactError{"E_NATIVE", error.what()}); }
129
167
  });
130
168
  }
131
169
  };
@@ -1,10 +1,49 @@
1
1
  #import "RNDesktopProcesses.h"
2
+ #import <RNDesktopApp/SparkBinaryJSI.h>
2
3
  #import <RNDesktopApp/SparkDesktop.h>
3
4
  #import <signal.h>
4
5
  #import <AppKit/AppKit.h>
5
6
  #import <spawn.h>
6
7
  #import <sys/wait.h>
7
8
  #import <vector>
9
+ static NSString *ResolveCommandPath(NSString *command)
10
+ {
11
+ NSString *homeDirectory = NSHomeDirectory();
12
+ NSArray<NSString *> *fallbackPaths = @[
13
+ [homeDirectory stringByAppendingPathComponent:@".local/bin"],
14
+ [homeDirectory stringByAppendingPathComponent:@".bun/bin"],
15
+ [homeDirectory stringByAppendingPathComponent:@"bin"],
16
+ @"/opt/homebrew/bin",
17
+ @"/usr/local/bin",
18
+ @"/usr/bin",
19
+ @"/bin",
20
+ @"/usr/sbin",
21
+ @"/sbin",
22
+ ];
23
+ NSMutableArray<NSString *> *searchPaths = [NSMutableArray array];
24
+ NSString *pathValue = [[NSProcessInfo processInfo] environment][@"PATH"];
25
+ if (pathValue.length > 0) {
26
+ [searchPaths addObjectsFromArray:[pathValue componentsSeparatedByString:@":"]];
27
+ }
28
+ for (NSString *fallback in fallbackPaths) {
29
+ if (![searchPaths containsObject:fallback]) {
30
+ [searchPaths addObject:fallback];
31
+ }
32
+ }
33
+
34
+ NSFileManager *fileManager = [NSFileManager defaultManager];
35
+ for (NSString *directory in searchPaths) {
36
+ if (![directory hasPrefix:@"/"]) continue;
37
+ NSString *candidate = [directory stringByAppendingPathComponent:command];
38
+ BOOL directoryEntry = NO;
39
+ if ([fileManager fileExistsAtPath:candidate isDirectory:&directoryEntry] && !directoryEntry && [fileManager isExecutableFileAtPath:candidate]) {
40
+ return candidate;
41
+ }
42
+ }
43
+
44
+ return nil;
45
+ }
46
+
8
47
  @interface SparkProcess : NSObject
9
48
  @property pid_t pid;
10
49
  @property BOOL running;
@@ -12,6 +51,7 @@
12
51
  @property BOOL timedOut;
13
52
  @property BOOL truncated;
14
53
  @property BOOL ended;
54
+ @property BOOL inputClosed;
15
55
  @property dispatch_queue_t inputQueue;
16
56
  @end
17
57
  @implementation SparkProcess
@@ -19,6 +59,7 @@
19
59
  @interface RNDesktopProcesses ()
20
60
  @property NSMutableDictionary<NSString *, SparkProcess *> *processes;
21
61
  @property BOOL invalidated;
62
+ @property (copy) void (^binaryEvent)(NSDictionary *event, dispatch_block_t delivered);
22
63
  @end
23
64
  @implementation RNDesktopProcesses
24
65
  RCT_EXPORT_MODULE(NativeDesktopProcesses)
@@ -33,23 +74,32 @@ RCT_EXPORT_MODULE(NativeDesktopProcesses)
33
74
  - (void)stopAll { for (SparkProcess *process in self.processes.allValues) if (process.running) kill(-process.pid, SIGKILL); }
34
75
  - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; }
35
76
 
36
- - (void)terminate:(SparkProcess *)process {
77
+ - (BOOL)terminate:(SparkProcess *)process {
37
78
  if (process.running) {
38
- kill(-process.pid, SIGTERM);
79
+ if (kill(-process.pid, SIGTERM) != 0 && errno != ESRCH) return NO;
39
80
  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
40
81
  if (process.running) kill(-process.pid, SIGKILL);
41
82
  });
42
83
  }
84
+ return YES;
43
85
  }
44
86
 
45
- - (void)call:(NSString *)method args:(NSString *)json resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
87
+ - (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]; }
88
+ - (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]; }
89
+ - (void)perform:(NSString *)method args:(NSDictionary *)args binary:(BOOL)binary resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
46
90
  dispatch_async(dispatch_get_main_queue(), ^{
47
- NSDictionary *args = SparkArgs(json); NSString *key = args[@"id"];
91
+ NSString *key = args[@"id"];
48
92
  if (self.invalidated) { reject(@"E_CLOSED", @"Process module is closed", nil); return; }
49
- SparkProcess *process = self.processes[key];
93
+ if ([method isEqual:@"resolveCommand"]) {
94
+ NSString *command = args[@"command"];
95
+ if (![command isKindOfClass:NSString.class] || !command.length) { SparkInvalid(reject, @"Expected a command name"); return; }
96
+ resolve(binary ? (ResolveCommandPath(command) ?: (id)NSNull.null) : SparkJSON(ResolveCommandPath(command) ?: (id)NSNull.null)); return;
97
+ }
98
+ SparkProcess *process = key ? self.processes[key] : nil;
50
99
  if ([method isEqual:@"spawn"]) {
51
100
  if (process) { reject(@"E_EXISTS", @"Process id already exists", nil); return; }
52
101
  NSString *executable = args[@"executable"];
102
+ if ([args[@"command"] boolValue]) { executable = ResolveCommandPath(executable); if (!executable) { reject(@"E_NOT_FOUND", @"Command not found", nil); return; } }
53
103
  if ([executable hasPrefix:@"helper:"]) {
54
104
  NSString *name = [executable substringFromIndex:7];
55
105
  if (!name.length || [name rangeOfCharacterFromSet:[[NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"] invertedSet]].location != NSNotFound) { SparkInvalid(reject, @"Invalid helper name"); return; }
@@ -63,6 +113,10 @@ RCT_EXPORT_MODULE(NativeDesktopProcesses)
63
113
  }
64
114
  }
65
115
  if (![executable hasPrefix:@"/"]) { SparkInvalid(reject, @"Executable path must be absolute"); return; }
116
+ NSUInteger captureLimit = [args[@"captureLimitBytes"] unsignedIntegerValue];
117
+ if (captureLimit > 8 * 1024 * 1024) { SparkInvalid(reject, @"Invalid capture limit"); return; }
118
+ NSData *initialInput = args[@"bytes"];
119
+ if (args[@"bytes"] && ![initialInput isKindOfClass:NSData.class]) { SparkInvalid(reject, @"Invalid process input"); return; }
66
120
  process = [SparkProcess new];
67
121
  process.inputQueue = dispatch_queue_create("desktop.process.input", DISPATCH_QUEUE_SERIAL);
68
122
  NSMutableDictionary *environment = [NSProcessInfo.processInfo.environment mutableCopy]; [environment addEntriesFromDictionary:args[@"env"] ?: @{}];
@@ -71,7 +125,11 @@ RCT_EXPORT_MODULE(NativeDesktopProcesses)
71
125
  // Create the process group atomically, before executable code can fork.
72
126
  // NSTask + setpgid after launch has a race and cannot provide this contract.
73
127
  posix_spawnattr_t attributes; posix_spawnattr_init(&attributes);
74
- posix_spawnattr_setflags(&attributes, POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_CLOEXEC_DEFAULT);
128
+ // GCD/host threads can block or ignore signals. Child defaults must not
129
+ // inherit that state or cooperative SIGTERM degrades into forced SIGKILL.
130
+ sigset_t mask, defaults; sigemptyset(&mask); sigfillset(&defaults);
131
+ posix_spawnattr_setsigmask(&attributes, &mask); posix_spawnattr_setsigdefault(&attributes, &defaults);
132
+ posix_spawnattr_setflags(&attributes, POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_CLOEXEC_DEFAULT | POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF);
75
133
  posix_spawnattr_setpgroup(&attributes, 0);
76
134
  posix_spawn_file_actions_t actions; posix_spawn_file_actions_init(&actions);
77
135
  posix_spawn_file_actions_adddup2(&actions, input.fileHandleForReading.fileDescriptor, STDIN_FILENO);
@@ -98,6 +156,9 @@ RCT_EXPORT_MODULE(NativeDesktopProcesses)
98
156
  NSFileHandle *handle = streams[index]; NSMutableData *buffer = index ? stderrData : stdoutData;
99
157
  NSString *stream = index ? @"stderr" : @"stdout";
100
158
  dispatch_group_async(group, dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
159
+ // Keep up to 1 MiB in flight per stream; ordinary delivery never waits
160
+ // for a per-chunk JS acknowledgement. A stalled consumer backpressures the pipe.
161
+ dispatch_semaphore_t slots = dispatch_semaphore_create(64);
101
162
  @try {
102
163
  while (YES) {
103
164
  // readDataOfLength can wait for the requested length. A service's
@@ -105,9 +166,13 @@ RCT_EXPORT_MODULE(NativeDesktopProcesses)
105
166
  char bytes[16384]; ssize_t count;
106
167
  do { count = read(handle.fileDescriptor, bytes, sizeof(bytes)); } while (count < 0 && errno == EINTR);
107
168
  if (count <= 0) break;
108
- NSData *data = [NSData dataWithBytes:bytes length:(NSUInteger)count];
109
- @synchronized(process) { NSUInteger remaining = 8 * 1024 * 1024 - buffer.length; if (data.length > remaining) process.truncated = YES; [buffer appendData:[data subdataWithRange:NSMakeRange(0, MIN(remaining, data.length))]]; }
110
- if ([args[@"streamOutput"] boolValue]) dispatch_sync(dispatch_get_main_queue(), ^{ if (!self.invalidated) SparkEmit(@{ @"type": @"processOutput", @"processId": key, @"stream": stream, @"base64": [data base64EncodedStringWithOptions:0] }); });
169
+ NSMutableData *data = [NSMutableData dataWithBytes:bytes length:(NSUInteger)count];
170
+ @synchronized(process) { NSUInteger remaining = captureLimit - buffer.length; if (data.length > remaining) process.truncated = YES; [buffer appendData:[data subdataWithRange:NSMakeRange(0, MIN(remaining, data.length))]]; }
171
+ if ([args[@"streamOutput"] boolValue]) {
172
+ while (!self.invalidated && dispatch_semaphore_wait(slots, dispatch_time(DISPATCH_TIME_NOW, 100 * NSEC_PER_MSEC))) {}
173
+ if (self.invalidated) break;
174
+ dispatch_sync(dispatch_get_main_queue(), ^{ if (!self.invalidated) self.binaryEvent(@{ @"type": @"processOutput", @"processId": key, @"stream": stream, @"bytes": data }, ^{ dispatch_semaphore_signal(slots); }); else dispatch_semaphore_signal(slots); });
175
+ }
111
176
  }
112
177
  } @catch (NSException *exception) { /* Closing the runtime interrupts pipe reads. */ }
113
178
  [handle closeFile];
@@ -123,31 +188,39 @@ RCT_EXPORT_MODULE(NativeDesktopProcesses)
123
188
  int terminationStatus = status;
124
189
  process.ended = YES;
125
190
  dispatch_async(process.inputQueue, ^{ @try { [process.input closeFile]; } @catch (NSException *exception) {} });
126
- if (!self.invalidated) SparkEmit(@{ @"type": @"processExit", @"processId": key, @"result": @{
127
- @"exitCode": @(WIFEXITED(terminationStatus) ? WEXITSTATUS(terminationStatus) : WTERMSIG(terminationStatus)), @"signal": @(WIFSIGNALED(terminationStatus)),
128
- @"stdout": [[NSString alloc] initWithData:stdoutData encoding:NSUTF8StringEncoding] ?: @"", @"stderr": [[NSString alloc] initWithData:stderrData encoding:NSUTF8StringEncoding] ?: @"",
129
- @"stdoutBase64": [stdoutData base64EncodedStringWithOptions:0], @"stderrBase64": [stderrData base64EncodedStringWithOptions:0],
130
- @"timedOut": @(process.timedOut), @"outputTruncated": @(process.truncated) } });
191
+ if (!self.invalidated) self.binaryEvent(@{ @"type": @"processExit", @"processId": key, @"result": @{
192
+ @"exitCode": @(WIFEXITED(terminationStatus) ? WEXITSTATUS(terminationStatus) : WTERMSIG(terminationStatus)), @"terminated": @(WIFSIGNALED(terminationStatus)), @"terminationSignal": WIFSIGNALED(terminationStatus) ? @(WTERMSIG(terminationStatus)) : (id)NSNull.null,
193
+ @"stdout": stdoutData, @"stderr": stderrData,
194
+ @"timedOut": @(process.timedOut), @"outputTruncated": @(process.truncated) } }, nil);
131
195
  [self.processes removeObjectForKey:key];
132
196
  });
133
197
  });
134
- if (args[@"input"]) { NSData *data = [args[@"input"] dataUsingEncoding:NSUTF8StringEncoding]; dispatch_async(process.inputQueue, ^{ @try { [process.input writeData:data]; } @catch (NSException *exception) {} }); }
198
+ if (initialInput) { NSData *data = initialInput; dispatch_async(process.inputQueue, ^{ @try { [process.input writeData:data]; } @catch (NSException *exception) {} }); }
135
199
  if (args[@"timeoutMs"]) dispatch_after(dispatch_time(DISPATCH_TIME_NOW, [args[@"timeoutMs"] doubleValue] * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
136
200
  if (process.running) { process.timedOut = YES; [self terminate:process]; }
137
201
  });
138
202
  } else {
139
- if (!process || process.ended) { if ([method isEqual:@"terminate"] || [method isEqual:@"closeInput"]) { resolve(@"null"); return; } reject(@"E_CLOSED", @"Process has exited", nil); return; }
140
- if ([method isEqual:@"terminate"]) [self terminate:process];
203
+ if (!process || process.ended) { if ([method isEqual:@"terminate"] || [method isEqual:@"closeInput"]) { resolve(binary ? NSNull.null : @"null"); return; } reject(@"E_CLOSED", @"Process has exited", nil); return; }
204
+ if ([method isEqual:@"terminate"]) {
205
+ if (![self terminate:process]) { SparkReject(reject, [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]); return; }
206
+ }
141
207
  else if ([method isEqual:@"write"] || [method isEqual:@"closeInput"]) {
208
+ NSData *data = [method isEqual:@"write"] ? args[@"bytes"] : nil;
209
+ if ([method isEqual:@"write"] && !data) { SparkInvalid(reject, @"Invalid process input"); return; }
142
210
  dispatch_async(process.inputQueue, ^{
143
- @try { if ([method isEqual:@"write"]) [process.input writeData:[args[@"text"] dataUsingEncoding:NSUTF8StringEncoding]]; else [process.input closeFile]; resolve(@"null"); }
144
- @catch (NSException *exception) { reject(@"E_PIPE", @"Process input is closed", nil); }
211
+ @try {
212
+ if ([method isEqual:@"write"]) {
213
+ if (process.inputClosed) { reject(@"E_CLOSED", @"Process input is closed", nil); return; }
214
+ [process.input writeData:data];
215
+ } else if (!process.inputClosed) { [process.input closeFile]; process.inputClosed = YES; }
216
+ resolve(binary ? NSNull.null : @"null");
217
+ } @catch (NSException *exception) { reject(@"E_CLOSED", @"Process input is closed", nil); }
145
218
  }); return;
146
219
  } else { SparkInvalid(reject, @"Unknown process operation"); return; }
147
220
  }
148
- resolve(@"null");
221
+ resolve(binary ? NSNull.null : @"null");
149
222
  });
150
223
  }
151
224
  - (void)invalidate { dispatch_async(dispatch_get_main_queue(), ^{ self.invalidated = YES; [self stopAll]; }); }
152
- - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params { return std::make_shared<facebook::react::NativeDesktopProcessesSpecJSI>(params); }
225
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params { auto invoker = params.jsInvoker; self.binaryEvent = ^(NSDictionary *event, dispatch_block_t delivered) { spark::binary::Emit(invoker, [event](spark::binary::jsi::Runtime &rt) { return spark::binary::Value(rt, event); }, [delivered] { if (delivered) delivered(); }); }; return std::make_shared<spark::binary::Module<facebook::react::NativeDesktopProcessesSpecJSI>>(params); }
153
226
  @end
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/spark-processes",
3
- "version": "0.0.1-next.2",
3
+ "version": "0.0.1-next.3",
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
  "NativeDesktopProcesses": "RNDesktopProcesses"
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.3"
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>("NativeDesktopProcesses");
3
+ export default TurboModuleRegistry.get<Spec>("NativeDesktopProcesses");