@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
@@ -11,23 +11,52 @@
11
11
  #include <atomic>
12
12
  #include <mutex>
13
13
  #include <set>
14
+ #include <functional>
14
15
  #include <winrt/Windows.Storage.Streams.h>
15
16
  namespace winrt::SparkAudio {
16
17
  namespace React = Microsoft::ReactNative;
17
18
  namespace Json = Windows::Data::Json;
18
- struct Entry { Windows::Media::Playback::MediaPlayer player{nullptr}; std::atomic<bool> ended{false}; std::atomic<bool> failed{false}; std::atomic<bool> loaded{false}; };
19
+ struct Entry {
20
+ Windows::Media::Playback::MediaPlayer player{nullptr};
21
+ std::atomic<bool> ended{false}, failed{false}, loaded{false}, observing{false}, closed{false};
22
+ React::ReactContext context;
23
+ std::string id;
24
+ std::mutex readinessMutex;
25
+ std::mutex playerMutex;
26
+ std::function<void(bool, bool)> readiness;
27
+ void FinishReadiness() {
28
+ std::function<void(bool, bool)> complete;
29
+ { std::lock_guard lock(readinessMutex); if (loaded || failed || closed) complete = std::move(readiness); }
30
+ if (complete) complete(loaded && !failed && !closed, closed);
31
+ }
32
+ void EmitStatus() {
33
+ if (!observing || closed) return;
34
+ std::lock_guard lock(playerMutex); if (closed) return;
35
+ auto session = player.PlaybackSession();
36
+ context.EmitJSEvent(L"RCTDeviceEventEmitter", L"sparkAudioStatus", React::JSValueObject{{"id", id}, {"value", React::JSValueObject{
37
+ {"volume", player.Volume()}, {"playing", session.PlaybackState() == Windows::Media::Playback::MediaPlaybackState::Playing},
38
+ {"currentTime", std::chrono::duration<double>(session.Position()).count()}, {"duration", std::chrono::duration<double>(session.NaturalDuration()).count()},
39
+ {"didJustFinish", ended.load()}, {"error", failed ? React::JSValue("Audio could not be decoded or loaded") : React::JSValue(nullptr)}
40
+ }}});
41
+ }
42
+ void Close() {
43
+ closed = true; observing = false; FinishReadiness();
44
+ std::lock_guard lock(playerMutex); player.Pause(); player.Close();
45
+ }
46
+ };
19
47
  struct MediaSession {
20
48
  Windows::Media::Playback::MediaPlayer player{nullptr};
21
49
  std::mutex mutex;
22
50
  std::set<std::wstring> allowed;
23
- Json::JsonArray commands;
51
+ React::ReactContext context;
52
+ std::string id;
24
53
  Json::JsonObject state;
25
54
  ~MediaSession() { if (player) { player.SystemMediaTransportControls().IsEnabled(false); player.Close(); } }
26
55
  void Command(hstring const &name, double position = -1) {
27
- std::lock_guard lock(mutex); if (!allowed.count(std::wstring(name)) || commands.Size() >= 128) return;
28
- Json::JsonObject value; value.SetNamedValue(L"command", Json::JsonValue::CreateStringValue(name));
29
- if (position >= 0) value.SetNamedValue(L"position", Json::JsonValue::CreateNumberValue(position));
30
- commands.Append(value);
56
+ std::lock_guard lock(mutex); if (!allowed.count(std::wstring(name))) return;
57
+ React::JSValueObject value{{"command", to_string(name)}};
58
+ if (position >= 0) value.emplace("position", position);
59
+ context.EmitJSEvent(L"RCTDeviceEventEmitter", L"sparkMediaCommand", React::JSValueObject{{"id", id}, {"value", std::move(value)}});
31
60
  }
32
61
  };
33
62
  inline void Metadata(Windows::Media::SystemMediaTransportControls const &controls, Json::JsonObject const &metadata) {
@@ -62,7 +91,7 @@ struct Players {
62
91
  std::map<std::string, std::shared_ptr<Entry>> values;
63
92
  std::string sessionID;
64
93
  std::shared_ptr<MediaSession> session;
65
- ~Players() { session.reset(); for (auto const &[id, entry] : values) { entry->player.Pause(); entry->player.Close(); } }
94
+ ~Players() { session.reset(); for (auto const &[id, entry] : values) entry->Close(); }
66
95
  };
67
96
  REACT_MODULE(SparkAudio, L"NativeSparkAudio")
68
97
  struct SparkAudio {
@@ -70,14 +99,14 @@ struct SparkAudio {
70
99
  std::shared_ptr<Players> players = std::make_shared<Players>();
71
100
  REACT_INIT(Initialize)
72
101
  void Initialize(React::ReactContext const &value) noexcept { context = value; }
73
- static fire_and_forget Invoke(std::shared_ptr<Players> players, std::string method, std::string encoded, React::ReactPromise<std::string> promise) {
102
+ static fire_and_forget Invoke(std::shared_ptr<Players> players, React::ReactContext context, std::string method, std::string encoded, React::ReactPromise<std::string> promise) {
74
103
  try {
75
104
  auto args = Json::JsonObject::Parse(to_hstring(encoded)); auto id = to_string(args.GetNamedString(L"id"));
76
105
  if (method.starts_with("session")) {
77
106
  if (method == "sessionCreate") {
78
107
  players->session.reset();
79
108
  for (auto const &[key, entry] : players->values) entry->player.SystemMediaTransportControls().IsEnabled(false);
80
- auto session = std::make_shared<MediaSession>(); session->player = Windows::Media::Playback::MediaPlayer();
109
+ auto session = std::make_shared<MediaSession>(); session->context = context; session->id = id; session->player = Windows::Media::Playback::MediaPlayer();
81
110
  session->player.CommandManager().IsEnabled(false);
82
111
  std::weak_ptr<MediaSession> weak = session; auto controls = session->player.SystemMediaTransportControls();
83
112
  controls.ButtonPressed([weak](auto const &, auto const &event) { if (auto current = weak.lock()) {
@@ -90,13 +119,11 @@ struct SparkAudio {
90
119
  players->session = session; players->sessionID = id;
91
120
  UpdateSession(session, args);
92
121
  } else {
122
+ if ((!players->session || players->sessionID != id) && method == "sessionRemove") { promise.Resolve("null"); co_return; }
93
123
  if (!players->session || players->sessionID != id) throw hresult_invalid_argument(L"Media session has been replaced");
94
124
  if (method == "sessionUpdate") UpdateSession(players->session, args);
95
125
  else if (method == "sessionRemove") { players->session.reset(); players->sessionID.clear(); }
96
- else if (method == "sessionCommands") {
97
- std::lock_guard lock(players->session->mutex);
98
- auto result = to_string(players->session->commands.Stringify()); players->session->commands.Clear(); promise.Resolve(result); co_return;
99
- } else throw hresult_invalid_argument(L"Unknown media session operation");
126
+ else throw hresult_invalid_argument(L"Unknown media session operation");
100
127
  }
101
128
  } else if (method == "create") {
102
129
  auto uri = args.GetNamedString(L"uri"); Windows::Media::Core::MediaSource source{nullptr};
@@ -107,23 +134,39 @@ struct SparkAudio {
107
134
  Windows::Media::Playback::MediaPlayer player; player.AutoPlay(false);
108
135
  auto controls = player.SystemMediaTransportControls(); controls.IsEnabled(!players->session); controls.IsPlayEnabled(true); controls.IsPauseEnabled(true);
109
136
  auto display = controls.DisplayUpdater(); display.Type(Windows::Media::MediaPlaybackType::Music); display.MusicProperties().Title(args.GetNamedString(L"title", L"Music")); display.Update();
110
- auto entry = std::make_shared<Entry>(); entry->player = player; std::weak_ptr<Entry> weak = entry;
111
- player.MediaEnded([weak](auto const &, auto const &) { if (auto entry = weak.lock()) entry->ended = true; });
112
- player.MediaFailed([weak](auto const &, auto const &) { if (auto entry = weak.lock()) entry->failed = true; });
113
- player.MediaOpened([weak](auto const &, auto const &) { if (auto entry = weak.lock()) entry->loaded = true; });
137
+ auto entry = std::make_shared<Entry>(); entry->player = player; entry->context = context; entry->id = id; std::weak_ptr<Entry> weak = entry;
138
+ player.MediaEnded([weak](auto const &, auto const &) { if (auto entry = weak.lock()) { entry->ended = true; entry->EmitStatus(); } });
139
+ player.MediaFailed([weak](auto const &, auto const &) { if (auto entry = weak.lock()) { entry->failed = true; entry->FinishReadiness(); entry->EmitStatus(); } });
140
+ player.MediaOpened([weak](auto const &, auto const &) { if (auto entry = weak.lock()) { entry->loaded = true; entry->FinishReadiness(); entry->EmitStatus(); } });
141
+ auto playback = player.PlaybackSession();
142
+ playback.PositionChanged([weak](auto const &, auto const &) { if (auto entry = weak.lock()) entry->EmitStatus(); });
143
+ playback.PlaybackStateChanged([weak](auto const &, auto const &) { if (auto entry = weak.lock()) entry->EmitStatus(); });
144
+ playback.NaturalDurationChanged([weak](auto const &, auto const &) { if (auto entry = weak.lock()) entry->EmitStatus(); });
145
+ player.VolumeChanged([weak](auto const &, auto const &) { if (auto entry = weak.lock()) entry->EmitStatus(); });
114
146
  player.Source(source);
115
147
  players->values.emplace(id, entry);
116
148
  } else {
117
149
  auto found = players->values.find(id);
150
+ if (found == players->values.end() && method == "remove") { promise.Resolve("null"); co_return; }
118
151
  if (found == players->values.end()) throw hresult_invalid_argument(L"Audio player is not available");
119
152
  auto entry = found->second; auto player = entry->player; auto session = player.PlaybackSession();
120
- if (method == "ready") { if (entry->failed) throw hresult_error(E_FAIL, L"Audio could not be decoded or loaded"); promise.Resolve(entry->loaded ? "true" : "false"); co_return; }
153
+ if (method == "awaitReady") {
154
+ { std::lock_guard lock(entry->readinessMutex);
155
+ if (entry->readiness) throw hresult_invalid_argument(L"Audio readiness is already pending");
156
+ entry->readiness = [promise](bool loaded, bool closed) {
157
+ if (loaded) promise.Resolve("true");
158
+ else promise.Reject(React::ReactError{closed ? "E_CLOSED" : "E_AUDIO", closed ? "Audio player was removed during loading" : "Audio could not be decoded or loaded"});
159
+ };
160
+ }
161
+ entry->FinishReadiness(); co_return;
162
+ }
121
163
  else if (method == "volume") { auto volume = args.GetNamedNumber(L"volume"); if (!std::isfinite(volume) || volume < 0 || volume > 1) throw hresult_invalid_argument(L"Invalid volume"); player.Volume(volume); }
122
164
  else if (method == "metadata") Metadata(player.SystemMediaTransportControls(), args.GetNamedObject(L"metadata"));
123
165
  else if (method == "play") { if (!players->session) player.SystemMediaTransportControls().IsEnabled(true); entry->ended = false; player.Play(); }
124
166
  else if (method == "pause") player.Pause();
125
167
  else if (method == "seek") { entry->ended = false; auto seconds = args.GetNamedNumber(L"seconds"); if (!std::isfinite(seconds) || seconds < 0) throw hresult_invalid_argument(L"Invalid seek position"); session.Position(std::chrono::duration_cast<Windows::Foundation::TimeSpan>(std::chrono::duration<double>(seconds))); }
126
- else if (method == "remove") { player.Pause(); player.Close(); players->values.erase(found); }
168
+ else if (method == "statusUpdates") { entry->observing = args.GetNamedBoolean(L"enabled"); entry->EmitStatus(); }
169
+ else if (method == "remove") { entry->Close(); players->values.erase(found); }
127
170
  else if (method == "status") {
128
171
  auto position = std::chrono::duration<double>(session.Position()).count(), duration = std::chrono::duration<double>(session.NaturalDuration()).count();
129
172
  Json::JsonObject status;
@@ -140,7 +183,11 @@ struct SparkAudio {
140
183
  }
141
184
  REACT_METHOD(call)
142
185
  void call(std::string method, std::string args, React::ReactPromise<std::string> promise) noexcept {
143
- context.UIDispatcher().Post([players = players, method, args, promise]() { Invoke(players, method, args, promise); });
186
+ context.UIDispatcher().Post([players = players, context = context, method, args, promise]() { Invoke(players, context, method, args, promise); });
144
187
  }
188
+ REACT_METHOD(addListener)
189
+ void addListener(std::string) noexcept {}
190
+ REACT_METHOD(removeListeners)
191
+ void removeListeners(double) noexcept {}
145
192
  };
146
193
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/spark-auth-session",
3
- "version": "0.0.1-next.2",
3
+ "version": "0.0.1-next.3",
4
4
  "license": "MIT",
5
5
  "main": "src/index",
6
6
  "react-native": "src/index",
@@ -8,8 +8,9 @@
8
8
  "src"
9
9
  ],
10
10
  "dependencies": {
11
- "@legendapp/spark-desktop-links": "0.0.1-next.2",
12
- "base64-js": "1.5.1"
11
+ "@legendapp/spark-desktop-links": "0.0.1-next.3",
12
+ "base64-js": "1.5.1",
13
+ "@legendapp/spark-desktop-app": "0.0.1-next.3"
13
14
  },
14
15
  "peerDependencies": {
15
16
  "react-native": "0.81.6",
@@ -1,68 +1,86 @@
1
+ import { SparkError, asyncRegistration, nativeError } from "@legendapp/spark-desktop-app/src/contracts";
1
2
  import { authorize, callbackMatches, timeout, validateRedirect, type AuthSession, type AuthSessionOptions, type AuthSessionResult } from "./types";
2
3
  export type AuthTransport = {
3
4
  randomState(): Promise<string>;
4
5
  prepare(id: string, port: number, path: string): Promise<string>;
5
- poll(id: string): Promise<string[]>;
6
+ subscribeLoopback(id: string, listener: (url: string) => void): Promise<{ remove(): void }>;
6
7
  close(id: string): Promise<void>;
7
8
  subscribe(listener: (url: string) => void): Promise<{ remove(): void }>;
8
9
  open(url: string): Promise<unknown>;
9
10
  };
10
11
  export function authSessions(transport: AuthTransport) {
11
12
  let busy = false, sequence = 0;
13
+ let orphan: { remove(): Promise<void> } | undefined;
12
14
  return async function createAuthSession(options: AuthSessionOptions = {}): Promise<AuthSession> {
13
15
  const duration = timeout(options);
14
- if (busy) throw Object.assign(new Error("An authentication session is already active"), { code: "E_BUSY" });
15
- if (options.signal?.aborted) throw Object.assign(new Error("Authentication was cancelled"), { name: "AbortError" });
16
+ options = { ...options };
17
+ const requestedRedirect = options.redirectUri;
18
+ let port = 0, path = "/auth/callback";
19
+ const loopback = !requestedRedirect || validateRedirect(requestedRedirect).protocol === "http:";
20
+ if (requestedRedirect) {
21
+ const uri = validateRedirect(requestedRedirect);
22
+ if (loopback) {
23
+ if (uri.hostname !== "127.0.0.1" || !uri.port || !/^\/[a-zA-Z0-9/_-]*$/.test(uri.pathname)) throw new SparkError("E_INVALID_ARGUMENT", "Desktop HTTP callbacks require 127.0.0.1, an explicit port, and a plain path");
24
+ port = Number(uri.port); path = uri.pathname;
25
+ } else if (uri.protocol === "https:") throw new SparkError("E_UNSUPPORTED_OPTION", "Desktop callbacks require a loopback URI or registered application scheme");
26
+ }
27
+ if (orphan) { const previous = orphan; await previous.remove(); if (orphan === previous) orphan = undefined; }
28
+ if (busy) throw new SparkError("E_BUSY", "An authentication session is already active");
29
+ if (options.signal?.aborted) throw new SparkError("E_ABORTED", "Authentication was cancelled");
16
30
  busy = true;
17
- const id = `auth-${Date.now()}-${++sequence}`;
18
- let loopback = false, closed = false, opened = false, subscription: { remove(): void } | undefined;
19
- let timer: ReturnType<typeof setTimeout> | undefined, polling: ReturnType<typeof setTimeout> | undefined;
20
- let resolve!: (result: AuthSessionResult) => void;
21
- const result = new Promise<AuthSessionResult>(done => { resolve = done; });
22
- let closing: Promise<void> | undefined, failure: unknown;
23
- const cleanup = () => closing ??= (async () => {
24
- closed = true; clearTimeout(timer); clearTimeout(polling); subscription?.remove(); options.signal?.removeEventListener("abort", cancel);
25
- try { if (loopback) await transport.close(id); } finally { busy = false; }
26
- })();
27
- const finish = async (value: AuthSessionResult) => { if (closed) return; try { await cleanup(); } catch (error) { failure = error; } finally { resolve(value); } };
28
- const cancel = () => { void finish({ type: "cancel" }).catch(console.error); };
31
+ const started = Date.now(), id = `auth-${started}-${++sequence}`;
32
+ let allocated = false, closed = false, opened = false, subscription: { remove(): void } | undefined;
33
+ let timer: ReturnType<typeof setTimeout> | undefined;
34
+ type Outcome = { value: AuthSessionResult; error?: unknown };
35
+ let resolve!: (outcome: Outcome) => void;
36
+ const result = new Promise<Outcome>(done => { resolve = done; });
37
+ const readResult = async () => { const outcome = await result; if (outcome.error) throw outcome.error; return outcome.value; };
38
+ const cleanup = asyncRegistration(() => {
39
+ closed = true; clearTimeout(timer); options.signal?.removeEventListener("abort", cancel);
40
+ }, async () => {
41
+ try {
42
+ subscription?.remove(); subscription = undefined;
43
+ if (allocated) await transport.close(id);
44
+ busy = false;
45
+ } catch (error) { throw nativeError(error); }
46
+ });
47
+ const finish = async (value: AuthSessionResult, error?: unknown) => {
48
+ if (closed) return;
49
+ try { await cleanup.remove(); } catch (cause) { error ??= cause; }
50
+ resolve({ value, error: error === undefined ? undefined : nativeError(error) });
51
+ };
52
+ const cancel = () => { void finish({ type: "cancel" }); };
29
53
  try {
30
54
  const state = await transport.randomState();
31
- let redirectUri = options.redirectUri;
32
- if (!redirectUri) { loopback = true; redirectUri = await transport.prepare(id, 0, "/auth/callback"); }
33
- else {
34
- const uri = validateRedirect(redirectUri);
35
- if (uri.protocol === "http:") {
36
- if (uri.hostname !== "127.0.0.1" || !uri.port || !/^\/[a-zA-Z0-9/_-]*$/.test(uri.pathname)) throw new TypeError("Desktop HTTP callbacks require 127.0.0.1, an explicit port, and a plain path");
37
- loopback = true; redirectUri = await transport.prepare(id, Number(uri.port), uri.pathname);
38
- } else if (uri.protocol === "https:") throw new TypeError("Desktop callbacks require a loopback URI or registered application scheme");
39
- }
40
- const redirect = redirectUri;
41
- const received = (url: string) => { if (opened && !closed && callbackMatches(url, redirect, state)) void finish({ type: "success", url }).catch(console.error); };
42
- if (!loopback) subscription = await transport.subscribe(received);
43
- async function poll() {
44
- try { for (const url of await transport.poll(id)) received(url); }
45
- catch (error) { if (!closed) { failure = error; await finish({ type: "dismiss" }); } }
46
- finally { if (!closed) polling = setTimeout(poll, 100); }
47
- }
55
+ let redirect = requestedRedirect!;
56
+ const received = (url: string) => { if (opened && !closed && callbackMatches(url, redirect, state)) void finish({ type: "success", url }); };
57
+ if (loopback) {
58
+ subscription = await transport.subscribeLoopback(id, received);
59
+ allocated = true; redirect = await transport.prepare(id, port, path);
60
+ } else subscription = await transport.subscribe(received);
48
61
  options.signal?.addEventListener("abort", cancel, { once: true });
49
- timer = setTimeout(() => { void finish({ type: "timeout" }).catch(console.error); }, duration);
62
+ const remaining = duration - (Date.now() - started);
50
63
  if (options.signal?.aborted) cancel();
64
+ else if (remaining <= 0) void finish({ type: "timeout" });
65
+ else timer = setTimeout(() => { void finish({ type: "timeout" }); }, remaining);
51
66
  return {
52
67
  state, redirectUri: redirect,
53
68
  async open(url) {
54
- if (opened) throw new Error("Auth sessions can only be opened once");
55
- if (closed) { const value = await result; if (failure) throw failure; return value; }
69
+ if (opened) throw new SparkError("E_CLOSED", "Auth sessions can only be opened once");
70
+ if (closed) return readResult();
56
71
  try {
57
72
  authorize(url, state, redirect); opened = true;
58
- if (loopback) void poll();
59
- // Don't let a slow browser launch suppress cancellation or a received callback.
60
- const launch = transport.open(url).then(() => result);
61
- const value = await Promise.race([result, launch]); if (failure) throw failure; return value;
62
- } catch (error) { try { await cleanup(); } finally { resolve({ type: "dismiss" }); } throw error; }
73
+ // A slow browser launch must not suppress cancellation or callback delivery.
74
+ const launch = transport.open(url).catch(async error => { await finish({ type: "dismiss" }, error); });
75
+ await Promise.race([result, launch.then(() => result)]);
76
+ return readResult();
77
+ } catch (error) { await finish({ type: "dismiss" }, error); return readResult(); }
63
78
  },
64
- async dismiss() { await finish({ type: "dismiss" }); if (failure) throw failure; },
79
+ async dismiss() { if (!closed) { await finish({ type: "dismiss" }); await readResult(); } else await cleanup.remove(); },
65
80
  };
66
- } catch (error) { try { await cleanup(); } finally { resolve({ type: "dismiss" }); } throw error; }
81
+ } catch (error) {
82
+ try { await cleanup.remove(); } catch { orphan = cleanup; }
83
+ throw nativeError(error);
84
+ }
67
85
  };
68
86
  }
@@ -1,22 +1,38 @@
1
- import Native from "@legendapp/spark-desktop-links/src/NativeDesktopLinks";
2
- import { onOpen, openURL } from "@legendapp/spark-desktop-links";
1
+ import { SparkError } from "@legendapp/spark-desktop-app/src/contracts";
2
+ import { callLinks, linksCommand } from "@legendapp/spark-desktop-links/src/native";
3
+ import { openURL } from "@legendapp/spark-desktop-links";
4
+ import { subscribeToOpenRequests } from "@legendapp/spark-desktop-app/src/open-requests";
5
+ import { onDesktopEvent } from "@legendapp/spark-desktop-app/src/events";
6
+ import { toByteArray, fromByteArray } from "base64-js";
3
7
  import { authSessions } from "./core";
4
- import { validateCount, validateDigest } from "./types";
8
+ import { validateCount, validateDigest, type CompleteAuthSessionResult } from "./types";
5
9
  export type * from "./types";
6
- async function call<T>(method: string, args: object): Promise<T> { return JSON.parse(await Native.call(method, JSON.stringify(args))); }
7
10
  export async function getRandomBytesAsync(count: number): Promise<Uint8Array> {
8
- validateCount(count); const base64 = await call<string>("cryptoRandom", { count });
9
- const { toByteArray } = await import("base64-js"); return toByteArray(base64);
11
+ validateCount(count);
12
+ const base64 = await callLinks("cryptoRandom", { count }, (value): value is string => typeof value === "string" && value.length === Math.ceil(count / 3) * 4 && /^[A-Za-z0-9+/]*={0,2}$/.test(value));
13
+ const bytes = toByteArray(base64);
14
+ if (bytes.length !== count || fromByteArray(bytes) !== base64) throw new SparkError("E_INVALID_DATA", "Native random bytes do not match the requested count");
15
+ return bytes;
10
16
  }
11
17
  export async function digestStringAsync(algorithm: "SHA-256", value: string): Promise<string> {
12
- validateDigest(algorithm, value); return call("cryptoDigest", { value });
18
+ validateDigest(algorithm, value);
19
+ return callLinks("cryptoDigest", { value }, (result): result is string => typeof result === "string" && /^[0-9a-f]{64}$/.test(result));
13
20
  }
14
21
  export const createAuthSession = authSessions({
15
22
  randomState: async () => Array.from(await getRandomBytesAsync(32), byte => byte.toString(16).padStart(2, "0")).join(""),
16
- prepare: (id, port, path) => call("authPrepare", { id, port, path }),
17
- poll: id => call("authPoll", { id }), close: id => call("authClose", { id }),
18
- subscribe: listener => onOpen(event => { if (event.type === "openURL") listener(event.url); }),
23
+ prepare: (id, port, path) => callLinks("authPrepare", { id, port, path }, (value): value is string => {
24
+ if (typeof value !== "string") return false;
25
+ try {
26
+ const url = new URL(value);
27
+ return url.protocol === "http:" && url.hostname === "127.0.0.1" && Number(url.port) > 0 && (!port || Number(url.port) === port) && url.pathname === path && !url.username && !url.password && !url.search && !url.hash;
28
+ } catch { return false; }
29
+ }),
30
+ subscribeLoopback: async (id, listener) => onDesktopEvent(event => {
31
+ if (typeof event.url === "string") listener(event.url);
32
+ }, { types: ["authRedirect"], target: { field: "id", value: id } }),
33
+ close: id => linksCommand("authClose", { id }),
34
+ subscribe: listener => subscribeToOpenRequests(event => { if (event.type === "url") listener(event.url); }),
19
35
  open: openURL,
20
36
  });
21
- /** Desktop receives callbacks natively; no popup-completion step is needed. */
22
- export function maybeCompleteAuthSession(): void {}
37
+ /** Expo-compatible completion result: native hosts do not use a browser popup. */
38
+ export function maybeCompleteAuthSession(): CompleteAuthSessionResult { return { type: "failed", message: "Not supported on this platform" }; }
@@ -1,52 +1,108 @@
1
1
  import * as Browser from "expo-web-browser";
2
2
  import * as Crypto from "expo-crypto";
3
- import { authorize, callbackMatches, timeout, validateRedirect, validateCount, validateDigest, type AuthSession, type AuthSessionOptions, type AuthSessionResult } from "./types";
3
+ import { AppState, Linking, Platform } from "react-native";
4
+ import { SparkError, asyncRegistration, invokeNative, nativeError, type Subscription } from "@legendapp/spark-desktop-app/src/contracts";
5
+ import { authorize, callbackMatches, timeout, validateRedirect, validateCount, validateDigest, type CompleteAuthSessionResult, type AuthSession, type AuthSessionOptions, type AuthSessionResult } from "./types";
4
6
  export type * from "./types";
5
- export function getRandomBytesAsync(count: number) { validateCount(count); return Crypto.getRandomBytesAsync(count); }
6
- export function digestStringAsync(algorithm: "SHA-256", value: string) { validateDigest(algorithm, value); return Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, value); }
7
+ export async function getRandomBytesAsync(count: number): Promise<Uint8Array> {
8
+ validateCount(count);
9
+ const bytes = await invokeNative(() => Crypto.getRandomBytesAsync(count));
10
+ if (!(bytes instanceof Uint8Array) || bytes.length !== count) throw new SparkError("E_INVALID_DATA", "Random bytes do not match the requested count");
11
+ return bytes;
12
+ }
13
+ export async function digestStringAsync(algorithm: "SHA-256", value: string): Promise<string> {
14
+ validateDigest(algorithm, value);
15
+ const digest = await invokeNative(() => Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, value));
16
+ if (typeof digest !== "string" || !/^[0-9a-f]{64}$/.test(digest)) throw new SparkError("E_INVALID_DATA", "Invalid SHA-256 response");
17
+ return digest;
18
+ }
7
19
  let busy = false;
8
- /** On web, invoke this on the redirect page so Expo can complete its popup session. */
9
- export const maybeCompleteAuthSession = Browser.maybeCompleteAuthSession;
20
+ /** Call on the web redirect page. Native hosts return Expo's unsupported result. */
21
+ export function maybeCompleteAuthSession(): CompleteAuthSessionResult {
22
+ try {
23
+ const result = Browser.maybeCompleteAuthSession();
24
+ if (!result || (result.type !== "success" && result.type !== "failed") || typeof result.message !== "string") throw new SparkError("E_INVALID_DATA", "Invalid popup completion response");
25
+ return { type: result.type, message: result.message };
26
+ } catch (error) { throw nativeError(error); }
27
+ }
10
28
  export async function createAuthSession(options: AuthSessionOptions = {}): Promise<AuthSession> {
11
29
  const duration = timeout(options);
12
- if (!options.redirectUri) throw new TypeError("Mobile/web authentication requires an explicit registered redirectUri");
30
+ options = { ...options };
31
+ if (!options.redirectUri) throw new SparkError("E_INVALID_ARGUMENT", "Mobile/web authentication requires an explicit registered redirectUri");
13
32
  const redirectUri = options.redirectUri; validateRedirect(redirectUri);
14
- if (busy) throw Object.assign(new Error("An authentication session is already active"), { code: "E_BUSY" });
15
- if (options.signal?.aborted) throw Object.assign(new Error("Authentication was cancelled"), { name: "AbortError" });
33
+ if (busy) throw new SparkError("E_BUSY", "An authentication session is already active");
34
+ if (options.signal?.aborted) throw new SparkError("E_ABORTED", "Authentication was cancelled");
16
35
  busy = true;
17
- try {
18
- const state = Array.from(await getRandomBytesAsync(32), byte => byte.toString(16).padStart(2, "0")).join("");
19
- let closed = false, opened = false;
20
- let resolve!: (value: AuthSessionResult) => void;
21
- const result = new Promise<AuthSessionResult>(done => { resolve = done; });
22
- const finish = (value: AuthSessionResult) => {
23
- if (closed) return; closed = true; clearTimeout(timer); options.signal?.removeEventListener("abort", cancel); busy = false;
24
- if (opened) { try { Browser.dismissAuthSession(); } catch { /* Android custom tabs may remain visible. */ } }
25
- resolve(value);
26
- };
27
- const cancel = () => finish({ type: "cancel" });
28
- const timer = setTimeout(() => finish({ type: "timeout" }), duration);
29
- options.signal?.addEventListener("abort", cancel, { once: true });
30
- if (options.signal?.aborted) cancel();
31
- return {
32
- state, redirectUri,
33
- async open(url) {
34
- if (opened) throw new Error("Auth sessions can only be opened once");
35
- if (closed) return result;
36
- try {
37
- authorize(url, state, redirectUri); opened = true;
38
- // Call synchronously within the user's gesture on web; do not await preparation here.
39
- const browser = Browser.openAuthSessionAsync(url, redirectUri).then(value => {
36
+ const started = Date.now();
37
+ let state: string;
38
+ try { state = Array.from(await getRandomBytesAsync(32), byte => byte.toString(16).padStart(2, "0")).join(""); }
39
+ catch (error) { busy = false; throw error; }
40
+ let closed = false, opened = false, browserFinished = false;
41
+ let timer: ReturnType<typeof setTimeout> | undefined;
42
+ let linking: Subscription | undefined, appState: Subscription | undefined;
43
+ type Outcome = { value: AuthSessionResult; error?: unknown };
44
+ let resolve!: (value: Outcome) => void;
45
+ const result = new Promise<Outcome>(done => { resolve = done; });
46
+ const readResult = async () => { const outcome = await result; if (outcome.error) throw outcome.error; return outcome.value; };
47
+ const cleanup = asyncRegistration(() => {
48
+ closed = true; clearTimeout(timer); options.signal?.removeEventListener("abort", cancel);
49
+ }, async () => {
50
+ try {
51
+ linking?.remove(); linking = undefined; appState?.remove(); appState = undefined;
52
+ // Android cannot close a Custom Tab. Spark owns and removes its callback observers.
53
+ if (opened && !browserFinished && Platform.OS !== "android") Browser.dismissAuthSession();
54
+ busy = false;
55
+ } catch (error) { throw nativeError(error); }
56
+ });
57
+ const finish = async (value: AuthSessionResult, error?: unknown) => {
58
+ if (closed) return;
59
+ try { await cleanup.remove(); } catch (cause) { error ??= cause; }
60
+ resolve({ value, error: error === undefined ? undefined : nativeError(error) });
61
+ };
62
+ const cancel = () => { void finish({ type: "cancel" }); };
63
+ options.signal?.addEventListener("abort", cancel, { once: true });
64
+ const remaining = duration - (Date.now() - started);
65
+ if (options.signal?.aborted) cancel();
66
+ else if (remaining <= 0) void finish({ type: "timeout" });
67
+ else timer = setTimeout(() => { void finish({ type: "timeout" }); }, remaining);
68
+ return {
69
+ state, redirectUri,
70
+ async open(url) {
71
+ if (opened) throw new SparkError("E_CLOSED", "Auth sessions can only be opened once");
72
+ if (closed) return readResult();
73
+ try {
74
+ authorize(url, state, redirectUri); opened = true;
75
+ if (Platform.OS === "android") {
76
+ // Expo's Android auth polyfill cannot cancel its pending redirect observer.
77
+ // Own these observers so cancellation never prevents the next session.
78
+ linking = Linking.addEventListener("url", event => {
79
+ if (callbackMatches(event.url, redirectUri, state)) void finish({ type: "success", url: event.url });
80
+ });
81
+ let departed = AppState.currentState !== null && AppState.currentState !== "active";
82
+ appState = AppState.addEventListener("change", value => {
83
+ if (value !== "active") departed = true;
84
+ else if (departed) void finish({ type: "dismiss" });
85
+ });
86
+ void Browser.openBrowserAsync(url).then(value => {
87
+ if (value.type === "cancel" || value.type === "dismiss") return finish({ type: value.type });
88
+ if (value.type !== "opened") return finish({ type: "dismiss" }, new SparkError("E_INVALID_DATA", "Invalid browser launch response"));
89
+ }).catch(error => finish({ type: "dismiss" }, error));
90
+ } else {
91
+ // Keep this call synchronous within the user's gesture on web.
92
+ void Browser.openAuthSessionAsync(url, redirectUri).then(value => {
93
+ browserFinished = true;
40
94
  if (closed) return;
41
95
  if (value.type === "success") {
42
- if (!callbackMatches(value.url, redirectUri, state)) throw new Error("Authentication callback state or redirect did not match");
43
- finish({ type: "success", url: value.url });
44
- } else finish({ type: value.type === "cancel" ? "cancel" : "dismiss" });
45
- });
46
- return await Promise.race([result, browser.then(() => result)]);
47
- } catch (error) { finish({ type: "dismiss" }); throw error; }
48
- },
49
- async dismiss() { finish({ type: "dismiss" }); },
50
- };
51
- } catch (error) { busy = false; throw error; }
96
+ if (!callbackMatches(value.url, redirectUri, state)) return finish({ type: "dismiss" }, new SparkError("E_INVALID_DATA", "Authentication callback state or redirect did not match"));
97
+ return finish({ type: "success", url: value.url });
98
+ }
99
+ if (value.type === "cancel" || value.type === "dismiss") return finish({ type: value.type });
100
+ return finish({ type: "dismiss" }, new SparkError("E_INVALID_DATA", "Invalid authentication browser response"));
101
+ }).catch(error => { browserFinished = true; return finish({ type: "dismiss" }, error); });
102
+ }
103
+ } catch (error) { await finish({ type: "dismiss" }, error); }
104
+ return readResult();
105
+ },
106
+ async dismiss() { if (!closed) { await finish({ type: "dismiss" }); await readResult(); } else await cleanup.remove(); },
107
+ };
52
108
  }
@@ -1,3 +1,4 @@
1
+ import { SparkError } from "@legendapp/spark-desktop-app/src/contracts";
1
2
  export type AuthSessionResult = { type: "success"; url: string } | { type: "cancel" | "dismiss" | "timeout" };
2
3
  export type AuthSessionOptions = { redirectUri?: string; timeoutMs?: number; signal?: AbortSignal };
3
4
  export interface AuthSession {
@@ -7,8 +8,12 @@ export interface AuthSession {
7
8
  dismiss(): Promise<void>;
8
9
  }
9
10
  export function timeout(options: AuthSessionOptions) {
11
+ if (!options || typeof options !== "object" || Array.isArray(options)) throw new SparkError("E_INVALID_ARGUMENT", "Expected authentication options");
12
+ for (const key of Object.keys(options)) if (!["redirectUri", "timeoutMs", "signal"].includes(key)) throw new SparkError("E_UNSUPPORTED_OPTION", `Unknown authentication option: ${key}`);
13
+ if (options.redirectUri !== undefined && (typeof options.redirectUri !== "string" || !options.redirectUri)) throw new SparkError("E_INVALID_ARGUMENT", "Expected a redirect URI");
14
+ if (options.signal !== undefined && (!options.signal || typeof options.signal.aborted !== "boolean" || typeof options.signal.addEventListener !== "function" || typeof options.signal.removeEventListener !== "function")) throw new SparkError("E_INVALID_ARGUMENT", "Expected an AbortSignal");
10
15
  const value = options.timeoutMs ?? 120_000;
11
- if (!Number.isInteger(value) || value < 1 || value > 600_000) throw new TypeError("Auth timeout must be 1–600000 milliseconds");
16
+ if (!Number.isInteger(value) || value < 1 || value > 600_000) throw new SparkError("E_INVALID_ARGUMENT", "Auth timeout must be 1–600000 milliseconds");
12
17
  return value;
13
18
  }
14
19
  export function callbackMatches(value: string, redirectUri: string, state: string) {
@@ -20,19 +25,25 @@ export function callbackMatches(value: string, redirectUri: string, state: strin
20
25
  } catch { return false; }
21
26
  }
22
27
  export function authorize(value: string, state: string, redirectUri: string) {
23
- const url = new URL(value);
24
- if ((url.protocol !== "https:" && !(url.protocol === "http:" && url.hostname === "127.0.0.1")) || url.username || url.password) throw new TypeError("Authorization requires HTTPS (or a local loopback test server)");
28
+ const url = parseURL(value);
29
+ if ((url.protocol !== "https:" && !(url.protocol === "http:" && url.hostname === "127.0.0.1")) || url.username || url.password) throw new SparkError("E_INVALID_ARGUMENT", "Authorization requires HTTPS (or a local loopback test server)");
25
30
  const states = url.searchParams.getAll("state");
26
- if (states.length !== 1 || states[0] !== state) throw new TypeError("Authorization URL must use this session's state");
31
+ if (states.length !== 1 || states[0] !== state) throw new SparkError("E_INVALID_ARGUMENT", "Authorization URL must use this session's state");
27
32
  const redirects = url.searchParams.getAll("redirect_uri");
28
- if (redirects.length > 1 || (redirects.length === 1 && redirects[0] !== redirectUri)) throw new TypeError("Authorization redirect_uri must match the prepared session");
33
+ if (redirects.length > 1 || (redirects.length === 1 && redirects[0] !== redirectUri)) throw new SparkError("E_INVALID_ARGUMENT", "Authorization redirect_uri must match the prepared session");
29
34
  }
30
35
  export function validateRedirect(uri: string) {
31
- const url = new URL(uri);
32
- if (url.username || url.password || url.search || url.hash || ["javascript:", "data:", "file:", "about:"].includes(url.protocol)) throw new TypeError("Invalid authentication redirect URI");
36
+ const url = parseURL(uri);
37
+ if (url.username || url.password || url.search || url.hash || ["javascript:", "data:", "file:", "about:"].includes(url.protocol)) throw new SparkError("E_INVALID_ARGUMENT", "Invalid authentication redirect URI");
33
38
  return url;
34
39
  }
35
- export function validateCount(count: number) { if (!Number.isInteger(count) || count < 1 || count > 1024) throw new TypeError("Random byte count must be 1–1024"); }
40
+ export function validateCount(count: number) { if (!Number.isInteger(count) || count < 1 || count > 1024) throw new SparkError("E_INVALID_ARGUMENT", "Random byte count must be 1–1024"); }
36
41
  export function validateDigest(algorithm: string, value: string) {
37
- if (algorithm !== "SHA-256" || typeof value !== "string" || value.length > 262144) throw new TypeError("Expected SHA-256 and at most 262144 characters");
42
+ if (algorithm !== "SHA-256" || typeof value !== "string" || value.length > 262144) throw new SparkError("E_INVALID_ARGUMENT", "Expected SHA-256 and at most 262144 characters");
38
43
  }
44
+
45
+ function parseURL(value: string): URL {
46
+ if (typeof value !== "string" || !value || value.trim() !== value) throw new SparkError("E_INVALID_ARGUMENT", "Expected an absolute URL");
47
+ try { return new URL(value); } catch (cause) { throw new SparkError("E_INVALID_ARGUMENT", "Expected an absolute URL", { cause }); }
48
+ }
49
+ export type CompleteAuthSessionResult = { type: "success" | "failed"; message: string };