@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.
- package/LICENSE +21 -0
- package/README.md +10 -4
- package/ai-codex.ts +1 -0
- package/ai.ts +1 -0
- package/app-documents.ts +1 -0
- package/config-plugin.d.cts +2 -0
- package/contracts.ts +2 -0
- package/diagnostics.ts +1 -0
- package/expo-config.d.cts +1 -0
- package/metro.cjs +4 -1
- package/metro.d.cts +12 -0
- package/native.cjs +3 -1
- package/native.d.cts +6 -0
- package/package.json +82 -14
- package/schema.json +276 -58
- package/settings-observable.ts +1 -0
- package/settings-window.ts +1 -0
- package/shortcuts-commands.ts +1 -0
- package/shortcuts-keyboard.ts +3 -0
- package/ui-classnames.ts +1 -0
- package/ui-glass.ts +1 -0
- package/ui-search.ts +1 -0
- package/ui-sidebar.ts +1 -0
- package/ui-split-view.ts +1 -0
- package/ui-symbol.ts +1 -0
- package/vendor/node_modules/@legendapp/spark-ai/package.json +19 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/availability.ts +17 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/index.ts +15 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/invocations.ts +34 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/output.ts +62 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/run.ts +12 -0
- package/vendor/node_modules/@legendapp/spark-ai/src/types.ts +16 -0
- package/vendor/node_modules/@legendapp/spark-audio/macos/RNSparkAudio.h +2 -1
- package/vendor/node_modules/@legendapp/spark-audio/macos/RNSparkAudio.mm +126 -24
- package/vendor/node_modules/@legendapp/spark-audio/package.json +5 -2
- package/vendor/node_modules/@legendapp/spark-audio/src/NativeSparkAudio.ts +5 -1
- package/vendor/node_modules/@legendapp/spark-audio/src/desktop.ts +36 -32
- package/vendor/node_modules/@legendapp/spark-audio/src/events.ts +11 -0
- package/vendor/node_modules/@legendapp/spark-audio/src/hooks.ts +65 -0
- package/vendor/node_modules/@legendapp/spark-audio/src/index.web.ts +92 -33
- package/vendor/node_modules/@legendapp/spark-audio/src/media-session-unavailable.ts +6 -4
- package/vendor/node_modules/@legendapp/spark-audio/src/media-session.ts +52 -19
- package/vendor/node_modules/@legendapp/spark-audio/src/media-session.web.ts +18 -10
- package/vendor/node_modules/@legendapp/spark-audio/src/media-types.ts +40 -13
- package/vendor/node_modules/@legendapp/spark-audio/src/mobile.ts +40 -32
- package/vendor/node_modules/@legendapp/spark-audio/src/player.ts +97 -0
- package/vendor/node_modules/@legendapp/spark-audio/src/status-listeners.ts +21 -15
- package/vendor/node_modules/@legendapp/spark-audio/src/types.ts +9 -4
- package/vendor/node_modules/@legendapp/spark-audio/windows/SparkAudio/SparkAudio.h +67 -20
- package/vendor/node_modules/@legendapp/spark-auth-session/package.json +4 -3
- package/vendor/node_modules/@legendapp/spark-auth-session/src/core.ts +60 -42
- package/vendor/node_modules/@legendapp/spark-auth-session/src/index.ts +28 -12
- package/vendor/node_modules/@legendapp/spark-auth-session/src/mobile.ts +97 -41
- package/vendor/node_modules/@legendapp/spark-auth-session/src/types.ts +20 -9
- package/vendor/node_modules/@legendapp/spark-cli/dist/add-desktop.js +50 -7
- package/vendor/node_modules/@legendapp/spark-cli/dist/build-mode.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/build.js +83 -48
- package/vendor/node_modules/@legendapp/spark-cli/dist/cli-arguments.js +60 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/commands.js +7 -2
- package/vendor/node_modules/@legendapp/spark-cli/dist/create.js +31 -5
- package/vendor/node_modules/@legendapp/spark-cli/dist/credentials.js +69 -23
- package/vendor/node_modules/@legendapp/spark-cli/dist/dev-command.js +12 -3
- package/vendor/node_modules/@legendapp/spark-cli/dist/dev.js +12 -9
- package/vendor/node_modules/@legendapp/spark-cli/dist/examples.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/expo-dev-patch.cjs +4 -2
- package/vendor/node_modules/@legendapp/spark-cli/dist/expo-metro.cjs +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/expo-node.js +3 -3
- package/vendor/node_modules/@legendapp/spark-cli/dist/index.js +10 -38
- package/vendor/node_modules/@legendapp/spark-cli/dist/local.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/metro.cjs +26 -5
- package/vendor/node_modules/@legendapp/spark-cli/dist/native-metadata.js +26 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/native-patch-preflight.js +84 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/native-preparation.js +88 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/notary-profiles.js +75 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/package.js +4 -2
- package/vendor/node_modules/@legendapp/spark-cli/dist/platform.js +16 -2
- package/vendor/node_modules/@legendapp/spark-cli/dist/project.js +66 -13
- package/vendor/node_modules/@legendapp/spark-cli/dist/release.js +84 -4
- package/vendor/node_modules/@legendapp/spark-cli/dist/release.json +23 -18
- package/vendor/node_modules/@legendapp/spark-cli/dist/runner-install.js +145 -31
- package/vendor/node_modules/@legendapp/spark-cli/dist/signing.js +4 -3
- package/vendor/node_modules/@legendapp/spark-cli/dist/updates.js +41 -4
- package/vendor/node_modules/@legendapp/spark-cli/dist/windows.js +4 -1
- package/vendor/node_modules/@legendapp/spark-cli/package.json +7 -6
- package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/App.tsx +6 -12
- package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/desktop.config.json +5 -3
- package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/package.json +11 -9
- package/vendor/node_modules/@legendapp/spark-cli/templates/diff-lite/Git.macos.tsx +2 -2
- package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/Integration.macos.tsx +23 -21
- package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/Integration.windows.tsx +23 -21
- package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/io.desktop.ts +11 -8
- package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/io.macos.ts +3 -3
- package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/Lifecycle.desktop.tsx +8 -14
- package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/OpenFiles.desktop.tsx +5 -10
- package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/io.desktop.ts +11 -8
- package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/io.macos.ts +3 -3
- package/vendor/node_modules/@legendapp/spark-cli/templates/music-lite/assets.desktop.ts +2 -2
- package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/Activation.desktop.tsx +2 -2
- package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/Desktop.desktop.ts +37 -15
- package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/session.ts +5 -6
- package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/window-session.ts +5 -4
- package/vendor/node_modules/@legendapp/spark-cli/templates/universal/desktop.config.json +5 -3
- package/vendor/node_modules/@legendapp/spark-cli/templates/universal/metro.config.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/templates/universal/package.json +13 -11
- package/vendor/node_modules/@legendapp/spark-cli/templates/universal/react-native.config.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/templates/windows/package.json +13 -11
- package/vendor/node_modules/@legendapp/spark-cli/templates/windows/react-native.config.js +1 -1
- package/vendor/node_modules/@legendapp/spark-clipboard/macos/RNDesktopClipboard.mm +10 -16
- package/vendor/node_modules/@legendapp/spark-clipboard/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-clipboard/src/NativeDesktopClipboard.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-clipboard/src/desktop.ts +55 -31
- package/vendor/node_modules/@legendapp/spark-clipboard/src/expo.ts +21 -11
- package/vendor/node_modules/@legendapp/spark-clipboard/src/options.ts +8 -0
- package/vendor/node_modules/@legendapp/spark-clipboard/src/types.ts +10 -0
- package/vendor/node_modules/@legendapp/spark-clipboard/windows/SparkClipboard/SparkClipboard.h +38 -15
- package/vendor/node_modules/@legendapp/spark-clipboard/windows/SparkClipboard/SparkClipboard.vcxproj +2 -0
- package/vendor/node_modules/@legendapp/spark-codex/RNCodex.podspec +22 -0
- package/vendor/node_modules/@legendapp/spark-codex/cpp/HybridCodexAppServer.hpp +23 -0
- package/vendor/node_modules/@legendapp/spark-codex/cpp/HybridCodexAppServer.mm +1529 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitro.json +21 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/.gitattributes +1 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex+autolinking.rb +62 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Bridge.cpp +17 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Bridge.hpp +27 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Umbrella.hpp +38 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodexAutolinking.mm +35 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodexAutolinking.swift +16 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/CodexAvailability.hpp +95 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/CodexRunResult.hpp +99 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/HybridCodexAppServerSpec.cpp +24 -0
- package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/HybridCodexAppServerSpec.hpp +71 -0
- package/vendor/node_modules/@legendapp/spark-codex/package.json +35 -0
- package/vendor/node_modules/@legendapp/spark-codex/react-native.config.js +11 -0
- package/vendor/node_modules/@legendapp/spark-codex/src/Codex.nitro.ts +30 -0
- package/vendor/node_modules/@legendapp/spark-codex/src/index.ts +65 -0
- package/vendor/node_modules/@legendapp/spark-codex/src/native.ts +6 -0
- package/vendor/node_modules/@legendapp/spark-commands/package.json +21 -0
- package/vendor/node_modules/@legendapp/spark-commands/src/bindings.ts +90 -0
- package/vendor/node_modules/@legendapp/spark-commands/src/index.tsx +400 -0
- package/vendor/node_modules/@legendapp/spark-commands/src/router.ts +138 -0
- package/vendor/node_modules/@legendapp/spark-commands/src/storage.ts +20 -0
- package/vendor/node_modules/@legendapp/spark-context-menu/macos/RNContextMenu.mm +7 -8
- package/vendor/node_modules/@legendapp/spark-context-menu/package.json +4 -3
- package/vendor/node_modules/@legendapp/spark-context-menu/src/NativeContextMenu.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-context-menu/src/index.ts +26 -26
- package/vendor/node_modules/@legendapp/spark-context-menu/windows/SparkContextMenu/SparkContextMenu.h +3 -2
- package/vendor/node_modules/@legendapp/spark-desktop-app/RNDesktopApp.podspec +2 -1
- package/vendor/node_modules/@legendapp/spark-desktop-app/cpp/SparkBinaryJSI.hpp +89 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/cpp/SparkBinaryWindows.hpp +22 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/RNDesktopApp.mm +13 -7
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkBinaryJSI.h +48 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktop.h +4 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktop.mm +33 -33
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktopError.h +20 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkLifecycle.h +26 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkLifecycle.mm +49 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkQuitCoordinator.h +9 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkQuitCoordinator.mm +70 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkWindow.mm +9 -6
- package/vendor/node_modules/@legendapp/spark-desktop-app/macos/recent-documents/RNRecentDocumentController.mm +55 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/package.json +5 -3
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/NativeDesktopApp.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/api.ts +86 -40
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/accelerator.ts +40 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/index.ts +56 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/menu.ts +114 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/native-buffer.ts +22 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/path.ts +24 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/events.ts +75 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/index.ts +2 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/index.windows.ts +2 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/open-requests.ts +43 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/restoration.ts +11 -0
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/transport.ts +10 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/app.plugin.cjs +10 -5
- package/vendor/node_modules/@legendapp/spark-desktop-config/app.plugin.d.cts +3 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/autolinking.cjs +35 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/config.cjs +16 -3
- package/vendor/node_modules/@legendapp/spark-desktop-config/config.d.cts +89 -9
- package/vendor/node_modules/@legendapp/spark-desktop-config/expo.d.cts +3 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/identity.cjs +3 -2
- package/vendor/node_modules/@legendapp/spark-desktop-config/lifecycle.cjs +20 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/package.json +6 -5
- package/vendor/node_modules/@legendapp/spark-desktop-config/schema.json +276 -58
- package/vendor/node_modules/@legendapp/spark-desktop-config/updates.cjs +1 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/validation.cjs +35 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/windows.plugin.cjs +2 -1
- package/vendor/node_modules/@legendapp/spark-desktop-host/AppDelegate.mm +129 -11
- package/vendor/node_modules/@legendapp/spark-desktop-host/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-desktop-host/windows/application.inc +277 -121
- package/vendor/node_modules/@legendapp/spark-desktop-links/common/AuthLoopback.h +5 -7
- package/vendor/node_modules/@legendapp/spark-desktop-links/macos/RNDesktopLinks.mm +10 -9
- package/vendor/node_modules/@legendapp/spark-desktop-links/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-desktop-links/src/NativeDesktopLinks.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-desktop-links/src/api.ts +27 -33
- package/vendor/node_modules/@legendapp/spark-desktop-links/src/expo.ts +3 -4
- package/vendor/node_modules/@legendapp/spark-desktop-links/src/native.ts +9 -0
- package/vendor/node_modules/@legendapp/spark-desktop-links/windows/SparkLinks/SparkLinks.h +22 -22
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/RNDesktopShortcuts.podspec +1 -0
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/RNDesktopShortcuts.mm +1 -1
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/keyboard-manager/RNKeyboardManager.h +9 -0
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/keyboard-manager/RNKeyboardManager.mm +307 -0
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/package.json +8 -5
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/NativeDesktopShortcuts.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/api.ts +39 -14
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/NativeKeyboardManager.ts +12 -0
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/codes.ts +108 -0
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/index.ts +99 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/RNDesktopWindows.podspec +2 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/RNDesktopWindows.mm +229 -103
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/SparkWindowGeometry.h +63 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/LegendWindowRegistry.h +20 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/LegendWindowRegistry.mm +27 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManager.h +12 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManager.mm +3130 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManagerStartup.mm +31 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/SidebarStartupFallbacks.mm +14 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/package.json +10 -6
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/NativeDesktopWindowManager.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/api.ts +144 -70
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/diagnostics.ts +17 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/index.ts +3 -1
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/index.windows.ts +3 -1
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/macos-adapter.ts +53 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/macos.ts +51 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/transport.ts +41 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/types.ts +140 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/validation.ts +162 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/window-manager/NativeWindowManager.ts +28 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/WindowProvider.tsx +20 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/createWindowsNavigator.tsx +78 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/index.ts +6 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/primaryWindowLifecycle.ts +34 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/types.ts +11 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/usePrimaryWindowLifecycle.ts +18 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/useWindowFocusEffect.ts +17 -0
- package/vendor/node_modules/@legendapp/spark-documents/package.json +22 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/controller.ts +97 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/documentTransition.ts +13 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/hooks.ts +139 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/index.tsx +50 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/reload.ts +39 -0
- package/vendor/node_modules/@legendapp/spark-documents/src/requests.ts +15 -0
- package/vendor/node_modules/@legendapp/spark-drag-drop/package.json +4 -3
- package/vendor/node_modules/@legendapp/spark-drag-drop/src/DesktopDragViewNativeComponent.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-drag-drop/src/contracts.ts +73 -18
- package/vendor/node_modules/@legendapp/spark-drag-drop/src/index.tsx +58 -15
- package/vendor/node_modules/@legendapp/spark-drag-drop/windows/SparkDragDrop/SparkDragDrop.h +14 -8
- package/vendor/node_modules/@legendapp/spark-file-dialog/ios/RNFileDialog.mm +34 -121
- package/vendor/node_modules/@legendapp/spark-file-dialog/package.json +12 -5
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/NativeFileDialog.ts +1 -5
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/index.ts +19 -84
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/native.ts +12 -0
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/options.ts +34 -14
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/types.ts +26 -0
- package/vendor/node_modules/@legendapp/spark-file-dialog/windows/SparkFileDialog/SparkFileDialog.h +42 -58
- package/vendor/node_modules/@legendapp/spark-file-system/RNDesktopFileSystem.podspec +1 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/RNDesktopFileSystem.mm +32 -21
- package/vendor/node_modules/@legendapp/spark-file-system/macos/SparkFileMutations.h +24 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/SparkFileMutations.mm +215 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScanner.h +9 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScanner.mm +82 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScannerCore.h +47 -0
- package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScannerCore.mm +306 -0
- package/vendor/node_modules/@legendapp/spark-file-system/package.json +8 -6
- package/vendor/node_modules/@legendapp/spark-file-system/src/NativeDesktopFileSystem.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-file-system/src/file-scanner/NativeFileScanner.ts +11 -0
- package/vendor/node_modules/@legendapp/spark-file-system/src/file-scanner/index.ts +93 -0
- package/vendor/node_modules/@legendapp/spark-file-system/src/handles.ts +48 -14
- package/vendor/node_modules/@legendapp/spark-file-system/src/index.ts +134 -45
- package/vendor/node_modules/@legendapp/spark-file-system/src/path.ts +1 -9
- package/vendor/node_modules/@legendapp/spark-file-system/windows/SparkFileSystem/SparkFileSystem.h +102 -26
- package/vendor/node_modules/@legendapp/spark-file-system/windows/SparkFileSystem/SparkFileSystem.vcxproj +2 -0
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/RNDesktopGlobalShortcuts.podspec +1 -0
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/macos/RNDesktopGlobalShortcuts.mm +7 -4
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/package.json +4 -3
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/NativeDesktopGlobalShortcuts.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/index.ts +39 -10
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/windows/SparkGlobalShortcuts/SparkGlobalShortcuts.h +5 -4
- package/vendor/node_modules/@legendapp/spark-message-dialog/macos/RNDesktopMessageDialog.mm +2 -2
- package/vendor/node_modules/@legendapp/spark-message-dialog/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-message-dialog/src/NativeDesktopMessageDialog.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-message-dialog/src/index.ts +48 -11
- package/vendor/node_modules/@legendapp/spark-native-menu/ios/RNNativeMenu.mm +141 -609
- package/vendor/node_modules/@legendapp/spark-native-menu/package.json +9 -4
- package/vendor/node_modules/@legendapp/spark-native-menu/src/NativeMenu.ts +2 -7
- package/vendor/node_modules/@legendapp/spark-native-menu/src/api.ts +76 -121
- package/vendor/node_modules/@legendapp/spark-native-menu/src/hooks.ts +97 -0
- package/vendor/node_modules/@legendapp/spark-native-menu/src/windows-menus.ts +43 -37
- package/vendor/node_modules/@legendapp/spark-notifications/macos/RNDesktopNotifications.mm +75 -30
- package/vendor/node_modules/@legendapp/spark-notifications/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-notifications/src/NativeDesktopNotifications.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-notifications/src/index.ts +130 -42
- package/vendor/node_modules/@legendapp/spark-notifications/windows/SparkNotifications/SparkNotifications.h +46 -8
- package/vendor/node_modules/@legendapp/spark-processes/macos/RNDesktopProcesses.mm +94 -21
- package/vendor/node_modules/@legendapp/spark-processes/package.json +4 -3
- package/vendor/node_modules/@legendapp/spark-processes/src/NativeDesktopProcesses.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-processes/src/index.ts +124 -28
- package/vendor/node_modules/@legendapp/spark-processes/src/types.ts +34 -0
- package/vendor/node_modules/@legendapp/spark-processes/windows/SparkProcesses/SparkProcesses.h +83 -34
- package/vendor/node_modules/@legendapp/spark-processes/windows/SparkProcesses/SparkProcesses.vcxproj +2 -0
- package/vendor/node_modules/@legendapp/spark-secure-storage/macos/RNDesktopSecureStorage.mm +49 -40
- package/vendor/node_modules/@legendapp/spark-secure-storage/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-secure-storage/src/NativeDesktopSecureStorage.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-secure-storage/src/desktop.ts +16 -14
- package/vendor/node_modules/@legendapp/spark-secure-storage/src/expo.ts +0 -1
- package/vendor/node_modules/@legendapp/spark-secure-storage/src/options.ts +3 -2
- package/vendor/node_modules/@legendapp/spark-secure-storage/src/unavailable.ts +6 -3
- package/vendor/node_modules/@legendapp/spark-settings/package.json +10 -3
- package/vendor/node_modules/@legendapp/spark-settings/src/fileStorage.ts +11 -0
- package/vendor/node_modules/@legendapp/spark-settings/src/index.ts +7 -9
- package/vendor/node_modules/@legendapp/spark-settings/src/storage/index.ts +142 -0
- package/vendor/node_modules/@legendapp/spark-settings/src/store.ts +63 -21
- package/vendor/node_modules/@legendapp/spark-settings-ui/package.json +20 -0
- package/vendor/node_modules/@legendapp/spark-settings-ui/src/index.tsx +436 -0
- package/vendor/node_modules/@legendapp/spark-settings-ui/src/options.ts +10 -0
- package/vendor/node_modules/@legendapp/spark-sqlite/package.json +3 -3
- package/vendor/node_modules/@legendapp/spark-sqlite/src/database.ts +182 -0
- package/vendor/node_modules/@legendapp/spark-sqlite/src/index.ts +19 -6
- package/vendor/node_modules/@legendapp/spark-sqlite/src/types.ts +22 -0
- package/vendor/node_modules/@legendapp/spark-system/macos/RNDesktopSystem.mm +36 -11
- package/vendor/node_modules/@legendapp/spark-system/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-system/src/NativeDesktopSystem.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-system/src/index.ts +89 -32
- package/vendor/node_modules/@legendapp/spark-system/windows/SparkSystem/SparkSystem.h +8 -4
- package/vendor/node_modules/@legendapp/spark-tray/macos/RNDesktopTray.mm +20 -8
- package/vendor/node_modules/@legendapp/spark-tray/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-tray/src/NativeDesktopTray.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-tray/src/index.ts +91 -34
- package/vendor/node_modules/@legendapp/spark-tray/windows/SparkTray/SparkTray.h +45 -15
- package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkButton.mm +2 -1
- package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkTextInput.mm +10 -2
- package/vendor/node_modules/@legendapp/spark-ui/macos/appkit-split-view/RNSidebarSplitViewComponent.h +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/appkit-split-view/RNSidebarSplitViewComponent.mm +1182 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/glass-effect-view/RNGlassEffectView.mm +72 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSegmentedControl.h +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSegmentedControl.mm +194 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSelect.h +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSelect.mm +180 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/sf-symbol/RNSFSymbol.h +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/sf-symbol/RNSFSymbol.mm +155 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/sidebar/RNSidebar.h +18 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/sidebar/RNSidebar.mm +645 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/text-input-search/RNTextInputSearch.h +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/text-input-search/RNTextInputSearch.mm +164 -0
- package/vendor/node_modules/@legendapp/spark-ui/package.json +16 -4
- package/vendor/node_modules/@legendapp/spark-ui/src/SparkSelectNativeComponent.ts +3 -1
- package/vendor/node_modules/@legendapp/spark-ui/src/SparkTextInputNativeComponent.ts +6 -2
- package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/SidebarSplitViewNativeComponent.ts +31 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/index.tsx +77 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/availability.ts +10 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/classnames/index.ts +8 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/control.ts +42 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/desktop.tsx +56 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/glass-effect-view/index.tsx +15 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/index.android.tsx +35 -21
- package/vendor/node_modules/@legendapp/spark-ui/src/index.ios.tsx +55 -26
- package/vendor/node_modules/@legendapp/spark-ui/src/index.tsx +1 -27
- package/vendor/node_modules/@legendapp/spark-ui/src/index.web.tsx +45 -14
- package/vendor/node_modules/@legendapp/spark-ui/src/index.windows.tsx +1 -42
- package/vendor/node_modules/@legendapp/spark-ui/src/mobile-input.tsx +11 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/native-select/NativeSegmentedControlNativeComponent.ts +17 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/native-select/NativeSelectNativeComponent.ts +17 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/select.ts +5 -3
- package/vendor/node_modules/@legendapp/spark-ui/src/sf-symbol/SFSymbolNativeComponent.ts +13 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/sf-symbol/index.tsx +27 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/SidebarItemNativeComponent.ts +25 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/SidebarNativeComponent.ts +24 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/index.tsx +104 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/specialized.ts +22 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/text-input-search/TextInputSearchNativeComponent.ts +33 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/text-input-search/index.tsx +34 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/types.ts +30 -19
- package/vendor/node_modules/@legendapp/spark-ui/uniwind.ts +3 -1
- package/vendor/node_modules/@legendapp/spark-ui/uniwind.web.ts +3 -1
- package/vendor/node_modules/@legendapp/spark-ui/windows/SparkUI/SparkUI.h +16 -11
- package/vendor/node_modules/@legendapp/spark-updates/macos/RNDesktopUpdates.mm +21 -8
- package/vendor/node_modules/@legendapp/spark-updates/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-updates/src/NativeDesktopUpdates.ts +3 -2
- package/vendor/node_modules/@legendapp/spark-updates/src/index.ts +71 -15
- package/vendor/node_modules/@legendapp/spark-webview/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-webview/src/WebView.tsx +11 -0
- package/vendor/node_modules/@legendapp/spark-webview/src/adapter.ts +55 -0
- package/vendor/node_modules/@legendapp/spark-webview/src/index.ts +2 -2
- package/vendor/node_modules/@legendapp/spark-webview/src/types.ts +32 -0
- package/vendor/node_modules/@legendapp/spark-webview/src/view-props.ts +28 -0
- package/vendor/node_modules/@legendapp/spark-window-options/index.cjs +51 -14
- package/vendor/node_modules/@legendapp/spark-window-options/index.d.ts +21 -8
- package/vendor/node_modules/@legendapp/spark-window-options/package.json +1 -1
- package/windows-macos.ts +1 -0
- package/expo-metro.cjs +0 -1
- package/message-dialog.ts +0 -1
- package/universal.cjs +0 -1
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/accelerator.ts +0 -22
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows-options.ts +0 -11
- package/vendor/node_modules/@legendapp/spark-file-dialog/src/index.windows.ts +0 -15
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/accelerator.ts +0 -22
- package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkSelect.h +0 -4
- 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 {
|
|
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
|
-
|
|
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))
|
|
28
|
-
|
|
29
|
-
if (position >= 0) value.
|
|
30
|
-
|
|
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)
|
|
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
|
|
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 == "
|
|
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 == "
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
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-${
|
|
18
|
-
let
|
|
19
|
-
let timer: ReturnType<typeof setTimeout> | undefined
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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
|
|
55
|
-
if (closed)
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
} catch (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" });
|
|
79
|
+
async dismiss() { if (!closed) { await finish({ type: "dismiss" }); await readResult(); } else await cleanup.remove(); },
|
|
65
80
|
};
|
|
66
|
-
} catch (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
|
|
2
|
-
import {
|
|
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);
|
|
9
|
-
const {
|
|
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);
|
|
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) =>
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
/**
|
|
22
|
-
export function maybeCompleteAuthSession():
|
|
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 {
|
|
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) {
|
|
6
|
-
|
|
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
|
-
/**
|
|
9
|
-
export
|
|
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
|
-
|
|
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
|
|
15
|
-
if (options.signal?.aborted) throw
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
options.signal?.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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))
|
|
43
|
-
finish({ type: "success", url: value.url });
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
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 =
|
|
24
|
-
if ((url.protocol !== "https:" && !(url.protocol === "http:" && url.hostname === "127.0.0.1")) || url.username || url.password) throw new
|
|
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
|
|
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
|
|
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 =
|
|
32
|
-
if (url.username || url.password || url.search || url.hash || ["javascript:", "data:", "file:", "about:"].includes(url.protocol)) throw new
|
|
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
|
|
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
|
|
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 };
|