diff --git a/.github/workflows/development.yaml b/.github/workflows/development.yaml index 5fb1ab85d..25b0592c7 100644 --- a/.github/workflows/development.yaml +++ b/.github/workflows/development.yaml @@ -23,7 +23,9 @@ env: VITE_CANNY_URL: ${{ vars.VITE_CANNY_URL }} VITE_BASE_URL: ${{ vars.VITE_BASE_URL }} VITE_SPARROW_LINKEDIN: ${{ vars.VITE_SPARROW_LINKEDIN }} + VITE_WEB_SOCKET_IO_API_URL: ${{ vars.VITE_WEB_SOCKET_IO_API_URL }} ACTIONS_ALLOW_UNSECURE_COMMANDS: true + CI: false jobs: release_win: @@ -62,6 +64,9 @@ jobs: Set-Content -Path apps/@sparrow-desktop/src-tauri/tauri.conf.json -Value $newContent shell: pwsh + - name: Increase Yarn network timeout + run: yarn config set network-timeout 600000 + - name: Build Tauri App run: | yarn cache clean @@ -130,6 +135,9 @@ jobs: echo "$newContent" > apps/@sparrow-desktop/src-tauri/tauri.conf.json shell: bash + - name: Increase Yarn network timeout + run: yarn config set network-timeout 600000 + - name: Build Tauri App run: | yarn cache clean @@ -145,6 +153,7 @@ jobs: APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + CI: false - name: file content run: | diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 18125ac29..81c4d79b8 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -23,7 +23,9 @@ env: VITE_CANNY_URL: ${{ vars.VITE_CANNY_URL }} VITE_BASE_URL: ${{ vars.VITE_BASE_URL }} VITE_SPARROW_LINKEDIN: ${{ vars.VITE_SPARROW_LINKEDIN }} + VITE_WEB_SOCKET_IO_API_URL: ${{ vars.VITE_WEB_SOCKET_IO_API_URL }} ACTIONS_ALLOW_UNSECURE_COMMANDS: true + CI: false jobs: release_win: @@ -62,6 +64,9 @@ jobs: Set-Content -Path apps/@sparrow-desktop/src-tauri/tauri.conf.json -Value $newContent shell: pwsh + - name: Increase Yarn network timeout + run: yarn config set network-timeout 600000 + - name: Build Tauri App run: | yarn cache clean @@ -130,6 +135,9 @@ jobs: echo "$newContent" > apps/@sparrow-desktop/src-tauri/tauri.conf.json shell: bash + - name: Increase Yarn network timeout + run: yarn config set network-timeout 600000 + - name: Build Tauri App run: | yarn cache clean @@ -146,6 +154,7 @@ jobs: APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + CI: false - name: dmg sig run: cat /Users/runner/work/sparrow-app/sparrow-app/apps/@sparrow-desktop/src-tauri/target/debug/bundle/macos/*.sig diff --git a/.github/workflows/sparrow-web-dev.yml b/.github/workflows/sparrow-web-dev.yml index 52588be0c..19788d86a 100644 --- a/.github/workflows/sparrow-web-dev.yml +++ b/.github/workflows/sparrow-web-dev.yml @@ -36,6 +36,7 @@ jobs: --build-arg VITE_WEB_MARKETING_URL=${{ vars.VITE_WEB_MARKETING_URL }} \ --build-arg VITE_WEB_SPARROW_DOCS=${{ vars.VITE_WEB_SPARROW_DOCS }} \ --build-arg VITE_WEB_PROXY_SERVICE=${{ vars.VITE_WEB_PROXY_SERVICE }} \ + --build-arg VITE_WEB_SOCKET_IO_API_URL=${{ vars.VITE_WEB_SOCKET_IO_API_URL }} \ -t sparrowprod.azurecr.io/sparrow-web:${{ github.run_number }} . docker push sparrowprod.azurecr.io/sparrow-web:${{ github.run_number }} diff --git a/.github/workflows/sparrow-web-prod.yml b/.github/workflows/sparrow-web-prod.yml index c54c63c41..d60a1baf3 100644 --- a/.github/workflows/sparrow-web-prod.yml +++ b/.github/workflows/sparrow-web-prod.yml @@ -36,6 +36,7 @@ jobs: --build-arg VITE_WEB_MARKETING_URL=${{ vars.VITE_WEB_MARKETING_URL }} \ --build-arg VITE_WEB_SPARROW_DOCS=${{ vars.VITE_WEB_SPARROW_DOCS }} \ --build-arg VITE_WEB_PROXY_SERVICE=${{ vars.VITE_WEB_PROXY_SERVICE }} \ + --build-arg VITE_WEB_SOCKET_IO_API_URL=${{ vars.VITE_WEB_SOCKET_IO_API_URL }} \ -t sparrowprod.azurecr.io/sparrow-web:${{ github.run_number }} . docker push sparrowprod.azurecr.io/sparrow-web:${{ github.run_number }} diff --git a/apps/@sparrow-desktop/package.json b/apps/@sparrow-desktop/package.json index 88bbdcb17..4c629b174 100644 --- a/apps/@sparrow-desktop/package.json +++ b/apps/@sparrow-desktop/package.json @@ -3,7 +3,7 @@ "private": true, "author": "Sparrow", "description": "The Sparrow App is your next go to API development buddy which can help you test, debug, distribute better APIs while collaborating with your colleagues and making you a better programmer.", - "version": "2.13.0", + "version": "2.14.0", "type": "module", "scripts": { "dev": "vite", @@ -132,19 +132,19 @@ "assets": [ { "path": "/Users/runner/work/sparrow-app/sparrow-app/apps/@sparrow-desktop/src-tauri/target/debug/bundle/dmg/*.dmg", - "label": "Sparrow_2.13.0.dmg" + "label": "Sparrow_2.14.0.dmg" }, { "path": "msi_files/*.exe", - "label": "Sparrow_2.13.0.exe" + "label": "Sparrow_2.14.0.exe" }, { "path": "msi_files/*.msi", - "label": "Sparrow_2.13.0.msi" + "label": "Sparrow_2.14.0.msi" }, { "path": "msi_files/*.zip", - "label": "Sparrow_2.13.0_x64_en_US.msi.zip" + "label": "Sparrow_2.14.0_x64_en_US.msi.zip" }, { "path": "/Users/runner/work/sparrow-app/sparrow-app/apps/@sparrow-desktop/src-tauri/target/debug/bundle/macos/Sparrow.app.tar.gz", diff --git a/apps/@sparrow-desktop/src-tauri/Cargo.lock b/apps/@sparrow-desktop/src-tauri/Cargo.lock index b60fd5caa..242069da6 100644 --- a/apps/@sparrow-desktop/src-tauri/Cargo.lock +++ b/apps/@sparrow-desktop/src-tauri/Cargo.lock @@ -4246,6 +4246,7 @@ version = "0.0.0" dependencies = [ "anyhow", "brotli 3.5.0", + "cocoa", "dotenv", "flate2", "futures", diff --git a/apps/@sparrow-desktop/src-tauri/Cargo.toml b/apps/@sparrow-desktop/src-tauri/Cargo.toml index 646de3ccf..d53a34ae8 100644 --- a/apps/@sparrow-desktop/src-tauri/Cargo.toml +++ b/apps/@sparrow-desktop/src-tauri/Cargo.toml @@ -48,5 +48,7 @@ tauri-plugin-shell = "2.2.0" tauri-plugin-dialog = "2.2.0" tauri-plugin-fs = "2.2.0" tauri-plugin-deep-link = "2.2.0" + [target.'cfg(target_os = "macos")'.dependencies] objc = "0.2" +cocoa = "0.26.0" diff --git a/apps/@sparrow-desktop/src-tauri/capabilities/migrated.json b/apps/@sparrow-desktop/src-tauri/capabilities/migrated.json index 7b39dbb3f..9ee8acb0b 100644 --- a/apps/@sparrow-desktop/src-tauri/capabilities/migrated.json +++ b/apps/@sparrow-desktop/src-tauri/capabilities/migrated.json @@ -2,7 +2,7 @@ "identifier": "migrated", "description": "permissions that were migrated from v1", "context": "local", - "windows": ["main"], + "windows": ["main", "windows"], "permissions": [ "core:path:default", "core:event:default", diff --git a/apps/@sparrow-desktop/src-tauri/gen/schemas/capabilities.json b/apps/@sparrow-desktop/src-tauri/gen/schemas/capabilities.json index dda6971a1..a093a75d6 100644 --- a/apps/@sparrow-desktop/src-tauri/gen/schemas/capabilities.json +++ b/apps/@sparrow-desktop/src-tauri/gen/schemas/capabilities.json @@ -1 +1 @@ -{"migrated":{"identifier":"migrated","description":"permissions that were migrated from v1","local":true,"windows":["main"],"permissions":["core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","deep-link:default","core:window:allow-center","core:window:allow-maximize","core:window:allow-toggle-maximize","core:window:allow-unmaximize","core:window:allow-minimize","core:window:allow-unminimize","core:window:allow-show","core:window:allow-hide","core:window:allow-close","core:window:allow-set-size","core:window:allow-set-fullscreen","core:window:allow-set-focus","core:window:allow-start-dragging","core:window:allow-start-resize-dragging","os:allow-platform","deep-link:allow-get-current","core:window:allow-set-shadow","updater:allow-check","updater:default","process:allow-restart","core:webview:allow-internal-toggle-devtools","core:webview:default","core:webview:allow-set-webview-focus","core:webview:allow-set-webview-zoom","shell:allow-open","dialog:allow-save","fs:allow-write-file","fs:allow-write-text-file","core:window:allow-set-max-size","core:window:allow-set-position"],"platforms":["linux","macOS","windows","android","iOS"]}} \ No newline at end of file +{"migrated":{"identifier":"migrated","description":"permissions that were migrated from v1","local":true,"windows":["main","windows"],"permissions":["core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","deep-link:default","core:window:allow-center","core:window:allow-maximize","core:window:allow-toggle-maximize","core:window:allow-unmaximize","core:window:allow-minimize","core:window:allow-unminimize","core:window:allow-show","core:window:allow-hide","core:window:allow-close","core:window:allow-set-size","core:window:allow-set-fullscreen","core:window:allow-set-focus","core:window:allow-start-dragging","core:window:allow-start-resize-dragging","os:allow-platform","deep-link:allow-get-current","core:window:allow-set-shadow","updater:allow-check","updater:default","process:allow-restart","core:webview:allow-internal-toggle-devtools","core:webview:default","core:webview:allow-set-webview-focus","core:webview:allow-set-webview-zoom","shell:allow-open","dialog:allow-save","fs:allow-write-file","fs:allow-write-text-file","core:window:allow-set-max-size","core:window:allow-set-position"],"platforms":["linux","macOS","windows","android","iOS"]}} \ No newline at end of file diff --git a/apps/@sparrow-desktop/src-tauri/gen/schemas/windows-schema.json b/apps/@sparrow-desktop/src-tauri/gen/schemas/windows-schema.json index a46a74266..7355bdc2f 100644 --- a/apps/@sparrow-desktop/src-tauri/gen/schemas/windows-schema.json +++ b/apps/@sparrow-desktop/src-tauri/gen/schemas/windows-schema.json @@ -84,7 +84,7 @@ } }, "permissions": { - "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ```", + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", "type": "array", "items": { "$ref": "#/definitions/PermissionEntry" @@ -140,7 +140,7 @@ "identifier": { "anyOf": [ { - "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n", + "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n", "type": "string", "const": "fs:default" }, @@ -984,6 +984,11 @@ "type": "string", "const": "fs:allow-seek" }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-size" + }, { "description": "Enables the stat command without any pre-configured scope.", "type": "string", @@ -1109,6 +1114,11 @@ "type": "string", "const": "fs:deny-seek" }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-size" + }, { "description": "Denies the stat command without any pre-configured scope.", "type": "string", @@ -1581,7 +1591,7 @@ "description": "FS scope entry.", "anyOf": [ { - "description": "FS scope path.", + "description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", "type": "string" }, { @@ -1591,7 +1601,7 @@ ], "properties": { "path": { - "description": "FS scope path.", + "description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", "type": "string" } } @@ -1605,7 +1615,7 @@ "description": "FS scope entry.", "anyOf": [ { - "description": "FS scope path.", + "description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", "type": "string" }, { @@ -1615,7 +1625,7 @@ ], "properties": { "path": { - "description": "FS scope path.", + "description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", "type": "string" } } @@ -3458,7 +3468,7 @@ "const": "dialog:deny-save" }, { - "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n", + "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n", "type": "string", "const": "fs:default" }, @@ -4302,6 +4312,11 @@ "type": "string", "const": "fs:allow-seek" }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-size" + }, { "description": "Enables the stat command without any pre-configured scope.", "type": "string", @@ -4427,6 +4442,11 @@ "type": "string", "const": "fs:deny-seek" }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-size" + }, { "description": "Denies the stat command without any pre-configured scope.", "type": "string", diff --git a/apps/@sparrow-desktop/src-tauri/src/main.rs b/apps/@sparrow-desktop/src-tauri/src/main.rs index 51975de03..94ce7e037 100644 --- a/apps/@sparrow-desktop/src-tauri/src/main.rs +++ b/apps/@sparrow-desktop/src-tauri/src/main.rs @@ -80,19 +80,113 @@ use tokio_tungstenite::connect_async; use tokio_tungstenite::tungstenite::protocol::Message; // Socket.IO imports +use futures_util::FutureExt; use rust_socketio::{ asynchronous::{Client as SocketClient, ClientBuilder}, Event as SocketIoEvent, Payload as SocketIoPayload, TransportType, }; - use tokio::sync::Mutex as SocketMutex; +use tauri_plugin_os::platform; -use futures_util::FutureExt; +// MacOs Window Titlebar Config Imports #[cfg(target_os = "macos")] +#[macro_use] extern crate objc; +#[cfg(target_os = "macos")] +extern crate cocoa; + +#[cfg(target_os = "macos")] +use cocoa::appkit::{NSWindow, NSWindowButton, NSWindowStyleMask, NSWindowTitleVisibility}; +use tauri::{Runtime, WebviewWindow}; + + +pub trait WindowExt { + fn set_transparent_titlebar(&self, title_transparent: bool, remove_toolbar: bool); + fn set_toolbar_visibility(&self, visible: bool); +} + +// Implementation for WebviewWindow +#[cfg(target_os = "macos")] +impl WindowExt for WebviewWindow { + fn set_transparent_titlebar(&self, title_transparent: bool, remove_toolbar: bool) { + unsafe { + let id = self.ns_window().unwrap() as cocoa::base::id; + + // Set titlebar transparency + NSWindow::setTitlebarAppearsTransparent_(id, cocoa::base::YES); + let mut style_mask = id.styleMask(); + style_mask.set( + NSWindowStyleMask::NSFullSizeContentViewWindowMask, + title_transparent, + ); + id.setStyleMask_(style_mask); + + // Adjust toolbar visibility + if remove_toolbar { + self.set_toolbar_visibility(false); + } + + id.setTitleVisibility_(if title_transparent { + NSWindowTitleVisibility::NSWindowTitleHidden + } else { + NSWindowTitleVisibility::NSWindowTitleVisible + }); + + id.setTitlebarAppearsTransparent_(if title_transparent { + cocoa::base::YES + } else { + cocoa::base::NO + }); + } + } + + fn set_toolbar_visibility(&self, visible: bool) { + unsafe { + let id = self.ns_window().unwrap() as cocoa::base::id; + + let visibility = if visible { + cocoa::base::NO + } else { + cocoa::base::YES + }; + let buttons = [ + id.standardWindowButton_(NSWindowButton::NSWindowCloseButton), + id.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton), + id.standardWindowButton_(NSWindowButton::NSWindowZoomButton), + ]; + + for button in buttons { + let _: () = msg_send![button, setHidden: visibility]; + } + } + } +} + +// Windows/Linux placeholder implementation (no-op) +#[cfg(not(target_os = "macos"))] +impl WindowExt for WebviewWindow { + fn set_transparent_titlebar(&self, _title_transparent: bool, _remove_toolbar: bool) { + // No-op: Not supported on Windows or Linux + } + + fn set_toolbar_visibility(&self, _visible: bool) { + // No-op: Not supported on Windows or Linux + } +} + // Commands +#[tauri::command] +fn hide_toolbar(window: tauri::WebviewWindow) { + window.set_toolbar_visibility(false); +} + +#[tauri::command] +fn show_toolbar(window: tauri::WebviewWindow) { + window.set_toolbar_visibility(true); +} + #[tauri::command] fn fetch_swagger_url_command(url: &str, headers: &str, workspaceid: &str) -> Value { let response = import_swagger_url(url, headers, workspaceid); @@ -1139,6 +1233,25 @@ fn main() { app.manage(Arc::new(SocketIoAppState { connections: Mutex::new(std::collections::HashMap::new()), })); + + // Hide Titlebar for MacOS and close the additional window + let platform_name = platform(); + if platform_name == "macos" { + // Fetch tauri windows + let macos_window = app.get_webview_window("main").unwrap(); + let windows_window: WebviewWindow = app.get_webview_window("windows").unwrap(); + + // Close Windows window which has decoration set to false + let _ = windows_window.close(); + + // Hide Titlebar + macos_window.set_transparent_titlebar(true, true); + } else { + // Close Mac window which has decoration set to true + let macos_window = app.get_webview_window("main").unwrap(); + let _ = macos_window.close(); + } + Ok(()) }) .invoke_handler(tauri::generate_handler![ @@ -1156,7 +1269,9 @@ fn main() { connect_socket_io, disconnect_socket_io, send_socket_io_message, - send_graphql_request + send_graphql_request, + show_toolbar, + hide_toolbar ]) .on_page_load(|wry_window, _payload| { if let Ok(url) = wry_window.url() { diff --git a/apps/@sparrow-desktop/src-tauri/tauri.conf.json b/apps/@sparrow-desktop/src-tauri/tauri.conf.json index 0cb49d351..830869b68 100644 --- a/apps/@sparrow-desktop/src-tauri/tauri.conf.json +++ b/apps/@sparrow-desktop/src-tauri/tauri.conf.json @@ -6,7 +6,7 @@ "windows": [ { "center": true, - "decorations": false, + "decorations": true, "fullscreen": false, "label": "main", "minHeight": 700, @@ -17,9 +17,23 @@ "focus": true, "dragDropEnabled": false, "maximized": true + }, + { + "center": true, + "decorations": false, + "fullscreen": false, + "label": "windows", + "minHeight": 700, + "minWidth": 1200, + "resizable": true, + "title": "Sparrow", + "shadow": true, + "focus": true, + "dragDropEnabled": false, + "maximized": true } ], - "withGlobalTauri": false + "withGlobalTauri": true }, "build": { "beforeBuildCommand": "yarn build", @@ -57,7 +71,7 @@ }, "windowSize": { "height": 500, - "width": 700 + "width": 770 }, "windowPosition": { "x": 400, @@ -85,5 +99,5 @@ } }, "productName": "Sparrow", - "version": "2.13.0" + "version": "2.14.0" } diff --git a/apps/@sparrow-desktop/src/components/App.svelte b/apps/@sparrow-desktop/src/components/App.svelte index f114b2634..5b9ea1a3e 100644 --- a/apps/@sparrow-desktop/src/components/App.svelte +++ b/apps/@sparrow-desktop/src/components/App.svelte @@ -16,6 +16,9 @@ import { singleInstanceHandler } from "@app/utils/singleinstance/app.singleinstance"; import { AppViewModel } from "./app.ViewModel"; import { getScaleFactor, setScaleFactorToDb } from "@app/utils/zoom"; + import { listen } from "@tauri-apps/api/event"; + import { invoke } from "@tauri-apps/api/core"; + import { platform } from "@tauri-apps/plugin-os"; const _viewModel = new AppViewModel(); @@ -29,7 +32,20 @@ } else isActiveInternet = true; }; + // Hide Toolbar when window is restored from maximized state + async function setupMaximizeToggleListenerForMac() { + if (platform() === "macos") { + listen("tauri://resize", async () => { + const isMaximized = await getCurrentWindow().isFullscreen(); + if (!isMaximized) { + await invoke("hide_toolbar"); + } + }); + } + } + onMount(async () => { + setupMaximizeToggleListenerForMac(); await _viewModel.registerDeepLinkHandler(); await singleInstanceHandler(); await setScaleFactorToDb(await getScaleFactor()); diff --git a/apps/@sparrow-desktop/src/components/app.ViewModel.ts b/apps/@sparrow-desktop/src/components/app.ViewModel.ts index e04360b76..482b5f34d 100644 --- a/apps/@sparrow-desktop/src/components/app.ViewModel.ts +++ b/apps/@sparrow-desktop/src/components/app.ViewModel.ts @@ -212,7 +212,7 @@ export class AppViewModel { isGuestUserActive.set(true); navigate("/guest/collections"); MixpanelEvent(Events.CONTINUE_WITHOUT_SIGNUP, { - source: "Entry Page", + source: "Sparrow Auth", }); } }; diff --git a/apps/@sparrow-desktop/src/containers/api/api.common.ts b/apps/@sparrow-desktop/src/containers/api/api.common.ts index 0c469f00d..633d01c49 100644 --- a/apps/@sparrow-desktop/src/containers/api/api.common.ts +++ b/apps/@sparrow-desktop/src/containers/api/api.common.ts @@ -400,7 +400,7 @@ const addSocketDataToMap = (tabId, url) => { try { updateSocketDataStore( tabId, - `Connected from ${url}`, + `Connected to ${url}`, "connecter", "connected", ); @@ -456,7 +456,7 @@ async function processMessageEvent(tabId, event) { // Retrieve tab data and check event inclusion const tabData = await tabRepository.getTabByTabId(tabId); const tabDataJSON = tabData?.toMutableJSON(); - console.log(event,"event"); + console.log(event, "event"); const socketIOresponse = event.payload; const eventName = socketIOresponse.event; const message = socketIOresponse.message; @@ -631,7 +631,7 @@ const connectWebSocket = async ( const wsData = webSocketDataMap.get(tabId); if (wsData) { wsData.messages.unshift({ - data: `Connected from ${url}`, + data: `Connected to ${url}`, transmitter: "connecter", timestamp: formatTime(new Date()), uuid: uuidv4(), diff --git a/apps/@sparrow-desktop/src/pages/auth-page/Auth.svelte b/apps/@sparrow-desktop/src/pages/auth-page/Auth.svelte index f811dcf02..571f4fcac 100644 --- a/apps/@sparrow-desktop/src/pages/auth-page/Auth.svelte +++ b/apps/@sparrow-desktop/src/pages/auth-page/Auth.svelte @@ -1,5 +1,6 @@ -{#if isEntry} - -

- If your browser doesn’t open automatically, please visit - Sparrow website - - - - - to sign In or copy URL - { - await copyToClipBoard(externalSparrowLink); - notifications.success("Link copied to clipboard."); + + {#if isTokenFormEnabled} +

+
- -
-

- Welcome to Sparrow! -

-

- The only API Sidekick you need -

+ {/if} +
+
+
+

Sparrow

+
+
+

+ Welcome to Sparrow! +

+

+ The only API Sidekick you need +

+
+ + {#if !isTokenFormEnabled}
(isPressed = true)} on:mouseup={() => (isPressed = false)} on:mouseleave={() => (isPressed = false)} - class="btn btn-primary w-100 border-0 {isPressed ? 'shadow-pressed' : 'shadow-none'}" + class="btn btn-primary w-100 border-0 {isPressed + ? 'shadow-pressed' + : 'shadow-none'}" on:click={() => { handleRedirect(true); openDefaultBrowser(); + isTokenFormEnabled = true; }} id="create_account_or_sign_in" > @@ -206,41 +170,143 @@ signing up-just the essentials to get started fast.
- + {:else} - -{/if} + {/if} + + +
{ - // debugger; - await invoke("send_websocket_message", { tabid: tab_id, message: message }) - .then(async (data: string) => { - try { - // Logic to handle response + // const selectedAgent = localStorage.getItem("selectedAgent"); + console.log("message", message); - webSocketDataStore.update((webSocketDataMap) => { - const wsData = webSocketDataMap.get(tab_id); - if (wsData) { - wsData.messages.unshift({ - data: message, - transmitter: "sender", - timestamp: formatTime(new Date()), - uuid: uuidv4(), - }); - webSocketDataMap.set(tab_id, wsData); - } - return webSocketDataMap; + try { + let listener; + webSocketDataStore.update((webSocketDataMap) => { + const wsData = webSocketDataMap.get(tab_id); + if (wsData) { + listener = wsData.listener; + // Check for the original agent type from the store, not the currently selected agent + // if (wsData.agent === WorkspaceUserAgentBaseEnum.BROWSER_AGENT) { + wsData.messages.unshift({ + data: message, + transmitter: "sender", + timestamp: formatTime(new Date()), + uuid: uuidv4(), }); - } catch (e) { - console.error(e); - return error("error"); + listener.send(message); + webSocketDataMap.set(tab_id, wsData); + // } } - }) - .catch((e) => { - console.error(e); - return error("error"); + return webSocketDataMap; }); + } catch (e) { + console.error(e); + notifications.error("Failed to send message"); + return error("error"); + } }; /** * Disconnects a WebSocket connection for a specific tab and handles the response. - * - * @param tab_id - The ID of the tab for which the WebSocket connection should be disconnected. - * */ const disconnectWebSocket = async (tab_id: string) => { let url = ""; @@ -300,40 +301,27 @@ const disconnectWebSocket = async (tab_id: string) => { } return webSocketDataMap; }); - await invoke("disconnect_websocket", { tabid: tab_id }) - .then(async (data: string) => { - try { - // Logic to handle response - webSocketDataStore.update((webSocketDataMap) => { - const wsData = webSocketDataMap.get(tab_id); - if (wsData) { - wsData.messages.unshift({ - data: `Disconnected from ${url}`, - transmitter: "disconnector", - timestamp: formatTime(new Date()), - uuid: uuidv4(), - }); - wsData.status = "disconnected"; - webSocketDataMap.set(tab_id, wsData); - } - return webSocketDataMap; - }); - notifications.success("WebSocket disconnected successfully."); - } catch (e) { - console.error(e); - notifications.error( - "Failed to disconnect WebSocket. Please try again.", - ); - return error("error"); - } - }) - .catch((e) => { - console.error(e); - notifications.error("Failed to disconnect WebSocket. Please try again."); - return error("error"); - }); + + webSocketDataStore.update((webSocketDataMap) => { + const wsData = webSocketDataMap.get(tab_id); + + if (wsData) { + const socketInsta = wsData.listener; + // if (wsData.agent === WorkspaceUserAgentBaseEnum.BROWSER_AGENT) { + socketInsta.close(); + // } + } + return webSocketDataMap; + }); }; +/** + * Disconnects a WebSocket connection for a specific tab and handles the response. + * + * @param tab_id - The ID of the tab for which the WebSocket connection should be disconnected. + * + */ + const convertWebSocketUrl = (url: string) => { // Check if the URL starts with 'wss://' if (url.startsWith("wss://")) { @@ -364,84 +352,114 @@ const connectWebSocket = async ( tabId: string, requestHeaders: string, ) => { - // debugger; - const httpurl = convertWebSocketUrl(url); - console.table({ url, httpurl, tabId, requestHeaders }); - webSocketDataStore.update((webSocketDataMap) => { - webSocketDataMap.set(tabId, { - messages: [], - status: "connecting", - search: "", - contentType: RequestDataTypeEnum.TEXT, - body: "", - filter: "All Messages", - url: url, + const selectedAgent = localStorage.getItem( + "selectedAgent", + ) as WorkspaceUserAgentBaseEnum; + // if (selectedAgent === "Browser Agent") { + + try { + // Parse the headers string to array of header objects + const headers = JSON.parse(requestHeaders); + + // Initialize WebSocket store + webSocketDataStore.update((webSocketDataMap) => { + webSocketDataMap.set(tabId, { + messages: [], + status: "connecting", + agent: selectedAgent, + search: "", + contentType: RequestDataTypeEnum.TEXT, + body: "", + filter: "All Messages", + url: url, + }); + return webSocketDataMap; }); - return webSocketDataMap; - }); - await invoke("connect_websocket", { - url: url, - httpurl: httpurl, - tabid: tabId, - headers: requestHeaders, - }) - .then(async (data: string) => { - try { - // Logic to handle response - if (data) { - const dt = JSON.parse(data); - } - // Store the WebSocket and initialize data + const ws = new WebSocket(url); + // Update store with WebSocket instance + webSocketDataStore.update((webSocketDataMap) => { + const wsData = webSocketDataMap.get(tabId); + if (wsData) { + wsData.listener = ws; + webSocketDataMap.set(tabId, wsData); + } + return webSocketDataMap; + }); + + return new Promise((resolve, reject) => { + ws.onopen = () => { webSocketDataStore.update((webSocketDataMap) => { const wsData = webSocketDataMap.get(tabId); if (wsData) { wsData.messages.unshift({ - data: `Connected from ${url}`, + data: `Connected to ${url}`, transmitter: "connecter", timestamp: formatTime(new Date()), uuid: uuidv4(), }); wsData.status = "connected"; + webSocketDataMap.set(tabId, wsData); } return webSocketDataMap; }); notifications.success("WebSocket connected successfully"); + resolve(); + }; - // All the response of particular web socket can be listened here. (Can be shifted to another place) - listen(`ws_message_${tabId}`, (event) => { - webSocketDataStore.update((webSocketDataMap) => { - const wsData = webSocketDataMap.get(tabId); - if (wsData) { - wsData.messages.unshift({ - data: event.payload, - transmitter: "receiver", - timestamp: formatTime(new Date()), - uuid: uuidv4(), - }); - webSocketDataMap.set(tabId, wsData); - } - return webSocketDataMap; - }); + ws.onmessage = (event) => { + webSocketDataStore.update((webSocketDataMap) => { + const wsData = webSocketDataMap.get(tabId); + if (wsData) { + wsData.messages.unshift({ + data: event.data, + transmitter: "receiver", + timestamp: formatTime(new Date()), + uuid: uuidv4(), + }); + webSocketDataMap.set(tabId, wsData); + } + return webSocketDataMap; }); - } catch (e) { - console.error(e); - notifications.error( - "Failed to fetch WebSocket response. Please try again.", - ); - return error("error"); - } - }) - .catch((e) => { - console.error(e); - webSocketDataStore.update((webSocketDataMap) => { - webSocketDataMap.delete(tabId); - return webSocketDataMap; - }); - notifications.error("Failed to connect WebSocket. Please try again."); - return error("error"); + }; + + ws.onerror = (error) => { + console.error("WebSocket error:", error); + webSocketDataStore.update((webSocketDataMap) => { + webSocketDataMap.delete(tabId); + return webSocketDataMap; + }); + }; + + ws.onclose = () => { + webSocketDataStore.update((webSocketDataMap) => { + const wsData = webSocketDataMap.get(tabId); + if (wsData) { + wsData.messages.unshift({ + data: `Disconnected from ${url}`, + transmitter: "disconnector", + timestamp: formatTime(new Date()), + uuid: uuidv4(), + }); + wsData.status = "disconnected"; + webSocketDataMap.set(tabId, wsData); + } + return webSocketDataMap; + }); + }; + }); + } catch (error) { + console.error("WebSocket connection error:", error); + webSocketDataStore.update((webSocketDataMap) => { + webSocketDataMap.delete(tabId); + return webSocketDataMap; }); + notifications.error("Failed to connect WebSocket"); + throw error; + } + // } else if (selectedAgent === "Cloud Agent") { + // } }; /** @@ -606,19 +624,21 @@ const makeHttpRequestV2 = async ( } }; - /** * Processes a Socket.IO message event for a specific tab, determining if the event should be included in the response. * @param _tabId - The unique identifier of the tab for which the event is being processed. * @param _event - The event object containing the payload with the event name and message data. * @returns A promise that resolves to a JSON string containing the event name and message (if included), or an empty string if the event is not included. */ -const processMessageEvent = async(_tabId: string, _event:{ - payload: { - event: string, - message: any[] - } -}): Promise => { +const processMessageEvent = async ( + _tabId: string, + _event: { + payload: { + event: string; + message: any[]; + }; + }, +): Promise => { await new Sleep().setTime(10); // Retrieve tab data and check event inclusion @@ -637,41 +657,36 @@ const processMessageEvent = async(_tabId: string, _event:{ if (socketIOresponse && isIncludeInResponse && message) { return JSON.stringify([ eventName, - typeof message[0] === "string" - ? message[0] - : JSON.stringify(message[0]), + typeof message[0] === "string" ? message[0] : JSON.stringify(message[0]), ]); } return ""; -} +}; /** * Generates a message indicating a successful connection to a specified URL. * @param _url - The URL of the connection. * @returns A message indicating the connection was established. */ -const processConnectEvent = (_url: string): string =>{ - return `Connected from ${_url}`; -} +const processConnectEvent = (_url: string): string => { + return `Connected to ${_url}`; +}; /** * Generates a message indicating a disconnection from a specified URL. - * @param _url - The URL of the disconnection. + * @param _url - The URL of the disconnection. * @returns A message indicating the disconnection occurred. */ -const processDisconnectEvent = (_url: string): string =>{ +const processDisconnectEvent = (_url: string): string => { return `Disconnected from ${_url}`; -} +}; /** * Adds a Socket.IO listener to the map associated with a specific tab ID. * @param _socketIo - The Socket.IO instance or listener to be associated with the tab. * @param _tabId - The unique identifier of the tab for which the listener is being set. */ -const insertSocketIoListenerToMap = ( - _socketIo :any, - _tabId: string, -) => { +const insertSocketIoListenerToMap = (_socketIo: any, _tabId: string) => { socketIoDataStore.update((webSocketDataMap) => { const wsData = webSocketDataMap.get(_tabId); if (wsData) { @@ -689,7 +704,11 @@ const insertSocketIoListenerToMap = ( * @param _data - The data message to be inserted into the Socket.IO map. * @param _transmitter - The transmitter type indicating the origin or purpose of the message (e.g., CONNECTER, DISCONNECTOR). */ -const insertSocketIoDataToMap = (_tabId: string, _data: string, _transmitter: SocketIORequestMessageTransmitterTabEnum) => { +const insertSocketIoDataToMap = ( + _tabId: string, + _data: string, + _transmitter: SocketIORequestMessageTransmitterTabEnum, +) => { socketIoDataStore.update((SocketIoDataMap) => { const wsData = SocketIoDataMap.get(_tabId); if (wsData) { @@ -699,17 +718,18 @@ const insertSocketIoDataToMap = (_tabId: string, _data: string, _transmitter: So timestamp: formatTime(new Date()), uuid: uuidv4(), }); - if( _transmitter === SocketIORequestMessageTransmitterTabEnum.CONNECTER){ + if (_transmitter === SocketIORequestMessageTransmitterTabEnum.CONNECTER) { wsData.status = SocketIORequestStatusTabEnum.CONNECTED; - } - else if( _transmitter === SocketIORequestMessageTransmitterTabEnum.DISCONNECTOR){ + } else if ( + _transmitter === SocketIORequestMessageTransmitterTabEnum.DISCONNECTOR + ) { wsData.status = SocketIORequestStatusTabEnum.DISCONNECTED; } SocketIoDataMap.set(_tabId, wsData); } return SocketIoDataMap; }); -} +}; /** * Removes Socket.IO from the tab data Map. @@ -750,24 +770,24 @@ const sendSocketIoMessage = async ( return socketIoDataMap; }); - socketIoDataStore.update((webSocketDataMap) => { - const wsData = webSocketDataMap.get(_tabId); - const event = []; - event.push(_eventName); - event.push(_eventMessage || "(empty)"); - if (wsData) { - const socketInsta = wsData.connectListener; - socketInsta.emit(_eventName, _eventMessage); - wsData.messages.unshift({ - data: JSON.stringify(event), - transmitter: SocketIORequestMessageTransmitterTabEnum.SENDER, - timestamp: formatTime(new Date()), - uuid: uuidv4(), - }); - webSocketDataMap.set(_tabId, wsData); - } - return webSocketDataMap; - }); + socketIoDataStore.update((webSocketDataMap) => { + const wsData = webSocketDataMap.get(_tabId); + const event = []; + event.push(_eventName); + event.push(_eventMessage || "(empty)"); + if (wsData) { + const socketInsta = wsData.connectListener; + socketInsta.emit(_eventName, _eventMessage); + wsData.messages.unshift({ + data: JSON.stringify(event), + transmitter: SocketIORequestMessageTransmitterTabEnum.SENDER, + timestamp: formatTime(new Date()), + uuid: uuidv4(), + }); + webSocketDataMap.set(_tabId, wsData); + } + return webSocketDataMap; + }); }; /** @@ -775,14 +795,16 @@ const sendSocketIoMessage = async ( * @param _url - Socket.IO server URL to connect. * @param _tabId - ID of the tab for which the Socket.IO connection should be established. * @param _headers - Headers to be sent with the Socket.IO connection. -*/ + */ const connectSocketIo = async ( _url: string, _tabId: string, _headers: string, ): Promise => { console.table({ URL: _url, Headers: _headers }); - const selectedAgent = localStorage.getItem("selectedAgent") as WorkspaceUserAgentBaseEnum; + const selectedAgent = localStorage.getItem( + "selectedAgent", + ) as WorkspaceUserAgentBaseEnum; socketIoDataStore.update((webSocketDataMap) => { webSocketDataMap.set(_tabId, { messages: [], @@ -811,31 +833,29 @@ const connectSocketIo = async ( removeSocketIoDataFromMap(_tabId); return; } - constants.API_URL + constants.API_URL; if (selectedAgent === WorkspaceUserAgentBaseEnum.CLOUD_AGENT) { const proxySocketIO = io(`${constants.SOCKET_IO_API_URL}/`, { path: "/socket.io", // Path to the WebSocket gateway transports: ["websocket"], // Ensure the transport is set to WebSocket query: { - targetUrl: urlObject.origin || "", - namespace: urlObject.pathname || "/", - headers: _headers + targetUrl: urlObject.origin || "", + namespace: urlObject.pathname || "/", + headers: _headers, }, - reconnection: false, + reconnection: false, }); - + // store listeners inside map against tab id for future removal - insertSocketIoListenerToMap( - proxySocketIO, - _tabId, - ); - - proxySocketIO.onAny(async(event, args) => { - if(event === "sparrow_internal_connect_error"){ // Connect_error listener from the target Socket.IO. + insertSocketIoListenerToMap(proxySocketIO, _tabId); + + proxySocketIO.onAny(async (event, args) => { + if (event === "sparrow_internal_connect_error") { + // Connect_error listener from the target Socket.IO. console.error(new DOMException(args + " (URL Issue)", "ConnectError")); removeSocketIoDataFromMap(_tabId); - } - else if(event === "sparrow_internal_connect"){ // Connect listener from the target Socket.IO. + } else if (event === "sparrow_internal_connect") { + // Connect listener from the target Socket.IO. const message = processConnectEvent(_url); insertSocketIoDataToMap( _tabId, @@ -845,57 +865,67 @@ const connectSocketIo = async ( notifications.success( `${SocketIORequestDefaultAliasBaseEnum.NAME} connected successfully.`, ); - } - else if(event === "sparrow_internal_disconnect"){ // Disconnect listener from the target Socket.IO. - console.error(new DOMException(args + " (Connection Lost)", "DisconnectError")); - const message = processDisconnectEvent(_url); - insertSocketIoDataToMap( - _tabId, - message, - SocketIORequestMessageTransmitterTabEnum.DISCONNECTOR, - ); - } - else{ // Message listener from the target Socket.IO. + } else if (event === "sparrow_internal_disconnect") { + // Disconnect listener from the target Socket.IO. + console.error( + new DOMException(args + " (Connection Lost)", "DisconnectError"), + ); + const message = processDisconnectEvent(_url); + insertSocketIoDataToMap( + _tabId, + message, + SocketIORequestMessageTransmitterTabEnum.DISCONNECTOR, + ); + } else { + // Message listener from the target Socket.IO. const message = await processMessageEvent(_tabId, { payload: { event: event, - message: args - } + message: args, + }, }); - if(message){ - insertSocketIoDataToMap(_tabId, message, SocketIORequestMessageTransmitterTabEnum.RECEIVER); + if (message) { + insertSocketIoDataToMap( + _tabId, + message, + SocketIORequestMessageTransmitterTabEnum.RECEIVER, + ); } } }); - // Listen for connect_error events from the proxy Socket.IO. - proxySocketIO.on("connect_error", (err) => { + // Listen for connect_error events from the proxy Socket.IO. + proxySocketIO.on("connect_error", (err) => { console.error(new DOMException(err + " (Proxy Failed)", "ConnectError")); removeSocketIoDataFromMap(_tabId); }); - - } - else{ + } else { const parsedHeaders = JSON.parse(_headers as string); - const headersObject: { [key: string]: string } = parsedHeaders.reduce((acc: Record , { key, value }: {key: string, value: string}) => { + const headersObject: { [key: string]: string } = parsedHeaders.reduce( + ( + acc: Record, + { key, value }: { key: string; value: string }, + ) => { acc[key] = value; return acc; - }, {} as { [key: string]: string }); + }, + {} as { [key: string]: string }, + ); delete headersObject["Sec-WebSocket-Key"]; delete headersObject["Sec-WebSocket-Version"]; - const targetSocketIO = io(`${urlObject.origin || ""}${urlObject.pathname || "/"}`, { - transports: ["websocket"], - query: headersObject, - reconnection: false, - }); - - // store listeners inside map against tab id for future removal - insertSocketIoListenerToMap( - targetSocketIO , - _tabId, + const targetSocketIO = io( + `${urlObject.origin || ""}${urlObject.pathname || "/"}`, + { + transports: ["websocket"], + query: headersObject, + reconnection: false, + }, ); + // store listeners inside map against tab id for future removal + insertSocketIoListenerToMap(targetSocketIO, _tabId); + // Listen for connect events from the target Socket.IO. targetSocketIO.on("connect", () => { const message = processConnectEvent(_url); @@ -908,17 +938,19 @@ const connectSocketIo = async ( `${SocketIORequestDefaultAliasBaseEnum.NAME} connected successfully.`, ); }); - + // Listen for connect_error events from the target Socket.IO. targetSocketIO.on("connect_error", (err) => { console.error(new DOMException(err + " (URL Issue)", "ConnectError")); removeSocketIoDataFromMap(_tabId); }); - + // Listen for disconnect events from the target Socket.IO. targetSocketIO.on("disconnect", (err) => { - console.error(new DOMException(err + " (Connection Lost)", "DisconnectError")); - const message = processDisconnectEvent(_url); + console.error( + new DOMException(err + " (Connection Lost)", "DisconnectError"), + ); + const message = processDisconnectEvent(_url); insertSocketIoDataToMap( _tabId, message, @@ -927,15 +959,19 @@ const connectSocketIo = async ( }); // Listen for all dynamic events from the target Socket.IO. - targetSocketIO.onAny(async(event: string, ...args: any[]) => { + targetSocketIO.onAny(async (event: string, ...args: any[]) => { const message = await processMessageEvent(_tabId, { payload: { event: event, - message: args - } + message: args, + }, }); - if(message){ - insertSocketIoDataToMap(_tabId, message, SocketIORequestMessageTransmitterTabEnum.RECEIVER); + if (message) { + insertSocketIoDataToMap( + _tabId, + message, + SocketIORequestMessageTransmitterTabEnum.RECEIVER, + ); } }); } @@ -959,22 +995,23 @@ const disconnectSocketIo = async (_tabId: string): Promise => { return socketIoDataMap; }); - socketIoDataStore.update((webSocketDataMap) => { - const wsData = webSocketDataMap.get(_tabId); - if (wsData) { - const socketInsta = wsData.connectListener; - if(wsData.agent === WorkspaceUserAgentBaseEnum.CLOUD_AGENT){ - socketInsta?.emit("sparrow_internal_disconnect","client io disconnect"); - } - else{ - socketInsta?.disconnect(); - } - } - return webSocketDataMap; - }); + socketIoDataStore.update((webSocketDataMap) => { + const wsData = webSocketDataMap.get(_tabId); + if (wsData) { + const socketInsta = wsData.connectListener; + if (wsData.agent === WorkspaceUserAgentBaseEnum.CLOUD_AGENT) { + socketInsta?.emit( + "sparrow_internal_disconnect", + "client io disconnect", + ); + } else { + socketInsta?.disconnect(); + } + } + return webSocketDataMap; + }); }; - export { makeRequest, getAuthHeaders, diff --git a/apps/@sparrow-web/src/pages/TeamExplorerPage/TeamExplorerPage.ViewModel.ts b/apps/@sparrow-web/src/pages/TeamExplorerPage/TeamExplorerPage.ViewModel.ts index f338c515d..f945b7e76 100644 --- a/apps/@sparrow-web/src/pages/TeamExplorerPage/TeamExplorerPage.ViewModel.ts +++ b/apps/@sparrow-web/src/pages/TeamExplorerPage/TeamExplorerPage.ViewModel.ts @@ -301,7 +301,7 @@ export class TeamExplorerPageViewModel { await this.tabRepository.createTab(initWorkspaceTab.getValue(), res._id); await this.workspaceRepository.setActiveWorkspace(res._id); // this will be removed when we unlock collection in web app. - // navigate("collections"); + navigate("collections"); notifications.success("New Workspace Created"); MixpanelEvent(Events.Create_New_Workspace_TeamPage); } @@ -347,7 +347,7 @@ export class TeamExplorerPageViewModel { await this.tabRepository.createTab(initWorkspaceTab.getValue(), id); await this.workspaceRepository.setActiveWorkspace(id); // Disabling the switching of workspace in web - // navigate("/dashboard/collections"); + navigate("/app/collections"); }; /** diff --git a/apps/@sparrow-web/src/pages/TeamExplorerPage/TeamExplorerPage.svelte b/apps/@sparrow-web/src/pages/TeamExplorerPage/TeamExplorerPage.svelte index 6bda9b76e..cb497b539 100644 --- a/apps/@sparrow-web/src/pages/TeamExplorerPage/TeamExplorerPage.svelte +++ b/apps/@sparrow-web/src/pages/TeamExplorerPage/TeamExplorerPage.svelte @@ -211,7 +211,7 @@ onCreateWorkspace={_viewModel.handleCreateWorkspace} onSwitchWorkspace={async (item) => { await _viewModel.handleSwitchWorkspace(item); - isWorkspaceOpen = true; + // isWorkspaceOpen = true; }} onRemoveMembersAtTeam={_viewModel.removeMembersAtTeam} onDemoteToMemberAtTeam={_viewModel.demoteToMemberAtTeam} diff --git a/apps/@sparrow-web/src/pages/Teams/Teams.svelte b/apps/@sparrow-web/src/pages/Teams/Teams.svelte index c8e9dfde0..e90ebbb25 100644 --- a/apps/@sparrow-web/src/pages/Teams/Teams.svelte +++ b/apps/@sparrow-web/src/pages/Teams/Teams.svelte @@ -24,6 +24,7 @@ import { Modal, Tooltip, Dropdown } from "@sparrow/library/ui"; import { pagesMotion } from "../../constants"; import { version } from "../../../package.json"; + import { CreateTeam } from "@sparrow/common/features"; const _viewModel = new TeamsViewModel(); const teamList: Observable = _viewModel.teams; @@ -108,7 +109,7 @@ }); let isGithubStarHover = false; - let activeIndex; + let activeIndex = ""; $: { if ($openTeam) { @@ -380,6 +381,24 @@ + { + isCreateTeamModalOpen = flag; + }} +> + { + isCreateTeamModalOpen = flag; + }} + onCreateTeam={_viewModel.createTeam} + /> + + diff --git a/packages/@sparrow-workspaces/src/features/socket-explorer/store/websocket.ts b/packages/@sparrow-workspaces/src/features/socket-explorer/store/websocket.ts index 6eb1c3202..d02c36faa 100644 --- a/packages/@sparrow-workspaces/src/features/socket-explorer/store/websocket.ts +++ b/packages/@sparrow-workspaces/src/features/socket-explorer/store/websocket.ts @@ -1,3 +1,4 @@ +import type { WorkspaceUserAgentBaseEnum } from "@sparrow/common/types/workspace/workspace-base"; import type { UnlistenFn } from "@tauri-apps/api/event"; import { writable } from "svelte/store"; @@ -9,13 +10,14 @@ export type WebSocketMessage = { }; export type WebSocketData = { messages: WebSocketMessage[]; + agent: WorkspaceUserAgentBaseEnum; status: "connected" | "disconnected" | "connecting" | "disconnecting"; search: string; body: string; contentType: string; url: string; filter: "All Messages" | "Sent" | "Received"; - listener: UnlistenFn | null; + listener?: any; }; export const webSocketDataStore = writable>( diff --git a/packages/@sparrow-workspaces/src/features/workspace-actions/layout/WorkspaceActions.svelte b/packages/@sparrow-workspaces/src/features/workspace-actions/layout/WorkspaceActions.svelte index 2dd32774b..18e0ef37e 100644 --- a/packages/@sparrow-workspaces/src/features/workspace-actions/layout/WorkspaceActions.svelte +++ b/packages/@sparrow-workspaces/src/features/workspace-actions/layout/WorkspaceActions.svelte @@ -262,6 +262,16 @@ showImportCurlPopup(); }, }, + { + name: "Add WebSocket", + icon: SocketIcon, + iconColor: "var(--icon-secondary-130)", + iconSize: "15px", + onclick: () => { + onItemCreated("web-socket", {}); + MixpanelEvent(Events.Add_WebSocket); + }, + }, { name: "Add Environment", icon: StackIcon,