@myrialabs/clopen 0.0.1
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/.env.example +6 -0
- package/.github/workflows/release.yml +60 -0
- package/.github/workflows/test.yml +40 -0
- package/CONTRIBUTING.md +499 -0
- package/LICENSE +21 -0
- package/README.md +209 -0
- package/backend/index.ts +156 -0
- package/backend/lib/chat/helpers.ts +42 -0
- package/backend/lib/chat/index.ts +2 -0
- package/backend/lib/chat/stream-manager.ts +1126 -0
- package/backend/lib/database/README.md +77 -0
- package/backend/lib/database/index.ts +119 -0
- package/backend/lib/database/migrations/001_create_projects_table.ts +31 -0
- package/backend/lib/database/migrations/002_create_chat_sessions_table.ts +33 -0
- package/backend/lib/database/migrations/003_create_messages_table.ts +32 -0
- package/backend/lib/database/migrations/004_create_prompt_templates_table.ts +34 -0
- package/backend/lib/database/migrations/005_create_settings_table.ts +24 -0
- package/backend/lib/database/migrations/006_add_user_to_messages.ts +58 -0
- package/backend/lib/database/migrations/007_create_stream_states_table.ts +41 -0
- package/backend/lib/database/migrations/008_create_message_snapshots_table.ts +62 -0
- package/backend/lib/database/migrations/009_add_delta_snapshot_fields.ts +41 -0
- package/backend/lib/database/migrations/010_add_soft_delete_and_branch_support.ts +70 -0
- package/backend/lib/database/migrations/011_git_like_commit_graph.ts +156 -0
- package/backend/lib/database/migrations/012_add_file_change_statistics.ts +41 -0
- package/backend/lib/database/migrations/013_checkpoint_tree_state.ts +118 -0
- package/backend/lib/database/migrations/014_add_engine_to_sessions.ts +18 -0
- package/backend/lib/database/migrations/015_add_model_to_sessions.ts +18 -0
- package/backend/lib/database/migrations/016_create_user_projects_table.ts +34 -0
- package/backend/lib/database/migrations/017_add_current_session_to_user_projects.ts +32 -0
- package/backend/lib/database/migrations/018_create_claude_accounts_table.ts +24 -0
- package/backend/lib/database/migrations/019_add_claude_account_to_sessions.ts +18 -0
- package/backend/lib/database/migrations/020_add_snapshot_tree_hash.ts +32 -0
- package/backend/lib/database/migrations/021_drop_prompt_templates_table.ts +33 -0
- package/backend/lib/database/migrations/index.ts +154 -0
- package/backend/lib/database/queries/checkpoint-queries.ts +87 -0
- package/backend/lib/database/queries/engine-queries.ts +75 -0
- package/backend/lib/database/queries/index.ts +9 -0
- package/backend/lib/database/queries/message-queries.ts +472 -0
- package/backend/lib/database/queries/project-queries.ts +117 -0
- package/backend/lib/database/queries/session-queries.ts +271 -0
- package/backend/lib/database/queries/settings-queries.ts +34 -0
- package/backend/lib/database/queries/snapshot-queries.ts +326 -0
- package/backend/lib/database/queries/utils-queries.ts +59 -0
- package/backend/lib/database/seeders/index.ts +13 -0
- package/backend/lib/database/seeders/settings_seeder.ts +84 -0
- package/backend/lib/database/utils/connection.ts +174 -0
- package/backend/lib/database/utils/index.ts +4 -0
- package/backend/lib/database/utils/migration-runner.ts +118 -0
- package/backend/lib/database/utils/seeder-runner.ts +121 -0
- package/backend/lib/engine/adapters/claude/environment.ts +164 -0
- package/backend/lib/engine/adapters/claude/error-handler.ts +60 -0
- package/backend/lib/engine/adapters/claude/index.ts +1 -0
- package/backend/lib/engine/adapters/claude/path-utils.ts +38 -0
- package/backend/lib/engine/adapters/claude/stream.ts +177 -0
- package/backend/lib/engine/adapters/opencode/index.ts +2 -0
- package/backend/lib/engine/adapters/opencode/message-converter.ts +862 -0
- package/backend/lib/engine/adapters/opencode/server.ts +104 -0
- package/backend/lib/engine/adapters/opencode/stream.ts +755 -0
- package/backend/lib/engine/index.ts +196 -0
- package/backend/lib/engine/types.ts +58 -0
- package/backend/lib/files/file-operations.ts +478 -0
- package/backend/lib/files/file-reading.ts +308 -0
- package/backend/lib/files/file-watcher.ts +383 -0
- package/backend/lib/files/path-browsing.ts +382 -0
- package/backend/lib/git/git-executor.ts +88 -0
- package/backend/lib/git/git-parser.ts +411 -0
- package/backend/lib/git/git-service.ts +505 -0
- package/backend/lib/mcp/README.md +1144 -0
- package/backend/lib/mcp/config.ts +316 -0
- package/backend/lib/mcp/index.ts +35 -0
- package/backend/lib/mcp/project-context.ts +236 -0
- package/backend/lib/mcp/servers/browser-automation/actions.ts +156 -0
- package/backend/lib/mcp/servers/browser-automation/browser.ts +419 -0
- package/backend/lib/mcp/servers/browser-automation/index.ts +791 -0
- package/backend/lib/mcp/servers/browser-automation/inspection.ts +501 -0
- package/backend/lib/mcp/servers/helper.ts +143 -0
- package/backend/lib/mcp/servers/index.ts +45 -0
- package/backend/lib/mcp/servers/weather/get-temperature.ts +56 -0
- package/backend/lib/mcp/servers/weather/index.ts +31 -0
- package/backend/lib/mcp/stdio-server.ts +103 -0
- package/backend/lib/mcp/types.ts +65 -0
- package/backend/lib/preview/browser/browser-audio-capture.ts +86 -0
- package/backend/lib/preview/browser/browser-console-manager.ts +263 -0
- package/backend/lib/preview/browser/browser-dialog-handler.ts +222 -0
- package/backend/lib/preview/browser/browser-interaction-handler.ts +421 -0
- package/backend/lib/preview/browser/browser-mcp-control.ts +415 -0
- package/backend/lib/preview/browser/browser-native-ui-handler.ts +512 -0
- package/backend/lib/preview/browser/browser-navigation-tracker.ts +104 -0
- package/backend/lib/preview/browser/browser-pool.ts +357 -0
- package/backend/lib/preview/browser/browser-preview-service.ts +882 -0
- package/backend/lib/preview/browser/browser-tab-manager.ts +935 -0
- package/backend/lib/preview/browser/browser-video-capture.ts +695 -0
- package/backend/lib/preview/browser/scripts/audio-stream.ts +292 -0
- package/backend/lib/preview/browser/scripts/cursor-tracking.ts +85 -0
- package/backend/lib/preview/browser/scripts/video-stream.ts +438 -0
- package/backend/lib/preview/browser/types.ts +359 -0
- package/backend/lib/preview/index.ts +24 -0
- package/backend/lib/project/index.ts +2 -0
- package/backend/lib/project/status-manager.ts +182 -0
- package/backend/lib/shared/index.ts +2 -0
- package/backend/lib/shared/port-utils.ts +25 -0
- package/backend/lib/shared/process-manager.ts +281 -0
- package/backend/lib/snapshot/blob-store.ts +227 -0
- package/backend/lib/snapshot/gitignore.ts +307 -0
- package/backend/lib/snapshot/helpers.ts +397 -0
- package/backend/lib/snapshot/snapshot-service.ts +483 -0
- package/backend/lib/terminal/helpers.ts +14 -0
- package/backend/lib/terminal/index.ts +8 -0
- package/backend/lib/terminal/pty-manager.ts +4 -0
- package/backend/lib/terminal/pty-session-manager.ts +387 -0
- package/backend/lib/terminal/shell-utils.ts +313 -0
- package/backend/lib/terminal/stream-manager.ts +293 -0
- package/backend/lib/tunnel/global-tunnel-manager.ts +243 -0
- package/backend/lib/tunnel/project-tunnel-manager.ts +311 -0
- package/backend/lib/user/helpers.ts +87 -0
- package/backend/lib/utils/ws.ts +944 -0
- package/backend/lib/vite-dev.ts +353 -0
- package/backend/middleware/cors.ts +15 -0
- package/backend/middleware/error-handler.ts +49 -0
- package/backend/middleware/logger.ts +9 -0
- package/backend/types/api.ts +24 -0
- package/backend/ws/README.md +1505 -0
- package/backend/ws/chat/background.ts +198 -0
- package/backend/ws/chat/index.ts +21 -0
- package/backend/ws/chat/stream.ts +707 -0
- package/backend/ws/engine/claude/accounts.ts +401 -0
- package/backend/ws/engine/claude/index.ts +13 -0
- package/backend/ws/engine/claude/status.ts +43 -0
- package/backend/ws/engine/index.ts +14 -0
- package/backend/ws/engine/opencode/index.ts +11 -0
- package/backend/ws/engine/opencode/status.ts +30 -0
- package/backend/ws/engine/utils.ts +36 -0
- package/backend/ws/files/index.ts +30 -0
- package/backend/ws/files/read.ts +189 -0
- package/backend/ws/files/search.ts +453 -0
- package/backend/ws/files/watch.ts +124 -0
- package/backend/ws/files/write.ts +143 -0
- package/backend/ws/git/branch.ts +106 -0
- package/backend/ws/git/commit.ts +39 -0
- package/backend/ws/git/conflict.ts +68 -0
- package/backend/ws/git/diff.ts +69 -0
- package/backend/ws/git/index.ts +24 -0
- package/backend/ws/git/log.ts +41 -0
- package/backend/ws/git/remote.ts +214 -0
- package/backend/ws/git/staging.ts +84 -0
- package/backend/ws/git/status.ts +90 -0
- package/backend/ws/index.ts +69 -0
- package/backend/ws/mcp/index.ts +61 -0
- package/backend/ws/messages/crud.ts +74 -0
- package/backend/ws/messages/index.ts +14 -0
- package/backend/ws/preview/browser/cleanup.ts +129 -0
- package/backend/ws/preview/browser/console.ts +114 -0
- package/backend/ws/preview/browser/interact.ts +513 -0
- package/backend/ws/preview/browser/mcp.ts +129 -0
- package/backend/ws/preview/browser/native-ui.ts +235 -0
- package/backend/ws/preview/browser/stats.ts +55 -0
- package/backend/ws/preview/browser/tab-info.ts +126 -0
- package/backend/ws/preview/browser/tab.ts +166 -0
- package/backend/ws/preview/browser/webcodecs.ts +293 -0
- package/backend/ws/preview/index.ts +146 -0
- package/backend/ws/projects/crud.ts +113 -0
- package/backend/ws/projects/index.ts +25 -0
- package/backend/ws/projects/presence.ts +46 -0
- package/backend/ws/projects/status.ts +116 -0
- package/backend/ws/sessions/crud.ts +327 -0
- package/backend/ws/sessions/index.ts +33 -0
- package/backend/ws/settings/crud.ts +112 -0
- package/backend/ws/settings/index.ts +14 -0
- package/backend/ws/snapshot/index.ts +17 -0
- package/backend/ws/snapshot/restore.ts +173 -0
- package/backend/ws/snapshot/timeline.ts +141 -0
- package/backend/ws/system/index.ts +14 -0
- package/backend/ws/system/operations.ts +49 -0
- package/backend/ws/terminal/index.ts +40 -0
- package/backend/ws/terminal/persistence.ts +153 -0
- package/backend/ws/terminal/session.ts +382 -0
- package/backend/ws/terminal/stream.ts +79 -0
- package/backend/ws/tunnel/index.ts +14 -0
- package/backend/ws/tunnel/operations.ts +91 -0
- package/backend/ws/types.ts +20 -0
- package/backend/ws/user/crud.ts +156 -0
- package/backend/ws/user/index.ts +14 -0
- package/bin/clopen.ts +307 -0
- package/bun.lock +1352 -0
- package/frontend/App.svelte +34 -0
- package/frontend/app.css +313 -0
- package/frontend/lib/app-environment.ts +10 -0
- package/frontend/lib/components/chat/ChatInterface.svelte +407 -0
- package/frontend/lib/components/chat/formatters/ErrorMessage.svelte +57 -0
- package/frontend/lib/components/chat/formatters/MessageFormatter.svelte +224 -0
- package/frontend/lib/components/chat/formatters/TextMessage.svelte +395 -0
- package/frontend/lib/components/chat/formatters/Tools.svelte +70 -0
- package/frontend/lib/components/chat/formatters/index.ts +3 -0
- package/frontend/lib/components/chat/input/ChatInput.svelte +421 -0
- package/frontend/lib/components/chat/input/components/ChatInputActions.svelte +78 -0
- package/frontend/lib/components/chat/input/components/DragDropOverlay.svelte +30 -0
- package/frontend/lib/components/chat/input/components/EditModeIndicator.svelte +33 -0
- package/frontend/lib/components/chat/input/components/EngineModelPicker.svelte +619 -0
- package/frontend/lib/components/chat/input/components/FileAttachmentPreview.svelte +48 -0
- package/frontend/lib/components/chat/input/components/LoadingIndicator.svelte +31 -0
- package/frontend/lib/components/chat/input/composables/use-animations.svelte.ts +201 -0
- package/frontend/lib/components/chat/input/composables/use-chat-actions.svelte.ts +148 -0
- package/frontend/lib/components/chat/input/composables/use-file-handling.svelte.ts +216 -0
- package/frontend/lib/components/chat/input/composables/use-input-state.svelte.ts +357 -0
- package/frontend/lib/components/chat/input/composables/use-textarea-resize.svelte.ts +57 -0
- package/frontend/lib/components/chat/message/ChatMessage.svelte +478 -0
- package/frontend/lib/components/chat/message/ChatMessages.svelte +541 -0
- package/frontend/lib/components/chat/message/DateSeparator.svelte +86 -0
- package/frontend/lib/components/chat/message/MessageBubble.svelte +86 -0
- package/frontend/lib/components/chat/message/MessageHeader.svelte +157 -0
- package/frontend/lib/components/chat/modal/DebugModal.svelte +59 -0
- package/frontend/lib/components/chat/modal/TokenUsageModal.svelte +124 -0
- package/frontend/lib/components/chat/shared/index.ts +2 -0
- package/frontend/lib/components/chat/shared/utils.ts +116 -0
- package/frontend/lib/components/chat/tools/BashOutputTool.svelte +35 -0
- package/frontend/lib/components/chat/tools/BashTool.svelte +46 -0
- package/frontend/lib/components/chat/tools/CustomMcpTool.svelte +139 -0
- package/frontend/lib/components/chat/tools/EditTool.svelte +48 -0
- package/frontend/lib/components/chat/tools/ExitPlanModeTool.svelte +32 -0
- package/frontend/lib/components/chat/tools/GlobTool.svelte +51 -0
- package/frontend/lib/components/chat/tools/GrepTool.svelte +90 -0
- package/frontend/lib/components/chat/tools/KillShellTool.svelte +26 -0
- package/frontend/lib/components/chat/tools/ListMcpResourcesTool.svelte +31 -0
- package/frontend/lib/components/chat/tools/NotebookEditTool.svelte +38 -0
- package/frontend/lib/components/chat/tools/ReadMcpResourceTool.svelte +34 -0
- package/frontend/lib/components/chat/tools/ReadTool.svelte +41 -0
- package/frontend/lib/components/chat/tools/TaskTool.svelte +64 -0
- package/frontend/lib/components/chat/tools/TodoWriteTool.svelte +75 -0
- package/frontend/lib/components/chat/tools/WebFetchTool.svelte +35 -0
- package/frontend/lib/components/chat/tools/WebSearchTool.svelte +84 -0
- package/frontend/lib/components/chat/tools/WriteTool.svelte +33 -0
- package/frontend/lib/components/chat/tools/components/CodeBlock.svelte +79 -0
- package/frontend/lib/components/chat/tools/components/DiffBlock.svelte +408 -0
- package/frontend/lib/components/chat/tools/components/FileHeader.svelte +45 -0
- package/frontend/lib/components/chat/tools/components/InfoLine.svelte +19 -0
- package/frontend/lib/components/chat/tools/components/StatsBadges.svelte +27 -0
- package/frontend/lib/components/chat/tools/components/TerminalCommand.svelte +54 -0
- package/frontend/lib/components/chat/tools/components/index.ts +7 -0
- package/frontend/lib/components/chat/tools/index.ts +26 -0
- package/frontend/lib/components/chat/widgets/FloatingTodoList.svelte +249 -0
- package/frontend/lib/components/chat/widgets/TokenUsage.svelte +78 -0
- package/frontend/lib/components/checkpoint/TimelineModal.svelte +391 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineEdge.svelte +26 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineGraph.svelte +87 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineNode.svelte +108 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineVersionGroup.svelte +59 -0
- package/frontend/lib/components/checkpoint/timeline/animation.ts +168 -0
- package/frontend/lib/components/checkpoint/timeline/config.ts +44 -0
- package/frontend/lib/components/checkpoint/timeline/graph-builder.ts +304 -0
- package/frontend/lib/components/checkpoint/timeline/types.ts +65 -0
- package/frontend/lib/components/checkpoint/timeline/utils.ts +53 -0
- package/frontend/lib/components/common/Alert.svelte +139 -0
- package/frontend/lib/components/common/AvatarBubble.svelte +56 -0
- package/frontend/lib/components/common/Button.svelte +71 -0
- package/frontend/lib/components/common/Card.svelte +102 -0
- package/frontend/lib/components/common/Checkbox.svelte +48 -0
- package/frontend/lib/components/common/Dialog.svelte +249 -0
- package/frontend/lib/components/common/FolderBrowser.svelte +843 -0
- package/frontend/lib/components/common/Icon.svelte +58 -0
- package/frontend/lib/components/common/Input.svelte +72 -0
- package/frontend/lib/components/common/Lightbox.svelte +233 -0
- package/frontend/lib/components/common/LoadingScreen.svelte +52 -0
- package/frontend/lib/components/common/LoadingSpinner.svelte +48 -0
- package/frontend/lib/components/common/Modal.svelte +177 -0
- package/frontend/lib/components/common/ModalProvider.svelte +28 -0
- package/frontend/lib/components/common/ModelSelector.svelte +110 -0
- package/frontend/lib/components/common/MonacoEditor.svelte +569 -0
- package/frontend/lib/components/common/NotificationToast.svelte +113 -0
- package/frontend/lib/components/common/PageTemplate.svelte +76 -0
- package/frontend/lib/components/common/ProjectUserAvatars.svelte +79 -0
- package/frontend/lib/components/common/Select.svelte +98 -0
- package/frontend/lib/components/common/Textarea.svelte +80 -0
- package/frontend/lib/components/common/ThemeToggle.svelte +44 -0
- package/frontend/lib/components/common/lucide-icons.ts +1642 -0
- package/frontend/lib/components/common/material-icons.ts +1082 -0
- package/frontend/lib/components/common/xterm/XTerm.svelte +796 -0
- package/frontend/lib/components/common/xterm/index.ts +16 -0
- package/frontend/lib/components/common/xterm/terminal-config.ts +68 -0
- package/frontend/lib/components/common/xterm/types.ts +31 -0
- package/frontend/lib/components/common/xterm/xterm-service.ts +353 -0
- package/frontend/lib/components/files/FileNode.svelte +384 -0
- package/frontend/lib/components/files/FileTree.svelte +681 -0
- package/frontend/lib/components/files/FileViewer.svelte +728 -0
- package/frontend/lib/components/files/SearchResults.svelte +303 -0
- package/frontend/lib/components/git/BranchManager.svelte +458 -0
- package/frontend/lib/components/git/ChangesSection.svelte +107 -0
- package/frontend/lib/components/git/CommitForm.svelte +76 -0
- package/frontend/lib/components/git/ConflictResolver.svelte +158 -0
- package/frontend/lib/components/git/DiffViewer.svelte +364 -0
- package/frontend/lib/components/git/FileChangeItem.svelte +97 -0
- package/frontend/lib/components/git/GitButton.svelte +33 -0
- package/frontend/lib/components/git/GitLog.svelte +361 -0
- package/frontend/lib/components/git/GitModal.svelte +80 -0
- package/frontend/lib/components/history/HistoryModal.svelte +563 -0
- package/frontend/lib/components/history/HistoryView.svelte +615 -0
- package/frontend/lib/components/index.ts +34 -0
- package/frontend/lib/components/preview/browser/BrowserPreview.svelte +549 -0
- package/frontend/lib/components/preview/browser/components/Canvas.svelte +1058 -0
- package/frontend/lib/components/preview/browser/components/ConsolePanel.svelte +757 -0
- package/frontend/lib/components/preview/browser/components/Container.svelte +450 -0
- package/frontend/lib/components/preview/browser/components/ContextMenu.svelte +236 -0
- package/frontend/lib/components/preview/browser/components/SelectDropdown.svelte +224 -0
- package/frontend/lib/components/preview/browser/components/Toolbar.svelte +339 -0
- package/frontend/lib/components/preview/browser/components/VirtualCursor.svelte +36 -0
- package/frontend/lib/components/preview/browser/core/cleanup.svelte.ts +155 -0
- package/frontend/lib/components/preview/browser/core/coordinator.svelte.ts +837 -0
- package/frontend/lib/components/preview/browser/core/interactions.svelte.ts +113 -0
- package/frontend/lib/components/preview/browser/core/mcp-handlers.svelte.ts +296 -0
- package/frontend/lib/components/preview/browser/core/native-ui-handlers.svelte.ts +391 -0
- package/frontend/lib/components/preview/browser/core/stream-handler.svelte.ts +231 -0
- package/frontend/lib/components/preview/browser/core/tab-manager.svelte.ts +210 -0
- package/frontend/lib/components/preview/browser/core/tab-operations.svelte.ts +239 -0
- package/frontend/lib/components/preview/index.ts +2 -0
- package/frontend/lib/components/settings/SettingsModal.svelte +235 -0
- package/frontend/lib/components/settings/SettingsView.svelte +36 -0
- package/frontend/lib/components/settings/appearance/AppearanceSettings.svelte +51 -0
- package/frontend/lib/components/settings/appearance/LayoutPresetSettings.svelte +160 -0
- package/frontend/lib/components/settings/appearance/LayoutPreview.svelte +76 -0
- package/frontend/lib/components/settings/engines/AIEnginesSettings.svelte +917 -0
- package/frontend/lib/components/settings/general/AdvancedSettings.svelte +187 -0
- package/frontend/lib/components/settings/general/DataManagementSettings.svelte +203 -0
- package/frontend/lib/components/settings/general/GeneralSettings.svelte +10 -0
- package/frontend/lib/components/settings/model/ModelSettings.svelte +357 -0
- package/frontend/lib/components/settings/notifications/NotificationSettings.svelte +205 -0
- package/frontend/lib/components/settings/user/UserSettings.svelte +197 -0
- package/frontend/lib/components/terminal/Terminal.svelte +368 -0
- package/frontend/lib/components/terminal/TerminalTabs.svelte +87 -0
- package/frontend/lib/components/terminal/TerminalView.svelte +55 -0
- package/frontend/lib/components/tunnel/TunnelActive.svelte +142 -0
- package/frontend/lib/components/tunnel/TunnelButton.svelte +54 -0
- package/frontend/lib/components/tunnel/TunnelInactive.svelte +284 -0
- package/frontend/lib/components/tunnel/TunnelModal.svelte +47 -0
- package/frontend/lib/components/tunnel/TunnelQRCode.svelte +49 -0
- package/frontend/lib/components/workspace/DesktopNavigator.svelte +382 -0
- package/frontend/lib/components/workspace/MobileNavigator.svelte +403 -0
- package/frontend/lib/components/workspace/PanelContainer.svelte +100 -0
- package/frontend/lib/components/workspace/PanelHeader.svelte +505 -0
- package/frontend/lib/components/workspace/ViewMenu.svelte +162 -0
- package/frontend/lib/components/workspace/WorkspaceLayout.svelte +169 -0
- package/frontend/lib/components/workspace/layout/DesktopLayout.svelte +15 -0
- package/frontend/lib/components/workspace/layout/MobileLayout.svelte +17 -0
- package/frontend/lib/components/workspace/layout/split-pane/Container.svelte +42 -0
- package/frontend/lib/components/workspace/layout/split-pane/Handle.svelte +85 -0
- package/frontend/lib/components/workspace/layout/split-pane/Layout.svelte +37 -0
- package/frontend/lib/components/workspace/panels/ChatPanel.svelte +274 -0
- package/frontend/lib/components/workspace/panels/FilesPanel.svelte +1261 -0
- package/frontend/lib/components/workspace/panels/GitPanel.svelte +1560 -0
- package/frontend/lib/components/workspace/panels/PreviewPanel.svelte +150 -0
- package/frontend/lib/components/workspace/panels/TerminalPanel.svelte +73 -0
- package/frontend/lib/constants/preview.ts +45 -0
- package/frontend/lib/services/chat/chat.service.ts +704 -0
- package/frontend/lib/services/chat/index.ts +7 -0
- package/frontend/lib/services/notification/global-stream-monitor.ts +86 -0
- package/frontend/lib/services/notification/index.ts +8 -0
- package/frontend/lib/services/notification/push.service.ts +144 -0
- package/frontend/lib/services/notification/sound.service.ts +127 -0
- package/frontend/lib/services/preview/browser/browser-console.service.ts +61 -0
- package/frontend/lib/services/preview/browser/browser-webcodecs.service.ts +1499 -0
- package/frontend/lib/services/preview/browser/mcp-integration.svelte.ts +67 -0
- package/frontend/lib/services/preview/index.ts +23 -0
- package/frontend/lib/services/project/index.ts +8 -0
- package/frontend/lib/services/project/status.service.ts +159 -0
- package/frontend/lib/services/snapshot/snapshot.service.ts +47 -0
- package/frontend/lib/services/terminal/background/index.ts +130 -0
- package/frontend/lib/services/terminal/background/session-restore.ts +274 -0
- package/frontend/lib/services/terminal/background/stream-manager.ts +286 -0
- package/frontend/lib/services/terminal/index.ts +14 -0
- package/frontend/lib/services/terminal/persistence.service.ts +260 -0
- package/frontend/lib/services/terminal/project.service.ts +953 -0
- package/frontend/lib/services/terminal/session.service.ts +364 -0
- package/frontend/lib/services/terminal/terminal.service.ts +369 -0
- package/frontend/lib/stores/core/app.svelte.ts +117 -0
- package/frontend/lib/stores/core/files.svelte.ts +73 -0
- package/frontend/lib/stores/core/presence.svelte.ts +48 -0
- package/frontend/lib/stores/core/projects.svelte.ts +317 -0
- package/frontend/lib/stores/core/sessions.svelte.ts +383 -0
- package/frontend/lib/stores/features/claude-accounts.svelte.ts +58 -0
- package/frontend/lib/stores/features/models.svelte.ts +89 -0
- package/frontend/lib/stores/features/settings.svelte.ts +87 -0
- package/frontend/lib/stores/features/terminal.svelte.ts +701 -0
- package/frontend/lib/stores/features/tunnel.svelte.ts +161 -0
- package/frontend/lib/stores/features/user.svelte.ts +96 -0
- package/frontend/lib/stores/ui/chat-input.svelte.ts +57 -0
- package/frontend/lib/stores/ui/chat-model.svelte.ts +61 -0
- package/frontend/lib/stores/ui/dialog.svelte.ts +59 -0
- package/frontend/lib/stores/ui/edit-mode.svelte.ts +214 -0
- package/frontend/lib/stores/ui/notification.svelte.ts +166 -0
- package/frontend/lib/stores/ui/settings-modal.svelte.ts +88 -0
- package/frontend/lib/stores/ui/theme.svelte.ts +179 -0
- package/frontend/lib/stores/ui/workspace.svelte.ts +754 -0
- package/frontend/lib/types/native-ui.ts +73 -0
- package/frontend/lib/utils/chat/date-separator.ts +39 -0
- package/frontend/lib/utils/chat/message-grouper.ts +219 -0
- package/frontend/lib/utils/chat/message-processor.ts +135 -0
- package/frontend/lib/utils/chat/tool-handler.ts +161 -0
- package/frontend/lib/utils/chat/virtual-scroll.svelte.ts +142 -0
- package/frontend/lib/utils/click-outside.ts +20 -0
- package/frontend/lib/utils/context-manager.ts +257 -0
- package/frontend/lib/utils/file-icon-mappings.ts +769 -0
- package/frontend/lib/utils/folder-icon-mappings.ts +1030 -0
- package/frontend/lib/utils/git-status.ts +68 -0
- package/frontend/lib/utils/platform.ts +113 -0
- package/frontend/lib/utils/port-check.ts +65 -0
- package/frontend/lib/utils/terminalFormatter.ts +207 -0
- package/frontend/lib/utils/theme.ts +6 -0
- package/frontend/lib/utils/tree-visualizer.ts +320 -0
- package/frontend/lib/utils/ws.ts +44 -0
- package/frontend/main.ts +13 -0
- package/index.html +70 -0
- package/package.json +111 -0
- package/scripts/generate-icons.ts +87 -0
- package/scripts/pre-publish-check.sh +142 -0
- package/scripts/setup-hooks.sh +134 -0
- package/scripts/validate-branch-name.sh +47 -0
- package/scripts/validate-commit-msg.sh +42 -0
- package/shared/constants/engines.ts +134 -0
- package/shared/types/database/connection.ts +16 -0
- package/shared/types/database/index.ts +6 -0
- package/shared/types/database/schema.ts +141 -0
- package/shared/types/engine/index.ts +45 -0
- package/shared/types/filesystem/index.ts +22 -0
- package/shared/types/git.ts +171 -0
- package/shared/types/messaging/index.ts +239 -0
- package/shared/types/messaging/tool.ts +526 -0
- package/shared/types/network/api.ts +18 -0
- package/shared/types/network/index.ts +5 -0
- package/shared/types/stores/app.ts +23 -0
- package/shared/types/stores/dialog.ts +21 -0
- package/shared/types/stores/index.ts +3 -0
- package/shared/types/stores/settings.ts +15 -0
- package/shared/types/terminal/index.ts +44 -0
- package/shared/types/ui/components.ts +61 -0
- package/shared/types/ui/icons.ts +23 -0
- package/shared/types/ui/index.ts +22 -0
- package/shared/types/ui/notifications.ts +14 -0
- package/shared/types/ui/theme.ts +12 -0
- package/shared/types/websocket/index.ts +43 -0
- package/shared/types/window.d.ts +13 -0
- package/shared/utils/anonymous-user.ts +168 -0
- package/shared/utils/async.ts +10 -0
- package/shared/utils/diff-calculator.ts +184 -0
- package/shared/utils/file-type-detection.ts +166 -0
- package/shared/utils/logger.ts +158 -0
- package/shared/utils/message-formatter.ts +79 -0
- package/shared/utils/path.ts +47 -0
- package/shared/utils/ws-client.ts +768 -0
- package/shared/utils/ws-server.ts +660 -0
- package/static/audio/notification.ogg +0 -0
- package/static/favicon.svg +8 -0
- package/static/fonts/dm-sans/dm-sans-italic-latin-ext.woff2 +0 -0
- package/static/fonts/dm-sans/dm-sans-italic-latin.woff2 +0 -0
- package/static/fonts/dm-sans/dm-sans-normal-latin-ext.woff2 +0 -0
- package/static/fonts/dm-sans/dm-sans-normal-latin.woff2 +0 -0
- package/static/fonts/dm-sans.css +96 -0
- package/svelte.config.js +20 -0
- package/vite.config.ts +33 -0
|
@@ -0,0 +1,882 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import type { Page } from 'puppeteer-core';
|
|
3
|
+
import { BrowserTabManager } from './browser-tab-manager.js';
|
|
4
|
+
import { BrowserConsoleManager } from './browser-console-manager.js';
|
|
5
|
+
import { BrowserInteractionHandler } from './browser-interaction-handler.js';
|
|
6
|
+
import { BrowserNavigationTracker } from './browser-navigation-tracker.js';
|
|
7
|
+
import { BrowserVideoCapture } from './browser-video-capture.js';
|
|
8
|
+
import { BrowserDialogHandler } from './browser-dialog-handler.js';
|
|
9
|
+
import { BrowserNativeUIHandler } from './browser-native-ui-handler.js';
|
|
10
|
+
import { browserMcpControl } from './browser-mcp-control.js';
|
|
11
|
+
import { ws } from '$backend/lib/utils/ws';
|
|
12
|
+
import { debug } from '$shared/utils/logger';
|
|
13
|
+
import type {
|
|
14
|
+
BrowserTab,
|
|
15
|
+
BrowserTabInfo,
|
|
16
|
+
BrowserConsoleMessage,
|
|
17
|
+
BrowserAutonomousAction,
|
|
18
|
+
DeviceSize,
|
|
19
|
+
Rotation,
|
|
20
|
+
BrowserDialogResponse,
|
|
21
|
+
BrowserSelectResponse,
|
|
22
|
+
BrowserContextMenuResponse,
|
|
23
|
+
BrowserContextMenuInfo
|
|
24
|
+
} from './types';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Browser Preview Service
|
|
28
|
+
*
|
|
29
|
+
* Main orchestrator for browser preview functionality.
|
|
30
|
+
* Tab-centric architecture - all operations work with tabs.
|
|
31
|
+
*
|
|
32
|
+
* Architecture:
|
|
33
|
+
* - Tabs are the primary unit (no separate session concept)
|
|
34
|
+
* - Each tab = isolated browser context + page
|
|
35
|
+
* - Event-driven communication with frontend
|
|
36
|
+
* - Manages all browser operations: streaming, interaction, console, etc.
|
|
37
|
+
* - **PROJECT ISOLATION**: Each instance is isolated per project
|
|
38
|
+
*/
|
|
39
|
+
export class BrowserPreviewService extends EventEmitter {
|
|
40
|
+
private tabManager: BrowserTabManager;
|
|
41
|
+
private consoleManager: BrowserConsoleManager;
|
|
42
|
+
private interactionHandler: BrowserInteractionHandler;
|
|
43
|
+
private navigationTracker: BrowserNavigationTracker;
|
|
44
|
+
private videoCapture: BrowserVideoCapture;
|
|
45
|
+
private dialogHandler: BrowserDialogHandler;
|
|
46
|
+
private nativeUIHandler: BrowserNativeUIHandler;
|
|
47
|
+
|
|
48
|
+
// Store context menu info for later action execution
|
|
49
|
+
private contextMenus = new Map<string, BrowserContextMenuInfo>();
|
|
50
|
+
|
|
51
|
+
// Project ID for isolation (REQUIRED)
|
|
52
|
+
private projectId: string;
|
|
53
|
+
|
|
54
|
+
constructor(projectId: string) {
|
|
55
|
+
super();
|
|
56
|
+
|
|
57
|
+
if (!projectId) {
|
|
58
|
+
throw new Error('projectId is required for BrowserPreviewService');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.projectId = projectId;
|
|
62
|
+
|
|
63
|
+
// Initialize managers with projectId for isolation
|
|
64
|
+
this.tabManager = new BrowserTabManager(projectId);
|
|
65
|
+
this.consoleManager = new BrowserConsoleManager();
|
|
66
|
+
this.interactionHandler = new BrowserInteractionHandler();
|
|
67
|
+
this.navigationTracker = new BrowserNavigationTracker();
|
|
68
|
+
this.videoCapture = new BrowserVideoCapture();
|
|
69
|
+
this.dialogHandler = new BrowserDialogHandler();
|
|
70
|
+
this.nativeUIHandler = new BrowserNativeUIHandler();
|
|
71
|
+
|
|
72
|
+
// Forward events from handlers to main service
|
|
73
|
+
this.setupEventForwarding();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private setupEventForwarding() {
|
|
77
|
+
// Forward console events
|
|
78
|
+
this.consoleManager.on('console-message', (data) => {
|
|
79
|
+
this.emit('preview:browser-console-message', data);
|
|
80
|
+
});
|
|
81
|
+
this.consoleManager.on('console-clear', (data) => {
|
|
82
|
+
this.emit('preview:browser-console-clear', data);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Forward interaction events (MCP cursor)
|
|
86
|
+
this.interactionHandler.on('cursor-position', (data) => {
|
|
87
|
+
this.emit('preview:browser-mcp-cursor-position', data);
|
|
88
|
+
});
|
|
89
|
+
this.interactionHandler.on('cursor-click', (data) => {
|
|
90
|
+
this.emit('preview:browser-mcp-cursor-click', data);
|
|
91
|
+
});
|
|
92
|
+
this.interactionHandler.on('test-completed', (data) => {
|
|
93
|
+
this.emit('preview:browser-mcp-test-completed', data);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Forward navigation events and handle video streaming restart
|
|
97
|
+
this.navigationTracker.on('navigation', async (data) => {
|
|
98
|
+
this.emit('preview:browser-navigation', data);
|
|
99
|
+
|
|
100
|
+
// After navigation completes, restart video streaming for the tab
|
|
101
|
+
// This re-injects the peer script and restarts CDP screencast
|
|
102
|
+
const { sessionId } = data;
|
|
103
|
+
if (this.videoCapture.isStreaming(sessionId)) {
|
|
104
|
+
const tab = this.getTab(sessionId);
|
|
105
|
+
if (tab) {
|
|
106
|
+
// Small delay to ensure page is fully loaded
|
|
107
|
+
setTimeout(async () => {
|
|
108
|
+
try {
|
|
109
|
+
const success = await this.videoCapture.handleNavigation(sessionId, tab);
|
|
110
|
+
if (success) {
|
|
111
|
+
this.emit('preview:browser-navigation-streaming-ready', { sessionId });
|
|
112
|
+
}
|
|
113
|
+
} catch (error) {
|
|
114
|
+
// Silently fail - frontend will request refresh if needed
|
|
115
|
+
}
|
|
116
|
+
}, 100);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Forward navigation loading events
|
|
122
|
+
this.navigationTracker.on('navigation-loading', (data) => {
|
|
123
|
+
this.emit('preview:browser-navigation-loading', data);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Forward new window events
|
|
127
|
+
this.tabManager.on('new-window', (data) => {
|
|
128
|
+
this.emit('preview:browser-new-window', data);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Forward tab events (already have correct event names from tab manager)
|
|
132
|
+
this.tabManager.on('preview:browser-tab-opened', (data) => {
|
|
133
|
+
this.emit('preview:browser-tab-opened', data);
|
|
134
|
+
});
|
|
135
|
+
this.tabManager.on('preview:browser-tab-closed', (data) => {
|
|
136
|
+
this.emit('preview:browser-tab-closed', data);
|
|
137
|
+
});
|
|
138
|
+
this.tabManager.on('preview:browser-tab-switched', (data) => {
|
|
139
|
+
this.emit('preview:browser-tab-switched', data);
|
|
140
|
+
});
|
|
141
|
+
this.tabManager.on('preview:browser-tab-navigated', (data) => {
|
|
142
|
+
this.emit('preview:browser-tab-navigated', data);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Forward video capture events
|
|
146
|
+
this.videoCapture.on('ice-candidate', (data) => {
|
|
147
|
+
this.emit('preview:browser-webcodecs-ice-candidate', data);
|
|
148
|
+
});
|
|
149
|
+
this.videoCapture.on('connection-state', (data) => {
|
|
150
|
+
this.emit('preview:browser-webcodecs-connection-state', data);
|
|
151
|
+
});
|
|
152
|
+
this.videoCapture.on('cursor-change', (data) => {
|
|
153
|
+
this.emit('preview:browser-cursor-change', data);
|
|
154
|
+
});
|
|
155
|
+
this.videoCapture.on('navigation-streaming-ready', (data) => {
|
|
156
|
+
this.emit('preview:browser-navigation-streaming-ready', data);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Forward dialog events
|
|
160
|
+
this.dialogHandler.on('dialog', (data) => {
|
|
161
|
+
this.emit('preview:browser-dialog', data);
|
|
162
|
+
});
|
|
163
|
+
this.dialogHandler.on('print', (data) => {
|
|
164
|
+
this.emit('preview:browser-print', data);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Forward native UI events
|
|
168
|
+
this.nativeUIHandler.on('copy-to-clipboard', (data) => {
|
|
169
|
+
this.emit('preview:browser-copy-to-clipboard', data);
|
|
170
|
+
});
|
|
171
|
+
this.nativeUIHandler.on('open-url-new-tab', (data) => {
|
|
172
|
+
this.emit('preview:browser-open-url-new-tab', data);
|
|
173
|
+
});
|
|
174
|
+
this.nativeUIHandler.on('download-image', (data) => {
|
|
175
|
+
this.emit('preview:browser-download-image', data);
|
|
176
|
+
});
|
|
177
|
+
this.nativeUIHandler.on('copy-image-to-clipboard', (data) => {
|
|
178
|
+
this.emit('preview:browser-copy-image-to-clipboard', data);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Get project ID for this service instance
|
|
184
|
+
*/
|
|
185
|
+
getProjectId(): string {
|
|
186
|
+
return this.projectId;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ============================================================================
|
|
190
|
+
// Tab Management Methods
|
|
191
|
+
// ============================================================================
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Create a new tab with optional URL
|
|
195
|
+
*
|
|
196
|
+
* If URL is provided, navigate to it immediately.
|
|
197
|
+
* If URL is not provided, create blank tab (about:blank).
|
|
198
|
+
*
|
|
199
|
+
* Default rotation depends on device size:
|
|
200
|
+
* - Desktop/laptop: landscape
|
|
201
|
+
* - Tablet/mobile: portrait
|
|
202
|
+
*/
|
|
203
|
+
async createTab(url?: string, deviceSize: DeviceSize = 'laptop', rotation?: Rotation): Promise<BrowserTab> {
|
|
204
|
+
// Use device-appropriate default rotation if not specified
|
|
205
|
+
const actualRotation = rotation || ((deviceSize === 'desktop' || deviceSize === 'laptop') ? 'landscape' : 'portrait');
|
|
206
|
+
// Pre-navigation setup callback for dialog bindings
|
|
207
|
+
const preNavigationSetup = async (page: Page) => {
|
|
208
|
+
// Note: We'll setup dialog bindings using tabId after tab is created
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Create tab
|
|
212
|
+
const tab = await this.tabManager.createTab(url, deviceSize, actualRotation, {
|
|
213
|
+
setActive: true,
|
|
214
|
+
preNavigationSetup
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Setup console and navigation tracking
|
|
218
|
+
await this.consoleManager.setupConsoleLogging(tab.id, tab.page, tab);
|
|
219
|
+
await this.navigationTracker.setupNavigationTracking(tab.id, tab.page, tab);
|
|
220
|
+
|
|
221
|
+
// Setup dialog bindings and handling
|
|
222
|
+
await this.dialogHandler.setupDialogBindings(tab.id, tab.page);
|
|
223
|
+
await this.dialogHandler.setupDialogHandling(tab.id, tab.page, tab);
|
|
224
|
+
|
|
225
|
+
return tab;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Navigate tab to a new URL
|
|
230
|
+
*/
|
|
231
|
+
async navigateTab(tabId: string, url: string): Promise<string> {
|
|
232
|
+
const actualUrl = await this.tabManager.navigateTab(tabId, url);
|
|
233
|
+
|
|
234
|
+
// Mark navigation for frame deduplication
|
|
235
|
+
this.markNavigation(tabId, url);
|
|
236
|
+
|
|
237
|
+
return actualUrl;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Close a tab and cleanup its resources
|
|
242
|
+
*/
|
|
243
|
+
async closeTab(tabId: string): Promise<{ success: boolean; newActiveTabId: string | null }> {
|
|
244
|
+
const tab = this.tabManager.getTab(tabId);
|
|
245
|
+
if (!tab) {
|
|
246
|
+
return { success: false, newActiveTabId: null };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Stop WebCodecs streaming first
|
|
250
|
+
await this.stopWebCodecsStreaming(tabId);
|
|
251
|
+
|
|
252
|
+
// Clear cursor tracking for this tab
|
|
253
|
+
this.interactionHandler.clearSessionCursor(tabId);
|
|
254
|
+
|
|
255
|
+
// Clear dialogs for this tab
|
|
256
|
+
this.dialogHandler.clearSessionDialogs(tabId);
|
|
257
|
+
|
|
258
|
+
// Close the tab (this will cleanup context, page, etc.)
|
|
259
|
+
const result = await this.tabManager.closeTab(tabId);
|
|
260
|
+
|
|
261
|
+
// Emit tab closed event (for MCP control manager and other listeners)
|
|
262
|
+
this.emit('preview:browser-tab-destroyed', { tabId });
|
|
263
|
+
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Switch to a specific tab
|
|
269
|
+
*/
|
|
270
|
+
switchTab(tabId: string): boolean {
|
|
271
|
+
return this.tabManager.setActiveTab(tabId);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Get a tab by ID
|
|
276
|
+
*/
|
|
277
|
+
getTab(tabId: string): BrowserTab | null {
|
|
278
|
+
return this.tabManager.getTab(tabId);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Get the active tab
|
|
283
|
+
*/
|
|
284
|
+
getActiveTab(): BrowserTab | null {
|
|
285
|
+
return this.tabManager.getActiveTab();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Get all tabs
|
|
290
|
+
*/
|
|
291
|
+
getAllTabs(): BrowserTab[] {
|
|
292
|
+
return this.tabManager.getAllTabs();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Change viewport settings (device size and rotation) for an existing tab
|
|
297
|
+
*/
|
|
298
|
+
async setViewport(tabId: string, deviceSize: DeviceSize, rotation: Rotation): Promise<boolean> {
|
|
299
|
+
return await this.tabManager.setViewport(tabId, deviceSize, rotation);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Get tab count
|
|
304
|
+
*/
|
|
305
|
+
getTabCount(): number {
|
|
306
|
+
return this.tabManager.getTabCount();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Get tab info
|
|
311
|
+
*/
|
|
312
|
+
getTabInfo(tabId: string): BrowserTabInfo | null {
|
|
313
|
+
return this.tabManager.getTabInfo(tabId);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Get all tabs info
|
|
318
|
+
*/
|
|
319
|
+
getAllTabsInfo(): BrowserTabInfo[] {
|
|
320
|
+
return this.tabManager.getAllTabsInfo();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Get available tab IDs
|
|
325
|
+
*/
|
|
326
|
+
getAvailableTabIds(): string[] {
|
|
327
|
+
return this.tabManager.getAvailableTabIds();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Get tabs status (for admin/debugging)
|
|
332
|
+
*/
|
|
333
|
+
getTabsStatus() {
|
|
334
|
+
return this.tabManager.getTabsStatus();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Update tab title from URL
|
|
339
|
+
*/
|
|
340
|
+
updateTabTitleFromUrl(tabId: string, url: string): void {
|
|
341
|
+
this.tabManager.updateTabTitleFromUrl(tabId, url);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Check if tab is valid
|
|
346
|
+
*/
|
|
347
|
+
isValidTab(tabId: string): boolean {
|
|
348
|
+
const tab = this.getTab(tabId);
|
|
349
|
+
return tab !== null && !tab.isDestroyed;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ============================================================================
|
|
353
|
+
// WebCodecs Streaming Methods (optimized, ~20-40ms, lower bandwidth)
|
|
354
|
+
// ============================================================================
|
|
355
|
+
async startWebCodecsStreaming(tabId: string): Promise<boolean> {
|
|
356
|
+
const tab = this.getTab(tabId);
|
|
357
|
+
if (!tab) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
return await this.videoCapture.startStreaming(
|
|
361
|
+
tabId,
|
|
362
|
+
tab,
|
|
363
|
+
() => this.isValidTab(tabId)
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async stopWebCodecsStreaming(tabId: string): Promise<void> {
|
|
368
|
+
const tab = this.getTab(tabId);
|
|
369
|
+
await this.videoCapture.stopStreaming(tabId, tab ?? undefined);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async updateWebCodecsScale(tabId: string, newScale: number): Promise<boolean> {
|
|
373
|
+
const tab = this.getTab(tabId);
|
|
374
|
+
if (!tab) {
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
return await this.videoCapture.updateScale(tabId, tab, newScale);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async updateWebCodecsViewport(tabId: string, width: number, height: number, newScale: number): Promise<boolean> {
|
|
381
|
+
const tab = this.getTab(tabId);
|
|
382
|
+
if (!tab) {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
return await this.videoCapture.updateViewport(tabId, tab, width, height, newScale);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async getWebCodecsOffer(tabId: string): Promise<RTCSessionDescriptionInit | null> {
|
|
389
|
+
const tab = this.getTab(tabId);
|
|
390
|
+
if (!tab) {
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
return await this.videoCapture.createOffer(tabId, tab);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async handleWebCodecsAnswer(tabId: string, answer: RTCSessionDescriptionInit): Promise<boolean> {
|
|
397
|
+
const tab = this.getTab(tabId);
|
|
398
|
+
if (!tab) {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
return await this.videoCapture.handleAnswer(tabId, tab, answer);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async addWebCodecsIceCandidate(tabId: string, candidate: RTCIceCandidateInit): Promise<boolean> {
|
|
405
|
+
const tab = this.getTab(tabId);
|
|
406
|
+
if (!tab) {
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
return await this.videoCapture.addIceCandidate(tabId, tab, candidate);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
isWebCodecsActive(tabId: string): boolean {
|
|
413
|
+
return this.videoCapture.isStreaming(tabId);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async getWebCodecsStats(tabId: string) {
|
|
417
|
+
const tab = this.getTab(tabId);
|
|
418
|
+
if (!tab) {
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
return await this.videoCapture.getStats(tabId, tab);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
markUserInteraction(tabId: string): void {
|
|
425
|
+
this.tabManager.markTabActivity(tabId);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Public method to mark tab activity (called from WS handlers)
|
|
429
|
+
markActiveTabActivity(): void {
|
|
430
|
+
const tab = this.getActiveTab();
|
|
431
|
+
if (tab) {
|
|
432
|
+
this.tabManager.markTabActivity(tab.id);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
markNavigation(tabId: string, _newUrl?: string): void {
|
|
437
|
+
// Navigation tracking is now handled by WebCodecs automatically
|
|
438
|
+
// This method is kept for API compatibility
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// ============================================================================
|
|
442
|
+
// Console Management Methods
|
|
443
|
+
// ============================================================================
|
|
444
|
+
getConsoleLogs(tabId: string): BrowserConsoleMessage[] {
|
|
445
|
+
const tab = this.getTab(tabId);
|
|
446
|
+
return tab ? this.consoleManager.getConsoleLogs(tab) : [];
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
clearConsoleLogs(tabId: string): boolean {
|
|
450
|
+
const tab = this.getTab(tabId);
|
|
451
|
+
return tab ? this.consoleManager.clearConsoleLogs(tab) : false;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
toggleConsoleLogging(tabId: string, enabled: boolean): boolean {
|
|
455
|
+
const tab = this.getTab(tabId);
|
|
456
|
+
return tab ? this.consoleManager.toggleConsoleLogging(tab, enabled) : false;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
async executeConsoleCommand(tabId: string, command: string): Promise<any> {
|
|
460
|
+
const tab = this.getTab(tabId);
|
|
461
|
+
if (!tab) throw new Error('Tab not found or invalid');
|
|
462
|
+
return this.consoleManager.executeConsoleCommand(tab, command);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ============================================================================
|
|
466
|
+
// Interaction & Autonomous Actions Methods
|
|
467
|
+
// ============================================================================
|
|
468
|
+
async performAutonomousActions(tabId: string, actions: BrowserAutonomousAction[]) {
|
|
469
|
+
const tab = this.getTab(tabId);
|
|
470
|
+
if (!tab) throw new Error('Tab not found or invalid');
|
|
471
|
+
|
|
472
|
+
const results = await this.interactionHandler.performAutonomousActions(
|
|
473
|
+
tabId,
|
|
474
|
+
tab,
|
|
475
|
+
actions,
|
|
476
|
+
() => this.isValidTab(tabId)
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
return results;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Perform autonomous actions using tab object directly
|
|
484
|
+
* More efficient when tab is already available
|
|
485
|
+
*/
|
|
486
|
+
async performAutonomousActionsWithTab(tab: BrowserTab, actions: BrowserAutonomousAction[]) {
|
|
487
|
+
const results = await this.interactionHandler.performAutonomousActions(
|
|
488
|
+
tab.id,
|
|
489
|
+
tab,
|
|
490
|
+
actions,
|
|
491
|
+
() => this.isValidTab(tab.id)
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
return results;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// ============================================================================
|
|
498
|
+
// Dialog Management Methods
|
|
499
|
+
// ============================================================================
|
|
500
|
+
async respondToDialog(response: BrowserDialogResponse): Promise<boolean> {
|
|
501
|
+
return await this.dialogHandler.respondToDialog(response);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// ============================================================================
|
|
505
|
+
// Native UI Methods (Select & Context Menu)
|
|
506
|
+
// ============================================================================
|
|
507
|
+
async checkForSelectElement(tabId: string, x: number, y: number) {
|
|
508
|
+
const tab = this.getTab(tabId);
|
|
509
|
+
if (!tab) return null;
|
|
510
|
+
|
|
511
|
+
const selectInfo = await this.nativeUIHandler.checkForSelect(tabId, tab.page, x, y);
|
|
512
|
+
if (selectInfo) {
|
|
513
|
+
this.emit('preview:browser-select', selectInfo);
|
|
514
|
+
}
|
|
515
|
+
return selectInfo;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
async handleSelectResponse(tabId: string, response: BrowserSelectResponse): Promise<boolean> {
|
|
519
|
+
const tab = this.getTab(tabId);
|
|
520
|
+
if (!tab) return false;
|
|
521
|
+
|
|
522
|
+
return await this.nativeUIHandler.handleSelectResponse(tab.page, response);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async checkForContextMenu(tabId: string, x: number, y: number) {
|
|
526
|
+
const tab = this.getTab(tabId);
|
|
527
|
+
if (!tab) return null;
|
|
528
|
+
|
|
529
|
+
const menuInfo = await this.nativeUIHandler.checkForContextMenu(tabId, tab.page, x, y);
|
|
530
|
+
if (menuInfo) {
|
|
531
|
+
// Store menu info for later action execution
|
|
532
|
+
this.contextMenus.set(menuInfo.menuId, menuInfo);
|
|
533
|
+
this.emit('preview:browser-context-menu', menuInfo);
|
|
534
|
+
}
|
|
535
|
+
return menuInfo;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
async handleContextMenuResponse(tabId: string, response: BrowserContextMenuResponse, clipboardText?: string): Promise<boolean> {
|
|
539
|
+
const tab = this.getTab(tabId);
|
|
540
|
+
if (!tab) return false;
|
|
541
|
+
|
|
542
|
+
// Get stored menu info
|
|
543
|
+
const menuInfo = this.contextMenus.get(response.menuId);
|
|
544
|
+
if (!menuInfo) return false;
|
|
545
|
+
|
|
546
|
+
const result = await this.nativeUIHandler.handleContextMenuResponse(tab.page, response, menuInfo, clipboardText);
|
|
547
|
+
|
|
548
|
+
// Clean up stored menu info
|
|
549
|
+
this.contextMenus.delete(response.menuId);
|
|
550
|
+
|
|
551
|
+
return result;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// ============================================================================
|
|
555
|
+
// Cleanup Methods
|
|
556
|
+
// ============================================================================
|
|
557
|
+
async cleanup() {
|
|
558
|
+
// Clear all cursor tracking
|
|
559
|
+
this.interactionHandler.clearAllSessionCursors();
|
|
560
|
+
// Cleanup tabs (this will also cleanup all contexts/pages/browser pool)
|
|
561
|
+
await this.tabManager.cleanup();
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
async cleanupInactiveTabs() {
|
|
565
|
+
return this.tabManager.cleanupInactiveTabs();
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
async forceCleanupAll() {
|
|
569
|
+
// First try normal cleanup
|
|
570
|
+
await this.cleanup();
|
|
571
|
+
|
|
572
|
+
// Cleanup video capture sessions
|
|
573
|
+
await this.videoCapture.cleanup();
|
|
574
|
+
|
|
575
|
+
// Clear all dialogs and context menus
|
|
576
|
+
this.dialogHandler.clearAllDialogs();
|
|
577
|
+
this.contextMenus.clear();
|
|
578
|
+
|
|
579
|
+
// Remove all listeners to prevent memory leaks
|
|
580
|
+
this.removeAllListeners();
|
|
581
|
+
this.consoleManager.removeAllListeners();
|
|
582
|
+
this.interactionHandler.removeAllListeners();
|
|
583
|
+
this.navigationTracker.removeAllListeners();
|
|
584
|
+
this.videoCapture.removeAllListeners();
|
|
585
|
+
this.dialogHandler.removeAllListeners();
|
|
586
|
+
this.nativeUIHandler.removeAllListeners();
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Browser Preview Service Manager
|
|
592
|
+
*
|
|
593
|
+
* Manages BrowserPreviewService instances per project.
|
|
594
|
+
* Provides project isolation - each project has its own browser tabs and state.
|
|
595
|
+
*/
|
|
596
|
+
class BrowserPreviewServiceManager {
|
|
597
|
+
private services = new Map<string, BrowserPreviewService>();
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Get or create a BrowserPreviewService for a project
|
|
601
|
+
*/
|
|
602
|
+
getService(projectId: string): BrowserPreviewService {
|
|
603
|
+
if (!projectId) {
|
|
604
|
+
throw new Error('projectId is required and cannot be empty');
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
if (!this.services.has(projectId)) {
|
|
608
|
+
debug.log('preview', `🆕 Creating new BrowserPreviewService for project: ${projectId}`);
|
|
609
|
+
const service = new BrowserPreviewService(projectId);
|
|
610
|
+
this.services.set(projectId, service);
|
|
611
|
+
|
|
612
|
+
// Setup WebSocket event forwarding for this service
|
|
613
|
+
this.setupWebSocketForwarding(service, projectId);
|
|
614
|
+
debug.log('preview', `✅ BrowserPreviewService fully initialized for project: ${projectId}`);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
return this.services.get(projectId)!;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Setup WebSocket event forwarding for a service instance
|
|
622
|
+
* Events are emitted to the specific project only
|
|
623
|
+
*/
|
|
624
|
+
private setupWebSocketForwarding(service: BrowserPreviewService, projectId: string): void {
|
|
625
|
+
debug.log('preview', `🔌 Setting up WebSocket forwarding for project: ${projectId}...`);
|
|
626
|
+
|
|
627
|
+
// Forward WebCodecs events
|
|
628
|
+
service.on('preview:browser-webcodecs-ice-candidate', (data) => {
|
|
629
|
+
ws.emit.project(projectId, 'preview:browser-stream-ice', {
|
|
630
|
+
sessionId: data.sessionId,
|
|
631
|
+
candidate: data.candidate,
|
|
632
|
+
from: data.from
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
service.on('preview:browser-webcodecs-connection-state', (data) => {
|
|
637
|
+
ws.emit.project(projectId, 'preview:browser-stream-state', data);
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
service.on('preview:browser-cursor-change', (data) => {
|
|
641
|
+
ws.emit.project(projectId, 'preview:browser-cursor-change', data);
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
// Forward navigation events
|
|
645
|
+
service.on('preview:browser-navigation-loading', (data) => {
|
|
646
|
+
ws.emit.project(projectId, 'preview:browser-navigation-loading', data);
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
service.on('preview:browser-navigation', (data) => {
|
|
650
|
+
ws.emit.project(projectId, 'preview:browser-navigation', data);
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
// Forward tab events
|
|
654
|
+
service.on('preview:browser-tab-opened', (data) => {
|
|
655
|
+
debug.log('preview', `🚀 Forwarding preview:browser-tab-opened to project ${projectId}:`, data);
|
|
656
|
+
ws.emit.project(projectId, 'preview:browser-tab-opened', data);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
service.on('preview:browser-tab-closed', (data) => {
|
|
660
|
+
ws.emit.project(projectId, 'preview:browser-tab-closed', data);
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
service.on('preview:browser-tab-switched', (data) => {
|
|
664
|
+
ws.emit.project(projectId, 'preview:browser-tab-switched', data);
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
service.on('preview:browser-tab-navigated', (data) => {
|
|
668
|
+
ws.emit.project(projectId, 'preview:browser-tab-navigated', data);
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
// Forward console events
|
|
672
|
+
service.on('preview:browser-console-message', (data) => {
|
|
673
|
+
ws.emit.project(projectId, 'preview:browser-console-message', data);
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
service.on('preview:browser-console-clear', (data) => {
|
|
677
|
+
ws.emit.project(projectId, 'preview:browser-console-clear', data);
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
// Forward MCP events
|
|
681
|
+
service.on('preview:browser-mcp-cursor-position', (data) => {
|
|
682
|
+
ws.emit.project(projectId, 'preview:browser-mcp-cursor-position', data);
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
service.on('preview:browser-mcp-cursor-click', (data) => {
|
|
686
|
+
ws.emit.project(projectId, 'preview:browser-mcp-cursor-click', data);
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
service.on('preview:browser-mcp-test-completed', (data) => {
|
|
690
|
+
ws.emit.project(projectId, 'preview:browser-mcp-test-completed', data);
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
// Forward dialog events
|
|
694
|
+
service.on('preview:browser-dialog', (data) => {
|
|
695
|
+
ws.emit.project(projectId, 'preview:browser-dialog', data);
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
service.on('preview:browser-print', (data) => {
|
|
699
|
+
ws.emit.project(projectId, 'preview:browser-print', data);
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
// Forward native UI events
|
|
703
|
+
service.on('preview:browser-select', (data) => {
|
|
704
|
+
ws.emit.project(projectId, 'preview:browser-select', data);
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
service.on('preview:browser-context-menu', (data) => {
|
|
708
|
+
ws.emit.project(projectId, 'preview:browser-context-menu', data);
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
service.on('preview:browser-copy-to-clipboard', (data) => {
|
|
712
|
+
ws.emit.project(projectId, 'preview:browser-copy-to-clipboard', data);
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
service.on('preview:browser-open-url-new-tab', (data) => {
|
|
716
|
+
ws.emit.project(projectId, 'preview:browser-open-url-new-tab', data);
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
service.on('preview:browser-download-image', (data) => {
|
|
720
|
+
ws.emit.project(projectId, 'preview:browser-download-image', data);
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
service.on('preview:browser-copy-image-to-clipboard', (data) => {
|
|
724
|
+
ws.emit.project(projectId, 'preview:browser-copy-image-to-clipboard', data);
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
// Forward new window events
|
|
728
|
+
service.on('preview:browser-new-window', (data) => {
|
|
729
|
+
ws.emit.project(projectId, 'preview:browser-new-window', data);
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
// Forward MCP control events (from singleton browserMcpControl)
|
|
733
|
+
browserMcpControl.on('control-start', (data) => {
|
|
734
|
+
debug.log('preview', `🚀 Forwarding mcp-control-start to project ${projectId}:`, data);
|
|
735
|
+
ws.emit.project(projectId, 'preview:browser-mcp-control-start', {
|
|
736
|
+
browserSessionId: data.browserTabId,
|
|
737
|
+
mcpSessionId: data.mcpSessionId,
|
|
738
|
+
timestamp: data.timestamp
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
browserMcpControl.on('control-end', (data) => {
|
|
743
|
+
debug.log('preview', `🚀 Forwarding mcp-control-end to project ${projectId}:`, data);
|
|
744
|
+
ws.emit.project(projectId, 'preview:browser-mcp-control-end', {
|
|
745
|
+
browserSessionId: data.browserTabId,
|
|
746
|
+
timestamp: data.timestamp
|
|
747
|
+
});
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
// Forward MCP cursor events
|
|
751
|
+
browserMcpControl.on('cursor-position', (data) => {
|
|
752
|
+
ws.emit.project(projectId, 'preview:browser-mcp-cursor-position', {
|
|
753
|
+
sessionId: data.tabId,
|
|
754
|
+
x: data.x,
|
|
755
|
+
y: data.y,
|
|
756
|
+
timestamp: data.timestamp,
|
|
757
|
+
source: 'mcp'
|
|
758
|
+
});
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
browserMcpControl.on('cursor-click', (data) => {
|
|
762
|
+
ws.emit.project(projectId, 'preview:browser-mcp-cursor-click', {
|
|
763
|
+
sessionId: data.tabId,
|
|
764
|
+
x: data.x,
|
|
765
|
+
y: data.y,
|
|
766
|
+
timestamp: data.timestamp,
|
|
767
|
+
source: 'mcp'
|
|
768
|
+
});
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
browserMcpControl.on('test-completed', (data) => {
|
|
772
|
+
ws.emit.project(projectId, 'preview:browser-mcp-test-completed', {
|
|
773
|
+
sessionId: data.tabId,
|
|
774
|
+
timestamp: data.timestamp,
|
|
775
|
+
source: 'mcp'
|
|
776
|
+
});
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
debug.log('preview', `🎉 All WebSocket event listeners registered for project: ${projectId}`);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* Check if a service exists for a project
|
|
784
|
+
*/
|
|
785
|
+
hasService(projectId: string): boolean {
|
|
786
|
+
if (!projectId) {
|
|
787
|
+
throw new Error('projectId is required and cannot be empty');
|
|
788
|
+
}
|
|
789
|
+
return this.services.has(projectId);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Remove a service for a project (cleanup)
|
|
794
|
+
*/
|
|
795
|
+
async removeService(projectId: string): Promise<void> {
|
|
796
|
+
if (!projectId) {
|
|
797
|
+
throw new Error('projectId is required and cannot be empty');
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
const service = this.services.get(projectId);
|
|
801
|
+
|
|
802
|
+
if (service) {
|
|
803
|
+
await service.forceCleanupAll();
|
|
804
|
+
this.services.delete(projectId);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* Cleanup all services
|
|
810
|
+
*/
|
|
811
|
+
async cleanup(): Promise<void> {
|
|
812
|
+
const cleanupPromises = Array.from(this.services.values()).map(service =>
|
|
813
|
+
service.forceCleanupAll().catch(error => {
|
|
814
|
+
console.error('Error cleaning up service:', error);
|
|
815
|
+
})
|
|
816
|
+
);
|
|
817
|
+
|
|
818
|
+
await Promise.all(cleanupPromises);
|
|
819
|
+
this.services.clear();
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Get all active project IDs
|
|
824
|
+
*/
|
|
825
|
+
getActiveProjects(): string[] {
|
|
826
|
+
return Array.from(this.services.keys());
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Get stats for all services
|
|
831
|
+
*/
|
|
832
|
+
getStats() {
|
|
833
|
+
const stats = new Map<string, any>();
|
|
834
|
+
|
|
835
|
+
for (const [projectId, service] of this.services.entries()) {
|
|
836
|
+
stats.set(projectId, {
|
|
837
|
+
projectId,
|
|
838
|
+
tabs: service.getTabsStatus()
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
return stats;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// Service manager instance (singleton)
|
|
847
|
+
export const browserPreviewServiceManager = new BrowserPreviewServiceManager();
|
|
848
|
+
|
|
849
|
+
// Graceful shutdown handlers
|
|
850
|
+
const gracefulShutdown = async (signal: string) => {
|
|
851
|
+
try {
|
|
852
|
+
await browserPreviewServiceManager.cleanup();
|
|
853
|
+
process.exit(0);
|
|
854
|
+
} catch (error) {
|
|
855
|
+
process.exit(1);
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
|
|
859
|
+
// Handle various termination signals
|
|
860
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
861
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
862
|
+
process.on('SIGHUP', () => gracefulShutdown('SIGHUP'));
|
|
863
|
+
|
|
864
|
+
// Handle Windows-specific signals
|
|
865
|
+
if (process.platform === 'win32') {
|
|
866
|
+
process.on('SIGBREAK', () => gracefulShutdown('SIGBREAK'));
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// Handle uncaught exceptions and unhandled rejections
|
|
870
|
+
process.on('uncaughtException', async (error) => {
|
|
871
|
+
await browserPreviewServiceManager.cleanup();
|
|
872
|
+
process.exit(1);
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
process.on('unhandledRejection', async (reason, promise) => {
|
|
876
|
+
await browserPreviewServiceManager.cleanup();
|
|
877
|
+
process.exit(1);
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
// Handle process exit
|
|
881
|
+
process.on('exit', (code) => {
|
|
882
|
+
});
|